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