From 8bad713e089ad66baeb818aaa92dead6b04a1266 Mon Sep 17 00:00:00 2001 From: naksyn <59816245+naksyn@users.noreply.github.com> Date: Fri, 27 Jan 2023 11:12:45 +0100 Subject: [PATCH] added execution of unmanaged EXEs Simply calling the .exe entrypoint can achieve execution using MemoryModule technique, .NET assemblies are not yet supported. --- pythonmemorymodule/__init__.py | 98 ++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/pythonmemorymodule/__init__.py b/pythonmemorymodule/__init__.py index 3c9ba79..9e1cdd2 100644 --- a/pythonmemorymodule/__init__.py +++ b/pythonmemorymodule/__init__.py @@ -116,12 +116,12 @@ PIMAGE_DOS_HEADER = POINTER(IMAGE_DOS_HEADER) ''' ref: https://github.com/wine-mirror/wine/blob/master/include/winnt.h typedef struct _IMAGE_TLS_DIRECTORY64 { - ULONGLONG StartAddressOfRawData; - ULONGLONG EndAddressOfRawData; - ULONGLONG AddressOfIndex; - ULONGLONG AddressOfCallBacks; - DWORD SizeOfZeroFill; - DWORD Characteristics; + ULONGLONG StartAddressOfRawData; + ULONGLONG EndAddressOfRawData; + ULONGLONG AddressOfIndex; + ULONGLONG AddressOfCallBacks; + DWORD SizeOfZeroFill; + DWORD Characteristics; } IMAGE_TLS_DIRECTORY64, *PIMAGE_TLS_DIRECTORY64; @@ -133,9 +133,9 @@ typedef VOID (CALLBACK *PIMAGE_TLS_CALLBACK)( #ref: https://github.com/arizvisa/syringe/blob/1f0ea1f514426fd774903c70d03638ecd40a97c3/lib/pecoff/portable/tls.py class IMAGE_TLS_CALLBACK(c_void_p): - ''' - void NTAPI IMAGE_TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved) - ''' + ''' + void NTAPI IMAGE_TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved) + ''' PIMAGE_TLS_CALLBACK = POINTER(IMAGE_TLS_CALLBACK) @@ -148,7 +148,7 @@ class IMAGE_TLS_DIRECTORY(Structure): ('SizeOfZeroFill', DWORD), ('Characteristics', DWORD), ] - + PIMAGE_TLS_DIRECTORY = POINTER(IMAGE_TLS_DIRECTORY) @@ -334,6 +334,9 @@ TLSexecProc = WINFUNCTYPE(BOOL, HINSTANCE, DWORD, LPVOID) PTLSExecProc = POINTER(TLSexecProc) HMEMORYMODULE = HMODULE +ExeEntryProc = WINFUNCTYPE(BOOL, LPVOID) +PExeEntryProc = POINTER(ExeEntryProc) + # Constants MEM_COMMIT = 0x00001000 MEM_DECOMMIT = 0x4000 @@ -480,12 +483,13 @@ class MemoryModule(pe.PE): print('DEBUG: %s' % msg) def load_module(self): - if not self.is_dll(): - raise WindowsError('The specified module does not appear to be a DLL.') + if not self.is_exe() and not self.is_dll(): + raise WindowsError('The specified module does not appear to be an exe nor a dll.') if self.PE_TYPE == pe.OPTIONAL_HEADER_MAGIC_PE and isx64: - raise WindowsError('The dll you attempted to load appears to be an 32-bit DLL, but you are using a 64-bit version of Python.') + raise WindowsError('The exe you attempted to load appears to be an 32-bit exe, but you are using a 64-bit version of Python.') elif self.PE_TYPE == pe.OPTIONAL_HEADER_MAGIC_PE_PLUS and not isx64: - raise WindowsError('The dll you attempted to load appears to be an 64-bit DLL, but you are using a 32-bit version of Python.') + raise WindowsError('The exe you attempted to load appears to be an 64-bit exe, but you are using a 32-bit version of Python.') + self._codebaseaddr = VirtualAlloc( self.OPTIONAL_HEADER.ImageBase, # To test relocations, add some values here i.e. +int(0x030000000) self.OPTIONAL_HEADER.SizeOfImage, @@ -530,7 +534,7 @@ class MemoryModule(pe.PE): szheaders = self.DOS_HEADER.e_lfanew + self.OPTIONAL_HEADER.SizeOfHeaders tmpheaders = create_unsigned_buffer(szheaders, self.__data__[:szheaders]) if not memmove(self._headersaddr, cast(tmpheaders, c_void_p), szheaders): - raise RuntimeError('memmove failed') + raise RuntimeError('memmove failed') del tmpheaders self._headersaddr += self.DOS_HEADER.e_lfanew @@ -554,24 +558,42 @@ class MemoryModule(pe.PE): self.ExecuteTLS() entryaddr = self.pythonmemorymodule.contents.headers.contents.OptionalHeader.AddressOfEntryPoint - self.dbg('Checking dll for entry point.') + + self.dbg('Checking for entry point.') if entryaddr != 0: - entryaddr += codebase - self.dbg('Found entry at address: 0x%x', entryaddr) - DllEntry = DllEntryProc(entryaddr) - if not bool(DllEntry): - self.free_library(self.pythonmemorymodule) - raise WindowsError('dll has no entry point.\n') - self.dbg("calling DllEntry with DLL_PROCESS_ATTACH") - self.dbg('entryaddr address: 0x%x', entryaddr) - try: - success = DllEntry(codebase, DLL_PROCESS_ATTACH, 0) - except Exception as e: - print(e) + entryaddr += codebase + if self.is_exe(): + ExeEntry = ExeEntryProc(entryaddr) + if not bool(ExeEntry): + self.free_library() + raise WindowsError('exe has no entry point.\n') + try: + self.dbg("Calling exe entrypoint 0x%x", entryaddr) + + success = ExeEntry(entryaddr) + except Exception as e: + print(e) + + elif self.is_dll(): + DllEntry = DllEntryProc(entryaddr) + if not bool(DllEntry): + self.free_library() + raise WindowsError('dll has no entry point.\n') + + try: + self.dbg("Calling dll entrypoint 0x%x with DLL_PROCESS_ATTACH", entryaddr) + success = DllEntry(codebase, DLL_PROCESS_ATTACH, 0) + except Exception as e: + print(e) + if not bool(success): - self.free_library(self.pythonmemorymodule) - raise WindowsError('dll could not be loaded.') + if self.is_dll(): + self.free_library() + raise WindowsError('dll could not be loaded.') + else: + self.free_exe() + raise WindowsError('exe could not be loaded') self.pythonmemorymodule.contents.initialized = 1 def IMAGE_FIRST_SECTION(self): @@ -623,8 +645,8 @@ class MemoryModule(pe.PE): if not bool(tlsres): raise WindowsError('TLS could not be executed.') else: - # 8 bytes step - this is the size of the callback field in the TLS callbacks table. Need to initialize callback to IMAGE_TLS_CALLBACK with - # the updated address, otherwise callback.value won't be null when the callback table is finished and the while won't exit + # 8 bytes step - this is the size of the callback field in the TLS callbacks table. Need to initialize callback to IMAGE_TLS_CALLBACK with + # the updated address, otherwise callback.value won't be null when the callback table is finished and the while won't exit self.dbg("TLS callback executed") callbackaddr+=sizeof(c_ulonglong) callback= IMAGE_TLS_CALLBACK.from_address(callbackaddr) @@ -670,7 +692,7 @@ class MemoryModule(pe.PE): protect = ProtectionFlags[executable][readable][writeable] self.dbg("Protection flag:%d",protect) if checkCharacteristic(section, IMAGE_SCN_MEM_NOT_CACHED): - print("not cached") + print("not cached") protect |= PAGE_NOCACHE @@ -807,6 +829,16 @@ class MemoryModule(pe.PE): HeapFree(GetProcessHeap(), 0, self.pythonmemorymodule) self.close() + + def free_exe(self): + self.dbg("Freeing exe") + if not bool(self.pythonmemorymodule): return + pmodule = pointer(self.pythonmemorymodule) + if bool(self._codebaseaddr): + VirtualFree(self._codebaseaddr, 0, MEM_RELEASE) + + HeapFree(GetProcessHeap(), 0, self.pythonmemorymodule) + self.close() def _proc_addr_by_ordinal(self, idx):