5Kas
How to correctly generate shellcode from a PyInstaller executable?
For context
I am using this Python script to execute the shellcode that is found in
loader.bin
#!/usr/bin/env python3 import ctypes import ctypes.wintypes as wt import logging import platform # Set up logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') class ShellcodeExecute: # A PyInstaller EXE converted into shellcode using Donut try: shellcode = open('loader.bin', 'rb').read() logging.info(f'Length of the shellcode is: {len(shellcode)}') except FileNotFoundError: logging.critical('loader.bin file not found. Please ensure the file is in the correct location.') raise except Exception as e: logging.critical(f'Error reading loader.bin: {e}') raise HEAP_CREATE_ENABLE_EXECUTE = 0x00040000 HEAP_ZERO_MEMORY = 0x00000008 PAGE_READWRITE_EXECUTE = 0x40 PAGE_READ_EXECUTE = 0x20 @staticmethod def configure_ctypes(): try: # CloseHandle() ShellcodeExecute.CloseHandle = ctypes.windll.kernel32.CloseHandle ShellcodeExecute.CloseHandle.argtypes = [wt.HANDLE] ShellcodeExecute.CloseHandle.restype = wt.BOOL # CreateRemoteThread() ShellcodeExecute.CreateRemoteThread = ctypes.windll.kernel32.CreateRemoteThread ShellcodeExecute.CreateRemoteThread.argtypes = [ wt.HANDLE, wt.LPVOID, ctypes.c_size_t, wt.LPVOID, wt.LPVOID, wt.DWORD, wt.LPVOID] ShellcodeExecute.CreateRemoteThread.restype = wt.HANDLE # CreateThread() ShellcodeExecute.CreateThread = ctypes.windll.kernel32.CreateThread ShellcodeExecute.CreateThread.argtypes = [ wt.LPVOID, ctypes.c_size_t, wt.LPVOID, wt.LPVOID, wt.DWORD, wt.LPVOID ] # HeapCreate() ShellcodeExecute.HeapCreate = ctypes.windll.kernel32.HeapCreate ShellcodeExecute.HeapCreate.argtypes = [wt.DWORD, ctypes.c_size_t, ctypes.c_size_t] ShellcodeExecute.HeapCreate.restype = wt.HANDLE # HeapAlloc() ShellcodeExecute.HeapAlloc = ctypes.windll.kernel32.HeapAlloc ShellcodeExecute.HeapAlloc.argtypes = [wt.HANDLE, wt.DWORD, ctypes.c_size_t] ShellcodeExecute.HeapAlloc.restype = wt.LPVOID # OpenProcess() ShellcodeExecute.OpenProcess = ctypes.windll.kernel32.OpenProcess ShellcodeExecute.OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD] ShellcodeExecute.OpenProcess.restype = wt.HANDLE # RtlMoveMemory() ShellcodeExecute.RtlMoveMemory = ctypes.windll.kernel32.RtlMoveMemory ShellcodeExecute.RtlMoveMemory.argtypes = [wt.LPVOID, wt.LPVOID, ctypes.c_size_t] ShellcodeExecute.RtlMoveMemory.restype = wt.LPVOID # VirtualAllocEx() ShellcodeExecute.VirtualAllocEx = ctypes.windll.kernel32.VirtualAllocEx ShellcodeExecute.VirtualAllocEx.argtypes = [wt.HANDLE, wt.LPVOID, ctypes.c_size_t, wt.DWORD, wt.DWORD] ShellcodeExecute.VirtualAllocEx.restype = wt.LPVOID # VirtualProtectEx() ShellcodeExecute.VirtualProtectEx = ctypes.windll.kernel32.VirtualProtectEx ShellcodeExecute.VirtualProtectEx.argtypes = [ wt.HANDLE, wt.LPVOID, ctypes.c_size_t, wt.DWORD, wt.LPVOID] ShellcodeExecute.VirtualProtectEx.restype = wt.BOOL # WaitForSingleObject ShellcodeExecute.WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject ShellcodeExecute.WaitForSingleObject.argtypes = [wt.HANDLE, wt.DWORD] ShellcodeExecute.WaitForSingleObject.restype = wt.DWORD # WriteProcessMemory() ShellcodeExecute.WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory ShellcodeExecute.WriteProcessMemory.argtypes = [ wt.HANDLE, wt.LPVOID, wt.LPCVOID, ctypes.c_size_t, wt.LPVOID] ShellcodeExecute.WriteProcessMemory.restype = wt.BOOL # GetExitCodeThread() ShellcodeExecute.GetExitCodeThread = ctypes.windll.kernel32.GetExitCodeThread ShellcodeExecute.GetExitCodeThread.argtypes = [wt.HANDLE, wt.LPDWORD] ShellcodeExecute.GetExitCodeThread.restype = wt.BOOL logging.info('ctypes configured successfully.') except Exception as e: logging.critical(f'Error configuring ctypes: {e}') raise def __init__(self, shellcode=None): logging.info(''' _______________________________________________________ shellcode.py: Simple shellcode execution on Python3 process heap. Version 1.0 (c) Joff Thyer Black Hills Information Security LLC River Gum Security LLC _______________________________________________________ ''') try: if shellcode is None and platform.architecture()[0] == '64bit': logging.info('[*] 64-Bit Python Interpreter') self.shellcode = self.shellcode self.execute() except Exception as e: logging.critical(f'Initialization error: {e}') raise def execute(self): try: heap = self.HeapCreate( self.HEAP_CREATE_ENABLE_EXECUTE, len(self.shellcode), 0) if not heap: logging.error('HeapCreate failed.') return self.HeapAlloc(heap, self.HEAP_ZERO_MEMORY, len(self.shellcode)) logging.info('[*] HeapAlloc() Memory at: {:08X}'.format(heap)) self.RtlMoveMemory(heap, self.shellcode, len(self.shellcode)) logging.info('[*] Shellcode copied into memory.') thread = self.CreateThread(0, 0, heap, 0, 0, 0) if not thread: logging.error('CreateThread failed.') return logging.info('[*] CreateThread() in same process.') self.WaitForSingleObject(thread, 0xFFFFFFFF) # Check if the thread execution was successful exit_code = wt.DWORD() result = self.GetExitCodeThread(thread, ctypes.byref(exit_code)) if not result: logging.error('Failed to get thread exit code.') else: if exit_code.value == 0: logging.info('[*] Shellcode executed successfully.') else: logging.error(f'Shellcode execution failed with exit code: {exit_code.value}') except Exception as e: logging.critical(f'Execution error: {e}') raise if __name__ == '__main__': try: ShellcodeExecute.configure_ctypes() ShellcodeExecute() except Exception as e: logging.critical(f'Fatal error: {e}') raise
And I am also using
donut
to generate shellcode from my EXE.
Now when I use donut
with common executables (e.g. ./donut -i .\calc.exe
) and run the shellcode execution script it is working without problems.
However when I have an EXE compiled using PyInstaller --onefile
and generate it's shellcode using ./donut -i PYINSTALLER_EXECUTABLE.exe
, when I try to execute the generated shellcode using the shellcode execution script here is what happens:
2024-11-05 04:58:39,014 - INFO - Length of the shellcode is: 34565956
2024-11-05 04:58:39,014 - INFO - ctypes configured successfully.
2024-11-05 04:58:39,014 - INFO -
_______________________________________________________
shellcode.py: Simple shellcode execution on Python3
process heap.
Version 1.0 (c) Joff Thyer
Black Hills Information Security LLC
River Gum Security LLC
_______________________________________________________
2024-11-05 04:58:39,014 - INFO - [*] 64-Bit Python Interpreter
2024-11-05 04:58:39,014 - INFO - [*] HeapAlloc() Memory at: 1926D150000
2024-11-05 04:58:39,029 - INFO - [*] Shellcode copied into memory.
2024-11-05 04:58:39,029 - INFO - [*] CreateThread() in same process.
[PYI-17660:ERROR] Could not load PyInstaller's embedded PKG archive from the executable (C:\Users\zaid2\AppData\Local\Programs\Python\Python311\python.exe)
Even though when I run the standalone EXE normally it is working.
Why is this happening? And is there a way to correctly generate shellcode for PyInstaller executables?