@@ -22,10 +22,16 @@ def initialize(info = {})
22
22
If the status returned is "STATUS_INSUFF_SERVER_RESOURCES", the machine does
23
23
not have the MS17-010 patch.
24
24
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
+
25
28
This module does not require valid SMB credentials in default server
26
29
configurations. It can log on as the user "\" and connect to IPC$.
27
30
} ,
28
- 'Author' => [ 'Sean Dillon <[email protected] >' ] ,
31
+ 'Author' =>
32
+ [ 'Sean Dillon <[email protected] >' ,
33
+ 'Luke Jennings' # DoublePulsar detection
34
+ ] ,
29
35
'References' =>
30
36
[
31
37
[ 'CVE' , '2017-0143' ] ,
@@ -43,19 +49,37 @@ def initialize(info = {})
43
49
44
50
def run_host ( ip )
45
51
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" )
47
59
48
60
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!" )
50
62
report_vuln (
51
63
host : ip ,
52
64
name : self . name ,
53
65
refs : self . references ,
54
66
info : 'STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$'
55
67
)
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
56
80
elsif status == "STATUS_ACCESS_DENIED" or status == "STATUS_INVALID_HANDLE"
57
81
# 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." )
59
83
else
60
84
print_bad ( "Unable to properly detect if host is vulnerable." )
61
85
end
@@ -72,19 +96,34 @@ def run_host(ip)
72
96
end
73
97
end
74
98
75
- def do_smb_probe ( ip )
99
+ def do_smb_setup_tree ( ipc_share )
76
100
connect
77
101
78
102
# logon as user \
79
103
simple . login ( datastore [ 'SMBName' ] , datastore [ 'SMBUser' ] , datastore [ 'SMBPass' ] , datastore [ 'SMBDomain' ] )
80
104
81
105
# connect to IPC$
82
- ipc_share = "\\ \\ #{ ip } \\ IPC$"
83
106
simple . connect ( ipc_share )
84
- tree_id = simple . shares [ ipc_share ]
85
107
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 )
87
115
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 )
88
127
# request transaction with fid = 0
89
128
pkt = make_smb_trans_ms17_010 ( tree_id )
90
129
sock . put ( pkt )
@@ -97,10 +136,47 @@ def do_smb_probe(ip)
97
136
# convert error code to string
98
137
code = pkt [ 'SMB' ] . v [ 'ErrorClass' ]
99
138
smberr = Rex ::Proto ::SMB ::Exceptions ::ErrorCode . new
100
- status = smberr . get_error ( code )
101
139
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
104
180
end
105
181
106
182
def make_smb_trans_ms17_010 ( tree_id )
0 commit comments