Hi, I've sent a couple of changes to Wireshark to add a table in the the SMB2 protocol preference to add Session ID => (secret) Session Key mappings so that Wireshark can derive the decryption keys and decrypt the traffic. The way wireshark prefs works allows you to pass them via the command line e.g.: % ./tshark -o 'uat:custom_smb2_seskey_list:"3d00009400480000","28f2847263c83dc00621f742dd3f2e7b"' -r ~/prog/smbcrypto/filt.pcap ...SMB2 172 Negotiate Protocol Request ...SMB2 318 Negotiate Protocol Response ...SMB2 190 Session Setup Request, NTLMSSP_NEGOTIATE ...SMB2 318 Session Setup Response, Error: STATUS_MORE_PROCESSING_REQUIRED, NTLMSSP_CHALLENGE ...SMB2 430 Session Setup Request, NTLMSSP_AUTH, User: SUSE\administrator ...SMB2 142 Session Setup Response ...SMB2 180 Tree Connect Request Tree: \\WS2016\encrypted ...SMB2 150 Tree Connect Response ...SMB2 268 Decrypted SMB3;Ioctl Request FSCTL_VALIDATE_NEGOTIATE_INFO ...SMB2 258 Decrypted SMB3;Ioctl Response FSCTL_VALIDATE_NEGOTIATE_INFO ...SMB2 250 Decrypted SMB3;Create Request File: .... Otherwise you can add them manually in Edit > Preferences > Protocols > SMB2. The change can be found here [1], any help in getting it reviewed/merged welcome. The session key is computed by the client, it's different than the "session key" generated in SMB1 ([2]) which is sent on the wire and not safe to use for anything cryptographic. I suspect the dissector was already trying to use this one but it's most likely wrong. As for obtaining the session key, I came up with 2 solutions: - Make the kernel dump them. I will send a patch soon after this email on linux-cifs that adds a CIFS_DEBUG_DUMP_KEYS config option that simply dumps the keys on the console. - Dump them by reading kernel memory. This can be done with gdb and /proc/kcore. I've attached a hackish gdb script to do that. Fist solution is more robust and does not require any tool but it's a bit dangerous to include code that simply dumps sensitive data like this. I've made it clear in the config name and description that it is not safe to enable this for anything other than debugging. Second solution simply needs root access and gdb but structure offsets are hardcoded in the script. If the kernel debug info package is installed on the system gdb can lookup structure offsets and so the script doesn't need to hardcode the offsets... but it's quite large (around a GB on my system). gdb -q /boot/vmlinux-4.4.62-18.6-default /proc/kcore \ -ex "source ~aaptel/prog/gdb-linux/cifs_dump_keys.py" \ -ex 'cifs-dump' Reading symbols from /boot/vmlinux-4.4.62-18.6-default... done. [New process 1] Core was generated by `BOOT_IMAGE=/boot/vmlinuz-4.4.62-18.6-default root=UUID=74a87444-aac1-4b24-a6d1-'. #0 0x0000000000000000 in irq_stack_union () [*] trying to get cifs symbols from kallsyms... [*] OK! cifs-dump command defined host <10.160.66.43> vers <1.0> dom <SLES11SP4> user <aaptel> pw <xxxxxxxxx> SID <64 00 00 00 00 00 00 00> SESKEY <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00> host <10.160.65.202> vers <2.0> dom <SUSE> user <administrator> pw <xxxxxxxxxxx> SID <25 00 00 44 00 50 00 00> SESKEY <00 00 00 00 80 ce ed fb 02 88 ff ff 80 76 1c 57> (gdb) q 1: https://code.wireshark.org/review/#/q/topic:smb2-decryption-uat 2: https://msdn.microsoft.com/en-us/library/ff470134.aspx Cheers, -- Aurélien Aptel / SUSE Labs Samba Team GPG: 1839 CB5F 9F5B FB9B AA97 8C99 03C8 A49B 521B D5D3 SUSE Linux GmbH, Maxfeldstraße 5, 90409 Nürnberg, Germany GF: Felix Imendörffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nürnberg)
# gdb python script to dump cifs.ko related structure # # Usage: # gdb -q /boot/vmlinux-4.4.62-18.6-default /proc/kcore -ex "source path/to/this.py" -ex "cifs-dump" # # Copyright (c) Aurelien Aptel <aaptel@xxxxxxxx>, 2017 # # This work is licensed under the terms of the GNU GPL version 3. # import gdb import subprocess def sizeof(t): return int(gdb.execute("p sizeof(%s)"%t, to_string=True).split("=")[1].strip()) # # offset of members in various kernel structures # OFF = { 'list_head': {'next': 0, 'prev': sizeof("void*")}, 'TCP_Server_Info': { 'tcp_ses_list': 0, 'smb_ses_list': 2*sizeof("void*"), 'hostname': 0x50, 'vals': 0x40 }, 'smb_version_values': {'version_string':0}, 'cifs_ses': { 'Suid': 0x78, 'auth_key': 0xf8, 'user_name':0xe0, 'domainName':0xe8, 'password':0xf0 }, 'session_key': {'response': sizeof("unsigned int")}, } CIFS_TCP_SES_LIST_ADDR = 0 def add_module_syms(mod): kofile = subprocess.check_output(['modinfo', '-n', mod]).strip() moddir = "/sys/module/%s/sections/" % mod def sec_addr(sec, pre=True): if pre: return "-s "+sec+" "+open(moddir+sec).read().strip() return open(moddir+sec).read().strip() cmd = " ".join(["add-symbol-file", kofile, sec_addr(".text", False), sec_addr(".data"), sec_addr(".bss")]) gdb.execute(cmd) def kallsyms_get_sym(sym): r = subprocess.check_output(["grep", sym, "/proc/kallsyms"]) return int(r.split()[0], 16) def to_byte(x): if isinstance(x, str): return ord(x[0]) return x[0] def hexdump(p, length): b=gdb.selected_inferior().read_memory(p, length) return ' '.join(['%02x'%to_byte(x) for x in b]) def ptr_to_int(p): if isinstance(p, gdb.Value): s = str(p).split()[0] if '0x' in s: p = int(s, 16) else: p = int(s, 10) return p def deref(p, to_string=False): p = ptr_to_int(p) if to_string: return gdb.parse_and_eval("*((char**)(0x%x))"%p).string() else: return ptr_to_int(gdb.parse_and_eval("*((void**)(0x%x))"%p)) # # linked list iterator # def iter_raw(base, list_off, member_off): base = ptr_to_int(base) p = deref(base + OFF['list_head']['next']) while p != base: yield p - list_off + member_off p = deref(p + OFF['list_head']['next']) class CIFSDump(gdb.Command): """Iterate over smb session and dump keys""" def __init__(self): super(CIFSDump, self).__init__("cifs-dump", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) def invoke(self, arg, from_tty): argv = gdb.string_to_argv(arg) for tcp in iter_raw(CIFS_TCP_SES_LIST_ADDR, OFF['TCP_Server_Info']['tcp_ses_list'], 0): host = deref(tcp+OFF['TCP_Server_Info']['hostname'], to_string=True) vers = deref(deref(tcp+OFF['TCP_Server_Info']['vals']) +OFF['smb_version_values']['version_string'], to_string=True) for ses in iter_raw(tcp+OFF['TCP_Server_Info']['smb_ses_list'], 0, 0): gdb.write("host <%s> vers <%s> dom <%s> user <%s> pw <%s>\nSID <%s>\nSESKEY <%s>\n\n"%( host, vers, deref(ses+OFF['cifs_ses']['domainName'], to_string=True), deref(ses+OFF['cifs_ses']['user_name'], to_string=True), deref(ses+OFF['cifs_ses']['password'], to_string=True), hexdump(ses+OFF['cifs_ses']['Suid'], 8), hexdump(ses+OFF['cifs_ses']['auth_key']+OFF['session_key']['response'], 16), )) try: gdb.write("[*] trying to get cifs module symbols...\n") add_module_syms("cifs") CIFS_TCP_SES_LIST_ADDR = ptr_to_int(gdb.parse_and_eval("cifs_tcp_ses_list").address) except Exception: gdb.write("[*] failed\n") if CIFS_TCP_SES_LIST_ADDR == 0: try: gdb.write("[*] trying to get cifs symbols from kallsyms...\n") CIFS_TCP_SES_LIST_ADDR = kallsyms_get_sym("cifs_tcp_ses_list") except Exception: gdb.write("[*] failed\n") if CIFS_TCP_SES_LIST_ADDR == 0: gdb.write("[*] cannot find addr for cifs_tcp_ses_list :(\n") else: CIFSDump() gdb.write("[*] OK! cifs-dump command defined\n")