Skip to content

Instantly share code, notes, and snippets.

@nmulasmajic
Last active May 31, 2022 07:14
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save nmulasmajic/f90661489f858237bcd68fbde5516abd to your computer and use it in GitHub Desktop.
Save nmulasmajic/f90661489f858237bcd68fbde5516abd to your computer and use it in GitHub Desktop.
Discovers the base address of ntoskrnl when IDA's GDB stub is loaded by leveraging the IDT.
'''
Module Name:
find_nt_imagebase_x64.py
Abstract:
Discovers the base address of ntoskrnl when IDA's GDB stub is
loaded by leveraging the IDT.
NOTE: This is only compatible for 64-bit editions of Windows.
Author:
Nemanja (Nemi) Mulasmajic <nm@triplefault.io>
http://triplefault.io
'''
from idaapi import *
# The size of a page on x86/AMD64.
PAGE_SIZE = 4096
def splice(string, start_token, end_token):
'''
Given an input 'string', extracts the contents between the
starting and ending tokens.
'''
start_pos = string.find(start_token)
end_pos = string.rfind(end_token)
# This means our tokens are invalid and don't exist in the string.
if start_pos == -1 or end_pos == -1:
return None
start_pos += len(start_token)
# Can't splice the string if this is true.
if start_pos > end_pos:
return None
# Splices the string.
return string[start_pos:end_pos]
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 = DbgWord(address + OFFSET_KIDTENTRY64_OFFSETLOW)
OffsetMiddle = DbgWord(address + OFFSET_KIDTENTRY64_OFFSETMIDDLE)
OffsetHigh = DbgDword(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)
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 = DbgWord(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 = DbgDword(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 = DbgDword(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
###########################################################
# Begin scripting logic.
###########################################################
print "=" * 100
print "Discovers the base address of ntoskrnl when IDA's GDB stub is loaded by leveraging the IDT.\n"
print "NOTE: This is only compatible for 64-bit editions of Windows."
print "\t\t\t~ http://triplefault.io ~"
print "=" * 100
# Ask for the idtr register from the VMware GDB stub.
monitor_result = SendDbgCommand("r idtr")
# The string is returned in the following format:
# idtr base=0xfffff800707c9070 limit=0xfff
try:
# Try to extract just the numerical base.
idt_base = int(splice(monitor_result, "base=", " limit"), 16)
except:
print "ERROR: Failed to retrieve IDT base from VMware's GDB stub."
exit(-1)
print "IDT base @ {}.".format(hex(idt_base))
idt_entry = read_idt_entry(idt_base)
if idt_entry is None:
print "ERROR: Failed to extract and parse KIDTENTRY64."
exit(-2)
print "_KIDTENTRY64[0] (nt!KiDivideErrorFault) @ {}.".format(hex(idt_entry))
# We have a symbol in the address space of nt!* (unless someone
# detoured the IDT entry...). At this point, we walk kernel
# memory backwards from the start of this symbol until we
# get to a valid PE header. This should be the base address of
# ntoskrnl.
ntoskrnl_base = find_base_address(idt_entry)
if ntoskrnl_base is not None:
print "\nThe base address of nt (ntoskrnl) is @ {}.".format(hex(ntoskrnl_base))
else:
print "\nERROR: Could not find the base address of ntoskrnl after searching all resident memory. Something clearly went wrong. Additionally, you waited a very long time. Sorry!"
exit(-3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment