I'm trying to find a gadget that translates into jmp esp
within the ntdll.dll
windows DLL for my Reverse Engineering course, but am facing some technical issues.
I wrote the following script:
import ctypes
from ctypes import wintypes, WinDLL
def GetModuleHandle(module):
kernel32 = WinDLL('kernel32', use_last_error=True)
kernel32.GetModuleHandleW.restype = wintypes.HMODULE
kernel32.GetModuleHandleW.argtypes = [wintypes.LPCWSTR]
hMod = kernel32.GetModuleHandleW(module)
return hMod
def find_bytes_in_module(module_name: str, module_size: int, search_bytes: bytes) -> int:
# Get the handle of the module
hModule = GetModuleHandle(module_name)
# Read the contents of the module into a ctypes array and convert to a bytes array
ctypes_arr = ctypes.c_ubyte * module_size
module_data = bytes(ctypes_arr.from_address(hModule))
# Search for the wanted bytes in the module data
index = module_data.find(search_bytes)
return hModule + index
def main():
module_name = 'ntdll.dll'
module_size = 2 * 1024 * 1024 # ntdll.dll on my system is under 2 MB
search_bytes = b'\xff\xe4'
addr = find_bytes_in_module(module_name, module_size, search_bytes)
print(f'`jmp esp` gadget found inside {module_name} at the absolute address: {hex(addr)}')
return 0
if __name__ == '__main__':
main()
The following code works when I run the commands inside find_bytes_in_module
using a 64-bit ipython terminal, but due to my course limitations I am only allowed to use a 32-bit python interpreter.
When I try using any other distribution of python (I even tried a normal 64 bit python interpreter) to run this script, it crashes on the line module_data = bytes(ctypes_arr.from_address(hModule))
. Specifically when trying to convert the ctypes array into a bytes array.
Exit code is -1073741819
. I tried running the terminal with administrator rights and I got the same results.
Does anyone have any idea how I can fix this? Maybe this isn't even the direction I should be taking (I am trying to find the offset from the beginning of the dll to the gadget) I am using a 64-bit windows 10 distribution, in case it matters (hopefully it shouldn't).
Edit: I found on google that this error code means that an Access Violation exception occured in the context of the program's main thread and was not caught by the program's code, thus causing the program's main() / WinMain() entry point to exit prematurely, terminating the process. However, I don't know what I should have done differently to prevent this, and why it didn't happen when I did the very same thing interactively in iPython...
Not sure why, I also tried with the current size of module, since the size you passed is a bit bigger than the module size I got on an up to date windows 10, but I also got the same interpreter crash...
Solved it easily by using ReadProcessMemory
instead. The code is quite simple:
GetModuleHandleW
.GetModuleInformation
.ReadProcessMemory
.This obviously works as long as the module is loaded into the process address space.
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import ctypes
import ctypes.wintypes as wt
# Windows Structs
class MODULEINFO(ctypes.Structure):
_fields_ = (
('lpBaseOfDll', ctypes.c_void_p),
('SizeOfImage', ctypes.c_uint32),
('EntryPoint', ctypes.c_void_p)
)
# Windows API
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
GetModuleHandle = kernel32.GetModuleHandleW
GetModuleHandle.restype = wt.HMODULE
GetModuleHandle.argtypes = (wt.LPCWSTR, )
ReadProcessMemory = kernel32.ReadProcessMemory
ReadProcessMemory.argtypes = (
ctypes.c_void_p, # HANDLE hProcess
ctypes.c_void_p, # LPCVOID lpBaseAddress
ctypes.c_void_p, # LPVOID lpBuffer
ctypes.c_size_t, # SIZE_T nSize
ctypes.POINTER(ctypes.c_size_t) # SIZE_T *lpNumberOfBytesRead
)
ReadProcessMemory.restype = wt.BOOL
psapi = ctypes.WinDLL("psapi", use_last_error=True)
GetModuleInformation = psapi.GetModuleInformation
GetModuleInformation.restype = wt.BOOL
GetModuleInformation.argtypes = (
ctypes.c_void_p, # HANDLE hProcess
ctypes.c_void_p, # HMODULE hModule
ctypes.POINTER(MODULEINFO), # LPMODULEINFO lpModinfo
ctypes.c_uint32, # cb
)
def get_module_info(h_module) -> MODULEINFO:
"""Get Module information for a module loaded into the python interpreter address space.
Args:
h_module: The HMODULE (module base address) to the module for which to get the information.
Returns:
An instance of the MODULEINFO structure.
"""
h_current_proc = ctypes.c_void_p(-1) # pseudo handle to current process.
module_info = MODULEINFO()
ret_val = GetModuleInformation(h_current_proc, h_module, ctypes.byref(module_info), ctypes.sizeof(module_info))
if ret_val == 0:
raise ctypes.WinError(ctypes.get_last_error())
return module_info
def read_module(base_addr: int, size: int) -> bytes:
"""Read the entirety of a module loaded in the Python interpreter.
Args:
base_addr: base address of the module to read.
size: Size of the module.
Returns:
A bytes instance containing the bytes read from the module.
"""
h_current_proc = ctypes.c_void_p(-1) # pseudo handle to current process.
buffer = ctypes.create_string_buffer(b'', size)
nobr = ctypes.c_size_t(0)
ret_val = ReadProcessMemory(h_current_proc, base_addr, buffer, size, ctypes.byref(nobr))
if not ret_val and ctypes.get_last_error() != 299: # ERROR_PARTIAL_COPY
raise ctypes.WinError()
return buffer.raw[:nobr.value]
def find_bytes_in_module(module_name: str, search_bytes: bytes) -> int:
# Get the handle of the module
h_module = GetModuleHandle(module_name)
if h_module == 0:
raise ctypes.WinError(ctypes.get_last_error())
module_info = get_module_info(h_module)
print(f"{module_name}: Base: {module_info.lpBaseOfDll:#016x}; Image Size: {module_info.SizeOfImage:#016x}")
# Read the contents of the module into a ctypes array and convert to a bytes array
module_data = read_module(h_module, module_info.SizeOfImage)
# Search for the wanted bytes in the module data
index = module_data.find(search_bytes)
if index == -1:
# oops, pattern not found...
return -1
return h_module + index
def main():
module_name = 'ntdll.dll'
search_bytes = b'\xff\xe4'
# no need to load ntdll, it's already in the process address space.
addr = find_bytes_in_module(module_name, search_bytes)
if addr < 0:
print("Could not find the pattern in the module.")
return 0
print(f'`jmp esp` gadget found inside {module_name} at the absolute address: {addr:#016x}')
return 0
if __name__ == '__main__':
main()