Skip to content

Instantly share code, notes, and snippets.

@acammack-r7
Last active April 27, 2017 04:36
Show Gist options
  • Save acammack-r7/565de40b8a0c1d80bb1669fc610ec915 to your computer and use it in GitHub Desktop.
Save acammack-r7/565de40b8a0c1d80bb1669fc610ec915 to your computer and use it in GitHub Desktop.
Detects DoublePulsar infections, requires Erlang 19, ./detect_doublepulsar.erl <IPs, CIDRs, OR FILES OF IPs AND CIDRs>
#!/usr/bin/env escript
%%! +A10 -noinput +K true
%% Get Erlang at https://www.erlang-solutions.com/resources/download.html
%% Copyright 2017, Rapid7, Inc
%% All rights reserved
%%
%% Redistribution and use in source and binary forms, with or without modification,
%% are permitted provided that the following conditions are met:
%%
%% * Redistributions of source code must retain the above copyright notice,
%% this list of conditions and the following disclaimer.
%%
%% * Redistributions in binary form must reproduce the above copyright notice,
%% this list of conditions and the following disclaimer in the documentation
%% and/or other materials provided with the distribution.
%%
%% * Neither the name of Rapid7, Inc. nor the names of its contributors
%% may be used to endorse or promote products derived from this software
%% without specific prior written permission.
%%
%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.
-mode(compile).
-define(SMB_NEGOTIATE, <<0,0,0,133,255,83,77,66,114,0,0,0,0,24,83,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,
254,0,0,64,0,0,98,0,2,80,67,32,78,69,84,87,79,82,75,32,80,82,79,71,82,65,77,
32,49,46,48,0,2,76,65,78,77,65,78,49,46,48,0,2,87,105,110,100,111,119,115,32,
102,111,114,32,87,111,114,107,103,114,111,117,112,115,32,51,46,49,97,0,2,76,
77,49,46,50,88,48,48,50,0,2,76,65,78,77,65,78,50,46,49,0,2,78,84,32,76,77,32,
48,46,49,50,0>>).
-define(SMB_SESSION, <<0,0,0,136,255,83,77,66,115,0,0,0,0,24,7,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,
254,0,0,64,0,13,255,0,136,0,4,17,10,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,212,0,0,0,
75,0,0,0,0,0,0,87,0,105,0,110,0,100,0,111,0,119,0,115,0,32,0,50,0,48,0,48,0,
48,0,32,0,50,0,49,0,57,0,53,0,0,0,87,0,105,0,110,0,100,0,111,0,119,0,115,0,
32,0,50,0,48,0,48,0,48,0,32,0,53,0,46,0,48,0,0,0>>).
-define(SMB_TREE, {<<0,0,0,96,255,83,77,66,117,0,0,0,0,24,7,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,254>>,
<<64,0,4,255,0,96,0,8,0,1,0,53,0,0,92,0,92,0,49,0,57,0,50,0,46,0,49,0,
54,0,56,0,46,0,49,0,55,0,53,0,46,0,49,0,50,0,56,0,92,0,73,0,80,0,67,0,36,0,0,
0,63,63,63,63,63,0>>}).
-define(SMB_TRANS2, {<<0,0,0,78,255,83,77,66,50,0,0,0,0,24,7,192,0,0,0,0,0,0,0,0,0,0,0,0>>,<<255,254>>,
<<65,0,15,12,0,0,0,1,0,0,0,0,0,0,0,166,217,164,0,0,0,12,0,66,0,0,0,78,
0,1,0,14,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>}).
main(Args) ->
[ run(Arg) || Arg <- Args ],
% Allow streams to flush
receive
after 500 ->
init:stop(),
receive
after infinity -> ok
end
end.
run(IPs) when is_binary(IPs) -> run(binary_to_list(IPs));
run(IPs) when is_list(IPs) ->
To_Scan = case file:read_file(IPs) of
{ok, IP_List} -> split_rows(IP_List);
_ -> case cidr_parse(IPs) of
undefined ->
io:format(standard_error, "Could not open file or parse address: ~p~n", [IPs]),
error(badarg);
Parsed_IPs -> Parsed_IPs
end
end,
% Increase the last argument for more concurrency, will require raising ulimit -n
connect(To_Scan, 0, 900).
xor_key(S) ->
16#FFFFFFFF band (2 * S bxor (((S band 16#FF00 bor (S bsl 16)) bsl 8) bor (((S bsr 16) bor (S band 16#FF0000) bsr 8)))).
% Jackpot
scan(_Conn, Target, <<_:18/bytes, Sig:32/unsigned-little, A:8/unsigned, _:3/bytes, _:8/bytes, 16#51, _/binary>>, trans2) ->
Arch = case A of
0 -> <<"x86">>;
_ -> <<"x86-64">>
end,
io:format("~s,true,0x~8.16.0B,~s~n", [inet:ntoa(Target), xor_key(Sig), Arch]),
done;
% No dice
scan(_Conn, Target, _, trans2) ->
io:format("~s,false,,~n", [inet:ntoa(Target)]),
done;
% Start the transaction
scan(Conn, _Target, <<_:28/bytes, Tree_ID:2/bytes, _/binary>>, #{user_id := User_ID}) ->
{Start, Middle, End} = ?SMB_TRANS2,
gen_tcp:send(Conn, <<Start/binary, Tree_ID/binary, Middle/binary, User_ID/binary, End/binary>>),
trans2;
% Connect the tree
scan(Conn, _Target, <<_:32/bytes, User_ID:2/bytes, _/binary>>, session) ->
{Start, End} = ?SMB_TREE,
gen_tcp:send(Conn, <<Start/binary, User_ID/binary, End/binary>>),
#{user_id => User_ID};
% Just connected
scan(Conn, _Target, _Data, new) ->
gen_tcp:send(Conn, ?SMB_SESSION),
session;
% Some other response, probably an error
scan(_Conn, Target, _Data, _) ->
io:format("~s,false,,~n", [inet:ntoa(Target)]).
%% Connection handling
% Wait for some sockets to close
connect(empty, 0, _Max) -> ok;
connect(empty, N, Max) ->
receive
done -> connect(empty, N - 1, Max)
after 120000 ->
io:format(standard_error, "No outstanding scans finished in the last 2 minutes, exiting~n", []),
exit(too_slow)
end;
connect(To_Scan, N, Max) when N > Max ->
receive
done -> connect(To_Scan, N - 1, Max)
after 120000 ->
io:format(standard_error, "No scans at the connection limit finished in the last 2 minutes, exiting~n", []),
exit(too_slow)
end;
connect(To_Scan, N, Max) ->
case next_ip(To_Scan) of
{Next, Later} ->
Me = self(),
spawn(fun() -> check(Next, Me) end),
connect(Later, N + 1, Max);
empty -> ok
end.
check(Target, Parent) ->
case gen_tcp:connect(Target, 445,
[binary, {active, once}, {packet, raw}, {send_timeout, 10000}, {send_timeout_close, true}], 15000) of
{ok, Conn} ->
gen_tcp:send(Conn, ?SMB_NEGOTIATE),
wait(Conn, Target, new, Parent);
{error, E} ->
io:format(standard_error, "Can't connect because reasons ~p~n", [E]),
io:format("~s,down,,~n", [inet:ntoa(Target)]),
Parent ! done
end.
wait(Conn, Target, State, Parent) ->
receive
{tcp_closed, Conn} ->
io:format("~s,false,,~n", [inet:ntoa(Target)]),
Parent ! done;
{tcp, Conn, Data} ->
% Drain the buffer
Moar_Data = case gen_tcp:recv(Conn, 0, 0) of
{ok, Bytes} -> Bytes;
_ -> <<>>
end,
% Rearm
inet:setopts(Conn, [{active, once}]),
case scan(Conn, Target, <<Data/binary, Moar_Data/binary>>, State) of
done ->
gen_tcp:close(Conn),
Parent ! done;
S ->
wait(Conn, Target, S, Parent)
end
after 10000 ->
io:format(standard_error, "Hung waiting for responses, giving up~n", []),
io:format("~s,false,,~n", [inet:ntoa(Target)]),
gen_tcp:close(Conn)
end.
%% IP traversal stuff
-define(ONES32, 16#FFFFFFFF).
-define(ONES128, 16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF).
next_ip(empty) -> empty;
next_ip(undefined) -> empty;
next_ip([]) -> empty;
next_ip([H | T]) ->
case next_ip(H) of
empty -> next_ip(T);
{IP, Rest} -> {IP, [Rest, T]}
end;
next_ip({Start, End, Inet}) when Start < End ->
{integer_to_ip(Start + 1, Inet), {Start + 1, End, Inet}};
next_ip({_Start, _End, _}) -> empty;
next_ip(IP) -> {IP, empty}.
split_rows(Bin) ->
lists:map(fun (IP) ->
cidr_parse(binary_to_list(IP))
end, binary:tokens(Bin, [<<$\n>>, <<$\r, $\n>>], [trim_all, global])).
cidr_parse(Cidr) ->
case string:tokens(Cidr, "/") of
[Base, Mask] ->
case inet:parse_address(Base) of
{error, einval} -> undefined;
{ok, IP} when tuple_size(IP) =:= 4 ->
Start = ip_to_integer(IP),
L = 32 - list_to_integer(Mask),
<<Top:L>> = <<?ONES32:L>>,
End = Start bor Top,
{Start, End, inet4};
{ok, IP} when tuple_size(IP) =:= 8 ->
Start = ip_to_integer(IP),
L = 128 - list_to_integer(Mask),
<<Top:L>> = <<?ONES128:L>>,
End = Start bor Top,
{Start, End, inet6}
end;
[Base] ->
case inet:parse_address(Base) of
{error, einval} -> undefined;
{ok, IP} -> IP
end
end.
ip_to_integer({O1, O2, O3, O4}) ->
(O1 bsl 24) bor (O2 bsl 16) bor (O3 bsl 8) bor O4;
ip_to_integer({S1, S2, S3, S4, S5, S6, S7, S8}) ->
(S1 bsl 112) bor (S2 bsl 96) bor (S3 bsl 80) bor (S4 bsl 64) bor (S5 bsl 48) bor (S6 bsl 32) bor (S7 bsl 16) bor S8.
integer_to_ip(I, inet4) ->
{I band (16#FF bsl 24) bsr 24,
I band (16#FF bsl 16) bsr 16,
I band (16#FF bsl 8) bsr 8,
I band 16#FF};
integer_to_ip(I, inet6) ->
{I band (16#FFFF bsl 112) bsr 112,
I band (16#FFFF bsl 96) bsr 96,
I band (16#FFFF bsl 80) bsr 80,
I band (16#FFFF bsl 64) bsr 64,
I band (16#FFFF bsl 48) bsr 48,
I band (16#FFFF bsl 32) bsr 32,
I band (16#FFFF bsl 16) bsr 16,
I band 16#FFFF}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment