-
-
Save diorcety/756db868180672e6b37566a659f5b567 to your computer and use it in GitHub Desktop.
Calling a thiscall DLL function in Python ctypes (x86)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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