Packages

A wrapper for `NMAP` written in Elixir.

Current section

Files

Jump to
hades lib helpers.ex
Raw

lib/helpers.ex

defmodule Hades.Helpers do
@moduledoc """
Provides some usefull functions for internal handling `NMAP` outputs.
"""
import SweetXml
alias Hades.Command
alias Hades.Argument
@doc """
This functions parses the given file that is located at `filepath` and
returns a `t:map/0` filled with the `hosts` and `time` content that was saved
from `NMAP` into the `XML` file.
Returns `t:map/0`.
## Example
iex> Hades.Helpers.parse_xml("test/mocs/test.xml")
%{
hosts: [
%{hostname: "FelixsMACNCHEESEPRO.root.box", ip: "192.168.120.42", ports: []}
],
time: %{
elapsed: 0.03,
endstr: "Fri Feb 7 09:55:09 2020",
startstr: "Fri Feb 7 09:55:09 2020",
unix: 1581065709
}
}
"""
@spec parse_xml(String.t()) :: map
def parse_xml(filepath) do
File.read!(filepath)
|> xmap(
hosts: [
~x"//host"l,
ip: ~x"./address/@addr"s,
hostname: ~x"./hostnames/hostname/@name[1]"s,
ports: [
~x"//port"l,
port: ~x"./@portid"i,
product: ~x"./service/@product"s,
name: ~x"./service/@name"s,
version: ~x"./service/@version"s,
script: ~x"./script/@id"sl,
output: ~x"./script/@output"l,
state: ~x"./state/@state"s
]
],
time: [
~x"//nmaprun",
startstr: ~x"./@startstr"s,
endstr: ~x"./runstats/finished/@timestr"s,
elapsed: ~x"./runstats/finished/@elapsed"f,
unix: ~x"./runstats/finished/@time"i
]
)
end
@doc """
With this function it is possible to validate given IP Adresses.
Returns `{:ok, ip_address}` if the given `ip_address` was validated successfully.
## Example
iex> Hades.Helpers.check_ip_address("192.168.120.1")
{:ok, "192.168.120.1"}
"""
# TODO: Also provide a technique to test for submask and FQDN's
@spec check_ip_address(String.t()) :: {atom, String.t()}
def check_ip_address(ip_address) do
case :inet.parse_address(String.to_charlist(ip_address)) do
{:ok, _ip} ->
{:ok, ip_address}
{:error, :einval} ->
{:error, "No valid IP-Address given."}
end
end
@doc """
Macro used to generate the argument functions that are described inside
the `Hades.Arguments` modules.
"""
defmacro option_functions(options_map) do
quote bind_quoted: [options_map: options_map] do
Enum.each(options_map, fn {k, v} ->
function_name = String.to_atom("arg_" <> Atom.to_string(k))
if v.argument do
@doc """
#{v.desc}
"""
def unquote(function_name)(argument),
do: Map.put(unquote(Macro.escape(v)), :argument, argument)
else
@doc """
#{v.desc}
"""
def unquote(function_name)(), do: unquote(Macro.escape(v))
end
end)
end
end
@doc """
If the context is not directly specified simply return `:ok`.
Returns `:ok`.
## Example
iex> Hades.Helpers.validate_contexts!(:unspecified, [:scan_type, :option])
:ok
"""
def validate_contexts!(:unspecified, _), do: :ok
@doc """
Checks if the given context is in the required ones.
Raises `raise(ArgumentError)` if the given context is not in the required ones.
## Examples
iex> Hades.Helpers.validate_contexts!(:option, [:scan_type, :option])
nil
iex> Hades.Helpers.validate_contexts!(:undefined, [:scan_type, :option])
** (ArgumentError) argument error
"""
def validate_contexts!(context, required) do
unless Enum.member?(required, context), do: raise(ArgumentError)
end
@doc """
Prepares the command to be executed, by converting the `%Command{}` into
proper parameters to be feeded to NMAP.
Under normal circumstances `Hades.scan/1` should be used, use `prepare`
only when converted args are needed.
Returns `{nmap_args_string, the_aimed_target}`.
## Example
iex> command = Hades.new_command()
...> |> Hades.add_argument(Hades.Arguments.ServiceVersionDetection.arg_sV())
...> |> Hades.add_argument(Hades.Arguments.ServiceVersionDetection.arg_version_all())
...> |> Hades.add_argument(Hades.Arguments.ScriptScan.arg_script("vulners"))
...> |> Hades.add_target("192.168.0.1")
iex> Hades.Helpers.prepare(command)
{"--script vulners --version-all -sV", "192.168.0.1"}
"""
# TODO: Refactor with some input validation
@spec prepare(command :: Command.t()) :: {binary() | nil, list(binary)}
def prepare(%Command{scan_types: scan_types, target: target}) do
options = Enum.map(scan_types, &arg_for_option/1) |> List.flatten()
{Enum.join(options, " "), target}
end
defp arg_for_option(%Argument{name: name, options: false, argument: nil}) do
~w(#{name} )
end
defp arg_for_option(%Argument{name: name, argument: arg}) when not is_nil(arg) do
~w(#{name} #{arg} )
end
@doc """
Read hades `timeout` from the config.
If unspecified, return the default `timeout` which is currently `300_000` (corresponds to 5 minutes).
This `timeout` is propagated to the function `Task.await()`.
If the specified `timeout` period is exceeded, it is assumed that the process running the NMAP command has timed out.
The `timeout` is specified in `ms`.
Returns `t:integer/0`.
## Example
iex> Hades.Helpers.hades_timeout()
300000
"""
def hades_timeout do
case Application.get_env(:hades, :timeout, nil) do
nil -> 300_000
timeout -> timeout
end
end
@doc """
Reads hades `output_path` from the config.
If there is nothing specified in the config then the default path will be returned.
Returns `t:binary/0`.
## Example
iex> Hades.Helpers.hades_path()
"/var/folders/c1/f0tm33sd3tgg_ds8kyhykyw80000gn/T/briefly-1581/hades-73279-791202-3hades/8b09d31e1a1142869ce8b15faf27ed45.xml"
"""
def hades_path do
case Application.get_env(:hades, :output_path, nil) do
nil ->
{:ok, path} = Briefly.create(directory: true)
path <> "/" <> UUID.uuid4(:hex) <> ".xml"
path ->
path
end
end
end