/
watcher.ex
73 lines (58 loc) · 2.27 KB
/
watcher.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
defmodule ExploitGuard.Watcher do
@moduledoc false
use GenServer
require Logger
import IEx.Helpers, only: [pid: 1]
alias ExploitGuard.ExploitHandler
def start_link(state \\ %{}) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
# Functions to be called externally: none
# This GenServer receives trace messages and prints
# a warning if it detects someone exploiting :erlang.binary_to_term/1 or /2
# Start tracing :erlang.binary_to_term
def init(state) do
# This match spec states:
# For erlang tracing, match on the first argument ({:hd,:"$1"}) of the function
# see if that first argument matching binary_part <<131, 112>>
# 131 means Erlang External Term Format
# 112 means NEW_FUN_EXT
# https://www.erlang.org/doc/apps/erts/erl_ext_dist.html#new_fun_ext
ms = [{:"$1", [{:==, {:binary_part, {:hd, :"$1"}, 0, 2}, <<131, 112>>}], [{:return_trace}]}]
# Trace calls to binary_to_term where a new function is being created at runtime
# {10,1000} means max 10 calls per 1 second with recon's rate limiter
:recon_trace.calls({:erlang, :binary_to_term, ms}, {10, 1000}, [{:io_server, self()}])
{:ok, state}
end
def handle_info({:io_request, p_id, tag, request}, state) do
Logger.alert("[Exploit Guard] Exploit behavior detected, binary_to_term created function")
{_, _, _, _, i} = request
exploit_term = IO.iodata_to_binary(i)
Logger.alert("[Exploit Guard] Exploit info: " <> exploit_term)
if :persistent_term.get(:mode) == :block do
exploit_pid = get_exploit_pid(exploit_term)
Process.exit(exploit_pid, :kill)
Logger.alert("[Exploit Guard] Block mode active, exploit process killed")
send(p_id, {:io_reply, tag, :ok})
else
Logger.alert("[Exploit Guard] Monitor mode active, no action taken")
send(p_id, {:io_reply, tag, :ok})
end
call_handler(%{exploit: exploit_term}, state)
{:noreply, state}
end
def get_exploit_pid(exploit_term) do
exploit_term
|> String.split(" ")
|> Enum.at(1)
|> String.split([".", "<", ">"], trim: true)
|> Enum.join(".")
|> pid()
end
defp call_handler(_, %{handler: nil}) do
:ok
end
defp call_handler(params, %{handler: handler}) do
ExploitHandler.call(handler, params)
end
end