# 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@suse.com>, 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")
