While messing with the protocol, I found 2 bugs in smb client implementations, nothing fancy, but I really appreciate buggy implementations of this protocol, therefore …
The first one is within nmap's msrpc nse library:
local function call_function(smbstate, opnum, arguments)
local i
local status, result
local parameters, data
local pos, align
local result
local first = true
local is_first, is_last
data = bin.pack("<CCCC>I<SSIISSA",
0x05, -- Version (major)
0x00, -- Version (minor)
0x00, -- Packet type (0x00 = request)
0x03, -- Packet flags (0x03 = first frag + last frag)
0x10000000, -- Data representation (big endian)
0x18 + string.len(arguments), -- Frag length (0x18 = the size of this data)
0x0000, -- Auth length
0x41414141, -- Call ID (I use 'AAAA' because it's easy to recognize)
0x00000038, -- Alloc hint
0x0000, -- Context ID
opnum, -- Opnum
arguments
)
The Alloc hint is wrong, as it is static, it has to be
string.length(arguments), -- Alloc hint
instead.
Seems to work with Windows though.
The effect is … nothing, besides dionaea fails to parse the request if alloc hint is set wrong.
Using a static Call ID makes it pretty easy to identify nmap scans, but you can always make things worse:
local REFERENT_ID = 0x50414d4e
Pointers also have interesting properties. A pointer is preceeded by a 4-byte value called (at least by Wireshark) the “referent id”. For a valid pointer, this can be anything except 0 (I use 'NMAP' for it). src: nmap's doc on msrpctypes
While this is not a bug per se, it makes it really easy to identify nmap smb-* nse scripted scans, ngrep for NMAP on port 445,139,135.
The metasploit bug was found as my fragmented Read AndX code was doing fragmentation wrong.
def read()
max_read = self.options['pipe_read_max_size'] || 1024*1024
min_read = self.options['pipe_read_min_size'] || max_read
raw_response = ''
# Are we reading from a remote pipe over SMB?
if (self.socket.class == Rex::Proto::SMB::SimpleClient::OpenPipe)
begin
# Max SMB read is 65535, cap it at 64000
max_read = [64000, max_read].min
min_read = [64000, min_read].min
read_limit = nil
while(true)
# Random read offsets will not work on Windows NT 4.0 (thanks Dave!)
read_cnt = (rand(max_read-min_read)+min_read)
if(read_limit)
if(read_cnt + raw_response.length > read_limit)
read_cnt = raw_response.length - read_limit
end
end
data = self.socket.read( read_cnt, rand(1024)+1)
break if !(data and data.length > 0)
raw_response += data
# Keep reading until we have at least the DCERPC header
next if raw_response.length < 10
# We now have to process the raw_response and parse out the DCERPC fragment length
# if we have read enough data. Once we have the length value, we need to make sure
# that we don't read beyond this amount, or it can screw up the SMB state
if (not read_limit)
begin
check = Rex::Proto::DCERPC::Response.new(raw_response)
read_limit = check.frag_len
rescue ::Rex::Proto::DCERPC::Exceptions::InvalidPacket
end
end
break if (read_limit and read_limit == raw_response.length)
end
rescue Rex::Proto::SMB::Exceptions::NoReply
# I don't care if I didn't get a reply...
rescue Rex::Proto::SMB::Exceptions::ErrorCode => exception
if exception.error_code != 0xC000014B
raise exception
end
end
# This must be a regular TCP or UDP socket
else
...
end
raw_response
end
Rex::Proto::DCERPC::Client revision 7248
The read code … reads from the remote, forcing the remote to fragment the data randomly.
Once it received enough data to read the frag_len, which indicates the length of the total DCERPC which has to be transferred, it will continue until it received exactly frag_len bytes DCERPC payload data.
To exploit, sent the first fragment with a size larger than indicated by your DCERPC response packet, and sent 1 bytes chunks afterwards, so the frag_len is never hit. metasploit will take some cycles on your cpu, and waste memory.
My fix would be to change the abort condition to:
break if (read_limit and read_limit <= raw_response.length)
UPDATE fixed in revision 9631
As the frag_len is a short, the max amount of data would be 64k, reducing the impact to a minimum.
One could make a Metasploit module for this, but due to metasploits performance, a denial of service to the metasploit smb client might cause a denial of service to the metasploit smb service as well, exhausting the cpu.