bata24 / gef

GEF - GDB Enhanced Features for exploit devs & reversers
Other
329 stars 23 forks source link

Idea: new command to load kernel module debuginfo #87

Closed unvariant closed 2 weeks ago

unvariant commented 2 weeks ago

The sect_attrs field of a module can be used to find the base addresses of all of a module's loaded sections, then the module can be fully loaded into memory while preserving debuginfo with add-symbol-file.

@register_command
class KernelLoadCommand(GenericCommand):
    """Load kernel module symbols."""
    _cmdline_ = "kload"
    _category_ = "08-d. Qemu-system Cooperation - Linux Advanced"

    parser = argparse.ArgumentParser(prog=_cmdline_)
    parser.add_argument("-n", "--no-pager", action="store_true", help="do not use less.")
    parser.add_argument("-q", "--quiet", action="store_true", help="enable quiet mode.")
    parser.add_argument("name", type=str, help="name of the loaded module.")
    parser.add_argument("path", type=str, help="path to compiled kernel module")
    _syntax_ = parser.format_help()

    _example_ = "{:s} load load.ko".format(_cmdline_)

    def get_offset_sect_attrs(self, module_addrs):
        for i in range(300):
            offset_sect_attrs = i * current_arch.ptrsize
            valid = True
            for module in module_addrs:
                # access check
                if not is_valid_addr(module + offset_sect_attrs):
                    valid = False
                    break
                sect_attrs = read_int_from_memory(module + offset_sect_attrs)
                if not is_valid_addr(sect_attrs):
                    valid = False
                    break
                nsections = read_int_from_memory(sect_attrs + 5 * current_arch.ptrsize) & 0xffffffff
                firstname = read_int_from_memory(sect_attrs + 6 * current_arch.ptrsize)
                if not is_valid_addr(firstname):
                    valid = False
                    break
                sectname = read_cstring_from_memory(firstname)
                # not really a requirement but oh well
                if sectname is None or not sectname.startswith("."):
                    valid = False
                    break
            if valid:
                if not self.quiet:
                    info("offsetof(module, sect_attrs): {:#x}".format(offset_sect_attrs))
                return offset_sect_attrs

        if not self.quiet:
            err("Not found module->sect_attrs")
        return None

    @parse_args
    @only_if_gdb_running
    @only_if_specific_gdb_mode(mode=("qemu-system", "vmware"))
    @only_if_in_kernel_or_kpti_disabled
    def do_invoke(self, args):
        self.dont_repeat()

        kmod = __LCO__["kmod"]
        kmod.quiet = args.quiet
        self.quiet = args.quiet
        module_addrs = kmod.get_modules_list()

        offset_name = kmod.get_offset_name(module_addrs)
        if offset_name is None:
            return

        offset_sect_attrs = self.get_offset_sect_attrs(module_addrs)
        if offset_sect_attrs is None:
            return

        for module in module_addrs:
            name_string = read_cstring_from_memory(module + offset_name)
            if name_string == args.name:
                sect_attrs = read_int_from_memory(module + offset_sect_attrs)
                nsections = read_int_from_memory(sect_attrs + 5 * current_arch.ptrsize) & 0xffffffff
                info("nsections = {}".format(nsections))

                sections = []
                for i in range(nsections):
                    name = read_cstring_from_memory(read_int_from_memory(sect_attrs + (6 + i * 9) * current_arch.ptrsize))
                    addr = read_int_from_memory(sect_attrs + (14 + i * 9) * current_arch.ptrsize)
                    info("name={:s}, addr={:#x}".format(name, addr))
                    sections.append((name, addr,))
                    gdb.execute("set ${:s} = {:#x}".format(name.replace(".", "").replace("-", ""), addr))

                command = " ".join(['-s {:s} {:#x}'.format(name, addr) for (name, addr) in sections])
                # info("sections={:s}".format(command))
                gdb.execute("add-symbol-file {:s} {:s}".format(args.path, command))
                break
bata24 commented 2 weeks ago

Is this a different feature from kmod -a?

Is it new that you can load not only debug symbols but also type information? If so, that sounds like a good feature because it eliminates the need to check the address where the module is loaded. If it is a new feature, I'll add it.

unvariant commented 2 weeks ago

kmod -a grabs symbol addresses but can't apply type information, using add-symbol-file is able to load type info if it is present in the module.

However some issues I have found while investigating this a bit more is that gdb does not properly resolve the addresses of global variables that are declared static, the type debuginfo exists but those variables are not marked as external in the dwarf which makes gdb ignore them for some reason. It would be possible to rewrite the dwarf data in a temp file before running add-symbol-file on it.

bata24 commented 2 weeks ago

I have implemented the command you suggested while still supporting older kernel versions. Can I close this for now?

unvariant commented 2 weeks ago

sure

unvariant commented 2 weeks ago

I want to try to write something to fix up the dwarf data, but I might not get around to that for a while.