├── .gitignore ├── src ├── scannerl.app.src ├── outmodules │ ├── out_stdout.erl │ ├── out_stdout_mini.erl │ ├── out_stdout_ip.erl │ ├── out_file.erl │ ├── out_file_mini.erl │ ├── out_file_ip.erl │ ├── out_file_resultonly.erl │ ├── out_behavior.erl │ ├── out_csv.erl │ └── out_csvfile.erl ├── utils │ ├── utils_fox.erl │ ├── ssh_key_cb.erl │ ├── utils_msgserver.erl │ ├── utils_mqtt.erl │ ├── utils_fp.erl │ ├── cntlist.erl │ ├── utils_slave.erl │ ├── utils_ssl.erl │ ├── tgt.erl │ ├── utils.erl │ └── utils_http.erl ├── fpmodules │ ├── fp_chargen.erl │ ├── fp_module.erl │ ├── fp_mqtt.erl │ ├── fp_mqtts.erl │ ├── fp_fox.erl │ ├── fp_bacnet.erl │ ├── fp_mysql_greeting.erl │ ├── fp_https_certif.erl │ ├── fp_pop3_certif.erl │ ├── fp_httpsbg.erl │ ├── fp_smtp_certif.erl │ ├── fp_imap_certif.erl │ ├── fp_modbus.erl │ ├── fp_ssh_host_key.erl │ └── fp_httpbg.erl ├── scannerl_worker_sup.erl ├── includes │ ├── opts.hrl │ └── args.hrl ├── fsms │ ├── fsm_udp.erl │ ├── statem_udp.erl │ ├── fsm_tcp.erl │ ├── statem_tcp.erl │ └── fsm_ssl.erl ├── scannerl.erl └── broker.erl ├── rebar.config ├── AUTHORS ├── DISCLAIMER.txt └── skeletons ├── out_skeleton.erl ├── fp_tcp.erl ├── fp_udp.erl └── fp_ssl.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *.*~ 2 | *.beam 3 | *.swp 4 | *.res 5 | *.dump 6 | ebin 7 | src/includes/fpmodules.hrl 8 | src/includes/outmodules.hrl 9 | src/includes/defines.hrl 10 | .rebar/ 11 | scannerl 12 | -------------------------------------------------------------------------------- /src/scannerl.app.src: -------------------------------------------------------------------------------- 1 | {application, scannerl, 2 | [ 3 | {description, ""}, 4 | {vsn, "1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {env, []} 11 | ]}. 12 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {escript_name, "scannerl"}. 2 | {escript_incl_apps, []}. 3 | {escript_shebang, "#!/usr/bin/env escript\n"}. 4 | {escript_comment, "%% The modular distributed fingerprinting engine.\n"}. 5 | %{escript_emu_args, "%%! -pa application/application/ebin\n"}. 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Scannerl was originally implemented by Adrien Giner (adrien.giner@kudelskisecurity.com) 2 | on an idea by Antoine Junod (antoine.junod@kudelskisecurity.com). 3 | 4 | Contributors 5 | ------------ 6 | Adrien Giner - adrien.giner@kudelskisecurity.com 7 | David Rossier - david.rossier@kudelskisecurity.com 8 | -------------------------------------------------------------------------------- /DISCLAIMER.txt: -------------------------------------------------------------------------------- 1 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2 | -------------------------------------------------------------------------------- /src/outmodules/out_stdout.erl: -------------------------------------------------------------------------------- 1 | %% output module - output to stdout 2 | 3 | -module(out_stdout). 4 | -behavior(out_behavior). 5 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 6 | 7 | -export([init/2, clean/1, output/2]). 8 | -export([get_description/0]). 9 | -export([get_arguments/0]). 10 | 11 | -define(DESCRIPTION, "output to stdout"). 12 | -define(ARGUMENTS, []). 13 | 14 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 15 | % API 16 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 17 | %% this is the initialization interface for this module 18 | %% returns {ok, Obj} or {error, Reason} 19 | init(_Scaninfo, _Options) -> 20 | {ok, []}. 21 | 22 | %% this is the cleaning interface for this module 23 | %% returns ok or {error, Reason} 24 | clean(_Obj) -> 25 | ok. 26 | 27 | get_description() -> 28 | ?DESCRIPTION. 29 | 30 | get_arguments() -> 31 | ?ARGUMENTS. 32 | 33 | %% this is the output interface 34 | %% output'ing to stdout 35 | %% returns ok or {error, Reason} 36 | output(_Obj, []) -> 37 | ok; 38 | output(Obj, [H|T]) -> 39 | output_one(Obj, H), 40 | output(Obj, T). 41 | 42 | output_one(_Obj, Msg) -> 43 | io:fwrite("Result: ~10000tp~n", [Msg]). 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/outmodules/out_stdout_mini.erl: -------------------------------------------------------------------------------- 1 | %% output module - only output the ip and the result to stdout 2 | 3 | -module(out_stdout_mini). 4 | -behavior(out_behavior). 5 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 6 | 7 | -export([init/2, clean/1, output/2]). 8 | -export([get_description/0]). 9 | -export([get_arguments/0]). 10 | 11 | -define(DESCRIPTION, "output to stdout (only ip and result)"). 12 | -define(ARGUMENTS, []). 13 | 14 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 15 | % API 16 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 17 | %% this is the initialization interface for this module 18 | %% returns {ok, Obj} or {error, Reason} 19 | init(_Scaninfo, _Options) -> 20 | {ok, []}. 21 | 22 | %% this is the cleaning interface for this module 23 | %% returns ok or {error, Reason} 24 | clean(_Obj) -> 25 | ok. 26 | 27 | get_description() -> 28 | ?DESCRIPTION. 29 | 30 | get_arguments() -> 31 | ?ARGUMENTS. 32 | 33 | %% this is the output interface 34 | %% output'ing to stdout 35 | %% returns ok or {error, Reason} 36 | output(_Obj, []) -> 37 | ok; 38 | output(Obj, [H|T]) -> 39 | output_one(Obj, H), 40 | output(Obj, T). 41 | 42 | output_one(_Object, {_Module, Target, _Port, Result}) -> 43 | io:fwrite("~p,~10000tp~n", [Target,Result]). 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/outmodules/out_stdout_ip.erl: -------------------------------------------------------------------------------- 1 | %% output module - output only the ip of the target if fp successful 2 | 3 | -module(out_stdout_ip). 4 | -behavior(out_behavior). 5 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 6 | 7 | -export([init/2, clean/1, output/2]). 8 | -export([get_description/0]). 9 | -export([get_arguments/0]). 10 | 11 | -define(ERR_ARG, "arg=[Output_file_path]"). 12 | -define(DESCRIPTION, "output to stdout (only IP)"). 13 | -define(ARGUMENTS, []). 14 | 15 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 16 | % API 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | %% this is the initialization interface for this module 19 | %% returns {ok, Obj} or {error, Reason} 20 | init(_Scaninfo, []) -> 21 | {ok, []}. 22 | 23 | %% this is the cleaning interface for this module 24 | %% returns ok or {error, Reason} 25 | clean(_Object) -> 26 | ok. 27 | 28 | get_description() -> 29 | ?DESCRIPTION. 30 | 31 | get_arguments() -> 32 | ?ARGUMENTS. 33 | 34 | %% this is the output interface 35 | %% output'ing to file 36 | %% returns ok or {error, Reason} 37 | output(_Obj, []) -> 38 | ok; 39 | output(Obj, [H|T]) -> 40 | output_one(Obj, H), 41 | output(Obj, T). 42 | 43 | output_one(_Object, {_Mod, {A,B,C,D}, _Port, {{ok,result},_Result}}) -> 44 | io:fwrite("~p.~p.~p.~p~n", [A,B,C,D]); 45 | output_one(_Object, _Msg) -> 46 | ok. 47 | 48 | -------------------------------------------------------------------------------- /src/utils/utils_fox.erl: -------------------------------------------------------------------------------- 1 | %%% FOX helper 2 | %%% 3 | 4 | -module(utils_fox). 5 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 6 | 7 | -export([parse_min/1, parse_full/1, forge_min_hello/0]). 8 | 9 | -define(CANARY, "fox"). 10 | -define(CANARY_HTTP, "HTTP"). 11 | -define(CANARY_SSH, "SSH"). 12 | -define(LINE_SEP, '\n'). 13 | -define(PKT_MIN_HELLO, 14 | [ 15 | 'fox a 1 -1 fox hello', ?LINE_SEP, 16 | '{', ?LINE_SEP, 17 | 'fox.version=s:1.0', ?LINE_SEP, 18 | 'id=i:1', ?LINE_SEP, 19 | '};;', ?LINE_SEP 20 | ] 21 | ). 22 | 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | %% Parsing packet 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | % expects a string 27 | % minimal parsing 28 | % true -> is fox 29 | % false -> is not 30 | parse_min(?CANARY ++ _Rest) -> 31 | true; 32 | parse_min(_Str) -> 33 | false. 34 | 35 | parse_full(?CANARY ++ Data) -> 36 | {true, ?CANARY ++ Data}; 37 | parse_full(?CANARY_HTTP ++ _Data) -> 38 | {false, http}; 39 | parse_full(?CANARY_SSH ++ _Data) -> 40 | {false, ssh}; 41 | parse_full(_) -> 42 | {false}. 43 | 44 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 45 | %% Forge packet 46 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 47 | % minimal hello 48 | forge_min_hello() -> 49 | lists:concat(?PKT_MIN_HELLO). 50 | 51 | 52 | -------------------------------------------------------------------------------- /skeletons/out_skeleton.erl: -------------------------------------------------------------------------------- 1 | %% output module - TODO 2 | 3 | %% rename this to the filename without the extension 4 | -module(out_todo). 5 | -behavior(out_behavior). 6 | -author("TODO"). 7 | 8 | -export([init/2, clean/1, output/2]). 9 | -export([get_description/0]). 10 | -export([get_arguments/0]). 11 | 12 | %% this is the error returned when wrong 13 | %% arguments are provided 14 | %% remove if no argument 15 | -define(ERR_ARG, "TODO"). 16 | %% this is the description displayed when 17 | %% listing the available modules 18 | -define(DESCRIPTION, "TODO"). 19 | %% this is the list of arguments 20 | %% as strings 21 | -define(ARGUMENTS, []). 22 | 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | % API 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | %% this is the initialization interface for this module 27 | %% returns {ok, Obj} or {error, Reason} 28 | init(_Scaninfo, []) -> 29 | ok; 30 | init(_Scaninfo, _) -> 31 | {error, ?ERR_ARG}. 32 | 33 | %% this is the cleaning interface for this module 34 | %% returns ok or {error, Reason} 35 | clean(_Obj) -> 36 | ok. 37 | 38 | get_description() -> 39 | ?DESCRIPTION. 40 | get_arguments() -> 41 | ?ARGUMENTS. 42 | 43 | %% this is the output interface 44 | %% returns ok or {error, Reason} 45 | output(_Obj, []) -> 46 | ok; 47 | output(Obj, [H|T]) -> 48 | output_one(Obj, H), 49 | output(Obj, T). 50 | 51 | output_one(_Obj, _Msg) -> 52 | ok. 53 | 54 | -------------------------------------------------------------------------------- /skeletons/fp_tcp.erl: -------------------------------------------------------------------------------- 1 | %%% TODO fingerprint module 2 | %%% 3 | %%% Output: 4 | %%% TODO 5 | %%% 6 | 7 | -module(fp_todo). 8 | -author("TODO"). 9 | 10 | -behavior(fp_module). 11 | -include("../includes/args.hrl"). 12 | 13 | -export([callback_next_step/1]). 14 | -export([get_default_args/0]). 15 | -export([get_description/0]). 16 | -export([get_arguments/0]). 17 | 18 | -define(TIMEOUT, 3000). % milli-seconds 19 | -define(PORT, 123). % TODO 20 | -define(TYPE, tcp). % transport type 21 | -define(MAXPKT, 2). % TODO 22 | -define(DESCRIPTION, "TCP/TODO: TODO"). 23 | -define(ARGUMENTS, []). 24 | 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | %% API 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 | get_default_args() -> 29 | #args{module=?MODULE, type=?TYPE, port=?PORT, 30 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 31 | 32 | get_description() -> 33 | ?DESCRIPTION. 34 | 35 | get_arguments() -> 36 | ?ARGUMENTS. 37 | 38 | callback_next_step(Args) when Args#args.moddata == undefined -> 39 | % TODO 40 | {continue, Args#args.maxpkt, "TODO", true}; 41 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 42 | {result, {{error,up}, timeout}}; 43 | callback_next_step(Args) -> 44 | % TODO parse 45 | {result, {{ok, result}, ok}}. 46 | 47 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 48 | %% debug 49 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 50 | % send debug 51 | debug(Args, Msg) -> 52 | utils:debug(fpmodules, Msg, 53 | {Args#args.target, Args#args.id}, Args#args.debugval). 54 | 55 | -------------------------------------------------------------------------------- /src/outmodules/out_file.erl: -------------------------------------------------------------------------------- 1 | %% output module - output to file 2 | 3 | -module(out_file). 4 | -behavior(out_behavior). 5 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 6 | 7 | -export([init/2, clean/1, output/2]). 8 | -export([get_description/0]). 9 | -export([get_arguments/0]). 10 | 11 | -define(ERR_ARG, "arg=[Output_file_path]"). 12 | -define(DESCRIPTION, "output to file"). 13 | -define(ARGUMENTS, ["File path"]). 14 | 15 | -record(opt, {path, fd}). 16 | 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | % API 19 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 20 | %% this is the initialization interface for this module 21 | %% returns {ok, Obj} or {error, Reason} 22 | init(_Scaninfo, [Path]) -> 23 | case file:open(Path, [write, delayed_write, {encoding, utf8}]) of 24 | {ok, Fd} -> 25 | Opts = #opt{path=Path, fd=Fd}, 26 | {ok, Opts}; 27 | {error, Reason} -> 28 | {error, Reason} 29 | end; 30 | init(_Scaninfo, _) -> 31 | {error, ?ERR_ARG}. 32 | 33 | %% this is the cleaning interface for this module 34 | %% returns ok or {error, Reason} 35 | clean(Object) -> 36 | file:close(Object#opt.fd). 37 | 38 | get_description() -> 39 | ?DESCRIPTION. 40 | 41 | get_arguments() -> 42 | ?ARGUMENTS. 43 | 44 | %% this is the output interface 45 | %% output'ing to file 46 | %% returns ok or {error, Reason} 47 | output(_Obj, []) -> 48 | ok; 49 | output(Obj, [H|T]) -> 50 | output_one(Obj, H), 51 | output(Obj, T). 52 | 53 | output_one(Object, Msg) -> 54 | Out = io_lib:fwrite("~999999tp~n", [Msg]), 55 | file:write(Object#opt.fd, Out). 56 | 57 | -------------------------------------------------------------------------------- /skeletons/fp_udp.erl: -------------------------------------------------------------------------------- 1 | %%% TODO fingerprinting module 2 | %%% 3 | %%% Output: 4 | %%% TODO 5 | %%% 6 | 7 | -module(fp_todo). 8 | -author("TODO"). 9 | 10 | -behavior(fp_module). 11 | -include("../includes/args.hrl"). 12 | 13 | -export([callback_next_step/1]). 14 | -export([get_default_args/0]). 15 | -export([get_description/0]). 16 | -export([get_arguments/0]). 17 | 18 | %% our records for this fingerprint 19 | -define(TIMEOUT, 3000). % milli-seconds 20 | -define(PORT, 123). % TODO 21 | -define(TYPE, udp). % transport type 22 | -define(MAXPKT, 2). % TODO 23 | -define(DESCRIPTION, "UDP/TODO: TODO"). 24 | -define(ARGUMENTS, []). 25 | 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | %% API 28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | get_default_args() -> 30 | #args{module=?MODULE, type=?TYPE, port=?PORT, 31 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 32 | 33 | get_description() -> 34 | ?DESCRIPTION. 35 | 36 | get_arguments() -> 37 | ?ARGUMENTS. 38 | 39 | callback_next_step(Args) when Args#args.moddata==undefined -> 40 | % TODO 41 | {continue, Args#args.maxpkt, "TODO", true}; 42 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 43 | {result, {{error,unknown}, timeout}}; 44 | callback_next_step(Args) -> 45 | % TODO parse 46 | {result, {{ok, result}, ok}}. 47 | 48 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 49 | %% debug 50 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 51 | % send debug 52 | debug(Args, Msg) -> 53 | utils:debug(fpmodules, Msg, 54 | {Args#args.target, Args#args.id}, Args#args.debugval). 55 | 56 | -------------------------------------------------------------------------------- /src/utils/ssh_key_cb.erl: -------------------------------------------------------------------------------- 1 | %% SSH callback for handling public keys and host keys 2 | %% to use with fp_ssh_host_key.erl 3 | %% 4 | %% this will simply store the host key provided by the 5 | %% server in a file which path is provided through the 6 | %% key_cb_private option. 7 | %% 8 | %% http://erlang.org/doc/man/ssh_client_key_api.html 9 | %% 10 | 11 | -module(ssh_key_cb). 12 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 13 | -behaviour(ssh_client_key_api). 14 | -export([add_host_key/3, is_host_key/4, user_key/2]). 15 | 16 | -include_lib("public_key/include/public_key.hrl"). 17 | 18 | %% per behavior: Checks if a host key is trusted. 19 | is_host_key(Pubkey, _Host, _Algorithm, Options) 20 | when is_record(Pubkey, 'RSAPublicKey') -> 21 | Modulus = Pubkey#'RSAPublicKey'.modulus, 22 | Exp = Pubkey#'RSAPublicKey'.publicExponent, 23 | Path = proplists:get_value(key_cb_private, Options), 24 | T = io_lib:fwrite("~s,~s", [integer_to_list(Modulus), 25 | integer_to_list(Exp)]), 26 | log_to_file(Path, T), 27 | false; 28 | is_host_key(_Pubkey, _Host, _Algorithm, _Options) -> 29 | false. 30 | 31 | %% per behavior: Fetches the users public key matching the Algorithm. 32 | user_key(_Algo, _Options) -> 33 | {error, "Not supported"}. 34 | 35 | %% per behavior: Adds a host key to the set of trusted host keys. 36 | add_host_key(_Host, _Pubkey, _Options) -> 37 | ok. 38 | 39 | % save to file 40 | % format: , 41 | log_to_file(Path, Text) -> 42 | case file:open(Path, [append]) of 43 | {ok, Fd} -> 44 | file:write(Fd, Text), 45 | file:close(Fd); 46 | {error, Reason} -> 47 | {error, Reason} 48 | end. 49 | -------------------------------------------------------------------------------- /skeletons/fp_ssl.erl: -------------------------------------------------------------------------------- 1 | %%% TODO fingerprinting module 2 | %%% 3 | %%% Output: 4 | %%% TODO 5 | %%% 6 | 7 | -module(fp_todo). 8 | -author("TODO"). 9 | 10 | -behavior(fp_module). 11 | -include("../includes/args.hrl"). 12 | 13 | -export([callback_next_step/1]). 14 | -export([get_default_args/0]). 15 | -export([get_description/0]). 16 | -export([get_arguments/0]). 17 | 18 | -define(TIMEOUT, 3000). % milli-seconds 19 | -define(PORT, 123). % TODO 20 | -define(TYPE, ssl). % transport type 21 | -define(MAXPKT, 2). % TODO 22 | -define(SSLOPTS, []). % TODO 23 | -define(DESCRIPTION, "SSL/TODO: TODO"). 24 | -define(ARGUMENTS, []). 25 | 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | %% API 28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | % public API to get {port, timeout} 30 | get_default_args() -> 31 | #args{module=?MODULE, type=?TYPE, port=?PORT, 32 | timeout=?TIMEOUT, fsmopts=?SSLOPTS, maxpkt=?MAXPKT}. 33 | 34 | get_description() -> 35 | ?DESCRIPTION. 36 | 37 | get_arguments() -> 38 | ?ARGUMENTS. 39 | 40 | callback_next_step(Args) when Args#args.moddata == undefined -> 41 | % TODO 42 | {continue, Args#args.maxpkt, "TODO", true}; 43 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 44 | {result, {{error, up}, timeout}}; 45 | callback_next_step(Args) -> 46 | % TODO parse 47 | {result, {{ok, result}, ok}}. 48 | 49 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 50 | %% debug 51 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 52 | % send debug 53 | debug(Args, Msg) -> 54 | utils:debug(fpmodules, Msg, 55 | {Args#args.target, Args#args.id}, Args#args.debugval). 56 | 57 | -------------------------------------------------------------------------------- /src/outmodules/out_file_mini.erl: -------------------------------------------------------------------------------- 1 | %% output module - output only the ip and the result to a file 2 | 3 | -module(out_file_mini). 4 | -behavior(out_behavior). 5 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 6 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 7 | 8 | -export([init/2, clean/1, output/2]). 9 | -export([get_description/0]). 10 | -export([get_arguments/0]). 11 | 12 | -define(ERR_ARG, "arg=[Output_file_path]"). 13 | -define(DESCRIPTION, "output to file (only ip and result)"). 14 | -define(ARGUMENTS, ["File path"]). 15 | 16 | -record(opt, {path, fd}). 17 | 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | % API 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | %% this is the initialization interface for this module 22 | %% returns {ok, Obj} or {error, Reason} 23 | init(_Scaninfo, [Path]) -> 24 | case file:open(Path, [write, delayed_write, {encoding, utf8}]) of 25 | {ok, Fd} -> 26 | Opts = #opt{path=Path, fd=Fd}, 27 | {ok, Opts}; 28 | {error, Reason} -> 29 | {error, Reason} 30 | end; 31 | init(_Scaninfo, _) -> 32 | {error, ?ERR_ARG}. 33 | 34 | %% this is the cleaning interface for this module 35 | %% returns ok or {error, Reason} 36 | clean(Object) -> 37 | file:close(Object#opt.fd). 38 | 39 | get_description() -> 40 | ?DESCRIPTION. 41 | 42 | get_arguments() -> 43 | ?ARGUMENTS. 44 | 45 | %% this is the output interface 46 | %% output'ing to file 47 | %% returns ok or {error, Reason} 48 | output(_Obj, []) -> 49 | ok; 50 | output(Obj, [H|T]) -> 51 | output_one(Obj, H), 52 | output(Obj, T). 53 | 54 | output_one(Object, {_Module, Target, _Port, Result}) -> 55 | Out = io_lib:fwrite("~p,~999999tp~n", [Target, Result]), 56 | file:write(Object#opt.fd, Out). 57 | -------------------------------------------------------------------------------- /src/outmodules/out_file_ip.erl: -------------------------------------------------------------------------------- 1 | %% output module - output to a file only the ip of the target when fp successful 2 | 3 | -module(out_file_ip). 4 | -behavior(out_behavior). 5 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 6 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 7 | 8 | -export([init/2, clean/1, output/2]). 9 | -export([get_description/0]). 10 | -export([get_arguments/0]). 11 | 12 | -define(ERR_ARG, "arg=[Output_file_path]"). 13 | -define(DESCRIPTION, "output to stdout (only ip)"). 14 | -define(ARGUMENTS, ["File path"]). 15 | 16 | -record(opt, {path, fd}). 17 | 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | % API 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | %% this is the initialization interface for this module 22 | %% returns {ok, Obj} or {error, Reason} 23 | init(_Scaninfo, [Path]) -> 24 | case file:open(Path, [write, delayed_write, {encoding, utf8}]) of 25 | {ok, Fd} -> 26 | Opts = #opt{path=Path, fd=Fd}, 27 | {ok, Opts}; 28 | {error, Reason} -> 29 | {error, Reason} 30 | end; 31 | init(_Scaninfo, _) -> 32 | {error, ?ERR_ARG}. 33 | 34 | %% this is the cleaning interface for this module 35 | %% returns ok or {error, Reason} 36 | clean(Object) -> 37 | file:close(Object#opt.fd). 38 | 39 | get_description() -> 40 | ?DESCRIPTION. 41 | 42 | get_arguments() -> 43 | ?ARGUMENTS. 44 | 45 | %% this is the output interface 46 | %% output'ing to file 47 | %% returns ok or {error, Reason} 48 | output(_Obj, []) -> 49 | ok; 50 | output(Obj, [H|T]) -> 51 | output_one(Obj, H), 52 | output(Obj, T). 53 | 54 | output_one(Object, {_Mod, {A,B,C,D}, _Port, {{ok,result},_Result}}) -> 55 | Out = io_lib:fwrite("~p.~p.~p.~p~n", [A,B,C,D]), 56 | file:write(Object#opt.fd, Out); 57 | output_one(_Object, _Msg) -> 58 | ok. 59 | 60 | -------------------------------------------------------------------------------- /src/outmodules/out_file_resultonly.erl: -------------------------------------------------------------------------------- 1 | %% output module - output only the result of successful fp to a file 2 | 3 | -module(out_file_resultonly). 4 | -behavior(out_behavior). 5 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 6 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 7 | 8 | -export([init/2, clean/1, output/2]). 9 | -export([get_description/0]). 10 | -export([get_arguments/0]). 11 | 12 | -define(ERR_ARG, "arg=[Output_file_path]"). 13 | -define(DESCRIPTION, "output to file (only result)"). 14 | -define(ARGUMENTS, ["File path"]). 15 | 16 | -record(opt, {path, fd}). 17 | 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | % API 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | %% this is the initialization interface for this module 22 | %% returns {ok, Obj} or {error, Reason} 23 | init(_Scaninfo, [Path]) -> 24 | case file:open(Path, [write, delayed_write, {encoding, utf8}]) of 25 | {ok, Fd} -> 26 | Opts = #opt{path=Path, fd=Fd}, 27 | {ok, Opts}; 28 | {error, Reason} -> 29 | {error, Reason} 30 | end; 31 | init(_Scaninfo, _) -> 32 | {error, ?ERR_ARG}. 33 | 34 | %% this is the cleaning interface for this module 35 | %% returns ok or {error, Reason} 36 | clean(Object) -> 37 | file:close(Object#opt.fd). 38 | 39 | get_description() -> 40 | ?DESCRIPTION. 41 | 42 | get_arguments() -> 43 | ?ARGUMENTS. 44 | 45 | %% this is the output interface 46 | %% output'ing to file 47 | %% returns ok or {error, Reason} 48 | output(_Obj, []) -> 49 | ok; 50 | output(Obj, [H|T]) -> 51 | output_one(Obj, H), 52 | output(Obj, T). 53 | 54 | output_one(Object, {_Module, _Target, _Port, {{ok,result},Result}}) -> 55 | Out = io_lib:fwrite("~999999tp~n", [Result]), 56 | file:write(Object#opt.fd, Out); 57 | output_one(_Object, _Message) -> 58 | ok. 59 | -------------------------------------------------------------------------------- /src/outmodules/out_behavior.erl: -------------------------------------------------------------------------------- 1 | %%% This modules set the required functions to implement in any output module. 2 | 3 | -module(out_behavior). 4 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 5 | -export([behaviour_info/1]). 6 | 7 | behaviour_info(callbacks) -> 8 | [ 9 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 10 | %%% init(Scaninfo, Options) 11 | %%% @return Object 12 | %%% Scaninfo contains information on the scan 13 | %%% Options is the options given to the module 14 | %%% (mod:opts) 15 | %%% Object is a record defining the informations 16 | %%% needed by the output module. 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | {init, 2}, 19 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 20 | %%% clean(Object, Scaninfo) 21 | %%% Object is a record defining the informations 22 | %%% needed by the output module. 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | {clean, 1}, 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | %%% get_description() 27 | %%% Used by ./scannerl -l to have description of the 28 | %%% output modules 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | {get_description, 0}, 31 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 32 | %%% get_arguments() 33 | %%% Used by ./scannerl -l to have the arguments of the 34 | %%% output modules 35 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 36 | {get_arguments, 0}, 37 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 38 | %%% output(Object, Msg) 39 | %%% Object is a record defining the informations 40 | %%% needed by the output module. 41 | %%% Msg is the data to write to the output 42 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 43 | {output, 2} 44 | ]; 45 | behaviour_info(_) -> 46 | undefined. 47 | -------------------------------------------------------------------------------- /src/fpmodules/fp_chargen.erl: -------------------------------------------------------------------------------- 1 | %%% Chargen fingerprinting module 2 | %%% 3 | %%% Output: 4 | %%% {{ok,result}, [chargen, {amplification_factor, Factor}]} 5 | %%% 6 | 7 | -module(fp_chargen). 8 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 9 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 10 | -behavior(fp_module). 11 | 12 | -include("../includes/args.hrl"). 13 | 14 | -export([get_default_args/0]). 15 | -export([callback_next_step/1]). 16 | -export([get_description/0]). 17 | -export([get_arguments/0]). 18 | 19 | -define(TIMEOUT, 3000). % milli-seconds 20 | -define(PORT, 19). % chargen port 21 | -define(TYPE, udp). % transport type 22 | -define(MAXPKT, 1). % max packet expected 23 | 24 | % chargen will discard the data anyway 25 | % but for the sake of the amplification calculation 26 | % and since the minimum payload of UDP is 18 bytes 27 | % here are some bytes 28 | -define(CHARGENDATA, [ 29 | 16#0a,16#0a,16#0a,16#0a, 30 | 16#0a,16#0a,16#0a,16#0a, 31 | 16#0a,16#0a,16#0a,16#0a, 32 | 16#0a,16#0a,16#0a,16#0a, 33 | 16#0a,16#0a 34 | ]). 35 | -define(QUERYSZ, length(?CHARGENDATA)). 36 | 37 | -define(DESCRIPTION, "UDP/19: Chargen amplification factor identification"). 38 | -define(ARGUMENTS, []). 39 | 40 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 41 | %% API 42 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 43 | % public API to get {port, timeout} 44 | get_default_args() -> 45 | #args{module=?MODULE, type=udp, port=?PORT, 46 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 47 | 48 | get_description() -> 49 | ?DESCRIPTION. 50 | 51 | get_arguments() -> 52 | ?ARGUMENTS. 53 | 54 | callback_next_step(Args) when Args#args.moddata == undefined -> 55 | {continue, Args#args.maxpkt, ?CHARGENDATA, true}; 56 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 57 | {result, {{error,unknown}, timeout}}; 58 | callback_next_step(Args) -> 59 | Amp = byte_size(Args#args.datarcv) / ?QUERYSZ, 60 | Res = [chargen, {amplification_factor, Amp}], 61 | {result, {{ok, result}, Res}}. 62 | 63 | -------------------------------------------------------------------------------- /src/fpmodules/fp_module.erl: -------------------------------------------------------------------------------- 1 | %%% This modules set the required functions to implement in any module we create 2 | 3 | -module(fp_module). 4 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 5 | -export([behaviour_info/1]). 6 | 7 | behaviour_info(callbacks) -> 8 | [ 9 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 10 | %%% get_default_args() 11 | %%% @return #args record (module, port, type, timeout) 12 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13 | {get_default_args, 0}, 14 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 15 | %%% get_description() 16 | %%% @return String text 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | {get_description, 0}, 19 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 20 | %%% get_arguments() 21 | %%% @return String text 22 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 23 | {get_arguments, 0}, 24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 25 | %%% callback_next_step(#args record) 26 | %%% @return {result, Result} | {continue, Nbpacket, Payload, ModData} 27 | %%% | {restart, {Target, Port}, ModData} 28 | %%% 29 | %%% result: FSM will send result to master 30 | %%% Result format is: 31 | %%% {{ok,result}, ResultStatus} 32 | %%% {{error,up}, Reason} 33 | %%% {{error,unknown}, Reason} 34 | %%% ResultStatus format is atom or List 35 | %%% 36 | %%% continue: send data from the same socket, 37 | %%% does not reopen if socket closed. 38 | %%% Nbpacket is number of packets to receive 39 | %%% Payload is data to send to the target 40 | %%% ModData is internal module data 41 | %%% 42 | %%% restart: open a new socket using Target,Port 43 | %%% Target is the new domain/ip addr to target 44 | %%% Port is the new port to target 45 | %%% ModData is internal module data 46 | %%% Use Target=undefined and Port=Undefined to reuse 47 | %%% the same target/port 48 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 49 | {callback_next_step, 1} 50 | ]; 51 | behaviour_info(_) -> 52 | undefined. 53 | -------------------------------------------------------------------------------- /src/fpmodules/fp_mqtt.erl: -------------------------------------------------------------------------------- 1 | %%% MQTT fingerprinting module 2 | %%% 3 | %%% Output: 4 | %%% mqtt or not_mqtt atoms 5 | %%% 6 | 7 | -module(fp_mqtt). 8 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 9 | 10 | -behavior(fp_module). 11 | 12 | -include("../includes/args.hrl"). 13 | 14 | -export([callback_next_step/1]). 15 | -export([get_default_args/0]). 16 | -export([get_description/0]). 17 | -export([get_arguments/0]). 18 | 19 | %% our record for this fingerprint 20 | -define(TIMEOUT, 3000). % milli-seconds 21 | -define(PORT, 1883). % HTTP port 22 | -define(TYPE, tcp). % transport type 23 | -define(MAXPKT, 1). % max packet expected 24 | -define(DESCRIPTION, "TCP/1883: MQTT identification"). 25 | -define(ARGUMENTS, []). 26 | 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 | %% API 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | % public API to get {port, timeout} 31 | get_default_args() -> 32 | #args{module=?MODULE, type=?TYPE, port=?PORT, 33 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 34 | 35 | get_description() -> 36 | ?DESCRIPTION. 37 | 38 | get_arguments() -> 39 | ?ARGUMENTS. 40 | 41 | % callback 42 | callback_next_step(Args) when Args#args.moddata == undefined -> 43 | % first packet 44 | debug(Args, "first packet"), 45 | {continue, Args#args.maxpkt, get_payload(), true}; 46 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 47 | % no packet received 48 | debug(Args, "no packet received"), 49 | {result, {{error, up}, timeout}}; 50 | callback_next_step(Args) -> 51 | debug(Args, "a packet received"), 52 | {result, parse_payload(Args#args.datarcv)}. 53 | 54 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 55 | %% debug 56 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 57 | % send debug 58 | debug(Args, Msg) -> 59 | utils:debug(fpmodules, Msg, 60 | {Args#args.target, Args#args.id}, Args#args.debugval). 61 | 62 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 63 | %% utils 64 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 65 | get_payload() -> 66 | utils_mqtt:forge_connect(). 67 | 68 | parse_payload(Pkt) -> 69 | {Val, _Res} = utils_mqtt:parse(Pkt), 70 | case Val of 71 | false -> 72 | {{error, up}, not_mqtt}; 73 | true -> 74 | {{ok, result}, mqtt} 75 | end. 76 | 77 | -------------------------------------------------------------------------------- /src/fpmodules/fp_mqtts.erl: -------------------------------------------------------------------------------- 1 | %%% MQTT over SSL fingerprinting module 2 | %%% 3 | %%% Output: 4 | %%% mqtt or not_mqtt atoms 5 | %%% 6 | 7 | -module(fp_mqtts). 8 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 9 | 10 | -behavior(fp_module). 11 | 12 | -include("../includes/args.hrl"). 13 | 14 | -export([callback_next_step/1]). 15 | -export([get_default_args/0]). 16 | -export([get_description/0]). 17 | -export([get_arguments/0]). 18 | 19 | %% our record for this fingerprint 20 | -define(TIMEOUT, 3000). % milli-seconds 21 | -define(PORT, 8883). % HTTP port 22 | -define(TYPE, ssl). % transport type 23 | -define(MAXPKT, 1). % max packet expected 24 | -define(DESCRIPTION, "TCP/8883: MQTT over SSL identification"). 25 | -define(ARGUMENTS, []). 26 | 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 | %% API 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | % public API to get {port, timeout} 31 | get_default_args() -> 32 | #args{module=?MODULE, type=?TYPE, port=?PORT, 33 | timeout=?TIMEOUT, fsmopts=[{sslcheck,false}], maxpkt=?MAXPKT}. 34 | 35 | get_description() -> 36 | ?DESCRIPTION. 37 | 38 | get_arguments() -> 39 | ?ARGUMENTS. 40 | 41 | % callback 42 | callback_next_step(Args) when Args#args.moddata == undefined -> 43 | % first packet 44 | debug(Args, "first packet"), 45 | {continue, Args#args.maxpkt, get_payload(), true}; 46 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 47 | % no packet received 48 | debug(Args, "no packet received"), 49 | {result, {{error, up}, timeout}}; 50 | callback_next_step(Args) -> 51 | debug(Args, "a packet received"), 52 | {result, parse_payload(Args#args.datarcv)}. 53 | 54 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 55 | %% debug 56 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 57 | % send debug 58 | debug(Args, Msg) -> 59 | utils:debug(fpmodules, Msg, 60 | {Args#args.target, Args#args.id}, Args#args.debugval). 61 | 62 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 63 | %% utils 64 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 65 | get_payload() -> 66 | utils_mqtt:forge_connect(). 67 | 68 | parse_payload(Pkt) -> 69 | {Val, _Res} = utils_mqtt:parse(Pkt), 70 | case Val of 71 | false -> 72 | {{error, up}, not_mqtt}; 73 | true -> 74 | {{ok, result}, mqtt} 75 | end. 76 | 77 | -------------------------------------------------------------------------------- /src/fpmodules/fp_fox.erl: -------------------------------------------------------------------------------- 1 | %%% FOX fingerprinting module 2 | %%% 3 | %%% Output: 4 | %%% true (is fox) or false (isn't fox) 5 | %%% 6 | 7 | -module(fp_fox). 8 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 9 | 10 | -behavior(fp_module). 11 | 12 | -include("../includes/args.hrl"). 13 | 14 | -export([callback_next_step/1]). 15 | -export([get_default_args/0]). 16 | -export([get_description/0]). 17 | -export([get_arguments/0]). 18 | 19 | %% our record for this fingerprint 20 | -define(TIMEOUT, 3000). % milli-seconds 21 | -define(PORT, 1911). % port 22 | -define(TYPE, tcp). % transport type 23 | -define(MAXPKT, 50). % max packet expected 24 | -define(DESCRIPTION, "TCP/1911: FOX identification"). 25 | -define(ARGUMENTS, []). 26 | 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 | %% API 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | % public API to get {port, timeout} 31 | get_default_args() -> 32 | #args{module=?MODULE, type=?TYPE, port=?PORT, 33 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 34 | 35 | get_description() -> 36 | ?DESCRIPTION. 37 | 38 | get_arguments() -> 39 | ?ARGUMENTS. 40 | 41 | % callback 42 | callback_next_step(Args) when Args#args.moddata == undefined -> 43 | % first packet 44 | debug(Args, "first packet"), 45 | {continue, Args#args.maxpkt, get_payload(), true}; 46 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 47 | % no packet received 48 | debug(Args, "no packet received"), 49 | {result, {{error, up}, timeout}}; 50 | callback_next_step(Args) -> 51 | debug(Args, "a packet received"), 52 | {result, parse_payload(Args#args.datarcv)}. 53 | 54 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 55 | %% debug 56 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 57 | % send debug 58 | debug(Args, Msg) -> 59 | utils:debug(fpmodules, Msg, 60 | {Args#args.target, Args#args.id}, Args#args.debugval). 61 | 62 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 63 | %% utils 64 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 65 | get_payload() -> 66 | utils_fox:forge_min_hello(). 67 | 68 | parse_payload(<< >>) -> 69 | {{error, up}, not_fox}; 70 | parse_payload(Pkt) -> 71 | Ret = utils_fox:parse_full(binary_to_list(Pkt)), 72 | case Ret of 73 | {false, Proto} -> 74 | {{error, up}, Proto}; 75 | {false} -> 76 | {{error, up}, not_fox}; 77 | {true, Stuff} -> 78 | {{ok, result}, Stuff} 79 | end. 80 | 81 | -------------------------------------------------------------------------------- /src/fpmodules/fp_bacnet.erl: -------------------------------------------------------------------------------- 1 | %%% bacnet fingerprinting module 2 | %%% 3 | %%% Output: 4 | %%% {{ok,result}, true} 5 | %%% 6 | 7 | -module(fp_bacnet). 8 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 9 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 10 | 11 | -behavior(fp_module). 12 | -include("../includes/args.hrl"). 13 | 14 | -export([callback_next_step/1]). 15 | -export([get_default_args/0]). 16 | -export([get_description/0]). 17 | -export([get_arguments/0]). 18 | 19 | %% our records for this fingerprint 20 | -define(TIMEOUT, 3000). % milli-seconds 21 | -define(PORT, 47808). % bacnet port 22 | -define(TYPE, udp). % transport type 23 | -define(MAXPKT, 2). % max packet expected 24 | -define(BACNETMAGIC, 16#81). 25 | -define(BACNET, [?BACNETMAGIC, % bacnet magic 26 | 16#0a, % unicast NPDU 27 | 16#00,16#23, % payload length 28 | 16#01, % protocol version 29 | 16#04, % expect a reply 30 | 16#00, % confirmed-request PDU with flags 0000 31 | 16#05, % max response size (up to 1476 bytes) 32 | 16#00, % invoke ID 33 | 16#0e, % ReadPropertyMultiple 34 | 16#0c, % open context tag 0 35 | 16#02,16#3f,16#ff,16#ff, % object identifier (Device) 36 | 16#1e, % open context tag 1 37 | 16#09, % open property tag 38 | 16#4b, % object ID 39 | 16#09, % new property 40 | 16#78, % vendor id 41 | 16#09,16#79, % object 42 | 16#09,16#2c, % object 43 | 16#09,16#0c, % object 44 | 16#09,16#4d, % object 45 | 16#09,16#46, % object 46 | 16#09,16#1c, % object 47 | 16#09, % last tag open 48 | 16#3a, % location 49 | 16#1f % close list tag 50 | ]). 51 | -define(DESCRIPTION, "UDP/47808: Bacnet identification"). 52 | -define(ARGUMENTS, []). 53 | 54 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 55 | %% API 56 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 57 | get_default_args() -> 58 | #args{module=?MODULE, type=?TYPE, port=?PORT, 59 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 60 | 61 | get_description() -> 62 | ?DESCRIPTION. 63 | 64 | get_arguments() -> 65 | ?ARGUMENTS. 66 | 67 | callback_next_step(Args) when Args#args.moddata==undefined -> 68 | {continue, Args#args.maxpkt, ?BACNET, true}; 69 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 70 | {result, {{error,unknown}, timeout}}; 71 | callback_next_step(Args) -> 72 | parse_bacnet(binary_to_list(Args#args.datarcv)). 73 | 74 | parse_bacnet([H|_T]) -> 75 | case H of 76 | ?BACNETMAGIC -> 77 | {result, {{ok,result}, true}}; 78 | _ -> 79 | {result, {{error,up}, unexpected_data}} 80 | end. 81 | 82 | -------------------------------------------------------------------------------- /src/fpmodules/fp_mysql_greeting.erl: -------------------------------------------------------------------------------- 1 | %%% MySql fingerprint module 2 | %%% returns MySQL version string 3 | %%% 4 | %%% Output: 5 | %%% server version string 6 | %%% 7 | 8 | -module(fp_mysql_greeting). 9 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 10 | 11 | -behavior(fp_module). 12 | -include("../includes/args.hrl"). 13 | 14 | -export([callback_next_step/1]). 15 | -export([get_default_args/0]). 16 | -export([get_description/0]). 17 | -export([get_arguments/0]). 18 | 19 | -define(TIMEOUT, 3000). % milli-seconds 20 | -define(PORT, 3306). % mysql port 21 | -define(TYPE, tcp). % transport type 22 | -define(MAXPKT, 1). % only greeting packet is needed 23 | -define(DESCRIPTION, "TCP/3306: Mysql version identification"). 24 | -define(ARGUMENTS, []). 25 | 26 | -define(PROTO, 16#0a). % only interested in protocol version 10 27 | 28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | %% API 30 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 31 | get_default_args() -> 32 | #args{module=?MODULE, type=?TYPE, port=?PORT, 33 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 34 | 35 | get_description() -> 36 | ?DESCRIPTION. 37 | 38 | get_arguments() -> 39 | ?ARGUMENTS. 40 | 41 | callback_next_step(Args) when Args#args.moddata == undefined -> 42 | {continue, Args#args.maxpkt, "", true}; 43 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 44 | {result, {{error,up}, timeout}}; 45 | callback_next_step(Args) -> 46 | {result, parse_header(Args, Args#args.datarcv)}. 47 | 48 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 49 | %% debug 50 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 51 | % send debug 52 | debug(Args, Msg) -> 53 | utils:debug(fpmodules, Msg, 54 | {Args#args.target, Args#args.id}, Args#args.debugval). 55 | 56 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 57 | %% parsing 58 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 59 | %% parse the header 60 | parse_header(Args, 61 | << 62 | Pldlen:24/little, % 3 bytes payload length 63 | _:8, % 1 byte sequence id or packet nb 64 | Pld/binary % content 65 | >>) -> 66 | debug(Args, "parsing header succeeded"), 67 | case byte_size(Pld) == Pldlen of 68 | true -> 69 | parse_content(Args, Pld); 70 | false -> 71 | debug(Args, "bad size provided"), 72 | {{error, up}, unexpected_data} 73 | end; 74 | parse_header(Args, _) -> 75 | debug(Args, "parsing header failed"), 76 | {{error, up}, unexpected_data}. 77 | 78 | %% parse the content 79 | parse_content(_Args, 80 | << 81 | ?PROTO:8, % protocol version 82 | Rest/binary 83 | >>) -> 84 | case find_null(Rest, 1) of 85 | error -> 86 | {{error, up}, unexpected_data}; 87 | {Version, _Bin} -> 88 | {{ok, result}, [binary_to_list(Version)]} 89 | end; 90 | parse_content(_Args, _) -> 91 | {{error, up}, unexpected_data}. 92 | 93 | %% find null terminated string 94 | find_null(Bin, Pos) -> 95 | case Bin of 96 | <> -> 97 | % found 98 | {Start, Rest}; 99 | <> -> 100 | % not found 101 | error; 102 | <<_:Pos/binary, _/binary>>=B -> 103 | % go forward 104 | find_null(B, Pos+1) 105 | end. 106 | -------------------------------------------------------------------------------- /src/scannerl_worker_sup.erl: -------------------------------------------------------------------------------- 1 | %% OTP Supervisor for workers. Very simple in the sense that children 2 | %% will be added dynamically. 3 | -module(scannerl_worker_sup). 4 | -behaviour(supervisor). 5 | -author("Antoine Junod - antoine.junod@kudelskisecurity.com"). 6 | 7 | %% Export interface 8 | -export([start_link/1]). 9 | 10 | %% Export callbacks 11 | -export([init/1]). 12 | 13 | % called to start the supervisor 14 | % MFA is the argument to provide to "init" 15 | start_link(MFA = {_M,_F,_A}) -> 16 | %io:format("[~s] start_link called~n",[?MODULE]), 17 | %% might be useful for debuggin 18 | %process_flag(trap_exit, true), 19 | 20 | supervisor:start_link( 21 | %% For the sake of debugging and developmen simplicity, we name 22 | %% our supervisor with the module name (scannerl_worker_sup 23 | %% here). 24 | {local, ?MODULE}, 25 | %% The callback module is ourself 26 | ?MODULE, 27 | %% Arguments provided to the init callback. In our case we 28 | %% provide the Module, Function and arguments common to all 29 | %% children that will take place below that supervisor. 30 | MFA). 31 | 32 | % this is called by "start_link" 33 | % MFA defines: 34 | % -M-odule of worker 35 | % -F-unction of worker to run 36 | % -A-rgument of worker 37 | init({M,F,A}) -> 38 | %io:format("[~s] init called~n",[?MODULE]), 39 | SupFlags = { 40 | 41 | %% strategy: All child processes are dynamically added instances 42 | %% of the same code 43 | simple_one_for_one, 44 | 45 | %% Intensity and Period, in seconds. The selected values mean 46 | %% that if more than 1 restart occurs in a period of 5 seconds, 47 | %% all childs are killed and then the supervisor itself is 48 | %% killed. Processes will die if for example we cannot open a 49 | %% socket. think about if we need to handle that in the 50 | %% worker itself by catching the exit signals, because it's not 51 | %% an expected behavior to kill all the processes if we cannot 52 | %% open a new socket. If I understand correctly, we don't care 53 | %% about these because we plan to have children of the type 54 | %% 'temporary', which are not restarted in case they die. 55 | 1, 5}, 56 | 57 | ChildSpec = { 58 | %% Id: used internally to identify the ChildSepc. We use the 59 | %% module name of the future children 60 | M, 61 | 62 | %% Start: module, function, argument of the worker to start. In 63 | %% our case, is provided to init by start_link, which gets it in 64 | %% arguments too. 65 | {M, F, A}, 66 | 67 | %% Restart: set it to temporary so that child processes will 68 | %% never be restarted. I guess it makes our Intensity and Period 69 | %% parameters caduc. 70 | temporary, 71 | 72 | %% Shutdown: set to 4 seconds. That's actually the timeout value 73 | %% plus one second. make 74 | %% that dynamic, as timeout for workers should be provided as 75 | %% argument. 76 | 4, 77 | 78 | %% Type: we're working with workers here. So set it to 79 | %% worker. This is also the default value. 80 | worker, 81 | 82 | %% Modules: Using the rule of thumb from the doc. As the 83 | %% processes will be probably be gen_server or gen_fsm it should 84 | %% be a list of one element, the callback module. This is the 85 | %% default value. 86 | [M]}, 87 | 88 | {ok,{SupFlags, [ChildSpec]}}. 89 | 90 | -------------------------------------------------------------------------------- /src/includes/opts.hrl: -------------------------------------------------------------------------------- 1 | % record to store the command line arguments as well 2 | % as other information retrieved from the modules to use 3 | % (output, fingerprinting, ...) 4 | -record(opts, { 5 | module, % the module to use for fingerprinting 6 | modarg, % module argument(s) (list of strings) 7 | target, % list of target provided on CLI 8 | targetfile, % file path containing target(s) 9 | domain, % list of domain provided on CLI 10 | domainfile, % file path containing domain(s) 11 | slave, % list of slave node provided on CLI 12 | slavefile, % file path containing slave node 13 | % ------------------------------------------------------------------- 14 | % optional arguments 15 | % ------------------------------------------------------------------- 16 | port, % [optional] port to fingerprint 17 | timeout, % [optional] fp module timeout in ms 18 | stimeout, % [optional] slave connection timeout in ms 19 | maxpkt, % [optional] fp module maxpkt 20 | checkwww, % [optional] dns will try to query www.target 21 | output, % [optional] list of output modules to use 22 | retry, % [optional] number of retry 23 | outmode, % [optional] output mode (0:on master, 1:on slave, 2:on broker) 24 | dry, % [optional] perform a dry run 25 | debugval, % [optional] enable debug, see below 26 | minrange, % [optional] sub-divide cidr bigger than this 27 | queuemax, % [optional] max unprocessed results in queue 28 | maxchild, % [optional] max nb of simult. process per broker 29 | privports, % [optional] use privilege ports as source (between 1-1024) 30 | progress, % [optional] show progress on master 31 | nosafe, % [optional] keep going even if some slaves fail 32 | config, % [optional] config file if any 33 | msg_port, % [optional] port to listen for message (optional) 34 | sockopt, % [optional] socket argument 35 | % ------------------------------------------------------------------- 36 | % fsm internals 37 | % ------------------------------------------------------------------- 38 | fsmopts, % [optional] fsm options 39 | % ------------------------------------------------------------------- 40 | % internals 41 | % ------------------------------------------------------------------- 42 | scaninfo, % [internal] scaninfo for output module 43 | slmodule, % [internal] modules to be sent to the slaves 44 | user, % [internal] user data for master/slave comm 45 | pause % [internal] pause 46 | }). 47 | 48 | % record to store the scaninfo (mostly for output modules) 49 | -record(scaninfo, { 50 | version, % scannerl version 51 | fpmodule, % fingerprinting module used 52 | port, % target port 53 | debugval % debugval 54 | }). 55 | 56 | % debugging levels 57 | % see -V 58 | -record(debugval, { 59 | value, % value 60 | % where to print (remote: message passing, local: stdout) 61 | % only use for debugging on single node 62 | where=remote, 63 | level1, % fpmodules 64 | level2, % outmodules 65 | level4, % broker 66 | level8, % master 67 | level16, % scannerl escript 68 | level32, % N/A 69 | level64, % N/A 70 | level128 % additional info 71 | }). 72 | 73 | -------------------------------------------------------------------------------- /src/includes/args.hrl: -------------------------------------------------------------------------------- 1 | % record to store the arguments that are given 2 | % to the fingerprinting module for each of the provided 3 | % targets. It also contains internal args used by the FSM. 4 | -record(args, { 5 | id, % the id of this process 6 | parent, % parent pid 7 | % ---------------------------------------------------------------------- 8 | % cli arguments 9 | % ---------------------------------------------------------------------- 10 | module, % the module to use for fingerprinting 11 | target, % [original] the target to fingerprint 12 | port, % [original] port to fingerprint if diff from global 13 | tgtarg=[], % [optional] target argument if any 14 | retry=0, % [optional] number of retry when timed out 15 | checkwww, % [optional] dns will check for www.target 16 | arguments=[], % [optional] module arguments (list of strings) 17 | debugval, % [optional] debugging value 18 | privports, % [optional] use privilege ports only 19 | sockopt, % [optional] socket argument 20 | % ---------------------------------------------------------------------- 21 | % module arguments 22 | % ---------------------------------------------------------------------- 23 | type, % fsm type (tcp, udp, ssl, ...) 24 | timeout=3000, % [optional] fp module timeout in ms 25 | stimeout=10000, % [optional] slave connection timeout in ms 26 | maxpkt=infinity, % [optional] max packet to receive 27 | dependencies=[], % [optional] of modules needed by the module to work 28 | % ---------------------------------------------------------------------- 29 | % fsm arguments 30 | % ---------------------------------------------------------------------- 31 | fsmopts=[], % [optional] fsm options 32 | % ---------------------------------------------------------------------- 33 | % output specific 34 | % when direct == true, outobj contains the output objects 35 | % when direct == false, parent is where result should be sent 36 | % ---------------------------------------------------------------------- 37 | outobj, % output objects 38 | direct=false, % direct output results (do not send to parent) 39 | % ---------------------------------------------------------------------- 40 | % FSM internals 41 | % ---------------------------------------------------------------------- 42 | ctarget, % effective target to connect to 43 | cport, % effective port to connect to 44 | ipaddr, % the ip of the target to fingerprint 45 | eaccess_retry=0, % retry cnt when receiving a eaccess with privports 46 | eaccess_max=2, % max retry when eacces occurs 47 | retrycnt, % retry counter 48 | packetrcv = 0, % amount of packet received 49 | sending = false, % helper for fsm 50 | datarcv = << >>, % the data packet received 51 | payload = << >>, % the payload to send 52 | socket, % socket used to connect 53 | nbpacket, % amount of packets to wait for 54 | result={{error,unknown},fsm_issue}, % the result data 55 | sndreason, % the reason when sending 56 | rcvreason, % the reason when receiving 57 | % ---------------------------------------------------------------------- 58 | % runtime reserved space 59 | % ---------------------------------------------------------------------- 60 | moddata % fp_module internal data 61 | }). 62 | -------------------------------------------------------------------------------- /src/outmodules/out_csv.erl: -------------------------------------------------------------------------------- 1 | %% output module - output in CSV format 2 | 3 | -module(out_csv). 4 | -behavior(out_behavior). 5 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 6 | 7 | -export([init/2, clean/1, output/2, elem_to_string/1]). 8 | -export([get_description/0]). 9 | -export([get_arguments/0]). 10 | 11 | -define(ERR_ARG, "arg=[saveall]"). 12 | -define(SEP, ","). 13 | -define(DESCRIPTION, "output to csv"). 14 | -define(ARGUMENTS, ["[true|false] save everything [Default:true]"]). 15 | 16 | -record(opt, { 17 | saveall 18 | }). 19 | 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | % API 22 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 23 | %% this is the initialization interface for this module 24 | %% returns {ok, Obj} or {error, Reason} 25 | init(_Scaninfo, [Saveall]) -> 26 | {ok, #opt{saveall=list_to_atom(Saveall)}}; 27 | init(_Scaninfo, _) -> 28 | {error, ?ERR_ARG}. 29 | 30 | %% this is the cleaning interface for this module 31 | %% returns ok or {error, Reason} 32 | clean(_Obj) -> 33 | ok. 34 | 35 | get_description() -> 36 | ?DESCRIPTION. 37 | 38 | get_arguments() -> 39 | ?ARGUMENTS. 40 | 41 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 42 | % output 43 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 44 | % convert to string 45 | elem_to_string({_,_,_,_}=Ip) -> 46 | [inet_parse:ntoa(Ip)]; 47 | elem_to_string(Elem) when is_atom(Elem) -> 48 | atom_to_list(Elem); 49 | elem_to_string(Elem) when is_tuple(Elem) -> 50 | tuple_to_list(Elem); 51 | elem_to_string(Elem) when is_binary(Elem) -> 52 | binary_to_list(Elem); 53 | elem_to_string(Elem) when is_float(Elem) -> 54 | float_to_list(Elem, [{decimals, 20}]); 55 | elem_to_string(Elem) when is_integer(Elem) -> 56 | integer_to_list(Elem); 57 | elem_to_string(Elem) when is_integer(Elem) -> 58 | integer_to_list(Elem); 59 | elem_to_string(Elem) -> 60 | Elem. 61 | 62 | % print stderr 63 | output_err(Elem) -> 64 | Val = io_lib:fwrite("~ts~n", [Elem]), 65 | io:put_chars(standard_error, Val). 66 | 67 | % print stdout 68 | output_elem(Elem) -> 69 | io:fwrite("~ts~n", [Elem]). 70 | 71 | % list to string 72 | list_to_strings(List) -> 73 | case io_lib:printable_list(List) of 74 | true -> 75 | List; 76 | false -> 77 | Fixed = lists:map(fun elem_to_string/1, List), 78 | string:join(Fixed, ?SEP) 79 | end. 80 | 81 | % list to CSV 82 | handle_elem(List) -> 83 | Fixed = list_to_strings(List), 84 | output_elem(Fixed). 85 | 86 | % handle list 87 | handle_list([]) -> 88 | ok; 89 | handle_list([H|T]) when is_list(H) -> 90 | handle_elem(H), 91 | handle_list(T); 92 | handle_list(List) -> 93 | handle_elem(List). 94 | 95 | % process result 96 | handle_result(Res) when is_list(Res) -> 97 | handle_list(Res); 98 | handle_result(Res) -> 99 | output_elem(elem_to_string(Res)). 100 | 101 | % process error 102 | handle_error(Tgt, State, Err) -> 103 | Fixed = list_to_strings(["ERROR", Tgt, State, Err]), 104 | output_err(Fixed). 105 | 106 | %% this is the output interface 107 | %% output'ing to stdout 108 | %% returns ok or {error, Reason} 109 | output(_Obj, []) -> 110 | ok; 111 | output(Obj, [H|T]) -> 112 | output_one(Obj, H), 113 | output(Obj, T). 114 | 115 | output_one(_Obj, {_Mod, _Tgt, _Port, {{ok, result}, [Msg]}}) -> 116 | handle_result(Msg); 117 | output_one(Obj, {_Mod, Tgt, _Port, {{error, State}, Err}}) when Obj#opt.saveall == true -> 118 | handle_error(Tgt, State, Err); 119 | output_one(_Obj, _) -> 120 | ok. 121 | 122 | -------------------------------------------------------------------------------- /src/utils/utils_msgserver.erl: -------------------------------------------------------------------------------- 1 | %% message server used to communicate with the master 2 | %% from the cli. This is a workaround around the fact 3 | %% erlang does not natively handle the linux signals. 4 | %% 5 | %% currently recognized message: 6 | %% - progress: display the progress 7 | %% - abort: abort the scan 8 | %% 9 | %% example: 10 | %% echo -n "abort" | nc -4u -q1 127.0.0.1 57005 11 | %% 12 | 13 | -module(utils_msgserver). 14 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 15 | 16 | -export([listen_udp/2]). 17 | -export([stop_udp/1]). 18 | 19 | -define(LOCALHOST, {127,0,0,1}). 20 | %-define(UDP_PORT, 57005). % 0xdead 21 | -define(MAX_PORT, 65535). 22 | -define(RETRY, 3). 23 | 24 | -define(UDP_OPT, [list, inet]). 25 | 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | % API 28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | listen_udp(Parent, Port) -> 30 | listen_udp(?LOCALHOST, Port, Parent). 31 | 32 | % returns either ok or {error, Reason} 33 | stop_udp(Port) when Port > ?MAX_PORT -> 34 | ok; 35 | stop_udp(Port) -> 36 | case gen_udp:open(0, ?UDP_OPT) of 37 | {ok, Socket} -> 38 | gen_udp:send(Socket, ?LOCALHOST, Port, "stop"), 39 | gen_udp:close(Socket); 40 | {error, Reason} -> 41 | {error, Reason} 42 | end. 43 | 44 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 45 | % UDP utils 46 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 47 | listen_udp(_Ip, Port, _Parent) when Port > ?MAX_PORT -> 48 | ok; 49 | listen_udp(Ip, Port, Parent) -> 50 | try_to_listen(Ip, Port, Parent, ?RETRY, ""). 51 | 52 | try_to_listen(_Ip, Port, Parent, 0, Reason) -> 53 | send_error(Parent, "UDP", Port, Reason); 54 | try_to_listen(Ip, Port, Parent, Cnt, _Reason) -> 55 | Opt = ?UDP_OPT ++ [{ip, Ip}], 56 | case gen_udp:open(Port, Opt) of 57 | {ok, Socket} -> 58 | send_ok(Parent, "UDP", Ip, Port), 59 | udp_loop(Socket, Parent); 60 | {error, NReason} -> 61 | try_to_listen(Ip, Port, Parent, Cnt-1, NReason) 62 | end. 63 | 64 | udp_loop(Socket, Parent) -> 65 | receive 66 | {udp, Socket, _Host, _Port, Message} -> 67 | case parse_message(Parent, Message) of 68 | stop -> 69 | gen_udp:close(Socket), 70 | ok; 71 | continue -> 72 | udp_loop(Socket, Parent) 73 | end 74 | end. 75 | 76 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 77 | % utils 78 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 | parse_message(_Parent, Message) when Message == "stop" -> 80 | % send by master to stop listening 81 | stop; 82 | parse_message(Parent, Message) when Message == "abort" -> 83 | % send to abort the scan 84 | send_message(Parent, Message), 85 | continue; 86 | parse_message(Parent, Message) when Message == "progress" -> 87 | % send to abort the scan 88 | send_message(Parent, Message), 89 | continue; 90 | parse_message(_Parent, _Message) -> 91 | % ignore message 92 | continue. 93 | 94 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 95 | % messaging 96 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 97 | send_error(Parent, Proto, Port, Reason) -> 98 | Parent ! {message, {error, Proto, Port, Reason}}. 99 | 100 | send_ok(Parent, Protocol, Ip, Port) -> 101 | Parent ! {message, {ok, Protocol, inet_parse:ntoa(Ip), Port}}. 102 | 103 | send_message(Parent, Message) -> 104 | Parent ! {message, {message, Message}}. 105 | 106 | -------------------------------------------------------------------------------- /src/fpmodules/fp_https_certif.erl: -------------------------------------------------------------------------------- 1 | %%% HTTPS fingerprint module to retrieve x509 certificate 2 | %%% 3 | %%% Output: 4 | %%% Certificate in PEM 5 | %%% 6 | 7 | -module(fp_https_certif). 8 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 9 | -behavior(fp_module). 10 | 11 | -include("../includes/args.hrl"). 12 | -include_lib("public_key/include/public_key.hrl"). 13 | 14 | -export([callback_next_step/1]). 15 | -export([get_default_args/0]). 16 | -export([get_description/0]). 17 | -export([get_arguments/0]). 18 | 19 | -define(TIMEOUT, 3000). %% milli-seconds 20 | -define(PORT, 443). %% HTTPS port 21 | -define(TYPE, ssl). %% transport type 22 | %% define SSL options 23 | -define(SSLOPTS, [{sslcheck,false}]). 24 | %-define(SSLOPTS, [{sslcheck,false}, {versions,['tlsv1.2']}]). 25 | %-define(SSLOPTS, [{sslcheck,false}, {versions,['tlsv1.2']}, {server_name_indication, disable}]). 26 | %-define(SSLOPTS, [{sslcheck,false}, {server_name_indication, disable}]). 27 | -define(MAXPKT, 5). 28 | -define(UALEN, 2). %% user-agent length 29 | -define(PAGE, "/"). 30 | -define(DESCRIPTION, "SSL/443: HTTPS certificate graber"). 31 | -define(ARGUMENTS, []). 32 | 33 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 34 | %% API 35 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 36 | % public API to get {port, timeout} 37 | get_default_args() -> 38 | #args{module=?MODULE, type=?TYPE, port=?PORT, 39 | timeout=?TIMEOUT, fsmopts=?SSLOPTS, maxpkt=?MAXPKT}. 40 | 41 | get_description() -> 42 | ?DESCRIPTION. 43 | 44 | get_arguments() -> 45 | ?ARGUMENTS. 46 | 47 | callback_next_step(Args) when Args#args.moddata == undefined -> 48 | % first packet 49 | {continue, Args#args.maxpkt, get_payload(Args#args.target), true}; 50 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 51 | % no packet received 52 | debug(Args, "no packet received"), 53 | {result, {{error, up}, timeout}}; 54 | callback_next_step(Args) -> 55 | % parse the result 56 | debug(Args, "packet received"), 57 | get_results(Args). 58 | 59 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 60 | %% response parser 61 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 62 | %% parse cert and return the results 63 | get_results(Args) -> 64 | case utils_ssl:get_certif(Args#args.socket) of 65 | {ok, Cert} -> 66 | {result, {{ok, result}, [Args#args.ipaddr, Cert]}}; 67 | {error, Reason} -> 68 | {result, {{error, up}, Reason}} 69 | end. 70 | 71 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 72 | %%% HTTPS packet request forger 73 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 74 | %% returns HTTPS payload 75 | randua(0, Acc) -> 76 | Acc; 77 | randua(N, Acc) -> 78 | randua(N - 1, [rand:uniform(26) + 96 | Acc]). 79 | 80 | %% return the payload 81 | payload(Host) -> 82 | Ua = randua(?UALEN, ""), 83 | Args = ["GET ", ?PAGE, " HTTP/1.1", "\r\n", "Host: ", Host, "\r\n", 84 | "User-Agent: ", Ua, "\r\n", 85 | "Accept: */*", "\r\n", 86 | "Language: en", "\r\n\r\n"], 87 | lists:concat(Args). 88 | 89 | %% return the payload with the target 90 | get_payload(Addr={_,_,_,_}) -> 91 | payload(inet:ntoa(Addr)); 92 | get_payload(Host) -> 93 | payload(Host). 94 | 95 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 96 | %% debug 97 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 98 | %% send debug 99 | debug(Args, Msg) -> 100 | utils:debug(fpmodules, Msg, 101 | {Args#args.target, Args#args.id}, Args#args.debugval). 102 | -------------------------------------------------------------------------------- /src/outmodules/out_csvfile.erl: -------------------------------------------------------------------------------- 1 | %% output module - output to file in CSV format 2 | 3 | -module(out_csvfile). 4 | -behavior(out_behavior). 5 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 6 | 7 | -export([init/2, clean/1, output/2]). 8 | -export([get_description/0]). 9 | -export([get_arguments/0]). 10 | 11 | -define(ERR_ARG, "arg=[saveall:Output_file_path]"). 12 | -define(SEP, ","). 13 | -define(DESCRIPTION, "output to csv file"). 14 | -define(ARGUMENTS, ["[true|false] save everything [Default:false]", "File path"]). 15 | 16 | -record(opt, { 17 | path, 18 | saveall, 19 | fd 20 | }). 21 | 22 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 23 | % API 24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 25 | %% this is the initialization interface for this module 26 | %% returns {ok, Obj} or {error, Reason} 27 | init(_Scaninfo, [Saveall, Path]) -> 28 | case file:open(Path, [write, delayed_write, {encoding, utf8}]) of 29 | {ok, Fd} -> 30 | Opts = #opt{path=Path, saveall=list_to_atom(Saveall), fd=Fd}, 31 | {ok, Opts}; 32 | {error, Reason} -> 33 | {error, Reason} 34 | end; 35 | init(_Scaninfo, _) -> 36 | {error, ?ERR_ARG}. 37 | 38 | %% this is the cleaning interface for this module 39 | %% returns ok or {error, Reason} 40 | clean(Object) -> 41 | file:close(Object#opt.fd). 42 | 43 | get_description() -> 44 | ?DESCRIPTION. 45 | 46 | get_arguments() -> 47 | ?ARGUMENTS. 48 | 49 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 50 | % output 51 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 52 | % convert to string 53 | elem_to_string({_,_,_,_}=Ip) -> 54 | [inet_parse:ntoa(Ip)]; 55 | elem_to_string(Elem) when is_atom(Elem) -> 56 | atom_to_list(Elem); 57 | elem_to_string(Elem) when is_tuple(Elem) -> 58 | tuple_to_list(Elem); 59 | elem_to_string(Elem) when is_binary(Elem) -> 60 | binary_to_list(Elem); 61 | elem_to_string(Elem) when is_float(Elem) -> 62 | float_to_list(Elem, [{decimals, 20}]); 63 | elem_to_string(Elem) when is_integer(Elem) -> 64 | integer_to_list(Elem); 65 | elem_to_string(Elem) when is_integer(Elem) -> 66 | integer_to_list(Elem); 67 | elem_to_string(Elem) -> 68 | Elem. 69 | 70 | % print line 71 | output_elem(Fd, Elem) -> 72 | file:write(Fd, Elem ++ "\n"). 73 | 74 | % list to string 75 | list_to_strings(List) -> 76 | case io_lib:printable_list(List) of 77 | true -> 78 | List; 79 | false -> 80 | Fixed = lists:map(fun elem_to_string/1, List), 81 | string:join(Fixed, ?SEP) 82 | end. 83 | 84 | % list to CSV 85 | handle_elem(Fd, List) -> 86 | Fixed = list_to_strings(List), 87 | output_elem(Fd, Fixed). 88 | 89 | % handle list 90 | handle_list(_Fd, []) -> 91 | ok; 92 | handle_list(Fd, [H|T]) when is_list(H) -> 93 | handle_elem(Fd, H), 94 | handle_list(Fd, T); 95 | handle_list(Fd, List) -> 96 | handle_elem(Fd, List). 97 | 98 | % process result 99 | handle_result(Fd, Res) when is_list(Res) -> 100 | handle_list(Fd, Res); 101 | handle_result(Fd, Res) -> 102 | output_elem(Fd, elem_to_string(Res)). 103 | 104 | % process error 105 | handle_error(Fd, Tgt, State, Err) -> 106 | Fixed = list_to_strings(["ERROR", Tgt, State, Err]), 107 | output_elem(Fd, Fixed). 108 | 109 | %% this is the output interface 110 | %% returns ok or {error, Reason} 111 | output(_Obj, []) -> 112 | ok; 113 | output(Obj, [H|T]) -> 114 | output_one(Obj, H), 115 | output(Obj, T). 116 | 117 | output_one(Obj, {_Mod, _Tgt, _Port, {{ok, result}, [Msg]}}) -> 118 | handle_result(Obj#opt.fd, Msg); 119 | output_one(Obj, {_Mod, Tgt, _Port, {{error, State}, Err}}) when Obj#opt.saveall == true -> 120 | handle_error(Obj#opt.fd, Tgt, State, Err); 121 | output_one(_Obj, _) -> 122 | ok. 123 | 124 | -------------------------------------------------------------------------------- /src/fpmodules/fp_pop3_certif.erl: -------------------------------------------------------------------------------- 1 | %%% POP3 STARTTLS certificate graber 2 | %%% 3 | %%% Output: 4 | %%% ip and certificate in pem 5 | %%% 6 | %%% https://tools.ietf.org/html/rfc2595 7 | %%% 8 | 9 | -module(fp_pop3_certif). 10 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 11 | -behavior(fp_module). 12 | 13 | -include("../includes/args.hrl"). 14 | 15 | -export([callback_next_step/1]). 16 | -export([get_default_args/0]). 17 | -export([get_description/0]). 18 | -export([get_arguments/0]). 19 | 20 | -define(TIMEOUT, 6000). % milli-seconds 21 | -define(PORT, 110). % POP3 port 22 | -define(TYPE, tcp). 23 | -define(MAXPKT, 1). 24 | -define(STARTTLS, "STLS"). 25 | -define(OK, "+OK"). 26 | -define(DESCRIPTION, "TCP/110: POP3 STARTTLS certificate graber"). 27 | -define(ARGUMENTS, []). 28 | 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | %% API 31 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 32 | % public API to get {port, timeout} 33 | get_default_args() -> 34 | #args{module=?MODULE, type=?TYPE, port=?PORT, 35 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 36 | 37 | get_description() -> 38 | ?DESCRIPTION. 39 | 40 | get_arguments() -> 41 | ?ARGUMENTS. 42 | 43 | callback_next_step(Args) when Args#args.moddata == undefined -> 44 | % first packet 45 | debug(Args, "sending empty payload"), 46 | {continue, Args#args.maxpkt, empty_payload(), first}; 47 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 48 | debug(Args, "no packet received"), 49 | {result, {{error, up}, timeout}}; 50 | callback_next_step(Args) when Args#args.moddata == first -> 51 | % first response 52 | debug(Args, io_lib:fwrite("first packet received: ~p", [Args#args.datarcv])), 53 | case isok(Args#args.datarcv) of 54 | true -> 55 | debug(Args, "sending STARTTLS"), 56 | {continue, Args#args.maxpkt, starttls(), second}; 57 | false -> 58 | debug(Args, "server is not ok"), 59 | {result, {{error,up}, unexpected_data}} 60 | end; 61 | callback_next_step(Args) when Args#args.moddata == second -> 62 | %% second response 63 | debug(Args, io_lib:fwrite("starttls response received: ~p", [Args#args.datarcv])), 64 | case isok(Args#args.datarcv) of 65 | true -> 66 | upgrade(Args); 67 | false -> 68 | {result, {{error,up}, no_starttls}} 69 | end. 70 | 71 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 72 | %% payloads 73 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 74 | %% empty payload since server must communicate first 75 | empty_payload() -> 76 | "". 77 | 78 | %% starttls payload 79 | starttls() -> 80 | lists:concat([?STARTTLS, "\r\n"]). 81 | 82 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 83 | %% parser 84 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 85 | %% is server ok 86 | isok(Payload) -> 87 | Up = string:to_upper(binary_to_list(Payload)), 88 | string:str(Up, ?OK) == 1. 89 | 90 | %% upgrade connection with ssl 91 | upgrade(Args) -> 92 | debug(Args, "upgrade connection with SSL/TLS"), 93 | case utils_ssl:upgrade_socket(Args#args.socket, [], ?TIMEOUT) of 94 | {ok, TLSSocket} -> 95 | case utils_ssl:get_certif(TLSSocket) of 96 | {ok, Cert} -> 97 | ssl:close(TLSSocket), 98 | {result, {{ok,result}, [Args#args.ipaddr, Cert]}}; 99 | {error, Reason} -> 100 | ssl:close(TLSSocket), 101 | {result, {{error, up}, Reason}} 102 | end; 103 | {error, Reason} -> 104 | {result, {{error,up}, Reason}} 105 | end. 106 | 107 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 108 | %% debug 109 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 110 | %% send debug 111 | debug(Args, Msg) -> 112 | utils:debug(fpmodules, Msg, 113 | {Args#args.target, Args#args.id}, Args#args.debugval). 114 | -------------------------------------------------------------------------------- /src/fpmodules/fp_httpsbg.erl: -------------------------------------------------------------------------------- 1 | %%% HTTPS banner grabing module 2 | %%% returns the Server entry in the response's header 3 | %%% 4 | %%% Output: 5 | %%% Server entry value in HTTP header 6 | %%% 7 | 8 | -module(fp_httpsbg). 9 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 10 | -behavior(fp_module). 11 | 12 | -include("../includes/args.hrl"). 13 | 14 | -export([callback_next_step/1]). 15 | -export([get_default_args/0]). 16 | -export([get_description/0]). 17 | -export([get_arguments/0]). 18 | 19 | -define(TIMEOUT, 3000). % milli-seconds 20 | -define(PORT, 443). % HTTPS port 21 | -define(TYPE, ssl). % transport type 22 | -define(MAXPKT, 1). 23 | -define(UALEN, 2). % user-agent length 24 | %-define(SSLOPTS, [{sslcheck,false}, {versions,['tlsv1.2']}, {server_name_indication, disable}]). 25 | %-define(SSLOPTS, [{sslcheck,false}, {versions,['tlsv1.2']}]). 26 | -define(SSLOPTS, [{sslcheck,false}]). 27 | -define(PAGE, "/"). 28 | -define(HDRKEY, "server"). 29 | -define(DESCRIPTION, "SSL/443: HTTPS Server header identification"). 30 | -define(ARGUMENTS, []). 31 | 32 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 33 | %% API 34 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 | % public API to get {port, timeout} 36 | get_default_args() -> 37 | #args{module=?MODULE, type=?TYPE, port=?PORT, 38 | timeout=?TIMEOUT, fsmopts=?SSLOPTS, maxpkt=?MAXPKT}. 39 | 40 | get_description() -> 41 | ?DESCRIPTION. 42 | 43 | get_arguments() -> 44 | ?ARGUMENTS. 45 | 46 | callback_next_step(Args) when Args#args.moddata == undefined -> 47 | % first packet 48 | {continue, Args#args.maxpkt, get_payload(Args#args.target), true}; 49 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 50 | debug(Args, "no packet received"), 51 | {result, {{error, up}, timeout}}; 52 | callback_next_step(Args) -> 53 | debug(Args, "packet received"), 54 | parse_payload(Args, binary_to_list(Args#args.datarcv)). 55 | 56 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 57 | %% debug 58 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 59 | % send debug 60 | debug(Args, Msg) -> 61 | utils:debug(fpmodules, Msg, 62 | {Args#args.target, Args#args.id}, Args#args.debugval). 63 | 64 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 65 | %%% HTTPS packet request forger 66 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 67 | %% returns HTTPS payload 68 | randua(0, Acc) -> 69 | Acc; 70 | randua(N, Acc) -> 71 | randua(N - 1, [rand:uniform(26) + 96 | Acc]). 72 | 73 | payload(Host) -> 74 | Ua = randua(?UALEN, ""), 75 | Args = ["GET ", ?PAGE, " HTTP/1.1", "\r\n", "Host: ", Host, "\r\n", 76 | "User-Agent: ", Ua, "\r\n", 77 | "Accept: */*", "\r\n", 78 | "Language: en", "\r\n\r\n"], 79 | lists:concat(Args). 80 | 81 | get_payload(Addr={_,_,_,_}) -> 82 | payload(inet:ntoa(Addr)); 83 | get_payload(Host) -> 84 | payload(Host). 85 | 86 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 87 | %%%% HTTPS response parser 88 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 89 | % parse http response 90 | parse_payload(Args, Response) -> 91 | case utils_http:parse_http(Args#args.target, ?PAGE, Response, 92 | {Args#args.target, Args#args.id, Args#args.debugval}) of 93 | {error, _} -> 94 | % error while parsing response 95 | {result, {{error, up}, unexpected_data}}; 96 | {redirect, _, {_Code, Header, _Body}} -> 97 | {result, {{ok, result}, [maps:get(?HDRKEY, Header, "")]}}; 98 | {ok, {_Code, Header, _Body}} -> 99 | {result, {{ok, result}, [maps:get(?HDRKEY, Header, "")]}}; 100 | {http, {_Code, Header, _Body}} -> 101 | {result, {{ok, result}, [maps:get(?HDRKEY, Header, "")]}}; 102 | {other, {_Code, _Header, _Body}} -> 103 | {result, {{error, up}, unexpected_data}} 104 | end. 105 | 106 | -------------------------------------------------------------------------------- /src/fpmodules/fp_smtp_certif.erl: -------------------------------------------------------------------------------- 1 | %%% SMTP STARTTLS certificate graber 2 | %%% 3 | %%% Output: 4 | %%% ip and certificate in pem 5 | %%% 6 | %%% https://tools.ietf.org/html/rfc2595 7 | %%% https://tools.ietf.org/html/rfc2487 8 | %%% 9 | 10 | -module(fp_smtp_certif). 11 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 12 | -behavior(fp_module). 13 | 14 | -include("../includes/args.hrl"). 15 | 16 | -export([callback_next_step/1]). 17 | -export([get_default_args/0]). 18 | -export([get_description/0]). 19 | -export([get_arguments/0]). 20 | 21 | -define(TIMEOUT, 6000). % milli-seconds 22 | -define(PORT, 25). 23 | -define(TYPE, tcp). 24 | -define(MAXPKT, 1). 25 | -define(STARTTLS, "STARTTLS"). 26 | -define(OK, "220"). 27 | -define(DESCRIPTION, "TCP/25: SMTP STARTTLS certificate graber"). 28 | -define(ARGUMENTS, []). 29 | 30 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 31 | %% API 32 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 33 | % public API to get {port, timeout} 34 | get_default_args() -> 35 | #args{module=?MODULE, type=?TYPE, port=?PORT, 36 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 37 | 38 | get_description() -> 39 | ?DESCRIPTION. 40 | 41 | get_arguments() -> 42 | ?ARGUMENTS. 43 | 44 | callback_next_step(Args) when Args#args.moddata == undefined -> 45 | % first packet 46 | debug(Args, "sending empty payload"), 47 | {continue, Args#args.maxpkt, empty_payload(), first}; 48 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 49 | debug(Args, "no packet received"), 50 | {result, {{error, up}, timeout}}; 51 | callback_next_step(Args) when Args#args.moddata == first -> 52 | % first response 53 | debug(Args, io_lib:fwrite("first packet received: ~p", [Args#args.datarcv])), 54 | case isok(Args#args.datarcv) of 55 | true -> 56 | debug(Args, "sending STARTTLS"), 57 | {continue, Args#args.maxpkt, starttls(), second}; 58 | false -> 59 | debug(Args, "server is not ok"), 60 | {result, {{error,up}, unexpected_data}} 61 | end; 62 | callback_next_step(Args) when Args#args.moddata == second -> 63 | %% second response 64 | debug(Args, io_lib:fwrite("starttls response received: ~p", [Args#args.datarcv])), 65 | case isok(Args#args.datarcv) of 66 | true -> 67 | upgrade(Args); 68 | false -> 69 | {result, {{error,up}, no_starttls}} 70 | end. 71 | 72 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 73 | %% payloads 74 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 75 | %% empty payload since server must communicate first 76 | empty_payload() -> 77 | "". 78 | 79 | %% starttls payload 80 | starttls() -> 81 | lists:concat([" ", ?STARTTLS, "\r\n"]). 82 | 83 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 84 | %% parser 85 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 86 | %% is server ok 87 | isok(Payload) -> 88 | Pld = binary_to_list(Payload), 89 | string:str(Pld, ?OK) == 1. 90 | 91 | %% upgrade connection with ssl 92 | upgrade(Args) -> 93 | debug(Args, "upgrade connection with SSL/TLS"), 94 | case utils_ssl:upgrade_socket(Args#args.socket, [], ?TIMEOUT) of 95 | {ok, TLSSocket} -> 96 | case utils_ssl:get_certif(TLSSocket) of 97 | {ok, Cert} -> 98 | ssl:close(TLSSocket), 99 | {result, {{ok,result}, [Args#args.ipaddr, Cert]}}; 100 | {error, Reason} -> 101 | ssl:close(TLSSocket), 102 | {result, {{error, up}, Reason}} 103 | end; 104 | {error, Reason} -> 105 | {result, {{error,up}, Reason}} 106 | end. 107 | 108 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 109 | %% debug 110 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 111 | %% send debug 112 | debug(Args, Msg) -> 113 | utils:debug(fpmodules, Msg, 114 | {Args#args.target, Args#args.id}, Args#args.debugval). 115 | -------------------------------------------------------------------------------- /src/utils/utils_mqtt.erl: -------------------------------------------------------------------------------- 1 | %%% MQTT helper 2 | %%% 3 | %%% control packet: 4 | %%% fixed header (present in all control packets) 5 | %%% variable header (present in some) 6 | %%% payload (present in some) 7 | %%% 8 | 9 | -module(utils_mqtt). 10 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 11 | 12 | -export([parse/1, forge_connect/0]). 13 | 14 | % control packet type 15 | % see https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718021 16 | -define(TYPE_CONNECT, 1). 17 | -define(TYPE_CONNACK, 2). 18 | -define(SZ_MOD, 128). 19 | 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | %% Parsing MQTT packet 22 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 23 | % returns: 24 | % {true, TYPE}: when it is mqtt 25 | % TYPE contains the mqtt type of the last message received 26 | % {false, Data}: when not mqtt 27 | % Data contains the data received 28 | parse(<< 29 | 2:4, % fixedhdr - type 30 | 0:4, % fixedhdr - flags 31 | 2:8, % fixedhdr - remaining length 32 | 0:7, % varhdr - reserved 33 | _:1, % varhdr - sessionPresent 34 | _Ret:8 % varhdr - returnCode 35 | >>) -> 36 | % this is a CONNACK (no payload) 37 | {true, ?TYPE_CONNACK}; 38 | parse(Data) -> 39 | {false, Data}. 40 | 41 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 42 | %% Forge MQTT packet 43 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 44 | % create a MQTT packet 45 | % fixedhdr + varhdr + payload 46 | forge_connect() -> 47 | Var = forge_connect_var(), 48 | Pld = forge_connect_pld(), 49 | Tmp = << Var/binary, Pld/binary >>, 50 | Fixed = get_fixed_header(?TYPE_CONNECT, get_length(Tmp)), 51 | << Fixed/binary, Tmp/binary >>. 52 | 53 | % create a MQTT header 54 | forge_connect_pld() -> 55 | % get a random char for the id 56 | Clientid = rand:uniform(26) + 65, 57 | << 0:8, 1:8, Clientid:8 >>. 58 | 59 | % create mqtt content 60 | forge_connect_var() -> 61 | << 62 | 0:8, % length msb 63 | 4:8, % length lsb 64 | 77:8, % 'M' 65 | 81:8, % 'Q' 66 | 84:8, % 'T' 67 | 84:8, % 'T' 68 | 4:8, % protocol level (3.1.1) - will return 0x01 if not supported 69 | 0:1, % username flag 70 | 0:1, % password flag 71 | 0:1, % will retain 72 | 0:2, % will QoS 73 | 0:1, % will flag 74 | 1:1, % clean session 75 | 0:1, % reserved field 76 | 0:8, % keep alive MSB 77 | 60:8 % keep alive LSB (60) 78 | >>. 79 | 80 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 81 | %% utils 82 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 83 | % returns the length of the Data part 84 | % for mqtt 85 | get_length(Data) -> 86 | get_length_val(byte_size(Data)). 87 | 88 | % remaining length 89 | % see https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718023 90 | get_length_val(Sz) when Sz < 128 -> 91 | % 1 digit 92 | << Sz:8 >>; 93 | get_length_val(Sz) when Sz < 16384 -> 94 | % 2 digit 95 | Mul = trunc(Sz / ?SZ_MOD), 96 | Rem = (Sz rem ?SZ_MOD) + ?SZ_MOD, 97 | << 98 | 1:1, Rem:7, 99 | Mul:8 100 | >>; 101 | get_length_val(Sz) when Sz < 2097152 -> 102 | % 3 digit 103 | Mul = trunc(Sz / ?SZ_MOD), 104 | Rem = (Sz rem ?SZ_MOD) + ?SZ_MOD, 105 | << 106 | 1:1, ?SZ_MOD:7, 107 | 1:1, Rem:7, 108 | Mul:8 109 | >>; 110 | get_length_val(Sz) when Sz < 268435456 -> 111 | % 4 digit 112 | Mul = trunc(Sz / ?SZ_MOD), 113 | Rem = (Sz rem ?SZ_MOD) + ?SZ_MOD, 114 | << 115 | 1:1, ?SZ_MOD:7, 116 | 1:1, ?SZ_MOD:7, 117 | 1:1, Rem:7, 118 | Mul:8 119 | >>. 120 | 121 | % Type is the type of the packet 122 | % Len is the length of variable length header and payload 123 | get_fixed_header(Type = ?TYPE_CONNECT, Len) -> 124 | << Type:4, 0:4, Len/binary >>. 125 | %get_fixed_header(Type, Len) -> 126 | % % flags (https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718022) 127 | % % change that if using for PUBREL | SUBSCRIBE | UNSUBSCRIBE | PUBLISH 128 | % Flag = 0, 129 | % << Type:4, Flag:4, Len/binary >>. 130 | 131 | -------------------------------------------------------------------------------- /src/utils/utils_fp.erl: -------------------------------------------------------------------------------- 1 | %% utils for the fingerprinting modules 2 | 3 | -module(utils_fp). 4 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 5 | 6 | -include_lib("kernel/include/inet.hrl"). 7 | 8 | -export([lookup/2, lookup/3, int_to_ip/1, cidr_contains/2, 9 | timestamp_string/0, timestamp_epoch/0, 10 | lookupwww/2, list_to_hex/1, int_to_hex/1, 11 | is_valid_integer/1, stringify/1, is_valid_string/1]). 12 | 13 | -define(DNSERR, "dns_"). 14 | -define(NXPRE, "www."). 15 | 16 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 17 | % lookup domain name 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | % if it's an IP, simply return it 20 | lookup({_,_,_,_}=Ip, _Timeout) -> 21 | {ok, Ip}; 22 | % otherwise, look it up and take the first response if any 23 | lookup(Domain, Timeout) -> 24 | try 25 | inet_db:add_resolv("/etc/resolv.conf"), 26 | case inet_res:getbyname(Domain ++ ".", a, Timeout) of 27 | {ok, Hostent} -> 28 | Addr = hd(Hostent#hostent.h_addr_list), 29 | {ok, Addr}; 30 | {error, Reason} -> 31 | En = ?DNSERR ++ atom_to_list(Reason), 32 | {error, list_to_atom(En)} 33 | end 34 | catch 35 | _:_ -> 36 | {error, list_to_atom(?DNSERR ++ "exception")} 37 | end. 38 | 39 | lookupwww(Domain, Timeout) -> 40 | case lookup(Domain, Timeout) of 41 | {ok, Addr} -> 42 | {ok, Addr}; 43 | {error, dns_nxdomain} -> 44 | case Domain of 45 | ?NXPRE ++ _ -> 46 | {error, dns_nxdomain}; 47 | _ -> 48 | lookup(?NXPRE ++ Domain, Timeout) 49 | end; 50 | {error, Reason} -> 51 | {error, Reason} 52 | end. 53 | 54 | lookup(Domain, Timeout, Www) when Www == true -> 55 | lookupwww(Domain, Timeout); 56 | lookup(Domain, Timeout, _) -> 57 | lookup(Domain, Timeout). 58 | 59 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 60 | % IP utilities 61 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 62 | % transform int to IP 63 | int_to_ip(Int) -> 64 | {Int bsr 24, (Int band 16777215) bsr 16, (Int band 65535) bsr 8, Int band 255}. 65 | 66 | % returns true if second argument is contained withing 67 | % low and top addresses 68 | cidr_contains({{A, B, C, D}, {E, F, G, H}}, {W, X, Y, Z}) -> 69 | (((W >= A) andalso (W =< E)) andalso 70 | ((X >= B) andalso (X =< F)) andalso 71 | ((Y >= C) andalso (Y =< G)) andalso 72 | ((A >= D) andalso (Z =< H))); 73 | cidr_contains(_, _) -> 74 | false. 75 | 76 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 77 | % utilities 78 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 | % return timestamp YYYYmmddHHMMSS 80 | timestamp_string() -> 81 | Now = calendar:local_time(), 82 | {{Y,M,D}, {H,Min,S}} = Now, 83 | Str = io_lib:fwrite("~w~2..0w~2..0w~2..0w~2..0w~2..0w", [Y, M, D, H, Min, S]), 84 | lists:flatten(Str). 85 | 86 | % return epoch 87 | timestamp_epoch() -> 88 | {M, S, _} = os:timestamp(), 89 | (M*1000000)+S. 90 | 91 | % hexlify binary 92 | list_to_hex(L) -> 93 | lists:map(fun(X) -> int_to_hex(X) end, L). 94 | int_to_hex(N) when N < 256 -> 95 | [hex(N div 16), hex(N rem 16)]. 96 | hex(N) when N < 10 -> 97 | $0+N; 98 | hex(N) when N >= 10, N < 16 -> 99 | $a + (N-10). 100 | 101 | % is a valid int 102 | % well this suits our need but might not be very ethical 103 | is_valid_integer(nil) -> 104 | true; 105 | is_valid_integer(Val) -> 106 | Int = (catch erlang:list_to_integer(Val)), 107 | is_number(Int). 108 | 109 | % is a valid string 110 | is_valid_string(List) when is_list(List) -> 111 | lists:all(fun isprint/1, List); 112 | is_valid_string(_) -> 113 | false. 114 | 115 | isprint(X) when X >= 32, X < 127 -> true; 116 | isprint(_) -> false. 117 | 118 | % Stringify 119 | % removes all unreadable char so that 120 | % the list is readable as a result 121 | % expects a list (not a binary) (use binary_to_list) 122 | stringify(TB) -> 123 | stringify(TB, []). 124 | stringify([], Acc) -> 125 | Acc; 126 | stringify([H|T], Acc) -> 127 | case isprint(H) of 128 | true -> 129 | stringify(T, Acc ++ [H]); 130 | false -> 131 | stringify(T, Acc) 132 | end. 133 | 134 | -------------------------------------------------------------------------------- /src/fpmodules/fp_imap_certif.erl: -------------------------------------------------------------------------------- 1 | %%% IMAP STARTTLS certificate graber 2 | %%% 3 | %%% Output: 4 | %%% ip and certificate in pem 5 | %%% 6 | %%% https://tools.ietf.org/html/rfc2595 7 | %%% 8 | 9 | -module(fp_imap_certif). 10 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 11 | -behavior(fp_module). 12 | 13 | -include("../includes/args.hrl"). 14 | 15 | -export([callback_next_step/1]). 16 | -export([get_default_args/0]). 17 | -export([get_description/0]). 18 | -export([get_arguments/0]). 19 | 20 | -define(TIMEOUT, 6000). % milli-seconds 21 | -define(PORT, 143). 22 | -define(TYPE, tcp). 23 | -define(MAXPKT, 1). 24 | -define(STARTTLS, "STARTTLS"). 25 | -define(OK, "OK"). 26 | -define(DESCRIPTION, "TCP/143: IMAP STARTTLS certificate graber"). 27 | -define(ARGUMENTS, []). 28 | 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | %% API 31 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 32 | % public API to get {port, timeout} 33 | get_default_args() -> 34 | #args{module=?MODULE, type=?TYPE, port=?PORT, 35 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 36 | 37 | get_description() -> 38 | ?DESCRIPTION. 39 | 40 | get_arguments() -> 41 | ?ARGUMENTS. 42 | 43 | callback_next_step(Args) when Args#args.moddata == undefined -> 44 | % first packet 45 | debug(Args, "sending empty payload"), 46 | {continue, Args#args.maxpkt, empty_payload(), first}; 47 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 48 | debug(Args, "no packet received"), 49 | {result, {{error, up}, timeout}}; 50 | callback_next_step(Args) when Args#args.moddata == first -> 51 | % first response 52 | debug(Args, io_lib:fwrite("first packet received: ~p", [Args#args.datarcv])), 53 | case supports_starttls(Args#args.datarcv) of 54 | true -> 55 | debug(Args, "sending STARTTLS"), 56 | {continue, Args#args.maxpkt, starttls(), second}; 57 | false -> 58 | debug(Args, "server is not ok"), 59 | {result, {{error,up}, unexpected_data}} 60 | end; 61 | callback_next_step(Args) when Args#args.moddata == second -> 62 | %% second response 63 | debug(Args, io_lib:fwrite("starttls response received: ~p", [Args#args.datarcv])), 64 | case isok(Args#args.datarcv) of 65 | true -> 66 | upgrade(Args); 67 | false -> 68 | {result, {{error,up}, no_starttls}} 69 | end. 70 | 71 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 72 | %% payloads 73 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 74 | %% empty payload since server must communicate first 75 | empty_payload() -> 76 | "". 77 | 78 | %% starttls payload 79 | starttls() -> 80 | lists:concat([randstring(1, []), " ", ?STARTTLS, "\r\n"]). 81 | 82 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 83 | %% parser 84 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 85 | %% is server ok 86 | isok(Payload) -> 87 | Pld = binary_to_list(Payload), 88 | string:str(Pld, ?OK) /= 0. 89 | 90 | 91 | %% upgrade connection with ssl 92 | upgrade(Args) -> 93 | debug(Args, "upgrade connection with SSL/TLS"), 94 | case utils_ssl:upgrade_socket(Args#args.socket, [], ?TIMEOUT) of 95 | {ok, TLSSocket} -> 96 | case utils_ssl:get_certif(TLSSocket) of 97 | {ok, Cert} -> 98 | ssl:close(TLSSocket), 99 | {result, {{ok,result}, [Args#args.ipaddr, Cert]}}; 100 | {error, Reason} -> 101 | ssl:close(TLSSocket), 102 | {result, {{error, up}, Reason}} 103 | end; 104 | {error, Reason} -> 105 | {result, {{error,up}, Reason}} 106 | end. 107 | 108 | %% is starttls an option ? 109 | supports_starttls(Payload) -> 110 | String = binary_to_list(Payload), 111 | string:str(String, ?STARTTLS) /= 0. 112 | 113 | %% returns a random string of length N 114 | randstring(0, Acc) -> 115 | Acc; 116 | randstring(N, Acc) -> 117 | randstring(N - 1, [rand:uniform(26) + 96 | Acc]). 118 | 119 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 120 | %% debug 121 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 122 | %% send debug 123 | debug(Args, Msg) -> 124 | utils:debug(fpmodules, Msg, 125 | {Args#args.target, Args#args.id}, Args#args.debugval). 126 | -------------------------------------------------------------------------------- /src/utils/cntlist.erl: -------------------------------------------------------------------------------- 1 | %% a {key, value} list for the slaves 2 | %% key: the slave 3 | %% value: number of entries 4 | 5 | -module(cntlist). 6 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 7 | 8 | -export([add/3, flatten/1, merge/1, count/1, sublist/2]). 9 | -export([flattenkeys/1, update_key/3, remove_key/2]). 10 | -define(ELEMOFF, 1). 11 | 12 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13 | % interface 14 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 15 | % add to the list Element "Elem" with cnt "Cnt" 16 | add(List, _Elem, Cnt) when Cnt == 0 -> 17 | List; 18 | add(List, Elem, Cnt) -> 19 | update_cnt(List, Elem, Cnt). 20 | 21 | % returns an erlang list with each entry being duplicated based on its count 22 | flatten(List) -> 23 | flat(List, []). 24 | 25 | % returns an erlang list with only the keys (each key once) 26 | flattenkeys(List) -> 27 | flatkey(List, []). 28 | 29 | % update the slave name (the key at ?ELEMOFF) from 30 | % the "Current" to "New" in the list "List" 31 | update_key(List, Current, New) -> 32 | case lists:keyfind(Current, ?ELEMOFF, List) of 33 | {Entry, Nb} -> 34 | lists:keydelete(Entry, ?ELEMOFF, List) ++ [{New, Nb}]; 35 | false -> 36 | List 37 | end. 38 | 39 | % remove a specific key 40 | remove_key(List, Key) -> 41 | case lists:keyfind(Key, ?ELEMOFF, List) of 42 | {Entry, _Nb} -> 43 | lists:keydelete(Entry, ?ELEMOFF, List); 44 | false -> 45 | List 46 | end. 47 | 48 | % take a list and create a cntlist 49 | merge(List) -> 50 | add_from_list(List, []). 51 | 52 | % return the total number of entries 53 | count(List) -> 54 | count_elem(List, 0). 55 | 56 | % return a sublist with cnt max elements. The elements will be proportionnally 57 | % divided for each host. If Cnt > count(List), return List 58 | sublist(List, Cnt) -> 59 | case Cnt > cntlist:count(List) of 60 | true -> 61 | List; 62 | false -> 63 | sublisthelper(List, [], Cnt, List, Cnt) 64 | end. 65 | 66 | % Put most of the Elements in the list proportionnally 67 | sublisthelper(_List, Acc, _CntTarget, _FullList, CntLeft) when CntLeft == 0 -> 68 | clean(Acc, []); 69 | sublisthelper([H|T], Acc, CntTarget, FullList, CntLeft) -> 70 | {Elem, ElCnt} = H, 71 | NewElCnt = ElCnt * CntTarget div cntlist:count(FullList), 72 | sublisthelper(T, Acc ++ [{Elem, NewElCnt}], CntTarget, FullList, CntLeft - NewElCnt); 73 | sublisthelper([], Acc, _CntTarget, FullList, CntLeft) -> 74 | sublisthelper_rest(Acc, FullList, CntLeft, Acc, FullList). 75 | 76 | % Helper for the CntLeft. 77 | sublisthelper_rest(_Acc1, _Acc2, CntLeft, Acc, _FullList) when CntLeft == 0 -> 78 | clean(Acc, []); 79 | sublisthelper_rest([H1|T1], [H2|T2], CntLeft, Acc, FullList) -> 80 | {El1,El1Cnt} = H1, 81 | {_El2,El2Cnt} = H2, 82 | case El1Cnt < El2Cnt of 83 | true -> % Still space for this host 84 | sublisthelper_rest(T1, T2, CntLeft - 1, 85 | cntlist:add(Acc, El1, 1), FullList); 86 | false -> % No space for this host, go to the next one. 87 | sublisthelper_rest(T1, T2, CntLeft,Acc, FullList) 88 | end; 89 | sublisthelper_rest([], [], CntLeft, Acc, FullList) -> 90 | sublisthelper_rest(Acc, FullList, CntLeft, Acc, FullList). 91 | 92 | % Delete nodes with 0 processes 93 | clean([], Acc) -> 94 | Acc; 95 | clean([H|T], Acc) -> 96 | {El, ElCnt} = H, 97 | clean(T, cntlist:add(Acc, El, ElCnt)). 98 | 99 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 100 | % private 101 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 102 | % add entry {Elem, Cnt} if does not exist in cntlist 103 | % or update it if exists 104 | update_cnt(List, Elem, Cnt) -> 105 | case lists:keyfind(Elem, ?ELEMOFF, List) of 106 | {Entry, Nb} -> 107 | lists:keyreplace(Entry, ?ELEMOFF, List, {Entry, Nb+Cnt}); 108 | false -> 109 | List ++ [{Elem, Cnt}] 110 | end. 111 | 112 | % create cntlist out of List of countlist elements 113 | add_from_list([], Agg) -> 114 | Agg; 115 | add_from_list([{Elem, Cnt}|L], Agg) -> 116 | New = add(Agg, Elem, Cnt), 117 | add_from_list(L, New). 118 | 119 | % count elements 120 | count_elem([], Agg) -> 121 | Agg; 122 | count_elem([{_, Cnt}|T], Agg) -> 123 | count_elem(T, Agg + Cnt). 124 | 125 | % returns an erlang list with each entry 126 | % being duplicated based on its count 127 | flat([], Agg) -> 128 | Agg; 129 | flat([{Elem, N}|T], Agg) -> 130 | flat(T, Agg ++ lists:duplicate(N, Elem)). 131 | 132 | % returns a flatten erlang list with only 133 | % the keys (node) without the count 134 | flatkey([], Agg) -> 135 | Agg; 136 | flatkey([{Elem, _}|T], Agg) -> 137 | flatkey(T, Agg ++ [Elem]). 138 | 139 | -------------------------------------------------------------------------------- /src/fpmodules/fp_modbus.erl: -------------------------------------------------------------------------------- 1 | %%% Modbus fingerprinting module 2 | %%% returns data from the modbus response 3 | %%% 4 | %%% Output: 5 | %%% true: it is modbus 6 | %%% false: it is modbus but a protocol error is returned 7 | %%% 8 | 9 | -module(fp_modbus). 10 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 11 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 12 | 13 | -behavior(fp_module). 14 | 15 | -include("../includes/args.hrl"). 16 | 17 | -export([callback_next_step/1]). 18 | -export([get_default_args/0]). 19 | -export([get_description/0]). 20 | -export([get_arguments/0]). 21 | 22 | %% our record for this fingerprint 23 | -define(TIMEOUT, 3000). % milli-seconds 24 | -define(PORT, 502). % Modbus port 25 | -define(TYPE, tcp). % transport type 26 | -define(MAXPKT, 4). % max packet expected 27 | -define(READ_DEVICE_ID_FUNCTION_CODE, 16#2b). 28 | -define(MAX_ERROR, 16#0B). 29 | -define(PAYLOAD, [16#00, % transaction ID 30 | 16#00, 31 | 16#00, % protocol 32 | 16#00, 33 | 16#00, % length 34 | 16#05, 35 | 16#00, % unit ID 36 | ?READ_DEVICE_ID_FUNCTION_CODE, % function code, Read device identification 37 | 16#0e, % subcode 38 | 16#01, % read device ID code 39 | 16#00 % object id 40 | ]). 41 | -define(DESCRIPTION, "TCP/502: Modbus identification"). 42 | -define(ARGUMENTS, []). 43 | 44 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 45 | %% API 46 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 47 | % public API to get {port, timeout} 48 | get_default_args() -> 49 | #args{module=?MODULE, type=?TYPE, port=?PORT, 50 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 51 | 52 | get_description() -> 53 | ?DESCRIPTION. 54 | 55 | get_arguments() -> 56 | ?ARGUMENTS. 57 | 58 | % callback 59 | callback_next_step(Args) when Args#args.moddata == undefined -> 60 | % first packet 61 | {continue, Args#args.maxpkt, ?PAYLOAD, true}; 62 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 63 | % no packet received 64 | debug(Args, "no packet received"), 65 | {result, {{error, up}, timeout}}; 66 | callback_next_step(Args) -> 67 | debug(Args, "packet received"), 68 | parse_payload(Args, binary_to_list(Args#args.datarcv)). 69 | 70 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 71 | %% debug 72 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 73 | % send debug 74 | debug(Args, Msg) -> 75 | utils:debug(fpmodules, Msg, 76 | {Args#args.target, Args#args.id}, Args#args.debugval). 77 | 78 | parse_payload(_Args, Data) -> 79 | LowerData = string:to_lower(utils_fp:stringify(Data)), 80 | 81 | List = [ 82 | {"ftp", {error, ftp} }, 83 | {"200 ", {error, ftp} }, 84 | {"250 ", {error, ftp} }, 85 | {"300 ", {error, ftp} }, 86 | {"printer", {error, printer} }, 87 | {"telnet", {error, telnet} }, 88 | {"firewall", {error, firewall} }, 89 | {"html", {error, http} }, 90 | {"550 ", {error, ftp} }, 91 | {"finger", {error, finger}}, 92 | {"http", {error, http} }, 93 | {"ssh", {error, ssh} }, 94 | {"login", {error, finger}}, 95 | {"user", {error, finger}}, 96 | {"email", {error, finger}} 97 | ], 98 | Result = check_list(List, LowerData), 99 | Final = case Result of 100 | {error, Reason} -> 101 | {{error,up}, Reason}; 102 | probable -> 103 | parse_modbus(Data) 104 | end, 105 | {result, Final}. 106 | 107 | % first check if does not contain other 108 | % specific recognized patterns 109 | check_list([], _Data) -> 110 | probable; 111 | check_list([H|T], Data) -> 112 | {Check, Result} = H, 113 | case string:str(Data, Check) > 0 of 114 | true -> 115 | Result; 116 | false -> 117 | check_list(T, Data) 118 | end. 119 | 120 | % then parse the modbus payload 121 | parse_modbus(Data) -> 122 | BinaryData = list_to_binary(Data), 123 | case BinaryData of 124 | << 125 | _TrId:16, 126 | _Protocol:16, 127 | 0, 128 | _Length:8, 129 | _UnitId, 130 | FctCode, 131 | _SubCode, 132 | _ReadDeviceCode, 133 | _OID/binary 134 | >> -> 135 | check_function_code(FctCode); 136 | << 137 | _TrId:16, 138 | _Protocol:16, 139 | Len:8, 140 | _Length:Len/binary-unit:8, 141 | _UnitId, 142 | FctCode, 143 | _SubCode, 144 | _ReadDeviceCode, 145 | _OID/binary 146 | >> -> 147 | check_function_code(FctCode); 148 | _ -> 149 | {{error, up}, unexpected_data} 150 | end. 151 | 152 | % check modbus fonction code 153 | check_function_code(?READ_DEVICE_ID_FUNCTION_CODE) -> 154 | {{ok,result}, true}; 155 | check_function_code(Code) when Code =< ?MAX_ERROR -> 156 | {{ok,result}, false}; 157 | check_function_code(_) -> 158 | {{error,up}, unexpected_data}. 159 | 160 | -------------------------------------------------------------------------------- /src/utils/utils_slave.erl: -------------------------------------------------------------------------------- 1 | %% utils to start remote slave for scannerl 2 | %% 3 | %% this starts remote nodes by some kind of waiting on 4 | %% it to respond until a specific timeout. 5 | %% a better way of doing it (as the slave module does it) 6 | %% would be to use a callback but for that, the module containing 7 | %% the callback should be present on the remote host. 8 | %% Would be doable but well ... it works 9 | %% 10 | 11 | -module(utils_slave). 12 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 13 | 14 | -export([start_link/5, stop/2]). 15 | -export([start_link_th/6]). 16 | 17 | -define(BIN, "erl"). 18 | -define(REMOTE_BIN, "ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"). 19 | -define(CONNARGS, "-detached -noinput"). 20 | -define(TCPPORT, "-kernel inet_dist_listen_min"). 21 | -define(ARGS, "-hidden +K true -smp enable -P 134217727 -connect_all false -kernel dist_nodelay false"). 22 | -define(POKE, 250). % poke every N ms 23 | 24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 25 | % API 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | % start a slave 28 | % @Hostname: the hostname (must resolve) 29 | % @Name: the name given to the node 30 | % @Portmin: TCP port to open for the communication (through firewall) 31 | % @Dbg: debugger flag 32 | % @Parent: to whom to report result 33 | % @Timeout: how long to wait (in ms) 34 | start_link_th(Hostname, Name, Portmin, Dbg, Parent, Timeout) -> 35 | {Ret, Node} = start_link(Hostname, Name, Portmin, Timeout, Dbg), 36 | Parent ! {Ret, Node, Hostname, Name}. 37 | 38 | % start a slave 39 | % @Hostname: the hostname (must resolve) 40 | % @Name: the name given to the node 41 | % @Portmin: TCP port to open for the communication (through firewall) 42 | % @Dbg: debugger flag 43 | % @Parent: to whom to report result 44 | % @Timeout: how long to wait (in ms) 45 | start_link(Hostname, Name, Portmin, Timeout, Dbg) -> 46 | Node = list_to_atom(lists:concat([Name, "@", Hostname])), 47 | utils:debug(master, io_lib:fwrite("[uslave] connecting to \"~p\"", [Node]), 48 | undefined, Dbg), 49 | case ping_it(Node, Dbg) of 50 | {ok, R} -> 51 | {ok, R}; 52 | error -> 53 | utils:debug(master, io_lib:fwrite("[uslave] starting \"~p\" with ssh on ~p", 54 | [Node, Hostname]), undefined, Dbg), 55 | start_it(Node, Hostname, Name, Portmin, Timeout, Dbg) 56 | end. 57 | 58 | % stop remote node 59 | stop(Node, Dbg) -> 60 | utils:debug(master, io_lib:fwrite("[uslave] halting \"~p\"", [Node]), 61 | undefined, Dbg), 62 | rpc:call(Node, erlang, halt, []), 63 | ok. 64 | 65 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 66 | % Utilities 67 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 68 | % Expect Node as an atom in the form @ 69 | ping_it(Node, Dbg) -> 70 | case net_adm:ping(Node) of 71 | pang -> 72 | utils:debug(master, io_lib:fwrite("[uslave] ping ~p failed", [Node]), 73 | undefined, Dbg), 74 | error; 75 | pong -> 76 | utils:debug(master, io_lib:fwrite("[uslave] ping ~p succeed", [Node]), 77 | undefined, Dbg), 78 | {ok, Node} 79 | end. 80 | 81 | % link it 82 | setup_slave(Node) -> 83 | link(Node), 84 | % see http://erlang.org/doc/man/erlang.html#process_flag-2 85 | receive 86 | % discard that 87 | _ -> 88 | ok 89 | end. 90 | 91 | % wait until the node is up 92 | wait_for_node(Node, Port, Timeout, Dbg) -> 93 | {ok, Ref} = timer:send_after(Timeout, {slavetimeout}), 94 | wait_for_it(Node, Port, Ref, Dbg, Timeout). 95 | 96 | % try to ping every ?POKE until node 97 | % is up or timeout occurs 98 | wait_for_it(Node, Port, Ref, Dbg, Timeout) -> 99 | receive 100 | {'EXIT', Port, normal} -> 101 | % ignore no err from port 102 | utils:debug(master, "[uslave] ssh command succeeded", 103 | undefined, Dbg), 104 | wait_for_it(Node, Port, Ref, Dbg, Timeout); 105 | {'EXIT', Port, Reason} -> 106 | % port communication threw an error 107 | Err = io_lib:fwrite("ssh communication failed: ~p", [Reason]), 108 | utils:debug(master, "[uslave] " ++ Err, undefined, Dbg), 109 | {error, Err}; 110 | {Port, {exit_status, 0}} -> 111 | % port status is ok 112 | utils:debug(master, "[uslave] ssh command returned 0", 113 | undefined, Dbg), 114 | wait_for_it(Node, Port, Ref, Dbg, Timeout); 115 | {Port, {exit_status, Status}} -> 116 | % port exit status != 0 117 | Err = "ssh command error code: " ++ integer_to_list(Status), 118 | utils:debug(master, "[uslave] " ++ Err, undefined, Dbg), 119 | {error, Err}; 120 | {slavetimeout} -> 121 | % timeout occurs 122 | Err = "SSH connection timed-out after " ++ integer_to_list(round(Timeout/1000)) ++ "s", 123 | utils:debug(master, "[uslave] " ++ Err, undefined, Dbg), 124 | {error, Err} 125 | after 126 | ?POKE -> 127 | case ping_it(Node, Dbg) of 128 | {ok, N} -> 129 | timer:cancel(Ref), 130 | setup_slave(Port), 131 | {ok, N}; 132 | error -> 133 | wait_for_it(Node, Port, Ref, Dbg, Timeout) 134 | end 135 | end. 136 | 137 | % start a remote node 138 | start_it(Node, Hostname, Name, Portmin, Timeout, Dbg) -> 139 | Fname = string:join([Name, "@", Hostname], ""), 140 | Cmd = string:join([?REMOTE_BIN, Hostname, ?BIN, "-sname", Fname, 141 | ?ARGS, ?TCPPORT, integer_to_list(Portmin), ?CONNARGS], " "), 142 | utils:debug(master, io_lib:fwrite("[uslave] SSH command: ~p", [Cmd]), 143 | undefined, Dbg), 144 | Old = process_flag(trap_exit, true), 145 | Port = open_port({spawn, Cmd}, [stream, exit_status]), 146 | Ret = wait_for_node(Node, Port, Timeout, Dbg), 147 | process_flag(trap_exit, Old), 148 | Ret. 149 | 150 | -------------------------------------------------------------------------------- /src/fpmodules/fp_ssh_host_key.erl: -------------------------------------------------------------------------------- 1 | %%% SSH host key fingerprint module 2 | %%% 3 | %%% Output: 4 | %%% ip and certificate in pem 5 | %%% 6 | %%% to test localhost on port 40000: 7 | %%% $ sudo /usr/sbin/sshd -p 40000 -D -d -h /etc/ssh/ssh_host_rsa_key 8 | %%% $ ssh-keyscan -p 40000 127.0.0.1 9 | %%% $ ./build.sh && ./scannerl -m ssh_host_key -f 127.0.0.1:40000 10 | %%% 11 | 12 | -module(fp_ssh_host_key). 13 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 14 | 15 | -behavior(fp_module). 16 | 17 | -include("../includes/args.hrl"). 18 | 19 | -export([callback_next_step/1]). 20 | -export([get_default_args/0]). 21 | -export([get_description/0]). 22 | -export([get_arguments/0]). 23 | 24 | %% our record for this fingerprint 25 | -define(TIMEOUT, 3000). %% milli-seconds 26 | -define(PORT, 22). %% SSH port 27 | -define(TYPE, tcp). %% transport type 28 | -define(MAXPKT, 1). 29 | -define(SSHOPT, [{connect_timeout, ?TIMEOUT}, 30 | {idle_time, ?TIMEOUT}, 31 | {user, "test"}, %% unused 32 | {password, "test"}, %% unused 33 | {inet, inet}, %% force ipv4 34 | {user_dir, "."}, %% make sure $HOME/.ssh is not used 35 | {id_string, random}, %% random id at each connect 36 | {user_interaction, false}, %% avoid querying the user 37 | {silently_accept_hosts, false}, %% ecdsa key 38 | {quiet_mode, true}, %% be quiet 39 | {pref_public_key_algs,['ssh-rsa']}, %% force RSA key 40 | {auth_methods, "publickey"} %% avoid user interaction 41 | ]). 42 | -define(MPATH, "/tmp/"). %% tmp dir for result certificate 43 | -define(PAYLOAD, "\n"). 44 | -define(DESCRIPTION, "TCP/22: SSH host key graber"). 45 | -define(ARGUMENTS, []). 46 | 47 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 48 | %% API 49 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 50 | get_default_args() -> 51 | #args{module=?MODULE, type=?TYPE, port=?PORT, 52 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 53 | get_description() -> 54 | ?DESCRIPTION. 55 | get_arguments() -> 56 | ?ARGUMENTS. 57 | 58 | callback_next_step(Args) when Args#args.moddata == undefined -> 59 | % first packet 60 | {continue, Args#args.maxpkt, get_payload(), true}; 61 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 62 | % no packet received 63 | {result, {{error, up}, timeout}}; 64 | callback_next_step(Args) -> 65 | % connect using SSH 66 | case Args#args.ctarget of 67 | {_,_,_,_} -> 68 | % convert target to string 69 | ssh_connect(Args, inet:ntoa(Args#args.ctarget)); 70 | _ -> 71 | ssh_connect(Args, Args#args.ctarget) 72 | end. 73 | 74 | % initiate the ssh connection using the ssh application 75 | ssh_connect(Args, Target) -> 76 | ssh:start(), 77 | %% this is pretty ugly but since erlang doesn't allow to re-assign 78 | %% variable and the communication with the callback is one-way only 79 | %% when providing custom options, there's no way to retrieve the certificate. 80 | %% Thus going through a file. 81 | Path = ?MPATH ++ randfilename(10, ""), 82 | Algos = keep_only_rsa(), 83 | Opts = ?SSHOPT ++ [{key_cb, {ssh_key_cb, [Path]}}] ++ 84 | [{preferred_algorithms, Algos}], 85 | case ssh:connect(Target, Args#args.cport, Opts, ?TIMEOUT) of 86 | {ok, Ref} -> 87 | debug(Args, "error, connection succeeded but shouldn't"), 88 | ssh:close(Ref), 89 | {result, {{error, up}, unexpected_data}}; 90 | {error, Reason} -> 91 | % the connection should actually failed since 92 | % we didn't accept the certificate 93 | debug(Args, "connection failed, good"), 94 | process_res(Args, Path, Reason) 95 | end. 96 | 97 | %% process the result by reading the file 98 | %% and in case it is not there, return the inital 99 | %% error returned by ssh 100 | process_res(Args, Path, Reason) -> 101 | case check_file(Args, Path, Reason) of 102 | {ok, Res} -> 103 | {result, {{ok, result}, Res}}; 104 | {error, R} -> 105 | {result, {{error, up}, R}} 106 | end. 107 | 108 | %% read file content 109 | open_read_delete(Args, Path) -> 110 | case file:read_file(Path) of 111 | {ok, Bin} -> 112 | debug(Args, "reading content succeeded"), 113 | L = binary_to_list(Bin), 114 | Res = case length(string:split(L, ",", all)) of 115 | 2 -> 116 | {ok, L}; 117 | _ -> 118 | {error, unexpected_data} 119 | end, 120 | file:delete(Path), 121 | Res; 122 | {error, Reason} -> 123 | debug(Args, "open file failed"), 124 | {error, Reason} 125 | end. 126 | 127 | %% read the result file if any or return 128 | %% the inital ssh error reason 129 | check_file(Args, Path, Oldreason) -> 130 | case filelib:is_file(Path) of 131 | true -> 132 | debug(Args, "file exists"), 133 | open_read_delete(Args, Path); 134 | false -> 135 | debug(Args, "file doesn't exist ???"), 136 | {error, Oldreason} 137 | end. 138 | 139 | %% construct a random filename 140 | randfilename(0, Acc) -> 141 | Acc; 142 | randfilename(N, Acc) -> 143 | randfilename(N - 1, [rand:uniform(26) + 96 | Acc]). 144 | 145 | %% this removes any public_key algorithm but ssh-rsa 146 | keep_only_rsa() -> 147 | lists:keyreplace(public_key, 1, ssh:default_algorithms(), 148 | {public_key, ['ssh-rsa']}). 149 | 150 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 151 | %% ssh payload 152 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 153 | %% provide the first packet payload 154 | get_payload() -> 155 | list_to_binary(?PAYLOAD). 156 | 157 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 158 | %%%%% debug 159 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 160 | % send debug 161 | debug(Args, Msg) -> 162 | utils:debug(fpmodules, Msg, 163 | {Args#args.target, Args#args.id}, Args#args.debugval). 164 | 165 | -------------------------------------------------------------------------------- /src/utils/utils_ssl.erl: -------------------------------------------------------------------------------- 1 | %%% SSL helper 2 | %%% 3 | %%% refs: 4 | %%% https://tools.ietf.org/search/rfc6125#section-6.4.3 5 | %%% http://erlang.org/doc/man/ssl.html#connect-3 6 | %%% 7 | 8 | -module(utils_ssl). 9 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 10 | 11 | -export([ 12 | get_ca_path/0, 13 | get_opts_verify/1, 14 | get_opts_noverify/0, 15 | get_certif/1, 16 | upgrade_socket/3 17 | ]). 18 | -include_lib("public_key/include/public_key.hrl"). 19 | 20 | -define(SSLPATH_DEBIAN, "/etc/ssl/certs/ca-certificates.crt"). 21 | -define(SSLDEPTH, 2). 22 | 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | %% API 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | get_ca_path() -> 27 | ?SSLPATH_DEBIAN. 28 | 29 | %% to use like 30 | %% ?COPTS ++ get_opts_verify([]) 31 | get_opts_verify([]) -> 32 | % do not check dns 33 | [ 34 | {cacertfile, get_ca_path()}, 35 | {verify, verify_peer}, 36 | {depth, ?SSLDEPTH} 37 | ]; 38 | get_opts_verify(Domain) -> 39 | % check everything as well as the domain matching 40 | [ 41 | {cacertfile, get_ca_path()}, 42 | {verify, verify_peer}, 43 | {depth, ?SSLDEPTH}, 44 | {verify_fun, {fun check_cert/3, [Domain]}} 45 | ]. 46 | get_opts_noverify() -> 47 | % do not verify anything about the certificate 48 | []. 49 | 50 | 51 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 52 | %% function to use as "verify_fun" for SSL 53 | %% will: 54 | %% - check for validity 55 | %% - check for expired 56 | %% - check for bad extension 57 | %% - check for hostname matching 58 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 59 | check_cert(_,{bad_cert, _} = Reason, _) -> 60 | {fail, Reason}; 61 | check_cert(_,{extension, _}, UserState) -> 62 | {unknown, UserState}; 63 | check_cert(_, valid, UserState) -> 64 | {valid, UserState}; 65 | check_cert(Cert, valid_peer, UserState) -> 66 | C = Cert#'OTPCertificate'.tbsCertificate, 67 | Ext = C#'OTPTBSCertificate'.extensions, 68 | {value, {_,_,_, DNSs}} = lists:keysearch(?'id-ce-subjectAltName', #'Extension'.extnID, Ext), 69 | [Ori] = UserState, 70 | DNSList = [Hostname || {_, Hostname} <- DNSs], 71 | Comp = dnsnames_match_dns(Ori, DNSList), 72 | case Comp of 73 | true -> 74 | {valid, UserState}; 75 | false -> 76 | {fail, bad_cert} 77 | end. 78 | 79 | % match the queried dns against a list 80 | % of DNSs provided in the certificate 81 | dnsnames_match_dns(_Queried, []) -> 82 | false; 83 | dnsnames_match_dns(Queried, [H|T]) -> 84 | Res = dnsname_match_dns(Queried, H), 85 | case Res of 86 | true -> 87 | true; 88 | false -> 89 | dnsnames_match_dns(Queried, T) 90 | end. 91 | 92 | % match the queried dns against a specific 93 | % provided DNS in the certificate 94 | dnsname_match_dns(Queried, Presented) -> 95 | Ql = lists:reverse(string:tokens(Queried, ".")), 96 | Pl = lists:reverse(string:tokens(Presented, ".")), 97 | match_dns(Ql, Pl). 98 | 99 | % match each DNS part in reverse 100 | % for two dns (left is the queried dns, 101 | % and right the certificate provided one). 102 | match_dns([], []) -> 103 | true; 104 | match_dns([], _) -> 105 | false; 106 | match_dns(_, []) -> 107 | false; 108 | match_dns([_H1|T1], ["*"|_T2]) -> 109 | match_dns(T1, []); 110 | match_dns([H1|T1], [H2|T2]) -> 111 | case string:str(H2, "*") == 0 of 112 | true -> 113 | case string:equal(H1, H2) of 114 | false -> 115 | false; 116 | true -> 117 | match_dns(T1, T2) 118 | end; 119 | false -> 120 | match_wildcard(H1, H2) 121 | end. 122 | 123 | % matching when wildcard is provided 124 | match_wildcard(Against, Wild) -> 125 | Idx = string:str(Wild, "*"), 126 | case Idx == 0 of 127 | true -> 128 | false; 129 | false -> 130 | Left = string:sub_string(Wild, 1, Idx-1), 131 | Right = string:sub_string(Wild, Idx+1), 132 | match_wildcard(Against, Left, Right) 133 | end. 134 | match_wildcard(Against, Left, Right) -> 135 | Ll = length(Left), 136 | Lr = length(Right), 137 | Aleft = string:left(Against, Ll), 138 | Aright = string:right(Against, Lr), 139 | string:equal(Aleft, Left) and string:equal(Aright, Right). 140 | 141 | % return {ok, Cert} with the certificate from an SSL-enabled socket 142 | % otherwise return {error, Reason} in case of error 143 | get_certif(Socket) -> 144 | try 145 | case ssl:peercert(Socket) of 146 | {error, Reason} -> 147 | {error, [bad_or_no_certificate, Reason]}; 148 | {ok, Cert} -> 149 | C = public_key:pkix_decode_cert(Cert, plain), 150 | D = public_key:pem_entry_encode('Certificate', C), 151 | E = public_key:pem_encode([D]), {ok, E} 152 | end 153 | catch 154 | %error:{Type, Ex} -> 155 | % %io:fwrite("ex: ~n~p~n", [Ex]), 156 | % {result, {{error, up}, Type}}; 157 | _Type:Exception -> 158 | %io:fwrite("Type: ~n~p~n", [Type]), 159 | %io:fwrite("Exception:~n~p~n", [Exception]), 160 | {error, [unexpected_data, Exception]} 161 | end. 162 | 163 | %% upgrade a socket with SSL 164 | %% returns {ok, NewSocket} or {error, Reason} 165 | %% Opt can be empty == [] 166 | upgrade_socket(Socket, Opt, Timeout) -> 167 | % first ensure the socket is opened 168 | case erlang:port_info(Socket) of 169 | undefined -> 170 | {error, socket_closed}; 171 | _ -> 172 | upgrade_socket_to_ssl(Socket, Opt, Timeout) 173 | end. 174 | 175 | %% upgrade socket with SSL 176 | upgrade_socket_to_ssl(Socket, Opt, Timeout) -> 177 | case ssl:start() of 178 | ok -> 179 | % set socket to active false 180 | inet:setopts(Socket, [{active, false}]), 181 | % upgrade the socket 182 | case ssl:connect(Socket, Opt, Timeout) of 183 | {ok, TLSSocket} -> 184 | {ok, TLSSocket}; 185 | {ok, TLSSocket, _Ext} -> 186 | {ok, TLSSocket}; 187 | {error, Reason} -> 188 | {error, Reason} 189 | end; 190 | {error, Reason} -> 191 | {error, Reason} 192 | end. 193 | -------------------------------------------------------------------------------- /src/utils/tgt.erl: -------------------------------------------------------------------------------- 1 | %%% helper function for targets 2 | %%% 3 | %%% a tgt can be: 4 | %%% - a single IP (ex: 192.168.0.1) 5 | %%% - a single IP on a port (ex: 192.168.0.1:8080) 6 | %%% - a cidr range (ex: 10.0.0.0/24) 7 | %%% - a cidr range on a port (ex: 10.0.0.0:8080/24) 8 | %%% - a domain (ex: www.myip.ch) 9 | %%% - a domain on a port (ex: www.myip.ch:81) 10 | %%% each of the above can have an additional argument 11 | %%% added with a "+" separator. It must be added at the end. 12 | %%% It is up to the fp module to use (or ignore) it. 13 | %%% 14 | 15 | -module(tgt). 16 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 17 | 18 | % exported function 19 | -export([get_tgts/1, minrange/2]). 20 | -export([parse_ip/2, parse_domain/2]). 21 | 22 | -record(tgt, { 23 | ip, 24 | prefix, 25 | port=undefined, 26 | arg=[] 27 | }). 28 | 29 | -define(MINCIDR, 24). 30 | -define(PORTSEP, ":"). 31 | -define(RANGESEP, "/"). 32 | -define(ADDSEP, "+"). 33 | 34 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 | %% convert 36 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 37 | ip_to_int({A, B, C, D}) -> 38 | (A*16777216)+(B*65536)+(C*256)+D. 39 | 40 | bin_to_ip(Bin) -> 41 | list_to_tuple(binary_to_list(Bin)). 42 | 43 | int_to_ip(Int) -> 44 | {Int bsr 24, (Int band 16777215) bsr 16, (Int band 65535) bsr 8, Int band 255}. 45 | 46 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 47 | %% cidr/ip bits op 48 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 49 | % returns the integer value of the masked IP 50 | get_base(Tgt) -> 51 | Ip = Tgt#tgt.ip, 52 | Prefix = Tgt#tgt.prefix, 53 | Mask = bnot(1 bsl Prefix), 54 | Int = ip_to_int(Ip), 55 | (Int bsr (32-Prefix)) band Mask. 56 | 57 | % mask an ip 58 | % @Ip: a valid ip addr 59 | % @Prefix: an int 60 | % returns a integer representation of the IP 61 | mask_ip(Ip, Prefix) -> 62 | Inv = 32 - Prefix, 63 | Tmp = #tgt{ip=Ip, prefix=Prefix}, 64 | Val = get_base(Tmp), 65 | (Val bsl (Inv)) bor (0 bsl (Inv)). 66 | 67 | % extend base integer with zeros 68 | complete_ip(Tgt, Val) -> 69 | Base = get_base(Tgt), 70 | Prefix = Tgt#tgt.prefix, 71 | Inv = 32 - Prefix - 1, 72 | Lip = bin_to_ip(<>), 73 | #tgt{ip=Lip,prefix=(Prefix+1),port=Tgt#tgt.port, arg=Tgt#tgt.arg}. 74 | 75 | % get lower range 76 | lower(Tgt) -> 77 | complete_ip(Tgt, 0). 78 | 79 | % get upper range 80 | higher(Tgt) -> 81 | complete_ip(Tgt, 1). 82 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 83 | %% utilities 84 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 85 | list_divide([], Agg) -> 86 | Agg; 87 | list_divide([H|T], Agg) -> 88 | list_divide(T, Agg ++ divide(H)). 89 | 90 | rec_divide(0, Acc) -> 91 | Acc; 92 | %list_divide(Acc, []); 93 | rec_divide(Cnt, Acc) -> 94 | rec_divide(Cnt-1, list_divide(Acc, [])). 95 | 96 | % divide a tgt in two if possible 97 | divide(Tgt = #tgt{ip=_,prefix=Prefix,port=_}) when Prefix > 31 -> 98 | [Tgt]; 99 | divide(Tgt = #tgt{ip=_,prefix=Prefix,port=_}) when Prefix < 0 -> 100 | [Tgt]; 101 | divide(Tgt = #tgt{ip=_,prefix=_,port=_}) -> 102 | [lower(Tgt), higher(Tgt)]; 103 | divide(_) -> 104 | []. 105 | 106 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 107 | %% range sub-division 108 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 109 | % subdivide range into /24 110 | minrange(Tgt=#tgt{ip=_,prefix=Prefix,port=_}, Minrange) when Prefix < Minrange -> 111 | % calculate how many times we need to divide 112 | Div = Minrange - Prefix, 113 | rec_divide(Div, [Tgt]); 114 | minrange(Tgt, _) -> 115 | [Tgt]. 116 | 117 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 118 | %% parse and count 119 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 120 | % parse prefix in the form / 121 | parse_prefix([]) -> 122 | 32; 123 | parse_prefix(String) -> 124 | try 125 | list_to_integer(String) 126 | catch 127 | error:badarg -> 128 | 32 129 | end. 130 | 131 | % parse the port part if any in the form : 132 | parse_port([], Defport) -> 133 | Defport; 134 | parse_port([Val], Defport) -> 135 | try 136 | list_to_integer(Val) 137 | catch 138 | error:badarg -> 139 | Defport 140 | end; 141 | parse_port(Val, Defport) -> 142 | try 143 | list_to_integer(Val) 144 | catch 145 | error:badarg -> 146 | Defport 147 | end. 148 | 149 | % parse a domain and return a tgt record 150 | % format: see header 151 | parse_domain(String, Defport) -> 152 | [H|T] = string:tokens(String, ?ADDSEP), 153 | E = string:tokens(H, ?PORTSEP), 154 | #tgt{ip=hd(E), prefix=32, port=parse_port(tl(E), Defport), arg=T}. 155 | 156 | % returns {Prefix, Port} 157 | parse_ip_more([], Defport) -> 158 | {32, Defport}; 159 | parse_ip_more(?RANGESEP ++ Rest, Defport) -> 160 | {parse_prefix(Rest), Defport}; 161 | parse_ip_more(?PORTSEP ++ Rest, Defport) -> 162 | E = string:tokens(Rest, ?RANGESEP), 163 | Prefix = parse_prefix(tl(E)), 164 | {Prefix, parse_port(hd(E), Defport)}. 165 | 166 | % parse an ip and return a tgt record 167 | % format: see header 168 | parse_ip(String, Defport) -> 169 | % first separate the option 170 | [H|T] = string:tokens(String, ?ADDSEP), 171 | try 172 | {A, Rest1} = string:to_integer(H), 173 | {B, Rest2} = string:to_integer(tl(Rest1)), 174 | {C, Rest3} = string:to_integer(tl(Rest2)), 175 | {D, Rest4} = string:to_integer(tl(Rest3)), 176 | {Prefix, Port} = parse_ip_more(Rest4, Defport), 177 | {ok, #tgt{ip=int_to_ip(mask_ip({A,B,C,D}, Prefix)), prefix=Prefix, port=Port, arg=T}} 178 | of 179 | {ok, Res} -> {ok, Res} 180 | catch 181 | _Exception:Reason -> {error, Reason} 182 | end. 183 | 184 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 185 | %% explode range 186 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 187 | % get a list of all target in a tgt record 188 | get_tgts(#tgt{ip=Ip,prefix=32,port=Port, arg=Arg}) -> 189 | [{Ip,Port,Arg}]; 190 | get_tgts(#tgt{ip=Ip,prefix=Prefix,port=Port,arg=Arg}) -> 191 | Inv = 32 - Prefix, 192 | Max = round(math:pow(2, Inv)) - 1, 193 | Base = ip_to_int(Ip), 194 | [{int_to_ip(Base+Inc),Port,Arg} || Inc <- lists:seq(0, Max)]. 195 | 196 | -------------------------------------------------------------------------------- /src/fpmodules/fp_httpbg.erl: -------------------------------------------------------------------------------- 1 | %%% HTTP banner grabing module 2 | %%% returns the Server entry in the response's header 3 | %%% 4 | %%% Output: 5 | %%% Server entry value in HTTP header 6 | %%% 7 | 8 | -module(fp_httpbg). 9 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 10 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 11 | 12 | -behavior(fp_module). 13 | 14 | -include("../includes/args.hrl"). 15 | 16 | -export([callback_next_step/1]). 17 | -export([get_default_args/0]). 18 | -export([get_description/0]). 19 | -export([get_arguments/0]). 20 | 21 | -define(TIMEOUT, 3000). % milli-seconds 22 | -define(PORT, 80). % HTTP port 23 | -define(TYPE, tcp). % transport type 24 | -define(UALEN, 2). % user-agent length 25 | -define(HDRKEY, "server"). 26 | -define(MAXPKT, 1). 27 | -define(MAXREDIRECT, 3). 28 | -define(MAXREDIR, maxredirection). 29 | -define(DESCRIPTION, "TCP/80: HTTP Server header identification"). 30 | -define(ARGUMENTS, ["[true|false] follow redirection [Default:false]"]). 31 | 32 | %% our record for this fingerprint 33 | -record(sd, { 34 | follow = false, 35 | host, 36 | requery = false, 37 | redircnt = 0, 38 | page="/" 39 | }). 40 | 41 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 42 | %% API 43 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 44 | % public API to get {port, timeout} 45 | get_default_args() -> 46 | #args{module=?MODULE, type=?TYPE, port=?PORT, 47 | timeout=?TIMEOUT, maxpkt=?MAXPKT}. 48 | 49 | get_description() -> 50 | ?DESCRIPTION. 51 | 52 | get_arguments() -> 53 | ?ARGUMENTS. 54 | 55 | % construct internal record with arguments 56 | get_internal_args(Arguments) when Arguments == ["true"] -> 57 | #sd{follow=true}; 58 | get_internal_args(_Arguments) -> 59 | #sd{}. 60 | 61 | % callback 62 | callback_next_step(Args) when Args#args.moddata == undefined -> 63 | % first packet 64 | Sd = get_internal_args(Args#args.arguments), 65 | debug(Args, io_lib:fwrite("query ~p ~p (follow:~p)", [Args#args.target, Sd#sd.page, Sd#sd.follow])), 66 | {continue, Args#args.maxpkt, get_payload(Args#args.target, Sd#sd.page), Sd#sd{host=Args#args.target}}; 67 | callback_next_step(Args) when Args#args.moddata#sd.requery == true -> 68 | % first packet 69 | Sd = Args#args.moddata, 70 | debug(Args, io_lib:fwrite("REquery ~p ~p", [Sd#sd.host, Sd#sd.page])), 71 | {continue, Args#args.maxpkt, get_payload(Sd#sd.host, Sd#sd.page), Sd#sd{requery=false}}; 72 | callback_next_step(Args) when Args#args.packetrcv < 1 -> 73 | % no packet received 74 | debug(Args, "no packet received"), 75 | {result, {{error, up}, timeout}}; 76 | callback_next_step(Args) -> 77 | debug(Args, "packet received"), 78 | parse_payload(Args, binary_to_list(Args#args.datarcv)). 79 | 80 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 81 | %% debug 82 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 83 | % send debug 84 | debug(Args, Msg) -> 85 | utils:debug(fpmodules, Msg, 86 | {Args#args.target, Args#args.id}, Args#args.debugval). 87 | 88 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 89 | %%% HTTP packet request forger 90 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 91 | %% returns HTTP payload 92 | randua(0, Acc) -> 93 | Acc; 94 | randua(N, Acc) -> 95 | randua(N - 1, [rand:uniform(26) + 96 | Acc]). 96 | 97 | payload(Host, Page) -> 98 | Ua = randua(?UALEN, ""), 99 | Args = ["GET ", Page, " HTTP/1.1", "\r\n", "Host: ", Host, "\r\n", 100 | "User-Agent: ", Ua, "\r\n", 101 | "Accept: */*", "\r\n", 102 | "Language: en", "\r\n\r\n"], 103 | lists:concat(Args). 104 | 105 | get_payload(Addr={_,_,_,_}, Page) -> 106 | payload(inet:ntoa(Addr), Page); 107 | get_payload(Host, Page) -> 108 | payload(Host, Page). 109 | % 110 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 111 | %%%% HTTP response parser 112 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 113 | % parse http response 114 | parse_payload(Args, Response) -> 115 | Sd = Args#args.moddata, 116 | case utils_http:parse_http(Sd#sd.host, Sd#sd.page, Response, 117 | {Args#args.target, Args#args.id, Args#args.debugval}) of 118 | {error, _Data} -> 119 | % error while parsing response 120 | {result,{{error, up}, unexpected_data}}; % RESULT 121 | {redirect, {ok, {Host, Page}}, {_Code, Header, _Body}} -> 122 | case Args#args.moddata#sd.follow of 123 | true -> 124 | case maps:find(?HDRKEY, Header) of 125 | {ok, Value} -> 126 | debug(Args, io_lib:fwrite("Server found in header: ~p", [Header])), 127 | {result, {{ok, result}, [Value]}}; 128 | error -> 129 | % follow redirection 130 | debug(Args, "httpbg is following redirection ..."), 131 | httpredirect(Args, Args#args.target, Host, Page, Sd) 132 | end; 133 | false -> 134 | % do not follow redirection 135 | {result, {{ok, result}, [maps:get(?HDRKEY, Header, "")]}} 136 | end; 137 | {redirect, {https, _}, {_Code, Header, _Body}} -> 138 | debug(Args, "Redirect error https"), 139 | {result, {{ok, result}, [maps:get(?HDRKEY, Header, "")]}}; 140 | {redirect, {error, Reason}, {_Code, Header, _Body}} -> 141 | debug(Args, io_lib:fwrite("Redirect error: ~p", [Reason])), 142 | {result, {{ok, result}, [maps:get(?HDRKEY, Header, "")]}}; 143 | {ok, {_Code, Header, _Body}} -> 144 | {result, {{ok, result}, [maps:get(?HDRKEY, Header, "")]}}; 145 | {http, {_Code, Header, _Body}} -> 146 | {result, {{ok, result}, [maps:get(?HDRKEY, Header, "")]}}; 147 | {other, {_Code, _Header, _Body}} -> 148 | {result,{{error, up}, unexpected_data}} 149 | end. 150 | 151 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 152 | %% redirection 153 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 154 | httpredirect(Data, Oritgt, Host, Page, Sd) 155 | when Host == Oritgt andalso (Page == Sd#sd.page orelse Page == "/") -> 156 | debug(Data, io_lib:fwrite("cyclic redirect to ~p -> ~p", [Host, Page])), 157 | {result,{{error, up}, redir_cyclic}}; % RESULT 158 | httpredirect(Data, _, Host, Page, Sd) when Sd#sd.redircnt < ?MAXREDIRECT -> 159 | Newsd = Sd#sd{page=Page, host=Host, redircnt=Sd#sd.redircnt+1, requery=true}, 160 | debug(Data, io_lib:fwrite("redirect to ~p -> ~p", [Host, Page])), 161 | {restart, {Host, undefined}, Newsd}; 162 | httpredirect(Data, _, _Host, _Page, _Md) -> 163 | debug(Data, "max redirection reached"), 164 | {result,{{error, up}, max_redirect}}. 165 | 166 | -------------------------------------------------------------------------------- /src/fsms/fsm_udp.erl: -------------------------------------------------------------------------------- 1 | %%% UDP FSM Module 2 | 3 | -module(fsm_udp). 4 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 5 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 6 | -behavior(gen_fsm). 7 | 8 | -export([start_link/1, start/1]). 9 | -export([init/1, terminate/3, handle_info/3]). 10 | -export([code_change/4, handle_sync_event/4, handle_event/3]). 11 | -export([connecting/2, callback/2]). 12 | 13 | -include("../includes/args.hrl"). 14 | 15 | % see http://erlang.org/doc/man/inet.html#setopts-2 16 | -define(COPTS, [binary, inet,{recbuf, 65536}, {active, false}]). 17 | 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | %% debug 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | % send debug 22 | debug(Args, Msg) -> 23 | utils:debug(fpmodules, Msg, 24 | {Args#args.target, Args#args.id}, Args#args.debugval). 25 | 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | %% API calls 28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | callback(timeout, Data) when Data#args.packetrcv == 0 30 | andalso Data#args.retrycnt > 0 andalso Data#args.payload /= << >> -> 31 | inet:setopts(Data#args.socket, [{active, once}]), 32 | gen_udp:send(Data#args.socket, Data#args.ipaddr, 33 | Data#args.cport, Data#args.payload), 34 | {next_state, callback, Data#args{retrycnt=Data#args.retrycnt-1}, Data#args.timeout}; 35 | callback(timeout, Data) -> 36 | case apply(Data#args.module, callback_next_step, [Data]) of 37 | {continue, Nbpacket, Payload, ModData} -> 38 | flush_socket(Data#args.socket), 39 | inet:setopts(Data#args.socket, [{active, once}]), 40 | gen_udp:send(Data#args.socket, Data#args.ipaddr, Data#args.cport, Payload), 41 | {next_state, callback, Data#args{moddata=ModData, payload=Payload, 42 | nbpacket=Nbpacket}, Data#args.timeout}; 43 | {restart, {Target, Port}, ModData} -> 44 | Newtarget = case Target == undefined of true -> Data#args.ctarget; false -> Target end, 45 | Newport = case Port == undefined of true -> Data#args.cport; false -> Port end, 46 | gen_udp:close(Data#args.socket), 47 | {next_state, connecting, Data#args{ctarget=Newtarget, cport=Newport, 48 | moddata=ModData, retrycnt=Data#args.retry, 49 | datarcv = << >>, payload = << >>, packetrcv=0}, 0}; 50 | {result, Result} -> 51 | gen_udp:close(Data#args.socket), 52 | {stop, normal, Data#args{result=Result}} % RESULT 53 | end. 54 | 55 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 56 | %% gen_fsm modules (http://www.erlang.org/doc/man/gen_fsm.html) 57 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 58 | % this is when there's no supervisor 59 | start_link(Args) -> 60 | gen_fsm:start_link(?MODULE, Args, []). 61 | % this is when it's part of a supervised tree 62 | start([Args]) -> 63 | gen_fsm:start(?MODULE, Args, []). 64 | 65 | flush_socket(Socket) -> 66 | case gen_udp:recv(Socket, 0, 0) of 67 | {error, _Reason} -> 68 | ok; 69 | {ok, _Result} -> 70 | flush_socket(Socket) 71 | end. 72 | 73 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 74 | %% gen_fsm callbacks 75 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 76 | % called by start/start_link 77 | init(Args) -> 78 | doit(Args#args{ctarget=Args#args.target, cport=Args#args.port, retrycnt=Args#args.retry}). 79 | 80 | % start the process 81 | doit(Args) -> 82 | % first let's call "connect" through "connecting" using a timeout of 0 83 | debug(Args, io_lib:fwrite("~p on ~p", [Args#args.module, Args#args.ctarget])), 84 | {ok, connecting, Args, 0}. 85 | 86 | % get privport opt 87 | get_privports(true) -> 88 | [{port, rand:uniform(1024)}]; 89 | get_privports(_) -> 90 | []. 91 | 92 | % provide the socket option 93 | get_options(Args) -> 94 | utils:merge_sockopt(?COPTS ++ get_privports(Args#args.privports) 95 | ++ Args#args.fsmopts, Args#args.sockopt). 96 | 97 | % State connecting is used to initiate the udp connection 98 | connecting(timeout, Data) -> 99 | Host = Data#args.ctarget, Timeout = Data#args.timeout, 100 | case utils_fp:lookup(Host, Timeout, Data#args.checkwww) of 101 | {ok, Addr} -> 102 | case gen_udp:open(0, get_options(Data)) of 103 | {ok, Socket} -> 104 | % Addr resolved, udp socket reserved, now send. 105 | {next_state, callback, Data#args{socket=Socket, ipaddr=Addr}, 0 }; 106 | %{next_state, sending, Data#args{socket=Socket, ipaddr=Addr}, 0 }; 107 | {error, Reason} -> 108 | gen_fsm:send_event(self(), {error, Reason}), 109 | {next_state, connecting, Data} 110 | end; 111 | {error, Reason} -> 112 | gen_fsm:send_event(self(), {error, Reason}), 113 | {next_state, connecting, Data} 114 | end; 115 | % called when source port is already taken 116 | connecting({error, tcp_eacces}, Data) 117 | when Data#args.privports == true, Data#args.eaccess_retry < Data#args.eaccess_max -> 118 | {next_state, connecting, Data#args{eaccess_retry=Data#args.eaccess_retry+1}, 0}; 119 | % called when connection failed 120 | connecting({error, Reason}, Data) -> 121 | {stop, normal, Data#args{result={{error, unknown}, Reason}}}. % RESULT 122 | 123 | %% called by stop 124 | terminate(_Reason, _State, Data) -> 125 | Result = {Data#args.module, Data#args.target, Data#args.port, Data#args.result}, 126 | debug(Data, io_lib:fwrite("~p done on ~p (outdirect:~p)", 127 | [Data#args.module, Data#args.target, Data#args.direct])), 128 | case Data#args.direct of 129 | true -> 130 | utils:outputs_send(Data#args.outobj, [Result]); 131 | false -> 132 | Data#args.parent ! Result 133 | end, 134 | ok. 135 | 136 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 137 | %% event handlers 138 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 139 | % called when a new packet is received 140 | handle_info({udp, Socket, Addr, Port, Packet}, callback, Data) 141 | when (Socket == Data#args.socket) andalso (Addr == Data#args.ipaddr) 142 | andalso (Port == Data#args.cport) -> 143 | case Data#args.nbpacket of 144 | infinity -> 145 | inet:setopts(Data#args.socket, [{active, once}]), 146 | {next_state, callback, Data#args{ 147 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 148 | packetrcv = Data#args.packetrcv + 1 149 | }, Data#args.timeout}; 150 | 1 -> % It is the last packet to receive 151 | {next_state, callback, Data#args{ 152 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 153 | nbpacket = 0, 154 | packetrcv = Data#args.packetrcv + 1 155 | }, 0}; 156 | 0 -> % If they didn't want any packet ? 157 | {stop, normal, Data#args{result={ 158 | {error,up},[toomanypacketreceived, Packet]}}}; % RESULT 159 | _Cnt -> % They are more packets (maybe) 160 | inet:setopts(Data#args.socket, [{active, once}]), 161 | {next_state, callback, Data#args{ 162 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 163 | nbpacket=Data#args.nbpacket-1, 164 | packetrcv = Data#args.packetrcv + 1 165 | }, Data#args.timeout} 166 | end; 167 | handle_info(_Msg, _State, Data) -> 168 | {stop, normal, Data#args{result={{error, unknown}, unexpected_host}}}. % RESULT 169 | 170 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 171 | %% UNUSED event handlers 172 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 173 | code_change(_Prev, State, Data, _Extra) -> 174 | {ok , State, Data}. 175 | handle_sync_event(_Ev, _From, _State, Data) -> 176 | {stop, unexpectedSyncEvent, Data}. 177 | handle_event(_Ev, _State, Data) -> 178 | {stop, unexpectedEvent, Data}. 179 | 180 | -------------------------------------------------------------------------------- /src/fsms/statem_udp.erl: -------------------------------------------------------------------------------- 1 | %%% UDP statem 2 | %%% 3 | 4 | -module(statem_udp). 5 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 6 | -behavior(gen_statem). 7 | 8 | -include("../includes/args.hrl"). 9 | 10 | % gen_statem imports 11 | -export([start_link/1, start/1]). 12 | -export([init/1, terminate/3, code_change/4]). 13 | -export([callback_mode/0]). 14 | 15 | % callbacks 16 | -export([connecting/3, callback/3]). 17 | 18 | % see http://erlang.org/doc/man/inet.html#setopts-2 19 | -define(COPTS, [binary, inet,{recbuf, 65536}, {active, false}]). 20 | 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | %% gen_statem specific 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | %% called by start/start_link 25 | init(Args) -> 26 | doit(Args#args{ctarget=Args#args.target, cport=Args#args.port, retrycnt=Args#args.retry}). 27 | 28 | %% start the process 29 | doit(Args) -> 30 | % first let's call "connect" through "connecting" using a timeout of 0 31 | debug(Args, io_lib:fwrite("~p on ~p", [Args#args.module, Args#args.ctarget])), 32 | {ok, connecting, Args, 0}. 33 | 34 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 | %% fsm callbacks 36 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 37 | %% called for sending first packet 38 | callback(timeout, _EventContent, Data) when Data#args.packetrcv == 0 39 | andalso Data#args.retrycnt > 0 andalso Data#args.payload /= << >> -> 40 | inet:setopts(Data#args.socket, [{active, once}]), 41 | gen_udp:send(Data#args.socket, Data#args.ipaddr, 42 | Data#args.cport, Data#args.payload), 43 | {next_state, callback, Data#args{retrycnt=Data#args.retrycnt-1}, Data#args.timeout}; 44 | %% called for sending additional packet when needed 45 | callback(timeout, _EventContent, Data) -> 46 | case apply(Data#args.module, callback_next_step, [Data]) of 47 | {continue, Nbpacket, Payload, ModData} -> 48 | flush_socket(Data#args.socket), 49 | inet:setopts(Data#args.socket, [{active, once}]), 50 | gen_udp:send(Data#args.socket, Data#args.ipaddr, Data#args.cport, Payload), 51 | {next_state, callback, Data#args{moddata=ModData, payload=Payload, 52 | nbpacket=Nbpacket}, Data#args.timeout}; 53 | {restart, {Target, Port}, ModData} -> 54 | Newtarget = case Target == undefined of true -> Data#args.ctarget; false -> Target end, 55 | Newport = case Port == undefined of true -> Data#args.cport; false -> Port end, 56 | gen_udp:close(Data#args.socket), 57 | {next_state, connecting, Data#args{ctarget=Newtarget, cport=Newport, 58 | moddata=ModData, retrycnt=Data#args.retry, 59 | datarcv = << >>, payload = << >>, packetrcv=0}, 0}; 60 | {result, Result} -> 61 | gen_udp:close(Data#args.socket), 62 | {stop, normal, Data#args{result=Result}} 63 | end; 64 | %% called when a new packet is received 65 | callback(info, {udp, Socket, Addr, Port, Packet}, Data) 66 | when (Socket == Data#args.socket) andalso (Addr == Data#args.ipaddr) 67 | andalso (Port == Data#args.cport) -> 68 | case Data#args.nbpacket of 69 | infinity -> 70 | inet:setopts(Data#args.socket, [{active, once}]), 71 | {next_state, callback, Data#args{ 72 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 73 | packetrcv = Data#args.packetrcv + 1 74 | }, Data#args.timeout}; 75 | 1 -> % It is the last packet to receive 76 | {next_state, callback, Data#args{ 77 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 78 | nbpacket = 0, 79 | packetrcv = Data#args.packetrcv + 1 80 | }, 0}; 81 | 0 -> % If they didn't want any packet ? 82 | {stop, normal, Data#args{result={ 83 | {error,up},[toomanypacketreceived, Packet]}}}; 84 | _Cnt -> % They are more packets (maybe) 85 | inet:setopts(Data#args.socket, [{active, once}]), 86 | {next_state, callback, Data#args{ 87 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 88 | nbpacket=Data#args.nbpacket-1, 89 | packetrcv = Data#args.packetrcv + 1 90 | }, Data#args.timeout} 91 | end; 92 | callback(info, {udp, _, _, _, _}, Data) -> 93 | {stop, normal, Data#args{result={{error, unknown}, unexpected_host}}}; 94 | callback(Event, EventContent, Data) -> 95 | {stop, normal, Data#args{result={{error, unknown}, [unexpected_event, Event, EventContent, Data]}}}. 96 | 97 | %% State connecting is used to initiate the udp connection 98 | connecting(timeout, _, Data) -> 99 | Host = Data#args.ctarget, Timeout = Data#args.timeout, 100 | case utils_fp:lookup(Host, Timeout, Data#args.checkwww) of 101 | {ok, Addr} -> 102 | case gen_udp:open(0, get_options(Data)) of 103 | {ok, Socket} -> 104 | % Addr resolved, udp socket reserved, now send. 105 | {next_state, callback, Data#args{socket=Socket, ipaddr=Addr}, 0 }; 106 | %{next_state, sending, Data#args{socket=Socket, ipaddr=Addr}, 0 }; 107 | {error, Reason} -> 108 | gen_statem:cast(self(), {error, Reason}), 109 | {next_state, connecting, Data} 110 | end; 111 | {error, Reason} -> 112 | gen_statem:cast(self(), {error, Reason}), 113 | {next_state, connecting, Data} 114 | end; 115 | %% called when source port is already taken 116 | connecting(cast, {error, tcp_eacces}, Data) 117 | when Data#args.privports == true, Data#args.eaccess_retry < Data#args.eaccess_max -> 118 | {next_state, connecting, Data#args{eaccess_retry=Data#args.eaccess_retry+1}, 0}; 119 | %% called when connection failed 120 | connecting(cast, {error, Reason}, Data) -> 121 | {stop, normal, Data#args{result={{error, unknown}, Reason}}}. 122 | 123 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 124 | %% utils 125 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 126 | %% get privport opt 127 | get_privports(true) -> 128 | [{port, rand:uniform(1024)}]; 129 | get_privports(_) -> 130 | []. 131 | 132 | %% provide the socket option 133 | get_options(Args) -> 134 | ?COPTS ++ get_privports(Args#args.privports) 135 | ++ Args#args.fsmopts. 136 | 137 | %% receive everything available from the socket 138 | flush_socket(Socket) -> 139 | case gen_udp:recv(Socket, 0, 0) of 140 | {error, _Reason} -> 141 | ok; 142 | {ok, _Result} -> 143 | flush_socket(Socket) 144 | end. 145 | 146 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 147 | %% helper for the fsm 148 | %% gen_statem http://erlang.org/doc/man/gen_statem.html 149 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 150 | %% this is when there's no supervisor 151 | start_link(Args) -> 152 | gen_statem:start_link(?MODULE, Args, []). 153 | %% this is when it's part of a supervised tree 154 | start([Args]) -> 155 | gen_statem:start(?MODULE, Args, []). 156 | 157 | %% set the callback mode for gen_statem 158 | callback_mode() -> 159 | state_functions. 160 | 161 | %% called by stop 162 | terminate(_Reason, _State, Data) -> 163 | Result = {Data#args.module, Data#args.target, Data#args.port, Data#args.result}, 164 | debug(Data, io_lib:fwrite("~p done on ~p (outdirect:~p)", 165 | [Data#args.module, Data#args.target, Data#args.direct])), 166 | case Data#args.direct of 167 | true -> 168 | utils:outputs_send(Data#args.outobj, [Result]); 169 | false -> 170 | Data#args.parent ! Result 171 | end, 172 | ok. 173 | 174 | %% unused callback 175 | code_change(_Prev, State, Data, _Extra) -> 176 | {ok , State, Data}. 177 | 178 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 179 | %% debug 180 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 181 | %% send debug 182 | debug(Args, Msg) -> 183 | utils:debug(fpmodules, Msg, 184 | {Args#args.target, Args#args.id}, Args#args.debugval). 185 | 186 | -------------------------------------------------------------------------------- /src/fsms/fsm_tcp.erl: -------------------------------------------------------------------------------- 1 | %%% TCP FSM Reference 2 | %%% 3 | 4 | -module(fsm_tcp). 5 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 6 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 7 | -behavior(gen_fsm). 8 | 9 | -include("../includes/args.hrl"). 10 | 11 | -export([start_link/1, start/1]). 12 | -export([init/1, terminate/3, handle_info/3]). 13 | -export([code_change/4, handle_sync_event/4, handle_event/3]). 14 | -export([connecting/2, callback/2]). 15 | 16 | % see http://erlang.org/doc/man/inet.html#setopts-2 17 | -define(COPTS, [binary, {packet, 0}, inet, {recbuf, 65536}, {active, false}, {reuseaddr, true}]). 18 | 19 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 20 | %% debug 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | % send debug 23 | debug(Args, Msg) -> 24 | utils:debug(fpmodules, Msg, 25 | {Args#args.target, Args#args.id}, Args#args.debugval). 26 | 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 | %% API calls 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | 31 | % called by "handle_info" when timeout occurs 32 | callback(timeout, Data) when Data#args.retrycnt > 0 andalso Data#args.packetrcv == 0 33 | andalso Data#args.payload /= << >> -> 34 | send_data(Data#args{retrycnt=Data#args.retrycnt-1}); 35 | callback(timeout, Data) -> 36 | case apply(Data#args.module, callback_next_step, [Data]) of 37 | {continue, Nbpacket, Payload, ModData} -> 38 | flush_socket(Data#args.socket), 39 | inet:setopts(Data#args.socket, [{active, once}]), % we want a packet 40 | send_data(Data#args{moddata=ModData,nbpacket=Nbpacket,payload=Payload}); 41 | {restart, {Target, Port}, ModData} -> 42 | Newtarget = case Target == undefined of true -> Data#args.ctarget; false -> Target end, 43 | Newport = case Port == undefined of true -> Data#args.cport; false -> Port end, 44 | gen_tcp:close(Data#args.socket), 45 | {next_state, connecting, Data#args{ctarget=Newtarget, cport=Newport, 46 | moddata=ModData, sending=false, retrycnt=Data#args.retry, 47 | datarcv = << >>, payload = << >>, packetrcv=0}, 0}; 48 | {result, Result} -> 49 | gen_tcp:close(Data#args.socket), 50 | {stop, normal, Data#args{result=Result}} % RESULT 51 | end. 52 | 53 | send_data(Data) -> 54 | case gen_tcp:send(Data#args.socket, Data#args.payload) of 55 | ok -> 56 | {next_state, callback, Data#args{ 57 | sending=true, 58 | datarcv = << >>, 59 | packetrcv = 0 60 | }, 61 | Data#args.timeout}; 62 | {error, Reason} -> 63 | {next_state, callback, Data#args{sndreason=Reason}, 0} 64 | end. 65 | 66 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 67 | %% gen_fsm modules (http://www.erlang.org/doc/man/gen_fsm.html) 68 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 69 | % this is when there's no supervisor Args is an #args record 70 | start_link(Args) -> 71 | gen_fsm:start_link(?MODULE, Args, []). 72 | % this is when it's part of a supervised tree 73 | start([Args]) -> 74 | gen_fsm:start(?MODULE, Args, []). 75 | 76 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 77 | %% gen_fsm callbacks 78 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 | % called by start/start_link 80 | init(Args) -> 81 | doit(Args#args{ctarget=Args#args.target, cport=Args#args.port, retrycnt=Args#args.retry}). 82 | 83 | % start the process 84 | doit(Args) -> 85 | debug(Args, io_lib:fwrite("~p on ~p", [Args#args.module, Args#args.ctarget])), 86 | % first let's call "connect" through "connecting" using a timeout of 0 87 | {ok, connecting, Args, 0}. 88 | 89 | % get privport opt 90 | get_privports(true) -> 91 | [{port, rand:uniform(1024)}]; 92 | get_privports(_) -> 93 | []. 94 | 95 | % provide the socket option 96 | get_options(Args) -> 97 | utils:merge_sockopt(?COPTS ++ get_privports(Args#args.privports) 98 | ++ Args#args.fsmopts, Args#args.sockopt). 99 | 100 | % State connecting is used to initiate the tcp connection 101 | connecting(timeout, Data) -> 102 | Host = Data#args.ctarget, Port = Data#args.cport, Timeout = Data#args.timeout, 103 | case utils_fp:lookup(Host, Timeout, Data#args.checkwww) of 104 | {ok, Addr} -> 105 | try 106 | case gen_tcp:connect(Addr, Port, get_options(Data), Timeout) of 107 | {ok, Socket} -> 108 | {next_state, callback, Data#args{socket=Socket, ipaddr=Addr}, 0}; 109 | {error, Reason} -> 110 | gen_fsm:send_event(self(), {error, list_to_atom("tcp_" ++ atom_to_list(Reason))}), 111 | {next_state, connecting, Data} 112 | end 113 | catch 114 | _:_ -> 115 | gen_fsm:send_event(self(), {error, tcp_conn_badaddr}), 116 | {next_state, connecting, Data} 117 | end; 118 | {error, Reason} -> 119 | gen_fsm:send_event(self(), {error, Reason}), 120 | {next_state, connecting, Data} 121 | end; 122 | % called when connection is refused 123 | connecting({error, econnrefused=Reason}, Data) -> 124 | {stop, normal, Data#args{result={{error, up}, Reason}}}; % RESULT 125 | % called when connection is reset 126 | connecting({error, econnreset=Reason}, Data) -> 127 | {stop, normal, Data#args{result={{error, up}, Reason}}}; % RESULT 128 | % called when source port is already taken 129 | connecting({error, tcp_eacces}, Data) 130 | when Data#args.privports == true, Data#args.eaccess_retry < Data#args.eaccess_max -> 131 | {next_state, connecting, Data#args{eaccess_retry=Data#args.eaccess_retry+1}, 0}; 132 | % called when connection failed 133 | connecting({error, Reason}, Data) -> 134 | {stop, normal, Data#args{result={{error, unknown}, Reason}}}. % RESULT 135 | 136 | %% called by stop 137 | terminate(_Reason, _State, Data) -> 138 | Result = {Data#args.module, Data#args.target, Data#args.port, Data#args.result}, 139 | debug(Data, io_lib:fwrite("~p done on ~p (outdirect:~p)", 140 | [Data#args.module, Data#args.target, Data#args.direct])), 141 | case Data#args.direct of 142 | true -> 143 | utils:outputs_send(Data#args.outobj, [Result]); 144 | false -> 145 | Data#args.parent ! Result 146 | end, 147 | ok. 148 | 149 | flush_socket(Socket) -> 150 | case gen_tcp:recv(Socket, 0, 0) of 151 | {error, _Reason} -> 152 | ok; 153 | {ok, _Result} -> 154 | flush_socket(Socket) 155 | end. 156 | 157 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 158 | %% event handlers 159 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 160 | % called when a new packet is received 161 | handle_info({tcp, _Socket, Packet}, callback, Data) -> 162 | case Data#args.nbpacket of 163 | infinity -> 164 | inet:setopts(Data#args.socket, [{active, once}]), % We want more 165 | {next_state, callback, Data#args{ 166 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 167 | packetrcv = Data#args.packetrcv + 1 168 | }, 169 | Data#args.timeout}; 170 | 1 -> % It is the last packet to receive 171 | {next_state, callback, Data#args{ 172 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 173 | nbpacket = 0, 174 | packetrcv = Data#args.packetrcv + 1 175 | }, 176 | 0}; 177 | 0 -> % If they didn't want any packet ? 178 | {stop, normal, Data#args{result={ 179 | {error,up},[toomanypacketreceived, Packet]}}}; % RESULT 180 | _ -> % They are more packets (maybe) 181 | inet:setopts(Data#args.socket, [{active, once}]), % We want more 182 | {next_state, callback, Data#args{ 183 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 184 | nbpacket=Data#args.nbpacket - 1, 185 | packetrcv = Data#args.packetrcv + 1 186 | }, 187 | Data#args.timeout} 188 | end; 189 | handle_info({tcp_closed=Reason, _Socket}, _State, Data) -> 190 | % Happen when there was still packets to receive 191 | % but the tcp port is closed 192 | {next_state, callback, Data#args{rcvreason=Reason}, 0}; 193 | % called when error on socket 194 | handle_info({tcp_error, _Socket, Reason}, _State, Data) -> 195 | {next_state, callback, Data#args{rcvreason=Reason}, 0}; 196 | % called when domain lookup failed 197 | handle_info({timeout, _Socket, inet}, _State, Data) -> 198 | {stop, normal, Data#args{result={{error, unknown}, dns_timeout}}}. % RESULT 199 | 200 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 201 | %% UNUSED event handlers 202 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 203 | code_change(_Prev, State, Data, _Extra) -> 204 | {ok , State, Data}. 205 | handle_sync_event(_Ev, _From, _State, Data) -> 206 | {stop, unexpectedSyncEvent, Data}. 207 | handle_event(_Ev, _State, Data) -> 208 | {stop, unexpectedEvent, Data}. 209 | 210 | -------------------------------------------------------------------------------- /src/utils/utils.erl: -------------------------------------------------------------------------------- 1 | %% utils 2 | 3 | -module(utils). 4 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 5 | 6 | -export([get_ts/0, scaninfo_print/1]). 7 | -export([debug_parse/1, debug_print/2, debug/4]). 8 | -export([outputs_init/2, outputs_clean/1, outputs_send/2]). 9 | -export([read_lines/1, tgt_to_string/1, merge_sockopt/2]). 10 | -export([replace_in_list_of_tuple/3]). 11 | 12 | -include("../includes/opts.hrl"). 13 | 14 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 15 | % output 16 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 17 | outputs_init(Outs, Scaninfo) -> 18 | output_init(Outs, Scaninfo, []). 19 | 20 | outputs_clean(Outs) -> 21 | output_clean(Outs). 22 | 23 | outputs_send(Outs, Msg) -> 24 | output_send(Outs, Msg). 25 | 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | % output utils 28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | % send message to output modules 30 | output_send([], _) -> 31 | ok; 32 | output_send([{Mod, _Args, Obj}|T], Msg) -> 33 | apply(Mod, output, [Obj, Msg]), 34 | output_send(T, Msg). 35 | 36 | % init output modules 37 | % returns either 38 | % {ok, Acc}: a list of tuple in the form {Out, [Args], Out-Object} 39 | % {error, Reason} 40 | output_init([], _, Acc) -> 41 | {ok, Acc}; 42 | output_init([{Mod, Args}|T], Scaninfo, Acc) -> 43 | % init the module 44 | try apply(Mod, init, [Scaninfo, Args]) of 45 | {ok, Obj} -> 46 | output_init(T, Scaninfo, [{Mod, Args, Obj}|Acc]); 47 | {error, Reason} -> 48 | output_clean(Acc), 49 | {error, Reason} 50 | catch 51 | X:Y -> 52 | utils_opts:print(io_lib:fwrite("[ERROR] output module ~p error: ~p ~p", [Mod, X, Y])), 53 | utils_opts:usage() 54 | end. 55 | 56 | % clean output modules 57 | output_clean([]) -> 58 | ok; 59 | output_clean([{Mod, _Args, Obj}|T]) -> 60 | case apply(Mod, clean, [Obj]) of 61 | ok -> 62 | output_clean(T); 63 | {error, _Reason} -> 64 | output_clean(T) 65 | end. 66 | 67 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 68 | % utils 69 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 70 | % get current timestamp 71 | get_ts() -> 72 | Now = calendar:local_time(), 73 | {{Y,M,D}, {H,Min,S}} = Now, 74 | io_lib:fwrite("~w~2..0w~2..0w~2..0w~2..0w~2..0w", [Y, M, D, H, Min, S]). 75 | 76 | scaninfo_print(Scaninfo) -> 77 | Dbgval = Scaninfo#scaninfo.debugval, 78 | utils:debug(scannerl, "", {}, Dbgval), 79 | utils:debug(scannerl, "-------- scaninfo -------", {}, Dbgval), 80 | utils:debug(scannerl, io_lib:fwrite("Version: ~p", 81 | [Scaninfo#scaninfo.version]), {}, Dbgval), 82 | utils:debug(scannerl, io_lib:fwrite("Module: ~p", 83 | [Scaninfo#scaninfo.fpmodule]), {}, Dbgval), 84 | utils:debug(scannerl, io_lib:fwrite("Port: ~p", 85 | [Scaninfo#scaninfo.port]), {}, Dbgval), 86 | utils:debug(scannerl, io_lib:fwrite("Debug: ~p", 87 | [Scaninfo#scaninfo.debugval]), {}, Dbgval), 88 | utils:debug(scannerl, "-------------------------", {}, Dbgval), 89 | utils:debug(scannerl, "", {}, Dbgval). 90 | 91 | % IP to string 92 | tgt_to_string(Tgt) when is_list(Tgt) -> 93 | Tgt; 94 | tgt_to_string(Tgt) -> 95 | inet_parse:ntoa(Tgt). 96 | 97 | % merge socket options 98 | % left, right list of options 99 | % where right is prefered 100 | merge_sockopt(Left, Right) -> 101 | merge_sockopt_sub(Left, Right, Left). 102 | 103 | merge_sockopt_sub(_Left, [], Acc) -> 104 | Acc; 105 | merge_sockopt_sub(Left, [{Key, _Value}=H|T], Acc) -> 106 | N = lists:keydelete(Key, 1, Acc), 107 | merge_sockopt_sub(Left, T, N++[H]); 108 | merge_sockopt_sub(Left, [{Key}=H|T], Acc) -> 109 | N = lists:keydelete(Key, 1, Acc), 110 | merge_sockopt_sub(Left, T, N++[H]). 111 | 112 | % replace a specific tuple in a list 113 | % this is not efficient and is O(N) 114 | replace_in_list_of_tuple(List, Key, Newvalue) -> 115 | replace_tuple(List, Key, Newvalue, []). 116 | 117 | replace_tuple([], _Key, _New, Acc) -> 118 | Acc; 119 | replace_tuple([{Key, _V}|T], Key, New, Acc) -> 120 | % found offending tuple 121 | replace_tuple(T, Key, New, Acc ++ [{Key, New}]); 122 | replace_tuple([{_, _}=Entry|T], Key, New, Acc) -> 123 | % some other tuple 124 | replace_tuple(T, Key, New, Acc ++ [Entry]). 125 | 126 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 127 | % debug 128 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 129 | debug_parse(Value) when Value == 0 -> 130 | #debugval{ 131 | value=Value, 132 | level1=false, 133 | level2=false, 134 | level4=false, 135 | level8=false, 136 | level16=false, 137 | level32=false, 138 | level64=false, 139 | level128=false 140 | }; 141 | debug_parse("true") -> 142 | debug_inner_parse(binary:encode_unsigned(255)); 143 | debug_parse(Value) -> 144 | % let's just hope the user doesn't do "-v abc" 145 | debug_inner_parse(binary:encode_unsigned(list_to_integer(Value))). 146 | 147 | debug_inner_parse(<< 148 | L128:1, 149 | L64:1, 150 | L32:1, 151 | L16:1, 152 | L8:1, 153 | L4:1, 154 | L2:1, 155 | L1:1 156 | >> = Value) -> 157 | case L16 == 1 orelse L128 == 1 of 158 | true -> 159 | io:fwrite("[DBG] [~s] [scannerl] level-1 (fpmodules): ~p~n", [get_ts(), L1==1]), 160 | io:fwrite("[DBG] [~s] [scannerl] level-2 (outmodules): ~p~n", [get_ts(), L2==1]), 161 | io:fwrite("[DBG] [~s] [scannerl] level-4 (broker): ~p~n", [get_ts(), L4==1]), 162 | io:fwrite("[DBG] [~s] [scannerl] level-8 (master): ~p~n", [get_ts(), L8==1]), 163 | io:fwrite("[DBG] [~s] [scannerl] level-16 (scannerl): ~p~n", [get_ts(), L16==1]), 164 | io:fwrite("[DBG] [~s] [scannerl] level-32 (?): ~p~n", [get_ts(), L32==1]), 165 | io:fwrite("[DBG] [~s] [scannerl] level-64 (?): ~p~n", [get_ts(), L64==1]), 166 | io:fwrite("[DBG] [~s] [scannerl] level-128 (additional): ~p~n", [get_ts(), L128==1]); 167 | false -> 168 | ok 169 | end, 170 | [Val|[]] = binary_to_list(Value), 171 | #debugval{ 172 | value=Val, 173 | level1=L1==1, 174 | level2=L2==1, 175 | level4=L4==1, 176 | level8=L8==1, 177 | level16=L16==1, 178 | level32=L32==1, 179 | level64=L64==1, 180 | level128=L128==1 181 | }. 182 | 183 | debug_more_info(Debugval, Msg) when Debugval#debugval.level128 -> 184 | {_, Cnt} = erlang:process_info(self(), message_queue_len), 185 | %erlang:memory() 186 | io_lib:fwrite("[ql:~p] ~s", [Cnt, Msg]); 187 | debug_more_info(_, Msg) -> 188 | Msg. 189 | 190 | % format debug message 191 | debug_format(From, Msg) -> 192 | io_lib:fwrite("[DBG] [~s] [~s] ~s\n", [get_ts(), atom_to_list(From), Msg]). 193 | 194 | % print debug to standard out 195 | % expects an atom in From 196 | debug_print(From, Msg) -> 197 | io:put_chars(standard_error, debug_format(From, Msg)). 198 | 199 | %% global function for debugging 200 | debug(fpmodules=Who, Msg, {Tgt, Id}, Dbg) when Dbg#debugval.level1 -> 201 | % from fpmodules to master 202 | Msg2 = io_lib:fwrite("[~s|~p]: ~s", [Id, Tgt, Msg]), 203 | debug_send(Who, debug_more_info(Dbg, Msg2), Dbg#debugval.where); 204 | debug(outmodules=Who, Msg, _, Dbg) when Dbg#debugval.level2-> 205 | % from outmodules to master 206 | debug_send(Who, debug_more_info(Dbg, Msg), Dbg#debugval.where); 207 | debug(broker=Who, Msg, {Id}, Dbg) when Dbg#debugval.level4 -> 208 | % from broker to master 209 | Msg2 = io_lib:fwrite("[~s]: ~s", [Id, Msg]), 210 | debug_send(Who, debug_more_info(Dbg, Msg2), Dbg#debugval.where); 211 | debug(master, Msg, _, Dbg) when Dbg#debugval.level8 -> 212 | % master to stderr 213 | debug_print(master, debug_more_info(Dbg, Msg)); 214 | debug(scannerl, Msg, _, Dbg) when Dbg#debugval.level16 -> 215 | % from scannerl to stderr 216 | debug_print(scannerl, debug_more_info(Dbg, Msg)); 217 | debug(_, _, _, _) -> 218 | ok. 219 | 220 | %% this allows to control where 221 | %% debugs are sent 222 | debug_send(From, What, local) -> 223 | io:fwrite(debug_format(From, What)); 224 | debug_send(From, Msg, remote) -> 225 | global:whereis_name(master) ! {debug, From, Msg}. 226 | 227 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 228 | % file reading 229 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 230 | validate_line([]) -> 231 | nil; 232 | validate_line("#" ++ _) -> 233 | nil; 234 | validate_line("%" ++ _) -> 235 | nil; 236 | validate_line(Line) -> 237 | Line. 238 | 239 | % string to list of line (separator is \n) 240 | list_to_lines("\n" ++ [], Acc) -> 241 | case validate_line(lists:reverse(Acc)) of 242 | nil -> []; 243 | Val -> [Val] 244 | end; 245 | list_to_lines("\n" ++ Rest, Acc) -> 246 | case validate_line(lists:reverse(Acc)) of 247 | nil -> list_to_lines(Rest, []); 248 | Val -> [Val | list_to_lines(Rest, [])] 249 | end; 250 | list_to_lines([H|T], Acc) -> 251 | list_to_lines(T, [H|Acc]); 252 | list_to_lines([], Acc) -> 253 | case validate_line(lists:reverse(Acc)) of 254 | nil -> []; 255 | Val -> [Val] 256 | end. 257 | 258 | read_lines(Path) -> 259 | case file:read_file(Path) of 260 | {ok, Bin} -> 261 | Lines = list_to_lines(binary_to_list(Bin), []), 262 | {ok, Lines}; 263 | {error, Reason} -> 264 | {error, Reason} 265 | end. 266 | 267 | -------------------------------------------------------------------------------- /src/fsms/statem_tcp.erl: -------------------------------------------------------------------------------- 1 | %%% TCP statem 2 | %%% 3 | 4 | -module(statem_tcp). 5 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 6 | -behavior(gen_statem). 7 | 8 | -include("../includes/args.hrl"). 9 | 10 | % gen_statem imports 11 | -export([start_link/1, start/1]). 12 | -export([init/1, terminate/3, code_change/4]). 13 | -export([callback_mode/0]). 14 | 15 | % callbacks 16 | -export([connecting/3, callback/3]). 17 | 18 | % see http://erlang.org/doc/man/inet.html#setopts-2 19 | -define(COPTS, [binary, {packet, 0}, inet, {recbuf, 65536}, {active, false}, {reuseaddr, true}]). 20 | 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | %% gen_statem specific 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | %% called by start/start_link 25 | init(Args) -> 26 | doit(Args#args{ctarget=Args#args.target, cport=Args#args.port, retrycnt=Args#args.retry}). 27 | 28 | %% start the process 29 | doit(Args) -> 30 | debug(Args, io_lib:fwrite("~p on ~p", [Args#args.module, Args#args.ctarget])), 31 | % first let's call "connect" through "connecting" using a timeout of 0 32 | {ok, connecting, Args, 0}. 33 | 34 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 | %% fsm callbacks 36 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 37 | %% called for sending first packet 38 | callback(timeout, _EventContent, Data) when Data#args.retrycnt > 0 andalso Data#args.packetrcv == 0 39 | andalso Data#args.payload /= << >> -> 40 | send_data(Data#args{retrycnt=Data#args.retrycnt-1}); 41 | %% called for sending additional packet when needed 42 | callback(timeout, _EventContent, Data) -> 43 | case apply(Data#args.module, callback_next_step, [Data]) of 44 | {continue, Nbpacket, Payload, ModData} -> 45 | flush_socket(Data#args.socket), 46 | inet:setopts(Data#args.socket, [{active, once}]), % we want a packet 47 | send_data(Data#args{moddata=ModData,nbpacket=Nbpacket,payload=Payload}); 48 | {restart, {Target, Port}, ModData} -> 49 | Newtarget = case Target == undefined of true -> Data#args.ctarget; false -> Target end, 50 | Newport = case Port == undefined of true -> Data#args.cport; false -> Port end, 51 | gen_tcp:close(Data#args.socket), 52 | {next_state, connecting, Data#args{ctarget=Newtarget, cport=Newport, 53 | moddata=ModData, sending=false, retrycnt=Data#args.retry, 54 | datarcv = << >>, payload = << >>, packetrcv=0}, 0}; 55 | {result, Result} -> 56 | gen_tcp:close(Data#args.socket), 57 | {stop, normal, Data#args{result=Result}} 58 | end; 59 | %% called when a new packet is received 60 | callback(info, {tcp, _Socket, Packet}, Data) -> 61 | case Data#args.nbpacket of 62 | infinity -> 63 | inet:setopts(Data#args.socket, [{active, once}]), % We want more 64 | {next_state, callback, Data#args{ 65 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 66 | packetrcv = Data#args.packetrcv + 1 67 | }, 68 | Data#args.timeout}; 69 | 1 -> % It is the last packet to receive 70 | {next_state, callback, Data#args{ 71 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 72 | nbpacket = 0, 73 | packetrcv = Data#args.packetrcv + 1 74 | }, 75 | 0}; 76 | 0 -> % If they didn't want any packet ? 77 | {stop, normal, Data#args{result={ 78 | {error,up},[toomanypacketreceived, Packet]}}}; 79 | _ -> % They are more packets (maybe) 80 | inet:setopts(Data#args.socket, [{active, once}]), % We want more 81 | {next_state, callback, Data#args{ 82 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 83 | nbpacket=Data#args.nbpacket - 1, 84 | packetrcv = Data#args.packetrcv + 1 85 | }, 86 | Data#args.timeout} 87 | end; 88 | %% called when tcp is closed on the other end 89 | callback(info, {tcp_closed=Reason, _Socket}, Data) -> 90 | % Happen when there was still packets to receive 91 | % but the tcp port is closed 92 | {next_state, callback, Data#args{rcvreason=Reason}, 0}; 93 | %% called when error on socket 94 | callback(info, {tcp_error, _Socket, Reason}, Data) -> 95 | {next_state, callback, Data#args{rcvreason=Reason}, 0}; 96 | %% called when domain lookup failed 97 | callback(info, {timeout, _Socket, inet}, Data) -> 98 | {stop, normal, Data#args{result={{error, unknown}, dns_timeout}}}; 99 | %% generic error 100 | callback(Event, EventContent, Data) -> 101 | {stop, normal, Data#args{result={{error, unknown}, [unexpected_event, Event, EventContent, Data]}}}. 102 | 103 | %% State connecting is used to initiate the tcp connection 104 | connecting(timeout, _, Data) -> 105 | Host = Data#args.ctarget, Port = Data#args.cport, Timeout = Data#args.timeout, 106 | case utils_fp:lookup(Host, Timeout, Data#args.checkwww) of 107 | {ok, Addr} -> 108 | try 109 | case gen_tcp:connect(Addr, Port, get_options(Data), Timeout) of 110 | {ok, Socket} -> 111 | {next_state, callback, Data#args{socket=Socket, ipaddr=Addr}, 0}; 112 | {error, Reason} -> 113 | gen_statem:cast(self(), {error, list_to_atom("tcp_" ++ atom_to_list(Reason))}), 114 | {next_state, connecting, Data} 115 | end 116 | catch 117 | _:_ -> 118 | gen_statem:cast(self(), {error, tcp_conn_badaddr}), 119 | {next_state, connecting, Data} 120 | end; 121 | {error, Reason} -> 122 | gen_statem:cast(self(), {error, Reason}), 123 | {next_state, connecting, Data} 124 | end; 125 | %% called when connection is refused 126 | connecting(cast, {error, tcp_econnrefused=Reason}, Data) -> 127 | {stop, normal, Data#args{result={{error, up}, Reason}}}; 128 | connecting(cast, {error, econnrefused=Reason}, Data) -> 129 | {stop, normal, Data#args{result={{error, up}, Reason}}}; 130 | %% called when connection is reset 131 | connecting(cast, {error, econnreset=Reason}, Data) -> 132 | {stop, normal, Data#args{result={{error, up}, Reason}}}; 133 | %% called when source port is already taken 134 | connecting(cast, {error, tcp_eacces}, Data) 135 | when Data#args.privports == true, Data#args.eaccess_retry < Data#args.eaccess_max -> 136 | {next_state, connecting, Data#args{eaccess_retry=Data#args.eaccess_retry+1}, 0}; 137 | %% called when connection failed 138 | connecting(cast, {error, Reason}, Data) -> 139 | {stop, normal, Data#args{result={{error, unknown}, Reason}}}; 140 | %% called when timeout on socket, usually happens when too many targets on a single host 141 | connecting(info, {Reason, _Socket, inet}, Data) -> 142 | {stop, normal, Data#args{result={{error, unknown}, list_to_atom("socket_" ++ atom_to_list(Reason))}}}; 143 | %% called for any other errors during connection, shouldn't happen 144 | connecting(info, Err, Data) -> 145 | {stop, normal, Data#args{result={{error, unknown}, Err}}}. 146 | 147 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 148 | %% utils 149 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 150 | %% get privport opt 151 | get_privports(true) -> 152 | [{port, rand:uniform(1024)}]; 153 | get_privports(_) -> 154 | []. 155 | 156 | %% provide the socket option 157 | get_options(Args) -> 158 | ?COPTS ++ get_privports(Args#args.privports) 159 | ++ Args#args.fsmopts. 160 | 161 | %% receive everything available from the socket 162 | flush_socket(Socket) -> 163 | case gen_tcp:recv(Socket, 0, 0) of 164 | {error, _Reason} -> 165 | ok; 166 | {ok, _Result} -> 167 | flush_socket(Socket) 168 | end. 169 | 170 | %% send data 171 | send_data(Data) -> 172 | case gen_tcp:send(Data#args.socket, Data#args.payload) of 173 | ok -> 174 | {next_state, callback, Data#args{ 175 | sending=true, 176 | datarcv = << >>, 177 | packetrcv = 0 178 | }, 179 | Data#args.timeout}; 180 | {error, Reason} -> 181 | {next_state, callback, Data#args{sndreason=Reason}, 0} 182 | end. 183 | 184 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 185 | %% helper for the fsm 186 | %% gen_statem http://erlang.org/doc/man/gen_statem.html 187 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 188 | %% this is when there's no supervisor 189 | start_link(Args) -> 190 | gen_statem:start_link(?MODULE, Args, []). 191 | %% this is when it's part of a supervised tree 192 | start([Args]) -> 193 | gen_statem:start(?MODULE, Args, []). 194 | 195 | %% set the callback mode for gen_statem 196 | callback_mode() -> 197 | state_functions. 198 | 199 | %% called by stop 200 | terminate(_Reason, _State, Data) -> 201 | Result = {Data#args.module, Data#args.target, Data#args.port, Data#args.result}, 202 | debug(Data, io_lib:fwrite("~p done on ~p (outdirect:~p)", 203 | [Data#args.module, Data#args.target, Data#args.direct])), 204 | case Data#args.direct of 205 | true -> 206 | utils:outputs_send(Data#args.outobj, [Result]); 207 | false -> 208 | Data#args.parent ! Result 209 | end, 210 | ok. 211 | 212 | %% unused callback 213 | code_change(_Prev, State, Data, _Extra) -> 214 | {ok , State, Data}. 215 | 216 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 217 | %% debug 218 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 219 | %% send debug 220 | debug(Args, Msg) -> 221 | utils:debug(fpmodules, Msg, 222 | {Args#args.target, Args#args.id}, Args#args.debugval). 223 | 224 | -------------------------------------------------------------------------------- /src/scannerl.erl: -------------------------------------------------------------------------------- 1 | %#!/usr/bin/env escript 2 | %%! -smp enable -sname scannerl -K true -P 134217727 -kernel dist_nodelay false 3 | % 4 | % distributed fingerprinting engine 5 | % 6 | % Be aware that: 7 | % - hostnames must resolve (on each side) 8 | % - SSH is used to connect to slave nodes 9 | % - master username is used 10 | % - key authentication must be working 11 | % - remote must be trusted (ECDSA key) 12 | % 13 | 14 | -module(scannerl). 15 | -export([main/1]). 16 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 17 | -include("includes/opts.hrl"). 18 | -include("includes/args.hrl"). 19 | -include("includes/defines.hrl"). 20 | 21 | -ifdef(USE_GENFSM). 22 | -define(TCPFSM, fsm_tcp). 23 | -define(UDPFSM, fsm_udp). 24 | -define(SSLFSM, fsm_ssl). 25 | -define(FSMMODE, "using genfsm"). 26 | -else. 27 | -define(TCPFSM, statem_tcp). 28 | -define(UDPFSM, statem_udp). 29 | -define(SSLFSM, statem_ssl). 30 | -define(FSMMODE, "using statem"). 31 | -endif. 32 | 33 | -define(MODULES, % master module 34 | [ 35 | master, % the master module 36 | utils, % utils 37 | utils_slave, % utils to start slave nodes 38 | cntlist % count list for slave nodes 39 | ]). 40 | -define(SLMODULES, % slave modules 41 | [ 42 | % base modules 43 | broker, % the slave broker 44 | scannerl_worker_sup, % the supervisor 45 | tgt, % the target parser/handler 46 | utils, % utils 47 | utils_fp, % fingerprinting utils 48 | fp_module, 49 | out_behavior, 50 | ?TCPFSM, 51 | ?UDPFSM, 52 | ?SSLFSM, 53 | % utilities 54 | utils_http, 55 | utils_ssl, 56 | utils_fox, 57 | ssh_key_cb, 58 | utils_mqtt 59 | ]). 60 | -define(SCANNERL, "scannerl"). 61 | -define(CHECKTO, 60000). % 1 minute 62 | 63 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 64 | % utilities 65 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 66 | msg_queue_len() -> 67 | {_, Cnt} = erlang:process_info(self(), message_queue_len), 68 | Cnt. 69 | 70 | % globally register myself as scannerl 71 | register_myself() -> 72 | yes = global:register_name(list_to_atom(?SCANNERL), self()), 73 | yes = global:re_register_name(list_to_atom(?SCANNERL), self()), 74 | global:sync(). 75 | 76 | % print duration 77 | duration(Start) -> 78 | Duration = calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - Start, 79 | DateTime = calendar:gregorian_seconds_to_datetime(Duration), 80 | {{_Year, _Month, _Day}, {Hour, Min, Sec}} = DateTime, 81 | [Hour, Min, Sec]. 82 | 83 | print(error, Msg) -> 84 | M = io_lib:fwrite("> ~s\n", [Msg]), 85 | io:put_chars(standard_error, M); 86 | print(stdout, Msg) -> 87 | M = io_lib:fwrite("> ~s\n", [Msg]), 88 | io:fwrite(M); 89 | print(normal, Msg) -> 90 | M = io_lib:fwrite("~s\n", [Msg]), 91 | io:put_chars(standard_error, M). 92 | 93 | progress(true, Nb, 0) -> 94 | M = io_lib:fwrite("[progress] ~p match on ? fingerprinted so far (queue_len: ~p)\n", 95 | [Nb, msg_queue_len()]), 96 | io:put_chars(standard_error, M); 97 | progress(true, Nb, Tot) -> 98 | M = io_lib:fwrite("[progress] ~p match on ~p fingerprinted (~.2f%) so far (queue_len: ~p)\n", 99 | [Nb, Tot, Nb*100/Tot, msg_queue_len()]), 100 | io:put_chars(standard_error, M); 101 | progress(false, _, _) -> 102 | ok. 103 | 104 | % print the number of results and a percentage if possible 105 | print_percentage(Cnt, 0) -> 106 | print(normal, io_lib:fwrite("nb result: ~p/~p", [Cnt, 0])); 107 | print_percentage(Cnt, Tot) -> 108 | print(normal, io_lib:fwrite("nb result: ~p/~p (~.2f%)", [Cnt, Tot, Cnt*100/Tot])). 109 | 110 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 111 | % output modules 112 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 113 | outs_init(Outs, Scaninfo, Outmode) when Outmode == 0 -> 114 | %print(normal, "init output module(s)"), 115 | case utils:outputs_init(Outs, Scaninfo) of 116 | {error, Reason} -> 117 | print(error, io_lib:fwrite("Output setup failed: ~p", [Reason])), 118 | utils_opts:usage(); 119 | {ok, Os} -> 120 | Os 121 | end; 122 | outs_init(_, _, _) -> 123 | print(normal, "output is not done on master"), 124 | []. 125 | 126 | outs_clean(_, Outmode) when Outmode /= 0 -> 127 | ok; 128 | outs_clean(Outs, _) -> 129 | utils:outputs_clean(Outs). 130 | 131 | outs_send(_, _, Outmode) when Outmode /= 0 -> 132 | ok; 133 | outs_send(Outs, Msg, _) -> 134 | utils:outputs_send(Outs, [Msg]). 135 | 136 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 137 | % communication 138 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 139 | % check the queue for queuemax 140 | check_queue(Opts) when Opts#opts.queuemax == 0 -> 141 | false; 142 | check_queue(Opts) when Opts#opts.pause == true -> 143 | Nb = msg_queue_len(), 144 | case Nb < Opts#opts.queuemax/2 of 145 | true -> 146 | % we need to resume 147 | utils:debug(scannerl, io_lib:fwrite("requiring master to resume ~p/~p~n", 148 | [Nb, Opts#opts.queuemax]), {}, Opts#opts.debugval), 149 | erlang:garbage_collect(), 150 | try 151 | erlang:whereis(masternode) ! {resume} 152 | catch 153 | _:_ -> 154 | ok 155 | end, 156 | false; 157 | false -> 158 | true 159 | end; 160 | check_queue(Opts) when Opts#opts.pause == false -> 161 | Nb = msg_queue_len(), 162 | case Nb > Opts#opts.queuemax of 163 | true -> 164 | % we need to pause 165 | utils:debug(scannerl, io_lib:fwrite("requiring master to pause ~p/~p~n", 166 | [Nb, Opts#opts.queuemax]), {}, Opts#opts.debugval), 167 | try 168 | erlang:whereis(masternode) ! {pause} 169 | catch 170 | _:_ -> 171 | ok 172 | end, 173 | true; 174 | false -> 175 | false 176 | end. 177 | 178 | % returns either 179 | % error: error occured 180 | % {total done, nb positiv result, nb nodes} 181 | rcv_loop(Opts, Outputs, Tot, Nbposres) -> 182 | Pause = check_queue(Opts), 183 | utils:debug(scannerl, 184 | io_lib:fwrite("(pause:~p) result received: ~p | queuelen: ~p", [Pause, 185 | Tot, msg_queue_len()]), {}, Opts#opts.debugval), 186 | receive 187 | {progress} -> 188 | % triggered by message server and sent by the master 189 | progress(true, Nbposres, Tot), 190 | rcv_loop(Opts, Outputs, Tot, Nbposres); 191 | {done, error} -> 192 | % sent by master 193 | {error}; 194 | {done, {Nbnodes}} -> 195 | % sent by master 196 | {Tot, Nbposres, Nbnodes}; 197 | Result = {_, _, _, {{ok, result}, _}} -> 198 | % sent by fp module 199 | outs_send(Outputs, Result, Opts#opts.outmode), 200 | progress(Opts#opts.progress, Nbposres, Tot), 201 | rcv_loop(Opts#opts{pause=Pause}, Outputs, Tot+1, Nbposres+1); 202 | Result = {_, _, _, {{_, _}, _}} -> 203 | % sent by fp module 204 | outs_send(Outputs, Result, Opts#opts.outmode), 205 | progress(Opts#opts.progress, Nbposres, Tot), 206 | rcv_loop(Opts#opts{pause=Pause}, Outputs, Tot+1, Nbposres); 207 | Msg -> 208 | % ? 209 | print(error, io_lib:fwrite("discarded message: ~p", [Msg])), 210 | rcv_loop(Opts#opts{pause=Pause}, Outputs, Tot, Nbposres) 211 | after 212 | ?CHECKTO -> 213 | rcv_loop(Opts#opts{pause=Pause}, Outputs, Tot, Nbposres) 214 | end. 215 | 216 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 217 | % master related 218 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 219 | % launch the scanning process 220 | doit(Opts) -> 221 | Pid = spawn_link(master, master, [Opts]), 222 | erlang:register(masternode, Pid). 223 | 224 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 225 | % entry point 226 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 227 | % end of scan 228 | finishit(Start, _Outputs, _Outmode, {error}) -> 229 | print(normal, "Failed"), 230 | print(normal, io_lib:fwrite("duration: ~2..0w:~2..0w:~2..0w", duration(Start))), 231 | halt(1); 232 | finishit(Start, Outputs, Outmode, {Cnt, Rescnt, _Nbslaves}) -> 233 | % clean outputs 234 | outs_clean(Outputs, Outmode), 235 | print_percentage(Rescnt, Cnt), 236 | print(normal, io_lib:fwrite("duration: ~2..0w:~2..0w:~2..0w", duration(Start))), 237 | halt(0). 238 | 239 | % entry point 240 | main(Args) -> 241 | _ = os:cmd("epmd -daemon"), 242 | Start = calendar:datetime_to_gregorian_seconds(calendar:universal_time()), 243 | register_myself(), 244 | 245 | % parsing the argument in a map 246 | %print(normal, "parsing args ..."), 247 | Map = utils_opts:getopt(Args), 248 | 249 | % print the banner 250 | utils_opts:banner(), 251 | 252 | % compile needed modules 253 | Tmp = ?SLMODULES ++ [maps:get("m", Map)], 254 | Omods = lists:filtermap(fun(X) -> {M, _} = X, {true, M} end, maps:get("o", Map)), 255 | Mods = lists:append([Tmp, Omods]), 256 | 257 | % print erlang version 258 | print(normal, io_lib:fwrite("compiled with erlang ~s (~s)", [?ERLANG_VERSION, ?FSMMODE])), 259 | 260 | % fill the opt record 261 | Opts = utils_opts:optfill(Map, Mods, ?VERSION), 262 | %case Opts#opts.queuemax of 263 | % Nb -> 264 | % print(normal, io_lib:fwrite("max queue length: ~p", [Nb])) 265 | % 0 -> 266 | % print(normal, "max queue length: infinite"); 267 | %end, 268 | 269 | % check and initialize the output modules 270 | Outputs = outs_init(Opts#opts.output, Opts#opts.scaninfo, Opts#opts.outmode), 271 | 272 | % and start the master 273 | print(normal, "starting master ..."), 274 | doit(Opts), 275 | 276 | % wait for end 277 | process_flag(priority, high), 278 | finishit(Start, Outputs, Opts#opts.outmode, rcv_loop(Opts, Outputs, 0, 0)). 279 | 280 | -------------------------------------------------------------------------------- /src/fsms/fsm_ssl.erl: -------------------------------------------------------------------------------- 1 | %%% ssl FSM Reference 2 | %%% 3 | %%% 4 | %%% specific option can be set to control how 5 | %%% the remote certificate is checked: 6 | %%% {sslcheck, true}: check the certificate validity 7 | %%% {sslcheck, full}: the above plus the domain check 8 | %%% {sslcheck, false}: disable ssl checking 9 | %%% {sni, disable}: disable sni 10 | %%% {sni, enable}: enable sni 11 | %%% defaults is erlang's defaults (http://erlang.org/doc/man/ssl.html) 12 | %%% 13 | 14 | -module(fsm_ssl). 15 | -author("David Rossier - david.rossier@kudelskisecurity.com"). 16 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 17 | -behavior(gen_fsm). 18 | 19 | -include("../includes/args.hrl"). 20 | 21 | -export([start_link/1, start/1]). 22 | -export([init/1, terminate/3, handle_info/3]). 23 | -export([code_change/4, handle_sync_event/4, handle_event/3]). 24 | -export([connecting/2, receiving/2, callback/2]). 25 | 26 | % see http://erlang.org/doc/man/inet.html#setopts-2 27 | -define(COPTS, [binary, {packet, 0}, inet, {recbuf, 65536}, {active, false}, {reuseaddr, true}]). 28 | 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | %% debug 31 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 32 | % send debug 33 | debug(Args, Msg) -> 34 | utils:debug(fpmodules, Msg, 35 | {Args#args.target, Args#args.id}, Args#args.debugval). 36 | 37 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 38 | %% API calls 39 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 40 | send_data(Data) -> 41 | case ssl:send(Data#args.socket, Data#args.payload) of 42 | ok -> 43 | {next_state, receiving, Data#args{ 44 | sending=true, 45 | datarcv = << >>, 46 | packetrcv = 0 47 | }, 48 | 0}; 49 | {error, Reason} -> 50 | {next_state, callback, Data#args{sndreason=Reason}, 0} 51 | end. 52 | 53 | 54 | % Defines what to do next. 55 | callback(timeout, Data) when Data#args.retrycnt > 0 andalso Data#args.packetrcv == 0 56 | andalso Data#args.payload /= << >> -> 57 | send_data(Data#args{retrycnt=Data#args.retrycnt-1}); 58 | callback({error, Reason}, Data) -> 59 | ssl:close(Data#args.socket), 60 | {stop, normal, Data#args{result={{error, up}, Reason}}}; 61 | callback(timeout, Data) -> 62 | case apply(Data#args.module, callback_next_step, [Data]) of 63 | {continue, Nbpacket, Payload, ModData} -> 64 | %flush_socket(Data#args.socket), 65 | send_data(Data#args{nbpacket=Nbpacket, payload=Payload, moddata=ModData}); 66 | {restart, {Target, Port}, ModData} -> 67 | Newtarget = case Target == undefined of true -> Data#args.ctarget; false -> Target end, 68 | Newport = case Port == undefined of true -> Data#args.cport; false -> Port end, 69 | ssl:close(Data#args.socket), 70 | {next_state, connecting, Data#args{ctarget=Newtarget, cport=Newport, 71 | moddata=ModData, sending=false, retrycnt=Data#args.retry, 72 | datarcv = << >>, payload = << >>, packetrcv=0}, 0}; 73 | {result, Result} -> 74 | ssl:close(Data#args.socket), 75 | {stop, normal, Data#args{result=Result}} % RESULT 76 | end. 77 | 78 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 | %% gen_fsm modules (http://www.erlang.org/doc/man/gen_fsm.html) 80 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 81 | % this is when there's no supervisor Args is an #args record 82 | start_link(Args) -> 83 | gen_fsm:start_link(?MODULE, Args, []). 84 | % this is when it's part of a supervised tree 85 | start([Args]) -> 86 | gen_fsm:start(?MODULE, Args, []). 87 | 88 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 89 | %% gen_fsm callbacks 90 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 91 | % called by start/start_link 92 | init(Args) -> 93 | % Remove this when the SSL library is fixed (see #6068) 94 | error_logger:tty(false), 95 | ssl:start(), 96 | doit(Args#args{ctarget=Args#args.target, cport=Args#args.port, retrycnt=Args#args.retry}). 97 | 98 | % start the process 99 | doit(Args) -> 100 | debug(Args, io_lib:fwrite("~p on ~p", [Args#args.module, Args#args.ctarget])), 101 | % first let's call "connect" through "connecting" using a timeout of 0 102 | {ok, connecting, Args, 0}. 103 | 104 | % parse options 105 | parse_ssl_opts([], _Tgt, Acc) -> 106 | Acc; 107 | parse_ssl_opts([{sslcheck, true}|T], Tgt, Acc) -> 108 | % parse sslcheck 109 | Opt = utils_ssl:get_opts_verify([]), 110 | parse_ssl_opts(T, Tgt, Acc ++ Opt); 111 | parse_ssl_opts([{sslcheck, false}|T], Tgt, Acc) -> 112 | % parse sslcheck 113 | Opt = utils_ssl:get_opts_noverify(), 114 | parse_ssl_opts(T, Tgt, Acc ++ Opt); 115 | parse_ssl_opts([{sslcheck, full}|T], Tgt, Acc) -> 116 | % parse sslcheck 117 | Opt = utils_ssl:get_opts_verify(Tgt), 118 | parse_ssl_opts(T, Tgt, Acc ++ Opt); 119 | parse_ssl_opts([{sni, enable}|T], Tgt, Acc) -> 120 | % parse sni 121 | Opt = [{server_name_indication, utils:tgt_to_string(Tgt)}], 122 | parse_ssl_opts(T, Tgt, Acc ++ Opt); 123 | parse_ssl_opts([{sni, disable}|T], Tgt, Acc) -> 124 | % parse sni 125 | Opt = [{server_name_indication, disable}], 126 | parse_ssl_opts(T, Tgt, Acc ++ Opt); 127 | parse_ssl_opts([H|T], Tgt, Acc) -> 128 | % parse the rest 129 | parse_ssl_opts(T, Tgt, Acc ++ [H]). 130 | 131 | % get privport opt 132 | get_privports(true) -> 133 | [{port, rand:uniform(1024)}]; 134 | get_privports(_) -> 135 | []. 136 | 137 | % provide the socket option 138 | get_options(Args) -> 139 | % opts from the module 140 | Opts1 = parse_ssl_opts(Args#args.fsmopts, Args#args.ctarget, []), 141 | % opts from the cli 142 | Opts2 = parse_ssl_opts(Args#args.sockopt, Args#args.ctarget, []), 143 | % merge all opts 144 | utils:merge_sockopt(?COPTS ++ get_privports(Args#args.privports) 145 | ++ Opts1, Opts2). 146 | 147 | % State connecting is used to initiate the ssl connection 148 | connecting(timeout, Data) -> 149 | Host = Data#args.ctarget, Port = Data#args.cport, Timeout = Data#args.timeout, 150 | case utils_fp:lookup(Host, Timeout, Data#args.checkwww) of 151 | {ok, Addr} -> 152 | try 153 | case ssl:connect(Addr, Port, get_options(Data), Timeout) of 154 | {ok, Socket} -> 155 | {next_state, callback, Data#args{socket=Socket,ipaddr=Addr}, 0}; 156 | {error, {tls_alert, Reason}} -> 157 | gen_fsm:send_event(self(), {error, {tls_error, Reason}}), 158 | {next_state, connecting, Data}; 159 | {error, Reason} -> 160 | gen_fsm:send_event(self(), {error, Reason}), 161 | {next_state, connecting, Data} 162 | end 163 | catch 164 | _:_ -> 165 | gen_fsm:send_event(self(), {error, unknown}), 166 | {next_state, connecting, Data} 167 | end; 168 | {error, Reason} -> 169 | gen_fsm:send_event(self(), {error, Reason}), 170 | {next_state, connecting, Data} 171 | end; 172 | % called when connection is refused 173 | connecting({error, econnrefused=Reason}, Data) -> 174 | {stop, normal, Data#args{result={{error, up}, Reason}}}; % RESULT 175 | % called when connection is reset 176 | connecting({error, econnreset=Reason}, Data) -> 177 | {stop, normal, Data#args{result={{error, up}, Reason}}}; % RESULT 178 | % called when source port is already taken 179 | connecting({error, tcp_eacces}, Data) 180 | when Data#args.privports == true, Data#args.eaccess_retry < Data#args.eaccess_max -> 181 | {next_state, connecting, Data#args{eaccess_retry=Data#args.eaccess_retry+1}, 0}; 182 | % called when tls alert occurs (badcert, ...) 183 | connecting({error, {tls_error=Type, R}}, Data) -> 184 | {stop, normal, Data#args{result={{error, up}, [Type, R]}}}; % RESULT 185 | % called when connection failed 186 | connecting({error, Reason}, Data) -> 187 | {stop, normal, Data#args{result={{error, unknown}, Reason}}}. % RESULT 188 | 189 | receiving(timeout, Data) -> 190 | try 191 | case ssl:recv(Data#args.socket, 0, Data#args.timeout) of 192 | {ok, Packet} -> 193 | handle_packet(Packet, Data); 194 | {error, Reason} -> 195 | {next_state, callback, Data#args{rcvreason=Reason}, 0} 196 | end 197 | catch 198 | _Err:_Exc -> 199 | gen_fsm:send_event(self(), {error, ssl_proto_error}), 200 | {next_state, callback, Data#args{rcvreason=ssl_proto_error}, 0} 201 | end. 202 | 203 | %% called by stop 204 | terminate(_Reason, _State, Data) -> 205 | Result = {Data#args.module, Data#args.target, Data#args.port, Data#args.result}, 206 | debug(Data, io_lib:fwrite("~p done on ~p (outdirect:~p)", 207 | [Data#args.module, Data#args.target, Data#args.direct])), 208 | case Data#args.direct of 209 | true -> 210 | utils:outputs_send(Data#args.outobj, [Result]); 211 | false -> 212 | Data#args.parent ! Result 213 | end, 214 | error_logger:tty(true), 215 | ok. 216 | 217 | % flush_socket(Socket) -> 218 | % case ssl:recv(Socket, 0, 0) of 219 | % {error, _Reason} -> 220 | % ok; 221 | % {ok, _Result} -> 222 | % flush_socket(Socket) 223 | % end. 224 | 225 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 226 | %% event handlers 227 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 228 | % called when a new packet is received 229 | handle_packet(Packet, Data) -> 230 | case Data#args.nbpacket of 231 | infinity -> 232 | {next_state, receiving, Data#args{ 233 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 234 | packetrcv = Data#args.packetrcv + 1 235 | }, 236 | 0}; 237 | 1 -> % It is the last packet to receive 238 | {next_state, callback, Data#args{ 239 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 240 | nbpacket = 0, 241 | packetrcv = Data#args.packetrcv + 1 242 | }, 243 | 0}; 244 | 0 -> % If they didn't want any packet ? 245 | {stop, normal, Data#args{result={ 246 | {error,up},[toomanypacketreceived, Packet]}}}; % RESULT 247 | _ -> % They are more packets (maybe) 248 | {next_state, receiving, Data#args{ 249 | datarcv = <<(Data#args.datarcv)/binary, Packet/binary>>, 250 | nbpacket=Data#args.nbpacket - 1, 251 | packetrcv = Data#args.packetrcv + 1 252 | }, 253 | 0} 254 | end. 255 | 256 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 257 | %% UNUSED event handlers 258 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 259 | code_change(_Prev, State, Data, _Extra) -> 260 | {ok , State, Data}. 261 | handle_sync_event(_Ev, _From, _State, Data) -> 262 | {stop, unexpectedSyncEvent, Data#args{result={{error, unknown}, unexpectedSyncEvent}}}. 263 | handle_event(_Ev, _State, Data) -> 264 | {stop, unexpectedEvent, Data#args{result={{error, unknown}, unexpectedEvent}}}. 265 | handle_info({ssl_closed, _Socket}, _State, Data) -> 266 | % called when ssl socket is abruptly closed 267 | {stop, normal, Data#args{result={{error, up}, ssl_closed}}}; 268 | handle_info({ssl_error, _Socket, {tls_alert, Err}}, _State, Data) -> 269 | % called when tls_alert 270 | {stop, normal, Data#args{result={{error, up}, [tls_alert, Err]}}}; 271 | handle_info(Ev, _State, Data) -> 272 | {stop, normal, Data#args{result={{error, up}, [unexpectedEvent2, Ev]}}}. 273 | 274 | -------------------------------------------------------------------------------- /src/utils/utils_http.erl: -------------------------------------------------------------------------------- 1 | %% utils for parsing http and handling redirection 2 | %% 3 | %% when redirections occurs, you still need to check 4 | %% that the redirection does not point back to your 5 | %% original target and page after some redirections 6 | %% as no internal stack of redirections is kept in here 7 | %% 8 | %% type of result returned: 9 | %% {ok, {Code, Headermap, Body}} 10 | %% HTTP 200 received and a Map containing the header options as well 11 | %% as the body are returned 12 | %% {error, Data}: 13 | %% unable to parse http 14 | %% {redirect, {error, empty}, {Code, Headermap, Body}} 15 | %% Redirection found (3XX) but no value 16 | %% given 17 | %% {redirect, {error, cyclic}, {Code, Headermap, Body}} 18 | %% redirection (3XX) is cyclic 19 | %% {redirect, {error, Location}, {Code, Headermap, Body}} 20 | %% redirection error while parsing the location 21 | %% {redirect, {ok, {Host, Page}}, {Code, Headermap, Body}} 22 | %% redirection to Host and Page 23 | %% {redirect, {https, {Host, Page}}, {Code, Headermap, Body}} 24 | %% redirection HTTPs on Host and Page 25 | %% {http, {Code, Headermap, Body}} 26 | %% a HTTP code was received that is not 2XX or 3XX 27 | %% {other, {Code, Headermap, Body}} 28 | %% Something was received that didn't seem to be HTTP 29 | %% 30 | 31 | -module(utils_http). 32 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 33 | 34 | -export([parse_http/3, parse_http/4]). 35 | 36 | % parsing defines 37 | -define(HTTP_OK, "2"). 38 | -define(HTTP_REDIRECT, "3"). 39 | -define(CRLF, "\r\n"). 40 | -define(LF, "\n"). 41 | -define(HDRFIELDSEP, ":"). 42 | -define(PAGE_SEP, "/"). 43 | -define(PAGE_RET, ".."). 44 | -define(HTTP_LOCATION, "location"). 45 | 46 | -record(rec, { 47 | code, 48 | page, 49 | host, 50 | headermap, 51 | header, 52 | body, 53 | dbg, 54 | protoline, 55 | payload 56 | }). 57 | 58 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 59 | % API 60 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 61 | % parse an http response from host Host (the entry in the Host: field of the 62 | % query) to page Page (for example "/" or "/readme.txt") 63 | % if Dbginfo is set to {Target, Id, Debugval} then 64 | % debug will be outputed if needed otherwise set it to {} 65 | parse_http(Host, Page, Payload) -> 66 | parse_http(Host, Page, Payload, {}). 67 | parse_http(Host, Page, Payload, DbgInfo) -> 68 | Resp = fix_crlf(Payload), 69 | case parse_response(Resp, []) of 70 | ["", Body] -> 71 | debug(DbgInfo, "this is no HTTP or no header found"), 72 | {other, {"", maps:new(), Body}}; 73 | [Header, Body] -> 74 | debug(DbgInfo, "HTTP parse successfully"), 75 | Rec = #rec{host=Host, page=Page, header=Header, body=Body, payload=Payload, dbg=DbgInfo}, 76 | match_header(Rec, get_proto_code(Header, [])) 77 | end. 78 | 79 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 80 | % matchers 81 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 82 | match_header(Rec, {[], _}) -> 83 | {error, Rec#rec.payload}; 84 | match_header(Rec, {Protoline, HeaderFields}) -> 85 | debug(Rec#rec.dbg, io_lib:fwrite("HTTP response: ~p", [Protoline])), 86 | % get a map of the header 87 | Headermap = parse_header(HeaderFields, maps:new(), []), 88 | Nrec = Rec#rec{protoline=Protoline, headermap=Headermap}, 89 | match_proto(Nrec). 90 | 91 | match_proto(Rec) when length(Rec#rec.protoline) < 2 -> 92 | {other, {lists:concat(Rec#rec.protoline), Rec#rec.header, Rec#rec.body}}; 93 | match_proto(Rec) -> 94 | case validate_http_code(lists:nth(2, Rec#rec.protoline)) of 95 | {ok, ?HTTP_OK ++ _ = Code} -> 96 | % 2XX 97 | {ok, {Code, Rec#rec.headermap, Rec#rec.body}}; 98 | {ok, ?HTTP_REDIRECT ++ _ = Code} -> 99 | % 3XX 100 | NRec = Rec#rec{code = Code}, 101 | handle_redirect(NRec); 102 | {ok, Code} -> 103 | {http, {Code, Rec#rec.headermap, Rec#rec.body}}; 104 | {error, _Code} -> 105 | % other stuff 106 | {other, {lists:concat(Rec#rec.protoline), Rec#rec.headermap, Rec#rec.body}} 107 | end. 108 | 109 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 110 | % handler 111 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 112 | % handle redirection 113 | handle_redirect(Rec) -> 114 | Loc = maps:get(?HTTP_LOCATION, Rec#rec.headermap, ""), 115 | debug(Rec#rec.dbg, io_lib:fwrite(" Header: ~p", [Rec#rec.headermap])), 116 | {redirect, redirect_location(Loc, Rec), 117 | {Rec#rec.code, Rec#rec.headermap, Rec#rec.body}}. 118 | 119 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 120 | % debug 121 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 122 | debug({}, _) -> 123 | ok; 124 | debug({Target, Id, Debugval}, Msg) -> 125 | utils:debug(fpmodules, Msg, 126 | {Target, Id}, Debugval). 127 | 128 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 129 | % parsing 130 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 131 | % parse each header field and return 132 | % a list with key, value 133 | parse_hdr_field(?HDRFIELDSEP ++ Rest, Acc) -> 134 | [string:strip(lists:reverse(Acc)), string:strip(Rest)]; 135 | parse_hdr_field([H|T], Acc) -> 136 | parse_hdr_field(T, [H|Acc]); 137 | parse_hdr_field([], Acc) -> 138 | [lists:reverse(Acc), ""]. 139 | 140 | % this allows to retrieve the http code 141 | % line from the header 142 | get_proto_code([], _) -> 143 | {[], []}; 144 | get_proto_code(?CRLF ++ Rest, Acc) -> 145 | {string:tokens(lists:reverse(Acc), " "), Rest}; 146 | get_proto_code([H|T], Acc) -> 147 | get_proto_code(T, [H|Acc]). 148 | 149 | % parse the header and isolate each 150 | % option to process 151 | % returns a map of the options 152 | parse_header(?CRLF ++ [], Map, Acc) -> 153 | [H, T] = parse_hdr_field(lists:reverse(Acc), []), 154 | maps:put(normalize_key(H), T, Map); 155 | parse_header(?CRLF ++ Rest, Map, Acc) -> 156 | [H, T] = parse_hdr_field(lists:reverse(Acc), []), 157 | Nmap = maps:put(normalize_key(H), T, Map), 158 | parse_header(Rest, Nmap, []); 159 | parse_header([H|T], Map, Acc) -> 160 | parse_header(T, Map, [H|Acc]); 161 | parse_header([], Map, _Agg) -> 162 | Map. 163 | 164 | % only parse header/body if we have HTTP code 165 | parse_response("HTTP" ++ _ = Res, Acc) -> 166 | sub_parse_response(Res, Acc); 167 | parse_response(Else, _Acc) -> 168 | ["", Else]. 169 | 170 | % parse the response and separate the 171 | % header and the body separated by two CRLF 172 | sub_parse_response(?CRLF ++ ?CRLF ++ Rest, Acc) -> 173 | [lists:reverse(Acc)++?CRLF, Rest]; 174 | sub_parse_response([H|T], Acc) -> 175 | sub_parse_response(T, [H|Acc]); 176 | sub_parse_response([], _Acc) -> 177 | ["", ""]. 178 | 179 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 180 | % utils 181 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 182 | % handles @&#($* not following RFC 183 | fix_crlf(Data) -> 184 | case string:str(Data, ?CRLF) of 185 | 0 -> 186 | re:replace(Data, ?LF, ?CRLF, [{return,list},global]); 187 | _ -> 188 | Data 189 | end. 190 | 191 | % loose validate HTTP code 192 | validate_http_code(Code) -> 193 | try 194 | case list_to_integer(Code) >= 100 andalso list_to_integer(Code) < 600 of 195 | true -> 196 | {ok, Code}; 197 | false -> 198 | {error, Code} 199 | end 200 | catch 201 | _:_ -> 202 | {error, Code} 203 | end. 204 | 205 | % normalize header option 206 | normalize_key(Key) -> 207 | string:strip(string:to_lower(Key)). 208 | 209 | %% RFC2616 (https://tools.ietf.org/html/rfc2616#section-14.30) specifies 210 | %% that the location should be an absolute URI. 211 | %% however since 2014 the new RFC (https://tools.ietf.org/html/rfc7231#section-7.1.2) 212 | %% allows relative and absolute URI 213 | %% relative-path definition is https://tools.ietf.org/html/rfc3986#section-4.2 214 | redirect_location([], _Rec) -> 215 | % empty redirect 216 | {error, empty}; 217 | redirect_location("http://" = Loc, _Rec) -> 218 | {error, Loc}; 219 | redirect_location("//" ++ Redir, Rec) -> 220 | % example: 221 | % redirect: /// 222 | redirect_location("http://" ++ Redir, Rec); 223 | redirect_location("/" ++ _ = Page, Rec) -> 224 | % example: 225 | % redirect: /frontend/../it/home" 226 | redirect_follow(Rec#rec.host, Rec#rec.page, Rec#rec.host, eval_redirect_page(Page, Rec#rec.dbg), Rec#rec.dbg); 227 | redirect_location("../" ++ _ = Page, Rec) -> 228 | % example: 229 | % redirect: ../home 230 | % redirect: ..//asplogin.asp 231 | % redirect: ../staff_online/staff/main/stafflogin.asp?action=start 232 | redirect_follow(Rec#rec.host, Rec#rec.page, Rec#rec.host, 233 | eval_redirect_page(Rec#rec.page ++ Page, Rec#rec.dbg), Rec#rec.dbg); 234 | redirect_location("http://" ++ Field, Rec) -> 235 | % now split host and page 236 | Ends = string:right(Field, 1) == ?PAGE_SEP, 237 | Fields = string:tokens(Field, ?PAGE_SEP), 238 | case Fields of 239 | [] -> 240 | {error, empty}; 241 | F -> 242 | Host = string:strip(hd(F), right, $.), 243 | Page = ?PAGE_SEP ++ string:join(tl(F), ?PAGE_SEP), 244 | NewPage = complete_page(Page, Ends), 245 | redirect_follow(Rec#rec.host, Rec#rec.page, Host, NewPage, Rec#rec.dbg) 246 | end; 247 | redirect_location("https://" ++ Field, _Rec) -> 248 | % now split host and page 249 | Ends = string:right(Field, 1) == ?PAGE_SEP, 250 | Fields = string:tokens(Field, ?PAGE_SEP), 251 | case Fields of 252 | [] -> 253 | {error, empty}; 254 | F -> 255 | Host = string:strip(hd(F), right, $.), 256 | Page = ?PAGE_SEP ++ string:join(tl(F), ?PAGE_SEP), 257 | NewPage = complete_page(Page, Ends), 258 | {https, {Host, NewPage}} 259 | end; 260 | redirect_location(Location, Rec) -> 261 | % complete current page with redirect 262 | NewPage = eval_redirect_page(Rec#rec.page ++ ?PAGE_SEP ++ Location, Rec#rec.dbg), 263 | redirect_follow(Rec#rec.host, Rec#rec.page, Rec#rec.host, NewPage, Rec#rec.dbg). 264 | 265 | redirect_follow(Curhost, Curpage, NewHost, NewPage, Dbg) -> 266 | case (NewHost == Curhost andalso NewPage == Curpage) of 267 | true -> 268 | debug(Dbg, io_lib:fwrite("cyclic !! ~p/~p => ~p/~p", [Curhost, Curpage, NewHost, NewPage])), 269 | {error, cyclic}; 270 | false -> 271 | debug(Dbg, io_lib:fwrite("redir ok to ~p ~p", [NewHost, NewPage])), 272 | {ok, {NewHost, NewPage}} 273 | end. 274 | 275 | complete_page(Page, AddSep) -> 276 | Ispresent = string:right(Page, 1) == ?PAGE_SEP, 277 | case AddSep of 278 | true -> 279 | case Ispresent of 280 | true -> 281 | Page; 282 | false -> 283 | Page ++ ?PAGE_SEP 284 | end; 285 | false -> 286 | Page 287 | end. 288 | 289 | % returns absolute path from relative path 290 | eval_redirect_list([[]|T], Agg) -> 291 | eval_redirect_list(T, Agg); 292 | eval_redirect_list([?PAGE_RET|T], []) -> 293 | eval_redirect_list(T, []); 294 | eval_redirect_list([?PAGE_RET|T], Agg) -> 295 | eval_redirect_list(T, tl(Agg)); 296 | eval_redirect_list([H|T], Agg) -> 297 | eval_redirect_list(T, [H|Agg]); 298 | eval_redirect_list([], []) -> 299 | % no redirection, points to same place 300 | [""]; 301 | eval_redirect_list([], Agg) -> 302 | case string:str(hd(Agg), ".") of 303 | 0 -> 304 | lists:reverse([[]|Agg]); 305 | _ -> 306 | lists:reverse(Agg) 307 | end. 308 | 309 | eval_redirect_page(?PAGE_SEP, _Dbg) -> 310 | ?PAGE_SEP; 311 | eval_redirect_page(?PAGE_SEP ++ ?PAGE_SEP ++ Rest, Dbg) -> 312 | eval_redirect_page(?PAGE_SEP ++ Rest, Dbg); 313 | eval_redirect_page(Page, Dbg) -> 314 | debug(Dbg, io_lib:fwrite("redirect to ~p", [Page])), 315 | case string:str(Page, ?PAGE_RET) of 316 | 0 -> 317 | Page; 318 | _ -> 319 | debug(Dbg, io_lib:fwrite("eval redirect being: ~p", [string:tokens(Page, ?PAGE_SEP)])), 320 | Ends = string:right(Page, 1) == ?PAGE_SEP, 321 | Res = eval_redirect_list(string:tokens(Page, ?PAGE_SEP), []), 322 | ?PAGE_SEP ++ string:join(Res, ?PAGE_SEP) ++ case Ends of true -> ?PAGE_SEP; _ -> "" end 323 | end. 324 | 325 | -------------------------------------------------------------------------------- /src/broker.erl: -------------------------------------------------------------------------------- 1 | %% the broker for the slave 2 | 3 | -module(broker). 4 | -author("Adrien Giner - adrien.giner@kudelskisecurity.com"). 5 | 6 | -export([scan/1]). 7 | -include("includes/opts.hrl"). 8 | -include("includes/args.hrl"). 9 | -include("includes/defines.hrl"). 10 | 11 | % check children every CHECKTO 12 | -define(CHECKTO, 3000). 13 | -define(PROGRESS_MOD, 1000). 14 | 15 | -ifdef(USE_GENFSM). 16 | -define(TCPFSM, fsm_tcp). 17 | -define(UDPFSM, fsm_udp). 18 | -define(SSLFSM, fsm_ssl). 19 | -else. 20 | -define(TCPFSM, statem_tcp). 21 | -define(UDPFSM, statem_udp). 22 | -define(SSLFSM, statem_ssl). 23 | -endif. 24 | 25 | % the option for this scan 26 | -record(obj, { 27 | args, % the args to pass to the children 28 | mdone, % master is done pushing 29 | dry, % dry run 30 | debugval, % debug val 31 | pause=false, % scan paused 32 | tcnt, % target received count 33 | maxchild, % max nb of child to spin 34 | outputs, % the output modules 35 | parent, % to whom result must be sent 36 | outbufsz, % how many result to bufferize 37 | outbuf, % output buffering 38 | cancel=false, % cancel has been called 39 | id % this node id 40 | }). 41 | 42 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 43 | % killing children 44 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 45 | % when master decides to cancel the whole process, 46 | % kill the children 47 | kill_child(_Master, []) -> 48 | %send_msg(Master, info, "done killing all children"), 49 | ok; 50 | kill_child(Master, [H|T]) -> 51 | {Id, _Child, _Type, _Modules} = H, 52 | case Id of 53 | undefined -> 54 | ok; 55 | _ -> 56 | supervisor:terminate_child(scannerl_worker_sup, Id) 57 | %send_msg(Master, info, io_lib:fwrite("killing ~p", [Id])) 58 | end, 59 | kill_child(Master, T). 60 | 61 | kill_children(Id) -> 62 | send_msg(Id, info, "KILLING ALL PROCESSES"), 63 | kill_child(Id, supervisor:which_children(scannerl_worker_sup)). 64 | 65 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 66 | % start child 67 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 68 | % add a new child to process a new target 69 | add_child(Target, Args, Obj) -> 70 | utils:debug(broker, 71 | io_lib:fwrite("new target started: ~p (pause:~p)", [Target, Obj#obj.pause]), {Obj#obj.id}, Obj#obj.debugval), 72 | List = tgt:get_tgts(Target), 73 | [spawn_link(supervisor, start_child, [scannerl_worker_sup, [ 74 | Args#args{target=T, port=P, parent=Obj#obj.parent, tgtarg=A}]]) || {T, P, A} <- List], 75 | length(List). 76 | 77 | % add new children for each of these targets 78 | add_childs([], _, Agg, _) -> 79 | Agg; 80 | add_childs([H|T], _Arg, Agg, Obj) when Obj#obj.pause == true -> 81 | utils:debug(broker, "broker in pause", {Obj#obj.id}, Obj#obj.debugval), 82 | self() ! {targets, [H|T]}, 83 | Agg; 84 | add_childs([H|T], Arg, Agg, Obj) -> 85 | case Obj#obj.maxchild /= 0 andalso get_children_count() >= Obj#obj.maxchild of 86 | true -> 87 | self() ! {targets, [H|T]}, 88 | Agg; 89 | false -> 90 | Cnt = add_child(H, Arg, Obj), 91 | add_childs(T, Arg, Agg+Cnt, Obj) 92 | end. 93 | 94 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 95 | % utilities 96 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 97 | % return the number of live chidren under supervision 98 | % make sure to catch exception of type "noproc" when 99 | % no supervisor actually exists. 100 | get_active_children_count() -> 101 | try 102 | proplists:get_value(active, supervisor:count_children(scannerl_worker_sup)) 103 | catch 104 | _:_ -> 105 | 0 106 | end. 107 | get_children_count() -> 108 | try 109 | proplists:get_value(workers, supervisor:count_children(scannerl_worker_sup)) 110 | catch 111 | _:_ -> 112 | 0 113 | end. 114 | 115 | % get the number of targets in queue 116 | % because we might have multiple targets per message 117 | % in the queue. However this is costly ! 118 | sub_nb_queued_targets([], Agg) -> 119 | Agg; 120 | sub_nb_queued_targets([{targets, List}|T], Agg) -> 121 | sub_nb_queued_targets(T, Agg + length(List)); 122 | sub_nb_queued_targets([_H|T], Agg) -> 123 | sub_nb_queued_targets(T, Agg). 124 | 125 | nb_queued_targets() -> 126 | {_, Msgs} = erlang:process_info(self(), messages), 127 | sub_nb_queued_targets(Msgs, 0). 128 | 129 | get_percent(Val, 0) -> 130 | io_lib:fwrite("~p/?%", [Val]); 131 | get_percent(Val, Tot) -> 132 | Percent = Val * 100 / Tot, 133 | io_lib:fwrite("~.2f%", [Percent]). 134 | 135 | 136 | % return the length of the message queue 137 | msg_queue_len() -> 138 | {_, Cnt} = erlang:process_info(self(), message_queue_len), 139 | Cnt. 140 | 141 | % send message to master 142 | send_msg(Id, info, "done") -> 143 | global:whereis_name(master) ! {info, node(), Id, "done"}; 144 | send_msg(Id, progress, Msg) -> 145 | Dummy = io_lib:format("~s", [Msg]), 146 | try 147 | global:whereis_name(master) ! {progress, node(), Id, Dummy} 148 | catch 149 | _:_ -> 150 | ok 151 | end; 152 | send_msg(Id, Type, Msg) -> 153 | Dummy = io_lib:format("~s", [Msg]), 154 | global:whereis_name(master) ! {Type, node(), Id, Dummy}. 155 | 156 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 157 | % communication 158 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 159 | % wait for all children to be done 160 | % gets only called when master is done pushing targets 161 | wait_exit(Obj, _) when Obj#obj.cancel -> 162 | utils:debug(broker, "cancelled", {Obj#obj.id}, Obj#obj.debugval), 163 | outs_send(Obj#obj.outputs, {}, Obj#obj.outbuf, Obj#obj.outbufsz); 164 | wait_exit(Obj, 0) -> 165 | utils:debug(broker, "flushing", {Obj#obj.id}, Obj#obj.debugval), 166 | flush_msg(Obj, msg_queue_len()), 167 | outs_send(Obj#obj.outputs, {}, Obj#obj.outbuf, Obj#obj.outbufsz); 168 | wait_exit(Obj, _) -> 169 | Nobj = rcv_loop(Obj), 170 | wait_exit(Nobj, get_active_children_count()). 171 | 172 | % make sure there's no message left in queue 173 | flush_msg(_, 0) -> 174 | ok; 175 | flush_msg(Obj, _) -> 176 | rcv_loop(Obj), 177 | flush_msg(Obj, msg_queue_len()). 178 | 179 | % start listening for commands 180 | listen(Obj, _) when Obj#obj.cancel -> 181 | utils:debug(broker, "cancelled", {Obj#obj.id}, Obj#obj.debugval), 182 | wait_exit(Obj, get_active_children_count()); 183 | listen(Obj, true) -> 184 | % master is done, wait for all children and quit 185 | utils:debug(broker, "wait for children to finish", {Obj#obj.id}, Obj#obj.debugval), 186 | wait_exit(Obj, get_active_children_count()); 187 | listen(Obj, false) -> 188 | Nobj = rcv_loop(Obj), 189 | listen(Nobj, Nobj#obj.mdone). 190 | 191 | rcv_loop(Obj) -> 192 | receive 193 | {prio, Msg} -> 194 | process_msg(Obj, Msg) 195 | after 0 -> 196 | receive 197 | {prio, Msg} -> 198 | process_msg(Obj, Msg); 199 | Msg -> 200 | process_msg(Obj, Msg) 201 | after 202 | ?CHECKTO -> 203 | Obj 204 | end 205 | end. 206 | 207 | process_msg(Obj, Msg) -> 208 | case Msg of 209 | {pause} -> 210 | % received by master 211 | utils:debug(broker, "broker pause", {Obj#obj.id}, Obj#obj.debugval), 212 | rcv_loop(Obj#obj{pause=true}); 213 | {resume} -> 214 | % received by master 215 | utils:debug(broker, "broker resume", {Obj#obj.id}, Obj#obj.debugval), 216 | erlang:garbage_collect(), 217 | rcv_loop(Obj#obj{pause=false}); 218 | {done} -> 219 | % master message done pushing target 220 | utils:debug(broker, "done received from master", {Obj#obj.id}, Obj#obj.debugval), 221 | rcv_loop(Obj#obj{mdone=true}); 222 | {cancel} -> 223 | % master message to cancel 224 | send_msg(Obj#obj.id, info, "cancel received"), 225 | utils:debug(broker, "cancel received from master", {Obj#obj.id}, Obj#obj.debugval), 226 | kill_children(Obj#obj.id), 227 | rcv_loop(Obj#obj{cancel=true}); 228 | {progress} -> 229 | % message from master to give progress 230 | % and send it to master 231 | Nbtgts = nb_queued_targets(), 232 | Proc = Obj#obj.tcnt, 233 | Percent = get_percent(Proc, Nbtgts + Proc), 234 | Msg = io_lib:fwrite("[~s] queue_len:~p|queued_tgts:~p|done_tgts:~p|children:~p/~p", 235 | [Percent, msg_queue_len(), Nbtgts, Obj#obj.tcnt, 236 | get_active_children_count(), get_children_count()]), 237 | send_msg(Obj#obj.id, progress, Msg), 238 | rcv_loop(Obj); 239 | {targets, Targets} when Obj#obj.cancel == false -> 240 | % master message for a new target 241 | case Obj#obj.dry of 242 | false -> 243 | Nb = add_childs(Targets, Obj#obj.args, 0, Obj), 244 | Cnt = Obj#obj.tcnt + Nb, 245 | rcv_loop(Obj#obj{tcnt=Cnt}); 246 | true -> 247 | rcv_loop(Obj) 248 | end; 249 | Result = {_, _, _, {{_, _}, _}} -> 250 | % new result received 251 | Buf = outs_send(Obj#obj.outputs, Result, Obj#obj.outbuf, Obj#obj.outbufsz), 252 | rcv_loop(Obj#obj{outbuf=Buf}); 253 | _ -> 254 | Obj 255 | end. 256 | 257 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 258 | % output modules 259 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 260 | outs_init(_, _, Outmode) when Outmode == 0 -> 261 | {ok, []}; 262 | outs_init(Outs, Scaninfo, _) -> 263 | utils:outputs_init(Outs, Scaninfo). 264 | 265 | outs_clean(_, Outmode) when Outmode == 0 -> 266 | ok; 267 | outs_clean(Outs, _) -> 268 | utils:outputs_clean(Outs). 269 | 270 | outs_send(_Outs, {}, [], _Sz) -> 271 | ok; 272 | outs_send(Outs, {}, Buffer, _Sz) -> 273 | utils:outputs_send(Outs, Buffer); 274 | outs_send(_Outs, Msg, Buffer, Sz) when length(Buffer) < Sz -> 275 | Buffer ++ [Msg]; 276 | outs_send(Outs, Msg, Buffer, _Sz) -> 277 | utils:outputs_send(Outs, Buffer ++ [Msg]), 278 | []. 279 | 280 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 281 | % entry point 282 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 283 | get_module_args(Opts, Id, Outs) -> 284 | Direct = case Opts#opts.outmode of 1 -> true; _ -> false end, 285 | #args{module=Opts#opts.module, id=Id, port=Opts#opts.port, 286 | timeout=Opts#opts.timeout, debugval=Opts#opts.debugval, 287 | retry=Opts#opts.retry, arguments=Opts#opts.modarg, 288 | checkwww=Opts#opts.checkwww, outobj=Outs, direct=Direct, 289 | privports=Opts#opts.privports, fsmopts=Opts#opts.fsmopts, 290 | maxpkt=Opts#opts.maxpkt, sockopt=Opts#opts.sockopt}. 291 | 292 | % starting the supervisor 293 | start_supervisor(Module, Id) -> 294 | Args = apply(Module, get_default_args, []), 295 | case Args#args.type of 296 | tcp -> 297 | scannerl_worker_sup:start_link({?TCPFSM,start_link,[]}); 298 | udp -> 299 | scannerl_worker_sup:start_link({?UDPFSM,start_link,[]}); 300 | ssl -> 301 | scannerl_worker_sup:start_link({?SSLFSM,start_link,[]}); 302 | other -> 303 | scannerl_worker_sup:start_link({Module,start_link,[]}); 304 | _ -> 305 | send_msg(Id, info, "ERROR: Invalid fsm TYPE. Should be tcp, udp or ssl."), 306 | send_msg(Id, info, "done") 307 | end. 308 | 309 | % called by the master 310 | scan(Opts) -> 311 | % first initialize output module if needed 312 | case outs_init(Opts#opts.output, Opts#opts.scaninfo, Opts#opts.outmode) of 313 | {ok, Outputs} -> 314 | % init internal record 315 | Parent = case Opts#opts.outmode of 0 -> global:whereis_name(scannerl); _ -> self() end, 316 | Obj = #obj{args=get_module_args(Opts, Opts#opts.user, Outputs), mdone=false, dry=Opts#opts.dry, 317 | debugval=Opts#opts.debugval, id=Opts#opts.user, tcnt=0, maxchild=Opts#opts.maxchild, 318 | parent=Parent, outputs=Outputs, outbuf=[], outbufsz=Opts#opts.outmode}, 319 | start_supervisor(Opts#opts.module, Opts#opts.user), 320 | utils:debug(broker, "start listening for message from master/slave", 321 | {Obj#obj.id}, Obj#obj.debugval), 322 | % start listening for message (from master mostly) 323 | listen(Obj, Obj#obj.mdone), 324 | % clean output 325 | outs_clean(Outputs, Opts#opts.outmode), 326 | % we're done 327 | send_msg(Obj#obj.id, info, "done"); 328 | {error, Reason} -> 329 | send_msg(Opts#opts.user, info, 330 | io_lib:fwrite("[ERROR] output setup failed: ~p", [Reason])), 331 | send_msg(Opts#opts.user, info, "done"), 332 | ok 333 | end. 334 | 335 | --------------------------------------------------------------------------------