Current section
Files
Jump to
Current section
Files
lib/watcher.ex
defmodule ExploitGuard.Watcher do
@moduledoc false
use GenServer
require Logger
import IEx.Helpers, only: [pid: 1]
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(_) 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, %{}}
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
{:noreply, %{}}
end
def get_exploit_pid(exploit_term) do
exploit_term
|> String.split(" ")
|> Enum.at(1)
|> String.split([".", "<", ">"], trim: true)
|> Enum.join(".")
|> pid()
end
end