require 'rex/text' require 'rex/proto/smb/constants' module Rex module Proto module SMB class Utils CONST = Rex::Proto::SMB::Constants # Creates an access mask for use with the CLIENT.open() call based on a string def self.open_mode_to_access(str) access = CONST::OPEN_ACCESS_READ | CONST::OPEN_SHARE_DENY_NONE str.each_byte { |c| case [c].pack('C').downcase when 'w' access |= CONST::OPEN_ACCESS_READWRITE end } return access end # Creates a mode mask for use with the CLIENT.open() call based on a string def self.open_mode_to_mode(str) mode = 0 str.each_byte { |c| case [c].pack('C').downcase when 'x' # Fail if the file already exists mode |= CONST::OPEN_MODE_EXCL when 't' # Truncate the file if it already exists mode |= CONST::OPEN_MODE_TRUNC when 'c' # Create the file if it does not exist mode |= CONST::OPEN_MODE_CREAT when 'o' # Just open the file, clashes with x mode |= CONST::OPEN_MODE_OPEN end } return mode end # Returns a disposition value for smb.create based on permission string def self.create_mode_to_disposition(str) str.each_byte { |c| case [c].pack('C').downcase when 'c' # Create the file if it does not exist return CONST::CREATE_ACCESS_OPENCREATE when 'o' # Just open the file and fail if it does not exist return CONST::CREATE_ACCESS_EXIST end } return CONST::CREATE_ACCESS_OPENCREATE end # NOTE: the difference below came from: Time.utc("1970-1-1") - Time.utc("1601-1-1") # Convert a 64-bit signed SMB time to a unix timestamp def self.time_smb_to_unix(thi, tlo) (((thi << 32) + tlo) / 10000000) - 11644473600 end # Convert a unix timestamp to a 64-bit signed server time def self.time_unix_to_smb(unix_time) t64 = (unix_time + 11644473600) * 10000000 thi = (t64 & 0xffffffff00000000) >> 32 tlo = (t64 & 0x00000000ffffffff) return [thi, tlo] end # Convert a name to its NetBIOS equivalent def self.nbname_encode(str) encoded = '' for x in (0..15) if (x >= str.length) encoded << 'CA' else c = str[x, 1].upcase[0,1].unpack('C*')[0] encoded << [ (c / 16) + 0x41, (c % 16) + 0x41 ].pack('CC') end end return encoded end # Convert a name from its NetBIOS equivalent def self.nbname_decode(str) decoded = '' str << 'A' if str.length % 2 != 0 while (str.length > 0) two = str.slice!(0, 2).unpack('C*') if (two.length == 2) decoded << [ ((two[0] - 0x41) * 16) + two[1] - 0x41 ].pack('C') end end return decoded end # # Prepends an ASN1 formatted length field to a piece of data # def self.asn1encode(str = '') res = '' # If the high bit of the first byte is 1, it contains the number of # length bytes that follow case str.length when 0 .. 0x7F res = [str.length].pack('C') + str when 0x80 .. 0xFF res = [0x81, str.length].pack('CC') + str when 0x100 .. 0xFFFF res = [0x82, str.length].pack('Cn') + str when 0x10000 .. 0xffffff res = [0x83, str.length >> 16, str.length & 0xFFFF].pack('CCn') + str when 0x1000000 .. 0xffffffff res = [0x84, str.length].pack('CN') + str else raise "ASN1 str too long" end return res end def self.make_ntlmv2_secblob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201) blob = "\x60" + self.asn1encode( "\x06" + self.asn1encode( "\x2b\x06\x01\x05\x05\x02" ) + "\xa0" + self.asn1encode( "\x30" + self.asn1encode( "\xa0" + self.asn1encode( "\x30" + self.asn1encode( "\x06" + self.asn1encode( "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" ) ) ) + "\xa2" + self.asn1encode( "\x04" + self.asn1encode( "NTLMSSP\x00" + [1, flags].pack('VV') + [ domain.length, #length domain.length, #max length 32 ].pack('vvV') + [ name.length, #length name.length, #max length domain.length + 32 ].pack('vvV') + domain + name ) ) ) ) ) return blob end def self.make_ntlmv2_secblob_auth(domain, name, user, lmv2, ntlm, flags = 0x080201) lmv2 ||= "\x00" * 24 ntlm ||= "\x00" * 24 domain_uni = Rex::Text.to_unicode(domain) user_uni = Rex::Text.to_unicode(user) name_uni = Rex::Text.to_unicode(name) session = '' ptr = 64 blob = "\xa1" + self.asn1encode( "\x30" + self.asn1encode( "\xa2" + self.asn1encode( "\x04" + self.asn1encode( "NTLMSSP\x00" + [ 3 ].pack('V') + [ # Lan Manager Response lmv2.length, lmv2.length, (ptr) ].pack('vvV') + [ # NTLM Manager Response ntlm.length, ntlm.length, (ptr += lmv2.length) ].pack('vvV') + [ # Domain Name domain_uni.length, domain_uni.length, (ptr += ntlm.length) ].pack('vvV') + [ # Username user_uni.length, user_uni.length, (ptr += domain_uni.length) ].pack('vvV') + [ # Hostname name_uni.length, name_uni.length, (ptr += user_uni.length) ].pack('vvV') + [ # Session Key (none) session.length, session.length, (ptr += name_uni.length) ].pack('vvV') + [ flags ].pack('V') + lmv2 + ntlm + domain_uni + user_uni + name_uni + session + "\x00" ) ) ) ) return blob end def self.make_negotiate_secblob_resp(account, domain) blob = "\x60" + self.asn1encode( "\x06" + self.asn1encode( "\x2b\x06\x01\x05\x05\x02" ) + "\xa0" + self.asn1encode( "\x30" + self.asn1encode( "\xa0" + self.asn1encode( "\x30" + self.asn1encode( "\x06" + self.asn1encode( "\x2a\x86\x48\x82\xf7\x12\x01\x02\x02" ) + "\x06" + self.asn1encode( "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" ) + "\x06" + self.asn1encode( "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" ) + "\x06" + self.asn1encode( "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" ) ) ) + "\xa3" + self.asn1encode( "\x30" + self.asn1encode( "\xa0" + self.asn1encode( "\x1b" + self.asn1encode( account + '@' + domain ) ) ) ) ) ) ) return blob end def self.make_ntlmv2_secblob_chall(win_domain, dns_domain, win_name, dns_name, chall, flags) win_domain = Rex::Text.to_unicode(win_domain) dns_domain = Rex::Text.to_unicode(dns_domain) win_name = Rex::Text.to_unicode(win_name) dns_name = Rex::Text.to_unicode(dns_name) addr_list = '' addr_list << [2, win_domain.length].pack('vv') + win_domain addr_list << [1, win_name.length].pack('vv') + win_name addr_list << [4, dns_domain.length].pack('vv') + dns_domain addr_list << [3, dns_name.length].pack('vv') + dns_name addr_list << [5, dns_domain.length].pack('vv') + dns_domain addr_list << [0, 0].pack('vv') ptr = 0 blob = "\xa1" + self.asn1encode( "\x30" + self.asn1encode( "\xa0" + self.asn1encode( "\x0a" + self.asn1encode( "\x01" ) ) + "\xa1" + self.asn1encode( "\x06" + self.asn1encode( "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" ) ) + "\xa2" + self.asn1encode( "\x04" + self.asn1encode( "NTLMSSP\x00" + [2].pack('V') + [ win_domain.length, # length win_domain.length, # max length (ptr += 48) ].pack('vvV') + [ flags ].pack('V') + chall + "\x00\x00\x00\x00\x00\x00\x00\x00" + [ addr_list.length, # length addr_list.length, # max length (ptr += win_domain.length) ].pack('vvV') + win_domain + addr_list ) ) ) ) return blob end def self.make_ntlmv2_secblob_success blob = "\xa1" + self.asn1encode( "\x30" + self.asn1encode( "\xa0" + self.asn1encode( "\x0a" + self.asn1encode( "\x00" ) ) ) ) return blob end # # Process Type 3 NTLM Message (in Base64) # # from http://www.innovation.ch/personal/ronald/ntlm.html # # struct { # byte protocol[8]; // 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' # byte type; // 0x03 # byte zero[3]; # # short lm_resp_len; // LanManager response length (always 0x18) # short lm_resp_len; // LanManager response length (always 0x18) # short lm_resp_off; // LanManager response offset # byte zero[2]; # # short nt_resp_len; // NT response length (always 0x18) # short nt_resp_len; // NT response length (always 0x18) # short nt_resp_off; // NT response offset # byte zero[2]; # # short dom_len; // domain string length # short dom_len; // domain string length # short dom_off; // domain string offset (always 0x40) # byte zero[2]; # # short user_len; // username string length # short user_len; // username string length # short user_off; // username string offset # byte zero[2]; # # short host_len; // host string length # short host_len; // host string length # short host_off; // host string offset # byte zero[6]; # # short msg_len; // message length # byte zero[2]; # # short flags; // 0x8201 # byte zero[2]; # # byte dom[*]; // domain string (unicode UTF-16LE) # byte user[*]; // username string (unicode UTF-16LE) # byte host[*]; // host string (unicode UTF-16LE) # byte lm_resp[*]; // LanManager response # byte nt_resp[*]; // NT response # } type_3_message # def self.process_type3_message(message) decode = Rex::Text.decode_base64(message.strip) type = decode[8,1].unpack("C").first if (type == 3) lm_len = decode[12,2].unpack("v").first lm_offset = decode[16,2].unpack("v").first lm = decode[lm_offset, lm_len].unpack("H*").first nt_len = decode[20,2].unpack("v").first nt_offset = decode[24,2].unpack("v").first nt = decode[nt_offset, nt_len].unpack("H*").first dom_len = decode[28,2].unpack("v").first dom_offset = decode[32,2].unpack("v").first domain = decode[dom_offset, dom_len] user_len = decode[36,2].unpack("v").first user_offset = decode[40,2].unpack("v").first user = decode[user_offset, user_len] host_len = decode[44,2].unpack("v").first host_offset = decode[48,2].unpack("v").first host = decode[host_offset, host_len] return domain, user, host, lm, nt else return "", "", "", "", "" end end # # Process Type 1 NTLM Messages, return a Base64 Type 2 Message # def self.process_type1_message(message, nonce = "\x11\x22\x33\x44\x55\x66\x77\x88", win_domain = 'DOMAIN', win_name = 'SERVER', dns_name = 'server', dns_domain = 'example.com', downgrade = true) dns_name = Rex::Text.to_unicode(dns_name + "." + dns_domain) win_domain = Rex::Text.to_unicode(win_domain) dns_domain = Rex::Text.to_unicode(dns_domain) win_name = Rex::Text.to_unicode(win_name) decode = Rex::Text.decode_base64(message.strip) type = decode[8,1].unpack("C").first if (type == 1) # A type 1 message has been received, lets build a type 2 message response reqflags = decode[12,4] reqflags = reqflags.unpack("V").first if (reqflags & CONST::REQUEST_TARGET) == CONST::REQUEST_TARGET if (downgrade) # At this time NTLMv2 and signing requirements are not supported if (reqflags & CONST::NEGOTIATE_NTLM2_KEY) == CONST::NEGOTIATE_NTLM2_KEY reqflags = reqflags - CONST::NEGOTIATE_NTLM2_KEY end if (reqflags & CONST::NEGOTIATE_ALWAYS_SIGN) == CONST::NEGOTIATE_ALWAYS_SIGN reqflags = reqflags - CONST::NEGOTIATE_ALWAYS_SIGN end end flags = reqflags + CONST::TARGET_TYPE_DOMAIN + CONST::TARGET_TYPE_SERVER tid = true tidoffset = 48 + win_domain.length tidbuff = [2].pack('v') + # tid type, win domain [win_domain.length].pack('v') + win_domain + [1].pack('v') + # tid type, server name [win_name.length].pack('v') + win_name + [4].pack('v') + # tid type, domain name [dns_domain.length].pack('v') + dns_domain + [3].pack('v') + # tid type, dns_name [dns_name.length].pack('v') + dns_name else flags = CONST::NEGOTIATE_UNICODE + CONST::NEGOTIATE_NTLM tid = false end type2msg = "NTLMSSP\0" + # protocol, 8 bytes "\x02\x00\x00\x00" # type, 4 bytes if (tid) type2msg += # Target security info, 8 bytes. Filled if REQUEST_TARGET [win_domain.length].pack('v') + # Length, 2 bytes [win_domain.length].pack('v') # Allocated space, 2 bytes end type2msg +="\x30\x00\x00\x00" + # Offset, 4 bytes [flags].pack('V') + # flags, 4 bytes nonce + # the nonce, 8 bytes "\x00" * 8 # Context (all 0s), 8 bytes if (tid) type2msg += # Target information security buffer. Filled if REQUEST_TARGET [tidbuff.length].pack('v') + # Length, 2 bytes [tidbuff.length].pack('v') + # Allocated space, 2 bytes [tidoffset].pack('V') + # Offset, 4 bytes (usually \x48 + length of win_domain) win_domain + # Target name data (domain in unicode if REQUEST_UNICODE) # Target information data tidbuff + # Type, 2 bytes # Length, 2 bytes # Data (in unicode if REQUEST_UNICODE) "\x00\x00\x00\x00" # Terminator, 4 bytes, all \x00 end type2msg = Rex::Text.encode_base64(type2msg).delete("\n") # base64 encode and remove the returns else # This is not a Type2 message type2msg = "" end return type2msg end # # Downgrading Type messages to LMv1/NTLMv1 and removing signing # def self.downgrade_type_message(message) decode = Rex::Text.decode_base64(message.strip) type = decode[8,1].unpack("C").first if (type > 0 and type < 4) reqflags = decode[12..15] if (type == 1 or type == 3) reqflags = decode[20..23] if (type == 2) reqflags = reqflags.unpack("V") # Remove NEGOTIATE_NTLMV2_KEY and NEGOTIATE_ALWAYS_SIGN, this lowers the negotiation # down to LMv1/NTLMv1. if (reqflags & CONST::NEGOTIATE_NTLM2_KEY) == CONST::NEGOTIATE_NTLM2_KEY reqflags = reqflags - CONST::NEGOTIATE_NTLM2_KEY end if (reqflags & CONST::NEGOTIATE_ALWAYS_SIGN) == CONST::NEGOTIATE_ALWAYS_SIGN reqflags = reqflags - CONST::NEGOTIATE_ALWAYS_SIGN end # Return the flags back to the decode so we can base64 it again flags = reqflags.to_s(16) 0.upto(8) do |idx| if (idx > flags.length) flags.insert(0, "0") end end idx = 0 0.upto(3) do |cnt| if (type == 2) decode[23-cnt] = [flags[idx,1]].pack("C") else decode[15-cnt] = [flags[idx,1]].pack("C") end idx += 2 end end return Rex::Text.encode_base64(decode).delete("\n") # base64 encode and remove the returns end end end end end