Skip to content

Commit dd12afd

Browse files
author
zerosum0x0
committed
added DoublePulsar detection
1 parent fc3a880 commit dd12afd

File tree

1 file changed

+87
-11
lines changed

1 file changed

+87
-11
lines changed

modules/auxiliary/scanner/smb/smb_ms17_010.rb

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,16 @@ def initialize(info = {})
2222
If the status returned is "STATUS_INSUFF_SERVER_RESOURCES", the machine does
2323
not have the MS17-010 patch.
2424
25+
If the machine is missing the MS17-010 patch, the module will check for an
26+
existing DoublePulsar (ring 0 shellcode/malware) infection.
27+
2528
This module does not require valid SMB credentials in default server
2629
configurations. It can log on as the user "\" and connect to IPC$.
2730
},
28-
'Author' => [ 'Sean Dillon <[email protected]>' ],
31+
'Author' =>
32+
[ 'Sean Dillon <[email protected]>',
33+
'Luke Jennings' # DoublePulsar detection
34+
],
2935
'References' =>
3036
[
3137
[ 'CVE', '2017-0143'],
@@ -43,19 +49,37 @@ def initialize(info = {})
4349

4450
def run_host(ip)
4551
begin
46-
status = do_smb_probe(ip)
52+
ipc_share = "\\\\#{ip}\\IPC$"
53+
54+
tree_id = do_smb_setup_tree(ipc_share)
55+
print_status("Connected to #{ipc_share} with TID = #{tree_id}")
56+
57+
status = do_smb_ms17_010_probe(tree_id)
58+
print_status("Received #{status} with FID = 0")
4759

4860
if status == "STATUS_INSUFF_SERVER_RESOURCES"
49-
print_warning("Host is likely VULNERABLE to MS17-010!")
61+
print_good("Host is likely VULNERABLE to MS17-010!")
5062
report_vuln(
5163
host: ip,
5264
name: self.name,
5365
refs: self.references,
5466
info: 'STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$'
5567
)
68+
69+
# vulnerable to MS17-010, check for DoublePulsar infection
70+
code = do_smb_doublepulsar_probe(tree_id)
71+
if code == 0x51
72+
print_warning("Host is likely INFECTED with DoublePulsar!")
73+
report_vuln(
74+
host: ip,
75+
name: "MS17-010 DoublePulsar Infection",
76+
refs: self.references,
77+
info: 'MultiPlexID = 0x51 on Trans2 request'
78+
)
79+
end
5680
elsif status == "STATUS_ACCESS_DENIED" or status == "STATUS_INVALID_HANDLE"
5781
# STATUS_ACCESS_DENIED (Windows 10) and STATUS_INVALID_HANDLE (others)
58-
print_good("Host does NOT appear vulnerable.")
82+
print_bad("Host does NOT appear vulnerable.")
5983
else
6084
print_bad("Unable to properly detect if host is vulnerable.")
6185
end
@@ -72,19 +96,34 @@ def run_host(ip)
7296
end
7397
end
7498

75-
def do_smb_probe(ip)
99+
def do_smb_setup_tree(ipc_share)
76100
connect
77101

78102
# logon as user \
79103
simple.login(datastore['SMBName'], datastore['SMBUser'], datastore['SMBPass'], datastore['SMBDomain'])
80104

81105
# connect to IPC$
82-
ipc_share = "\\\\#{ip}\\IPC$"
83106
simple.connect(ipc_share)
84-
tree_id = simple.shares[ipc_share]
85107

86-
print_status("Connected to #{ipc_share} with TID = #{tree_id}")
108+
# return tree
109+
return simple.shares[ipc_share]
110+
end
111+
112+
def do_smb_doublepulsar_probe(tree_id)
113+
# make doublepulsar knock
114+
pkt = make_smb_trans2_doublepulsar(tree_id)
87115

116+
sock.put(pkt)
117+
bytes = sock.get_once
118+
119+
# convert packet to response struct
120+
pkt = Rex::Proto::SMB::Constants::SMB_TRANS_RES_HDR_PKT.make_struct
121+
pkt.from_s(bytes[4..-1])
122+
123+
return pkt['SMB'].v['MultiplexID']
124+
end
125+
126+
def do_smb_ms17_010_probe(tree_id)
88127
# request transaction with fid = 0
89128
pkt = make_smb_trans_ms17_010(tree_id)
90129
sock.put(pkt)
@@ -97,10 +136,47 @@ def do_smb_probe(ip)
97136
# convert error code to string
98137
code = pkt['SMB'].v['ErrorClass']
99138
smberr = Rex::Proto::SMB::Exceptions::ErrorCode.new
100-
status = smberr.get_error(code)
101139

102-
print_status("Received #{status} with FID = 0")
103-
status
140+
return smberr.get_error(code)
141+
end
142+
143+
def make_smb_trans2_doublepulsar(tree_id)
144+
# make a raw transaction packet
145+
# this one is a trans2 packet, the checker is trans
146+
pkt = Rex::Proto::SMB::Constants::SMB_TRANS2_PKT.make_struct
147+
simple.client.smb_defaults(pkt['Payload']['SMB'])
148+
149+
# opcode 0x0e = SESSION_SETUP
150+
setup = "\x0e\x00\x00\x00"
151+
setup_count = 1 # 2 words
152+
trans = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
153+
154+
# calculate offsets to the SetupData payload
155+
base_offset = pkt.to_s.length + (setup.length) - 4
156+
param_offset = base_offset + trans.length
157+
data_offset = param_offset # + 0
158+
159+
# packet baselines
160+
pkt['Payload']['SMB'].v['Command'] = Rex::Proto::SMB::Constants::SMB_COM_TRANSACTION2
161+
pkt['Payload']['SMB'].v['Flags1'] = 0x18
162+
pkt['Payload']['SMB'].v['MultiplexID'] = 65
163+
pkt['Payload']['SMB'].v['Flags2'] = 0xc007 # 0xc803 would unicode
164+
pkt['Payload']['SMB'].v['TreeID'] = tree_id
165+
pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
166+
pkt['Payload'].v['Timeout'] = 0x00a4d9a6
167+
pkt['Payload'].v['ParamCountTotal'] = 12
168+
pkt['Payload'].v['ParamCount'] = 12
169+
pkt['Payload'].v['ParamCountMax'] = 1
170+
pkt['Payload'].v['DataCountMax'] = 0
171+
pkt['Payload'].v['ParamOffset'] = 66
172+
pkt['Payload'].v['DataOffset'] = 78
173+
174+
# actual magic: PeekNamedPipe FID=0, \PIPE\
175+
pkt['Payload'].v['SetupCount'] = setup_count
176+
pkt['Payload'].v['SetupData'] = setup
177+
pkt['Payload'].v['Payload'] = trans
178+
179+
pkt.to_s
104180
end
105181

106182
def make_smb_trans_ms17_010(tree_id)

0 commit comments

Comments
 (0)