• caglararli@hotmail.com
  • 05386281520

How to correctly generate shellcode from a PyInstaller executable?

Çağlar Arlı      -    40 Views

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?