@@ -22,10 +22,17 @@ 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
+ [
33
+ 'Sean Dillon <[email protected] >' , # @zerosum0x0
34
+ 'Luke Jennings' # DoublePulsar detection Python code
35
+ ] ,
29
36
'References' =>
30
37
[
31
38
[ 'CVE' , '2017-0143' ] ,
@@ -35,27 +42,55 @@ def initialize(info = {})
35
42
[ 'CVE' , '2017-0147' ] ,
36
43
[ 'CVE' , '2017-0148' ] ,
37
44
[ 'MSB' , 'MS17-010' ] ,
45
+ [ 'URL' , 'https://zerosum0x0.blogspot.com/2017/04/doublepulsar-initial-smb-backdoor-ring.html' ] ,
46
+ [ 'URL' , 'https://github.com/countercept/doublepulsar-detection-script' ] ,
38
47
[ 'URL' , 'https://technet.microsoft.com/en-us/library/security/ms17-010.aspx' ]
39
48
] ,
40
49
'License' => MSF_LICENSE
41
50
) )
42
51
end
43
52
53
+ # algorithm to calculate the XOR Key for DoublePulsar knocks
54
+ def calculate_doublepulsar_xor_key ( s )
55
+ x = ( 2 * s ^ ( ( ( s & 0xff00 | ( s << 16 ) ) << 8 ) | ( ( ( s >> 16 ) | s & 0xff0000 ) >> 8 ) ) )
56
+ x & 0xffffffff # this line was added just to truncate to 32 bits
57
+ end
58
+
44
59
def run_host ( ip )
45
60
begin
46
- status = do_smb_probe ( ip )
61
+ ipc_share = "\\ \\ #{ ip } \\ IPC$"
62
+
63
+ tree_id = do_smb_setup_tree ( ipc_share )
64
+ vprint_status ( "Connected to #{ ipc_share } with TID = #{ tree_id } " )
65
+
66
+ status = do_smb_ms17_010_probe ( tree_id )
67
+ vprint_status ( "Received #{ status } with FID = 0" )
47
68
48
69
if status == "STATUS_INSUFF_SERVER_RESOURCES"
49
- print_warning ( "Host is likely VULNERABLE to MS17-010!" )
70
+ print_good ( "Host is likely VULNERABLE to MS17-010! ( #{ simple . client . peer_native_os } ) " )
50
71
report_vuln (
51
72
host : ip ,
52
73
name : self . name ,
53
74
refs : self . references ,
54
- info : 'STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$'
75
+ info : 'STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$ -- (#{simple.client.peer_native_os}) '
55
76
)
77
+
78
+ # vulnerable to MS17-010, check for DoublePulsar infection
79
+ code , signature = do_smb_doublepulsar_probe ( tree_id )
80
+
81
+ if code == 0x51
82
+ xor_key = calculate_doublepulsar_xor_key ( signature ) . to_s ( 16 ) . upcase
83
+ print_warning ( "Host is likely INFECTED with DoublePulsar! - XOR Key: #{ xor_key } " )
84
+ report_vuln (
85
+ host : ip ,
86
+ name : "MS17-010 DoublePulsar Infection" ,
87
+ refs : self . references ,
88
+ info : 'MultiPlexID += 0x10 on Trans2 request - XOR Key: #{xor_key}'
89
+ )
90
+ end
56
91
elsif status == "STATUS_ACCESS_DENIED" or status == "STATUS_INVALID_HANDLE"
57
92
# STATUS_ACCESS_DENIED (Windows 10) and STATUS_INVALID_HANDLE (others)
58
- print_good ( "Host does NOT appear vulnerable." )
93
+ print_bad ( "Host does NOT appear vulnerable." )
59
94
else
60
95
print_bad ( "Unable to properly detect if host is vulnerable." )
61
96
end
@@ -72,19 +107,34 @@ def run_host(ip)
72
107
end
73
108
end
74
109
75
- def do_smb_probe ( ip )
110
+ def do_smb_setup_tree ( ipc_share )
76
111
connect
77
112
78
113
# logon as user \
79
114
simple . login ( datastore [ 'SMBName' ] , datastore [ 'SMBUser' ] , datastore [ 'SMBPass' ] , datastore [ 'SMBDomain' ] )
80
115
81
116
# connect to IPC$
82
- ipc_share = "\\ \\ #{ ip } \\ IPC$"
83
117
simple . connect ( ipc_share )
84
- tree_id = simple . shares [ ipc_share ]
85
118
86
- print_status ( "Connected to #{ ipc_share } with TID = #{ tree_id } " )
119
+ # return tree
120
+ return simple . shares [ ipc_share ]
121
+ end
122
+
123
+ def do_smb_doublepulsar_probe ( tree_id )
124
+ # make doublepulsar knock
125
+ pkt = make_smb_trans2_doublepulsar ( tree_id )
87
126
127
+ sock . put ( pkt )
128
+ bytes = sock . get_once
129
+
130
+ # convert packet to response struct
131
+ pkt = Rex ::Proto ::SMB ::Constants ::SMB_TRANS_RES_HDR_PKT . make_struct
132
+ pkt . from_s ( bytes [ 4 ..-1 ] )
133
+
134
+ return pkt [ 'SMB' ] . v [ 'MultiplexID' ] , pkt [ 'SMB' ] . v [ 'Signature1' ]
135
+ end
136
+
137
+ def do_smb_ms17_010_probe ( tree_id )
88
138
# request transaction with fid = 0
89
139
pkt = make_smb_trans_ms17_010 ( tree_id )
90
140
sock . put ( pkt )
@@ -97,10 +147,46 @@ def do_smb_probe(ip)
97
147
# convert error code to string
98
148
code = pkt [ 'SMB' ] . v [ 'ErrorClass' ]
99
149
smberr = Rex ::Proto ::SMB ::Exceptions ::ErrorCode . new
100
- status = smberr . get_error ( code )
101
150
102
- print_status ( "Received #{ status } with FID = 0" )
103
- status
151
+ return smberr . get_error ( code )
152
+ end
153
+
154
+ def make_smb_trans2_doublepulsar ( tree_id )
155
+ # make a raw transaction packet
156
+ # this one is a trans2 packet, the checker is trans
157
+ pkt = Rex ::Proto ::SMB ::Constants ::SMB_TRANS2_PKT . make_struct
158
+ simple . client . smb_defaults ( pkt [ 'Payload' ] [ 'SMB' ] )
159
+
160
+ # opcode 0x0e = SESSION_SETUP
161
+ setup = "\x0e \x00 \x00 \x00 "
162
+ setup_count = 1 # 1 word
163
+ trans = "\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 "
164
+
165
+ # calculate offsets to the SetupData payload
166
+ base_offset = pkt . to_s . length + ( setup . length ) - 4
167
+ param_offset = base_offset + trans . length
168
+ data_offset = param_offset # + 0
169
+
170
+ # packet baselines
171
+ pkt [ 'Payload' ] [ 'SMB' ] . v [ 'Command' ] = Rex ::Proto ::SMB ::Constants ::SMB_COM_TRANSACTION2
172
+ pkt [ 'Payload' ] [ 'SMB' ] . v [ 'Flags1' ] = 0x18
173
+ pkt [ 'Payload' ] [ 'SMB' ] . v [ 'MultiplexID' ] = 65
174
+ pkt [ 'Payload' ] [ 'SMB' ] . v [ 'Flags2' ] = 0xc007
175
+ pkt [ 'Payload' ] [ 'SMB' ] . v [ 'TreeID' ] = tree_id
176
+ pkt [ 'Payload' ] [ 'SMB' ] . v [ 'WordCount' ] = 14 + setup_count
177
+ pkt [ 'Payload' ] . v [ 'Timeout' ] = 0x00a4d9a6
178
+ pkt [ 'Payload' ] . v [ 'ParamCountTotal' ] = 12
179
+ pkt [ 'Payload' ] . v [ 'ParamCount' ] = 12
180
+ pkt [ 'Payload' ] . v [ 'ParamCountMax' ] = 1
181
+ pkt [ 'Payload' ] . v [ 'DataCountMax' ] = 0
182
+ pkt [ 'Payload' ] . v [ 'ParamOffset' ] = 66
183
+ pkt [ 'Payload' ] . v [ 'DataOffset' ] = 78
184
+
185
+ pkt [ 'Payload' ] . v [ 'SetupCount' ] = setup_count
186
+ pkt [ 'Payload' ] . v [ 'SetupData' ] = setup
187
+ pkt [ 'Payload' ] . v [ 'Payload' ] = trans
188
+
189
+ pkt . to_s
104
190
end
105
191
106
192
def make_smb_trans_ms17_010 ( tree_id )
0 commit comments