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
