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.
CODE:
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
NUM_IDT_ENTRIES = 256
# Constants for interrupt types
INTERRUPT_GATE = 0xE
TRAP_GATE = 0xF
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.
'''
# nt!_IMAGE_DOS_HEADER
'''
+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
'''
IMAGE_DOS_SIGNATURE = 0x5A4D # 'MZ'
# Relevant structure offsets.
OFFSET_IMAGE_DOS_HEADER_E_MAGIC = 0x0
OFFSET_IMAGE_DOS_HEADER_E_LFANEW = 0x3c
# nt!_IMAGE_NT_HEADERS
'''
+0x000 Signature : Uint4B
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64
'''
IMAGE_NT_SIGNATURE = 0x00004550 # 'PE00'
# Relevant structure offsets.
OFFSET_IMAGE_NT_HEADERS_SIGNATURE = 0x0
# 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.
OFFSET_KIDTENTRY64_OFFSETLOW = 0x0
OFFSET_KIDTENTRY64_OFFSETMIDDLE = 0x6
OFFSET_KIDTENTRY64_OFFSETHIGH = 0x8
# 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.")
exit(-2)
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}")
else:
print("Failed to find the base address of ntoskrnl.exe.")