Skip to content

Instantly share code, notes, and snippets.

@diorcety
Forked from ess7/thiscall.py
Created August 19, 2024 12:38
Show Gist options
  • Select an option

  • Save diorcety/756db868180672e6b37566a659f5b567 to your computer and use it in GitHub Desktop.

Select an option

Save diorcety/756db868180672e6b37566a659f5b567 to your computer and use it in GitHub Desktop.
Calling a thiscall DLL function in Python ctypes (x86)
import ctypes
def _thiscallcode(address):
buf = ctypes.windll.kernel32.VirtualAlloc(0, 4096, 0x3000, 0x40)
# Stack: ret addr, this, arg1, arg2, ...
# [esp] [esp+4] ...
code = b""
# code += b"\xcc"
code += b"\x8b\x4c\x24\x04" # mov ecx, dword ptr [esp+4]
code += b"\x8F\x04\x24" # pop dword ptr [esp+0]
code += b"\x68" + address.to_bytes(4, byteorder='little') # push address
# Stack: func addr, ret addr, arg1, arg2, ...
code += b"\xc3" # ret
ctypes.memmove(buf, code, len(code))
return buf
def _thiscallbackcode(address):
buf = ctypes.windll.kernel32.VirtualAlloc(0, 4096, 0x3000, 0x40)
# Stack: ret addr, arg1, arg2, arg3, ...
# [esp] [esp+4] ...
code = b""
# code += b"\xcc"
code += b"\xff\x34\x24" # push dword ptr [esp]
code += b"\x89\x4C\x24\x04" # mov dword ptr [esp+0x4],ecx
code += b"\x68" + address.to_bytes(4, byteorder='little') # push address
# Stack: func addr, ret addr, this, arg1, arg2, arg3, ...
code += b"\xc3" # ret
ctypes.memmove(buf, code, len(code))
return buf
def _release(buf):
ctypes.windll.kernel32.VirtualFree(buf, 0, 0x8000)
_thiscall_functype_cache = {}
def THISCALLFUNCTYPE(restype, thistype, *argtypes, **kw):
from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL, FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \
FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR, CFuncPtr as _CFuncPtr
# docstring set later (very similar to CFUNCTYPE.__doc__)
flags = _FUNCFLAG_STDCALL
if kw.pop("use_errno", False):
flags |= _FUNCFLAG_USE_ERRNO
if kw.pop("use_last_error", False):
flags |= _FUNCFLAG_USE_LASTERROR
if kw:
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
try:
return _thiscall_functype_cache[(restype, argtypes, flags)]
except KeyError:
pass
class ThisCallFunctionType(_CFuncPtr):
# Add function address
_argtypes_ = [thistype] + list(argtypes)
_restype_ = restype
_flags_ = flags
def __new__(cls, *args, **kwargs):
assert len(args) == 1
if isinstance(args[0], Callable):
obj = _CFuncPtr.__new__(cls, args[0])
fct_addr = ctypes.cast(obj, ctypes.c_void_p).value
new_fct = _thiscallbackcode(fct_addr)
new_obj = _CFuncPtr.__new__(cls, new_fct)
new_obj._old_obj = obj
obj = new_obj
obj._new_fct = new_fct
else:
new_fct = _thiscallcode(args[0])
obj = _CFuncPtr.__new__(cls, new_fct)
obj._new_fct = new_fct
return obj
def __del__(self):
_release(self._new_fct)
_thiscall_functype_cache[(restype, argtypes, flags)] = ThisCallFunctionType
return ThisCallFunctionType
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment