I'm following this tutorial here https://www.triplefault.io/2017/07/loading-kernel-symbols-vmm-debugging.html to load kernel symbols with debugging.

Setting up the debug environment with IDA Pro using gdb, works fine. But the python script that the author of the tutorial uses a command called SendDbgCommand to view gs registers and when attempting to run this script it fails with SendDbgCommand is not defined.

I've tried defining the function like this:

def SendDbgCommand(cmd):
    """Sends a command to the debugger and returns the output string.
    An exception will be raised if the debugger is not running or the current debugger does not export
    the 'SendDbgCommand' IDC command.
    s = Eval('SendDbgCommand("%s");' % cmd)
    if s.startswith("IDC_FAILURE"):
        raise Exception, "Debugger command is available only when the debugger is active!"
    return s

This throws the exception in the code, so I'm not really sure how to send a command to gdb using python is this feature removed? Any help is appreciated.

Some information that might be useful:

  • Windows 10 OS using VMWARE Workstation Player (non licensed)
  • Ida pro 7.7
  • Python3

1 Answer 1


Here is updated code in case anyone ever runs across this issue in the future. The error was happening because idt_entry was missing the first for 0xFFFF some reason just appended the missing value and it works good now.


import idaapi
import ida_dbg
import re

# Constants for the IDT entry size and the number of entries
IDT_ENTRY_SIZE = 16  # Each entry is 16 bytes

# Constants for interrupt types
PAGE_SIZE = 4096

def page_align(address):
        Aligns the 'address' on an architecture page boundary (0x1000).
    return (address & ~(PAGE_SIZE - 1))

def find_base_address(address, verbose = True):
        Walks memory backwards from the starting 'address' until a 
        valid PE header is located.

        +0x000 e_magic          : Uint2B
        +0x002 e_cblp           : Uint2B
        +0x004 e_cp             : Uint2B
        +0x006 e_crlc           : Uint2B
        +0x008 e_cparhdr        : Uint2B
        +0x00a e_minalloc       : Uint2B
        +0x00c e_maxalloc       : Uint2B
        +0x00e e_ss             : Uint2B
        +0x010 e_sp             : Uint2B
        +0x012 e_csum           : Uint2B
        +0x014 e_ip             : Uint2B
        +0x016 e_cs             : Uint2B
        +0x018 e_lfarlc         : Uint2B
        +0x01a e_ovno           : Uint2B
        +0x01c e_res            : [4] Uint2B
        +0x024 e_oemid          : Uint2B
        +0x026 e_oeminfo        : Uint2B
        +0x028 e_res2           : [10] Uint2B
        +0x03c e_lfanew         : Int4B

    # Relevant structure offsets.
       +0x000 Signature        : Uint4B
       +0x004 FileHeader       : _IMAGE_FILE_HEADER
       +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER64
    IMAGE_NT_SIGNATURE = 0x00004550 # 'PE00'
    # Relevant structure offsets.
    # Find the page aligned offset of the specified symbol's address by
    # stripping off the page RVA.
    DosHeader = page_align(address)
    if verbose:
        print ("\nSearching for base address of symbol @ {} ({}).".format(hex(address), hex(DosHeader)))
        print ("=" * 100)
    while DosHeader != 0:
        e_magic = read_dbg_word(DosHeader + OFFSET_IMAGE_DOS_HEADER_E_MAGIC)
        # If we can't read the page, it's most likely invalid (not 
        # mapped in). In the kernel most PE images (like ntoskrnl) 
        # are more or less guaranteed to have their PE header in 
        # the NonPagedPool. We skip invalid pages here.
        if e_magic is not None:
            if verbose:
                print ("{} --> {}".format(hex(DosHeader), hex(e_magic)))
            # Do we have an 'MZ'?
            if e_magic == IMAGE_DOS_SIGNATURE:
                # Extract the e_lfanew.
                e_lfanew = read_dbg_dword(DosHeader + OFFSET_IMAGE_DOS_HEADER_E_LFANEW)
                # Go to the (potential) IMAGE_NT_HEADERS at this location.
                NtHeaders = DosHeader + e_lfanew
                # The IMAGE_NT_HEADERS should be on the same 
                # page as the IMAGE_DOS_HEADER. If this is not true, 
                # something's weird and we shouldn't read from this address.
                if page_align(NtHeaders) == DosHeader:
                    Signature = read_dbg_dword(NtHeaders + OFFSET_IMAGE_NT_HEADERS_SIGNATURE)
                    if verbose:
                        print ("\t{} --> {}".format(hex(NtHeaders), hex(Signature)))
                    # Do we have a 'PE00'?
                    if Signature == IMAGE_NT_SIGNATURE:
                        if verbose:
                            print ("\t{} Base address located @ {}.".format("^" * 50, hex(DosHeader)))
                        # At this point, it looks like we have both a valid 
                        # DOS and NT header. This should be the right base 
                        # address.
                        return DosHeader        
        # Try another page.
        DosHeader -= PAGE_SIZE
    # If we get to here... someone left this script running way too long.
    return None

def read_idt_entry(address):
    Extracts the virtual address of the _KIDTENTRY64 at 'address'.
    # nt!_KIDTENTRY64
    +0x000 OffsetLow        : Uint2B
    +0x002 Selector         : Uint2B
    +0x004 IstIndex         : Pos 0, 3 Bits
    +0x004 Reserved0        : Pos 3, 5 Bits
    +0x004 Type             : Pos 8, 5 Bits
    +0x004 Dpl              : Pos 13, 2 Bits
    +0x004 Present          : Pos 15, 1 Bit
    +0x006 OffsetMiddle     : Uint2B
    +0x008 OffsetHigh       : Uint4B
    +0x00c Reserved1        : Uint4B
    +0x000 Alignment        : Uint8B
    # Relevant structure offsets.

    # Read the data.
    OffsetLow = read_dbg_word(address + OFFSET_KIDTENTRY64_OFFSETLOW)
    OffsetMiddle = read_dbg_word(address + OFFSET_KIDTENTRY64_OFFSETMIDDLE)
    OffsetHigh = read_dbg_word(address + OFFSET_KIDTENTRY64_OFFSETHIGH)
    # Failed to read some part of the offset.
    if OffsetLow is None or OffsetMiddle is None or OffsetHigh is None:
        return None
    # Build the 64-bit address representing this structure.
    return (((OffsetHigh << 32) + (OffsetMiddle << 16) + OffsetLow) | 0xfffff00000000000)

def is_valid_idt_entry(entry):
    Check if the IDT entry represents an interrupt gate (0xE) or trap gate (0xF).
    gate_type = (entry[5] >> 8) & 0xF
    return gate_type in (INTERRUPT_GATE, TRAP_GATE)

def extract_hex_number(idtr_str):
    Extract the hex number from the idtr_str.
    match = re.search(r'idtr base=([0-9a-fA-Fx]+)', idtr_str)
    if match:
        hex_value = match.group(1)
        return hex_value
    return None

def find_ntoskrnl_base():
    Find the base address of ntoskrnl.exe using the IDT.
    idt_base = None

    # Get the IDT base address from GDB
    idtr_result = send_dbg_command("r idtr")
    if idtr_result and isinstance(idtr_result, str) and len(idtr_result) > 0:
        hex_base = extract_hex_number(idtr_result)
        if hex_base:
            idt_base = int(hex_base, 16)
            print("idt base -> @ {}".format(hex(idt_base)))

    idt_entry = read_idt_entry(idt_base)

    if idt_entry is None:
        print("ERROR: Failed to extract KIDTENTRY64.")

    print("KIDTENTRY64[0] @ {}".format(hex(idt_entry)))

    # Now, let's walk backward from KIDTENTRY64 to find ntoskrnl.exe base
    real_base = find_base_address(idt_entry)

    if real_base is not None:
        print("FINALLY FOUND IT @ {}".format(hex(real_base)))

    return None

# Main script
ntoskrnl_base = find_ntoskrnl_base()

if ntoskrnl_base is not None:
    print(f"The base address of ntoskrnl.exe is: {ntoskrnl_base}")
    print("Failed to find the base address of ntoskrnl.exe.")

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.