Загрузчик, шеллкод, без рантайма...

Тема в разделе "VB", создана пользователем Thetrik, 9 дек 2016.

  1. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Всем привет! Когда-то давно я исследовал PE-формат, в особенности EXE. Я решил создать простой загрузчик исполняемых файлов специально для VB6-скомпилированных приложений. Этот загрузчик, по моим задумкам, должен загружать любое VB6-скомпилированное приложение из памяти, миную запись в файл. ВСЕ ЭТО БЫЛО СДЕЛАНО ДЛЯ ЭКСПЕРИМЕНТАЛЬНЫХ ЦЕЛЕЙ ДЛЯ ТОГО ЧТОБЫ ПРОВЕРИТЬ ТАКУЮ ВОЗМОЖНОСТЬ НА VB6. Из-за того что VB6-скомпилированные приложения не используют большинство PE-фичей это было довольно легкой задачей. Также большинство программистов говорят что любая VB6-скомпилированная программа неукоснительно связана с VB6-рантаймом (msvbvm60) и что такая программа не будет работать без рантайма и рантайм является довольно медленным. Сегодня я докажу что можно написать приложение абсолютно не использующее рантайм (хотя я такое уже делал в драйвере). Я думаю что это могло бы быть интересным для тех кто хочет изучить базовые принципы работы с PE файлами.
    Прежде чем мы начнем я бы хотел сказать пару слов о проектах. Эти проекты не тестировались достаточно хорошо, поэтому они могут содержать различные проблемы. Также загрузчик не поддерживает множество возможностей PE-файлов следовательно некоторые приложения могут не работать.
    Итак...
    Этот обзор включает три проекта:
    • Compiler - самый большой проект из всех. Он позволяет создавать лаунчер базируемый на загрузчике, пользовательских файлах, командах и манифесте;
    • Loader - простейший загрузчик который выполняет команды, распаковывает файлы и запускает EXE из памяти;
    • Patcher - маленькая утилита которая удаляет рантайм из VB6-скомпилированного приложения.
    Я буду называть EXE что содержит команды, файлы и исполнительный файл - инсталляцией. Главная идея этой задумки - это положить информацию об инсталляции в ресурсы загрузчика. Когда загрузчик загружается он считывает эту информацию и выполняет команды из ресурсов. Я решил использовать специальное хранилище для хранения файлов и EXE и отдельное хранилище для команд.
    Перое хранилище хранит все файлы которые будут распакованы и главный EXE который будет запускаться из памяти. Второе хранилище хранит команды которые будут переданы в функцию ShellExecuteEx после процесса того как процесс распаковки будет окончен.
    Загрузчик поддерживает следующие подставляемые символы (для путей):
    • <app> - путь, откуда запущен EXE;
    • <win> - системная директория;
    • <sys> - System32;
    • <drv> - системный диск;
    • <tmp> - временная директория;
    • <dtp> - рабочий стол.
     
  2. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Компилятор.
    [​IMG]
    Это приложение формирующее информацию для инсталляции и размещающее ее в ресурсах загрузчика. Вся информация хранится в файлах проекта. Вы можете сохранять и загружать проекты из файлов. Класс clsProject описывает такой проект. Компилятор содержит 3 секции: storage, execute, mainfest.
    Секция 'storage' позволяет добавлять файлы которые будут скопированы в момент запуска приложения. Каждая запись в списке имеет флаги: 'replace if exists', 'main executable', 'ignore error'. Если выбрана 'replace if exists' то файл будет скопирован из ресурсов даже если он есть на диске. Флаг 'main executable' может быть установлен только единственного исполняемого файла который будет запущен когда все операции будут исполнены. И наконец 'ignore error' просто заставляет игнорировать все ошибки и не выводить сообщения. Порядок расположения записей в списке соответствует порядку распаковки файлов, исключая главный исполняемый файл. Главный исполняемый файл не извлекается и запускается после всех операций. Класс clsStorage описывает данную секцию. Этот класс содержит коллекцию объектов класса
    clsStorageItem и дополнительные методы. Свойство MainExecutable определяет индекс главного исполняемого файла в хранилище. Когда этот параметр равен -1 значит главный исполняемый файл не задан. Класс clsStoragaItem описывает одну запись из списка хранилища, который содержит свойства определяющие поведение итема. Секция 'storage' полезна если вы хотите скопировать файлы на диск перед выполнением главного приложения (различные ресурсы/OCX/DLL и т.п.).
    Следующая секция называется 'execute'. Она содержит список выполняемых команд. Эти команды просто передаются в функцию ShellExecuteEx. Таким образом можно к примеру зарегистрировать библиотеки или сделать что-то еще. Каждый элемент этого списка имеет два свойства: путь и параметры. Стоит отметить что все команды выполняються синхронно в порядке заданным в списке. Также каждый элемент списка может иметь флаг 'ignore error' который предотвращает вывод каких-либо сообщений об ошибках. Секция 'execute' представлена двумя классами clsExecute and clsExecuteItem которые очень похожи на классы хранилища.
    Последняя секция - 'manifest'. Это просто текстовый файл который добавляеться в финальный файл в качестве манифеста. Для того чтобы включить манифест в EXE нужно просто выбрать флажок 'include manifest' во вкладке 'mainfest'. Это может быть полезно для использования библиотек без регистрации, визуальных стилей и т.п.
    Все классы ссылаються на объект проекта (clsProject) который управляет ими. Каждый класс который ссылается на проект может быть сохранен или заружен используя PropertyBag в качестве контейнера. Все ссылки сохраняються с относительными путями (как в .vbp файле) поэтому можно перемещать папку с проектом без проблем с путями. Для того чтобы транслировать из/то относительного/абсолютного пути я использовал функции PathRelativePathTo и PathCanonicalize.
    Итак, это была базовая информация о проекте Compiler. Сейчас я расскажу о процедуре компиляции. Как я уже сказал вся информация об инсталляции сохраняется в ресурсы загрузчика. Вначале на нужно определить формат данных:
    Код (Visual Basic):
    1. ' // Storage list item
    2. Private Type BinStorageListItem
    3.     ofstFileName        As Long            ' // Offset of file name
    4.     ofstDestPath        As Long            ' // Offset of file path
    5.     dwSizeOfFile        As Long            ' // Size of file
    6.     ofstBeginOfData     As Long            ' // Offset of beginning data
    7.     dwFlags             As FileFlags       ' // Flags
    8. End Type
    9. ' // Execute list item
    10. Private Type BinExecListItem
    11.     ofstFileName        As Long            ' // Offset of file name
    12.     ofstParameters      As Long            ' // Offset of parameters
    13.     dwFlags             As ExeFlags        ' // Flags
    14. End Type
    15. ' // Storage descriptor
    16. Private Type BinStorageList
    17.     dwSizeOfStructure   As Long            ' // Size of structure
    18.     iExecutableIndex    As Long            ' // Index of main executable
    19.     dwSizeOfItem        As Long            ' // Size of BinaryStorageItem structure
    20.     dwNumberOfItems     As Long            ' // Number of files in storage
    21. End Type
    22. ' // Execute list descriptor
    23. Private Type BinExecList
    24.     dwSizeOfStructure   As Long            ' // Size of structure
    25.     dwSizeOfItem        As Long            ' // Size of BinaryExecuteItem structure
    26.     dwNumberOfItems     As Long            ' // Number of items
    27. End Type
    28. ' // Base information about project
    29. Private Type BinProject
    30.     dwSizeOfStructure   As Long            ' // Size of structure
    31.     storageDescriptor   As BinStorageList  ' // Storage descriptor
    32.     execListDescriptor  As BinExecList     ' // Command descriptor
    33.     dwStringsTableLen   As Long            ' // Size of strings table
    34.     dwFileTableLen      As Long            ' // Size of data table
    35. End Type
    Структура BinProject размещается в начале ресурсов. Заметьте что проект сохраняется как RT_RCDATA с именем PROJECT. Поле dwSizeOfStructure определяет размер структуры BinProject. storageDescriptor и execListDescriptor определяют описатели хранилища и команд соответственно. Поле dwStringsTableLen показывает размер строковой таблицы. Строковая таблица содержит все имена и команды в формате UNICODE. Поле dwFileTableLen определяет размер всех данных в хранилище. И хранилище BinStorageList и списки команд BinExecList также имеют поля dwSizeOfItem и dwSizeOfStructure которые определяют размер структуры описателя и размер одного элемента в списке. Эти структуры также содержат поле dwNumberOfItems которое показывает количество элементов в списке. Поле iExecutableIndex содержит индекс исполняемого файла в хранилище. Общая структура показана на рисунке:
    [​IMG]
    Любой элемент может ссылаться на таблицу строк и таблицу файлов. Для этой цели используется смещение относительно начала таблицы. Все итемы расположены одна за другой. Теперь мы знаем внутренний формат проекта и можем поговорить о том как постороить загрузчик который будет содержать эти данные. Как я уже сказал мы сохраняем данные в ресурсы загрузчика. О самом загрузчике я расскажу позднее, а сейчас я хотел бы заметить одну важную особенность. Когда мы ложим данные проекта в EXE файл загрузчика то это не затрагивает другие данные в ресурсах. Для примера, если запустить такой EXE то информация хранящаяся в ресурсах внутреннего EXE не будет загружена. Тоже самое относится к иконкам и версии приложения. Для избежания данных проблем нужно скопировать все ресурсы из внутреннего EXE в загрузчик. WinAPI предоставляет набор функций для замены ресурсов. Для того чтобы получить список ресурсов нам нужно распарсить EXE файл и извлечь данные. Я написал функцию LoadResources которая извлекает все ресурсы EXE файла в массив.
     
  3. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    PE формат.
    Для того чтобы получить ресурсы из EXE файла, запустить EXE из памяти и хорошо разбираться в структуре EXE фала мы должны изучить PE (portable executable) формат. PE формат имеет довольно сложную структуру. Когда загрузчик запускает PE file (exe или dll) он делает довольно много работы. Каждый PE файл начинается со специальной структуры IMAGE_DOS_HEADER aka. DOS-заглушка. Поскольку и DOS и Windows приложения имеют расширение exe существует возможность запуска exe файла в DOS, но если попытаться сделать это в DOS то он выполнит это заглушку. Обычно в этом случае показывается сообщение: "This program cannot be run in DOS mode", но мы можем написать там любую программу:
    [​IMG]
    Код (Visual Basic):
    1. Type IMAGE_DOS_HEADER
    2.     e_magic                     As Integer
    3.     e_cblp                      As Integer
    4.     e_cp                        As Integer
    5.     e_crlc                      As Integer
    6.     e_cparhdr                   As Integer
    7.     e_minalloc                  As Integer
    8.     e_maxalloc                  As Integer
    9.     e_ss                        As Integer
    10.     e_sp                        As Integer
    11.     e_csum                      As Integer
    12.     e_ip                        As Integer
    13.     e_cs                        As Integer
    14.     e_lfarlc                    As Integer
    15.     e_ovno                      As Integer
    16.     e_res(0 To 3)               As Integer
    17.     e_oemid                     As Integer
    18.     e_oeminfo                   As Integer
    19.     e_res2(0 To 9)              As Integer
    20.     e_lfanew                    As Long
    21. End Type
    Но поскольку мы не пишем DOS программы для нас эта структура не важна. Нам интересно только поля e_magic и e_lfanew. Первое поле должно содержать сигнатуру 'MZ' aka. IMAGE_DOS_SIGNATURE а второе смещение до очень важной структуры IMAGE_NT_HEADERS:
    Код (Visual Basic):
    1. Type IMAGE_NT_HEADERS
    2.     Signature                       As Long
    3.     FileHeader                      As IMAGE_FILE_HEADER
    4.     OptionalHeader                  As IMAGE_OPTIONAL_HEADER
    5. End Type
    Первое поле этой структуры содержит сигнатуру 'PE\0\0' (aka. IMAGE_NT_SIGNATURE). Следующее поле описывает исполняемый файл и имеет следующий формат:
    Код (Visual Basic):
    1. Type IMAGE_FILE_HEADER
    2.     Machine                         As Integer
    3.     NumberOfSections                As Integer
    4.     TimeDateStamp                   As Long
    5.     PointerToSymbolTable            As Long
    6.     NumberOfSymbols                 As Long
    7.     SizeOfOptionalHeader            As Integer
    8.     Characteristics                 As Integer
    9. End Type
    Поле Machine определяет архитектуру процессора и должно иметь значение IMAGE_FILE_MACHINE_I386 в нашем случае. Поле NumberOfSections определяет количество секций в PE файле.
    • Любой EXE файл содержит секции. Каждая секция занимает место в адресном пространстве процесса и опционально в файле. Секция может содержать как код так и данные (инизиализированные или не), а также имеет имя. Наиболее распространенные имена: .text, .data, .rsrc. Обычно секция .text содержит код, .data инициализированные данные, а .rsrc - ресурсы. Можно изменять это поведение используя дериктивы линкера. Каждая секция имеет адрес называемый виртуальным адресом. В общем в PE формате существует несколько типов адресации. Первый - относительный виртуальный адрес (RVA). Из-за того что PE фал может быть загружен по любому адресу все ссылки внутри PE файла имеют относительную адресацию. RVA - это смещение относительно базового адреса (адреса первого байта PE-образа в памяти). Сумма RVA и базового адреса называется виртуальным адресом (VA). Также существует RAW-смещение которое показывает смещение относительно начала файла относительно RVA. Заметьте что RVA <> RAW. Когда модуль загружается каждая секция размещается по виртуальному адресу. Для примера модуль может иметь секцию что не имеет инициализированных данных. Такая секция не будет занимать место в PE-файле, но будет в памяти. Это очень важный момент поскольку мы будем работать с сырым EXE файлом.
    Поле TimeDateStamp содержит дату создания PE модуля в формате UTC. Поля PointerToSymbolTable and NumberOfSymbols содержат информацию о символах в PE файлах. В общем эти поля содержат нули, но эти поля всегда используються в объектных файлах (*.OBJ, *.LIB) для разрешения ссылок во время линковки а также содержат отладочную информацию для PE модуля. Следующее поле SizeOfOptionalHeader содержит размер структуры расположенной после IMAGE_FILE_HEADER так называемой IMAGE_OPTIONAL_HEADER которая всегда присутствует в PE файлах (хотя может отсутствовать в OBJ файлах). Эта структура являеться очень важной для загрузки PE модуля в память. Заметьте что эта структура различается в 32 битных и 64 битных PE-модулях. И наконец поле Characteristics содержит PE-аттрибуты.
    Структура IMAGE_OPTIONAL_HEADER имеет следующий формат:
    Код (Visual Basic):
    1. Type IMAGE_OPTIONAL_HEADER
    2.     Magic                           As Integer
    3.     MajorLinkerVersion              As Byte
    4.     MinorLinkerVersion              As Byte
    5.     SizeOfCode                      As Long
    6.     SizeOfInitializedData           As Long
    7.     SizeOfUnitializedData           As Long
    8.     AddressOfEntryPoint             As Long
    9.     BaseOfCode                      As Long
    10.     BaseOfData                      As Long
    11.     ImageBase                       As Long
    12.     SectionAlignment                As Long
    13.     FileAlignment                   As Long
    14.     MajorOperatingSystemVersion     As Integer
    15.     MinorOperatingSystemVersion     As Integer
    16.     MajorImageVersion               As Integer
    17.     MinorImageVersion               As Integer
    18.     MajorSubsystemVersion           As Integer
    19.     MinorSubsystemVersion           As Integer
    20.     W32VersionValue                 As Long
    21.     SizeOfImage                     As Long
    22.     SizeOfHeaders                   As Long
    23.     CheckSum                        As Long
    24.     SubSystem                       As Integer
    25.     DllCharacteristics              As Integer
    26.     SizeOfStackReserve              As Long
    27.     SizeOfStackCommit               As Long
    28.     SizeOfHeapReserve               As Long
    29.     SizeOfHeapCommit                As Long
    30.     LoaderFlags                     As Long
    31.     NumberOfRvaAndSizes             As Long
    32.     DataDirectory(15)               As IMAGE_DATA_DIRECTORY
    33. End Type
     
    Alexey нравится это.
  4. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Первое поле содержит тип образа (x86, x64 или ROM образ). Нас интересует только IMAGE_NT_OPTIONAL_HDR32_MAGIC который представляет собой 32 битное приложение. Следующие 2 поля не являются важными (они использовались на старых системах) и содержат 4. Следующая группа полей содержит размер всех секций с кодом, инициализированными данными и неинициализированными данными. Эти значения должны быть кратными значению SectionAlignment этой структуры (см. далее). Поле AddressOfEntryPoint является очень важным RVA значением которое определяет точку входа в программу. Мы будем использовать это поле когда загрузим PE образ в память для запуска кода. Следующим важным полем является ImageBase которое задает предпочитаемый виртуальный адрес загрузки модуля. Когда загрузчик начинает загружать модуль, то он старается сделать это по предпочитаемому виртуальному адресу (находящимся в ImageBase). Если этот адрес занят, то загрузчик проверяет поле Characteristics структуры IMAGE_FILE_HEADER. Если это поле содержит флаг IMAGE_FILE_RELOCS_STRIPPED то модуль не сможет быть загружен. Для того чтобы загрузить такие модули нам нужно добавить информацию о релокации которая позволит загрузчику настроить адреса внутри PE-образа если модуль не может загрузится по предпочитаемому базовому адресу. Мы будем использоват это поле вместе с SizeOfImage для того чтобы зарезервировать память под распакованный EXE. Поля SectionAlignment and FileAlignment содержат выравнивание секций в памяти и в файле соответственно. Изменяя файловое выравнивание можно уменьшить размер PE файла, но система может не загрузить данный PE файл. Выравнивание секций обычно равно размеру страницы в памяти. Поле SizeOfHeaders задает размер всех заголовков (DOS Заголовок, NT заголовок, заголовки секций) выровненное на FileAlignment. Значения SizeOfStackReserve и SizeOfStackCommit определяют общий размер стека и начальный размер стека. Тоже самое и для полей SizeOfHeapReserve и SizeOfHeapCommit, но для кучи. Поле NumberOfRvaAndSizes содержит количество элементов в массиве DataDirectory. Это поле всегда равно 16. Массив DataDirectory является также очень важным поскольку в нем содержатся каталоги данных которые содержат нужную информацию об импорте, экспорте, ресурсах, релокациях и т.д. Мы будем использовать только несколько элементов из этого каталога которые используются VB6 компилятором. Я расскажу о каталогах немного позже, давайте посмотрим что находится за каталогами. За каталогами содержаться описатели секций. Количество этих описателей, если вспомнить, мы получили из структуры IMAGE_FILE_HEADER. Рассмотрим формат заголовка секции:
    Код (Visual Basic):
    1. Type IMAGE_SECTION_HEADER
    2.     SectionName(7)              As Byte
    3.     VirtualSize                 As Long
    4.     VirtualAddress              As Long
    5.     SizeOfRawData               As Long
    6.     PointerToRawData            As Long
    7.     PointerToRelocations        As Long
    8.     PointerToLinenumbers        As Long
    9.     NumberOfRelocations         As Integer
    10.     NumberOfLinenumbers         As Integer
    11.     Characteristics             As Long
    12. End Type
    Первое поле содержит имя секции в формате UTF-8 c завершающим нуль-терминалом. Это имя ограничено 8-ю символами (если имя секции имеет размер 8 символов то нуль-терминатор игнорируется). COFF файл может иметь имя больше чем 8 символов в этом случае имя начинается с символа '/' за которым следует ASCII строка с десятичным значением смещения в строковой таблице (поле IMAGE_FILE_HEADER). PE файл не поддерживает длинные имена секций. Поля VirtualSize и VirtualAddress содержат размер секции в памяти и адрес (RVA). Поля SizeOfRawData и PointerToRawData содержат RAW адрес данных в файле (если секция содержит инициализированные данные). Это ключевой момент потому что мы можем вычислить RAW адрес с помощью относительного виртуального адреса используя информацию из заголовка секций. Я написал функцию для перевода RVA адресации в RAW смещение в файле:
    Код (Visual Basic):
    1. ' // RVA to RAW
    2. Function RVA2RAW( _
    3.                  ByVal rva As Long, _
    4.                  ByRef sec() As IMAGE_SECTION_HEADER) As Long
    5.     Dim index As Long
    6.  
    7.     For index = 0 To UBound(sec)
    8.      
    9.         If rva >= sec(index).VirtualAddress And _
    10.            rva < sec(index).VirtualAddress + sec(index).VirtualSize Then
    11.             RVA2RAW = sec(index).PointerToRawData + (rva - sec(index).VirtualAddress)
    12.             Exit Function
    13.         End If
    14.      
    15.     Next
    16.  
    17.     RVA2RAW = rva
    18.  
    19. End Function
    Эта функция перечисляет все секции и проверяет если переданный адрес находится в пределах секции. Следующие 5 полей используються только в COFF файлах и не важны в PE файлах. Поле Characteristics содержит атрибуты секции такие как права доступа к памяти и управление. Мы будем использовать это поле для защиты памяти exe файла в загрузчике.
    Давайте теперь вернемся к каталогам данных. Как мы видели существует 16 элементов в данном каталоге. Обычно PE файл не использует их все. Давайте рассмотрим структуру элемента каталога:
    Код (Visual Basic):
    1. Private Type IMAGE_DATA_DIRECTORY
    2.     VirtualAddress                  As Long
    3.     Size                            As Long
    4. End Type
    Эта структура содержит два поля. Первое поле содержит RVA адрес данных каталога, воторое - размер. Когда элемент каталога не представлен в PE файле то оба поля содержат нули. Вообще большинство VB6-компилируемых приложений имеют только 4 каталога: таблица импорта, таблица ресурсов, таблица связанного импорта и таблица адресов импорта (IAT). Сейчас мы рассмотрим таблицу ресурсов которая имеет индекс IMAGE_DIRECTORY_ENTRY_RESOURCE потому что мы работаем с этой информацией в проекте Compiler.
    Все ресурсы в EXE файле представлены в виде трехуровнего дерева. Первый уровень определяет тип ресурса (RT_BITMAP, RT_MANIFEST, RT_RCDATA, и т.д.), следующий - идентификатор ресурса и наконец третий - язык. В стандартном редакторе ресурсов VB Resource Editor можно изменять только первые 2 уровня. Все ресурсы размещаются таблице ресурсов расположенной в секции .rsrc EXE файла. Благодаря такой структуре мы можем изменять ресурсы даже в готовом EXE файле. Для того чтобы добраться до самих данных в секции ресурсов нам сначала нужно прочитать IMAGE_DIRECTORY_ENTRY_RESOURCE из опционального хидера. Поле VirtualAddress содержит RVA таблицы ресурсов которая имеет следующий формат:
    Код (Visual Basic):
    1. Type IMAGE_RESOURCE_DIRECTORY
    2.     Characteristics             As Long
    3.     TimeDateStamp               As Long
    4.     MajorVersion                As Integer
    5.     MinorVersion                As Integer
    6.     NumberOfNamedEntries        As Integer
    7.     NumberOfIdEntries           As Integer
    8. End Type
    Эта структура описывает все ресурсы в PE файле. Первые 4 поля не важны для нас; поле NumberOfNamedEntries и NumberOfIdEntries содержат количество именованных записей и записей с числовыми идентификаторами соответственно. Для примера, когда мы добавляем картинку в стандартном редакторе это добавит запись с числовым идентификатором равным 2 (RT_BITMAP). Сами записи расположены сразу после IMAGE_RESOURCE_DIRECTORY и имеют следующую структуру:
    Код (Visual Basic):
    1. Type IMAGE_RESOURCE_DIRECTORY_ENTRY
    2.     NameId                      As Long
    3.     OffsetToData                As Long
    4. End Type
    Первое поле этой структуры определяет является ли это именованной запись либо это запись с числовым идентификатором в зависимости от старшего бита. Если этот бит установлен то остальные биты определяют смещение от начала ресурсов к структуре IMAGE_RESOURCE_DIR_STRING_U которая имет следующий формат:
    Код (Visual Basic):
    1. Type IMAGE_RESOURCE_DIR_STRING_U
    2.     Length                      As Integer
    3.     NameString                  As String
    4. End Type
    Заметьте что это не правильная VB-структура и показана для наглядности. Первые два байта являются беззнаковым целым которые показывают длину строки в формате UNICODE (в символах) которая следует за ними. Таким образом для того чтобы получить строку нам нужно прочитать первые два байта с размером, выделить память для строки согласно этого размера и прочитать данные в строковую переменную. Напротив, если старший бит поля NameId сброшен то оно содержит числовой идентификатор ресурса (RT_BITMAP в примере). Поле OffsetToData имеет также двойную интерпретацию. Если старший бит установлен то это смещение (от начала ресурсов) до следующего уровня дерева ресурсов, т.е. до структуры IMAGE_RESOURCE_DIRECTORY. Иначе - это смещение до структуры IMAGE_RESOURCE_DATA_ENTRY:
    Код (Visual Basic):
    1. Type IMAGE_RESOURCE_DATA_ENTRY
    2.     OffsetToData                As Long
    3.     Size                        As Long
    4.     CodePage                    As Long
    5.     Reserved                    As Long
    6. End Type
    Наиболее важными для нас являются поля OffsetToData and Size которые содержат RVA и размер сырых данных ресурса. Теперь мы можем извлечь все данные из ресурсов любого PE файла.
     
    Последнее редактирование: 9 дек 2016
  5. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Компиляция.
    Итак, когда мы начинаем компиляцию проекта то вызывается метод Compile объекта класса clsProject. Вначале упаковываются все элементы хранилища и команд в бинарный формат (BinProject, BinStorageListItem, и т.д.) и формируются таблица строк и файловая таблица. Строковая таблица сохраняется как набор строк разделенных нуль-терминалом. Я использую специальный класс clsStream для безопасной работы с бинарными данными. Этот класс позволяет читать и писать любые данные или потоки в двоичный буфер, сжимать буфер. Я использую функцию RtlCompressBuffer для сжатия потока которая использует LZ-сжатие. После упаковки и сжатия проверяется выходной формат файла. Поддерживаются 2 типа файлов: бинарный (сырые данные проекта) и исполняемый (загрузчик). Двоичный формат не интересен поэтому мы будем рассматривать исполняемый формат. Вначале извлекаются все ресурсы из главного исполняемого файла в трехуровневый каталог. Эта операция выполняется с помощью функции ExtractResorces. Имена-идентификаторы сохраняются в строковом виде с префиксом '#'. Потом клонируется шаблон загрузчика в результирующий файл, начинается процесс модификации ресурсов в EXE файле используя функцию BeginUpdateResource. После этого последовательно копируются все извлеченные ресурсы (UpdateResource), двоичный проект и манифест (если нужно) в результирующий файл и применяются изменения функцией EndUpdateResource. Опять повторюсь, бинарный проект сохраняется с именем PROJECT и имеет тип RT_DATA. В общем все.

    Загрузчик.
    Итак. я думаю это наиболее интересная часть. Итак, нам нужно избегать использование рантайма. Как этого добится? Я дам некоторые правила:
    • Установить в качестве стартовой функции пользовательскую функцию;
    • Избегать любых объектов и классов в проекте;
    • Избегать непосредственных массивов. Массивы фиксированного размера в пользовательских типах не запрещены;
    • Избегать строковых переменных а также Variant/Object переменных. В некоторых случаях Currency/Date;
    • Избегать API функции задекларированые с помощью ключевого слова Declare;
    • Избегать VarPtr/StrPtr/ObjPtr и некоторые стандартные функции;
    • ...
    • ...
    Это неполный список ограничений, а во время выполнения шеллкода добавляются дополнительные ограничения.
    Итак, начнем. Для того чтобы избежать использования строковых переменных я храню все строковые переменные как Long указатели на строки. Существует проблема с загрузкой строк поскольку мы не можем обращаться к любой строке чтобы загрузить ее. Я решил использовать ресурсы в качестве хранилища строк и загружать их по числовому идентификатору. Таким образом мы можем хранить указатель в переменной Long без обращения к рантайму. Я использовал TLB (библиотеку типов) для всех API функций без атрибута usesgetlasterror чтобы избежать объявление через Declare. Для установки стартовой функции я использую опции линкера. Стартовая функция в загрузчике - Main. Обратите внимание, если в IDE выбрать стартовую функцию Main на самом деле это не будет стартовой функцией приложения потому что VB6-скомпилированное приложение начинается с функции __vbaS которая вызывает функцию ThunRTMain из рантайма, которая инициализирует рантайм и поток.
    Загрузчик содержит три модуля:
    • modMain - стартовая функция и работа с хранилищем;
    • modConstants - работа со строковыми константами;
    • modLoader - загрузчик EXE файла.
    Когда загрузчик запустился выполняется функция Main:
    Код (Visual Basic):
    1. ' // Startup subroutine
    2. Sub Main()
    3.     ' // Load constants
    4.     If Not LoadConstants Then
    5.         MessageBox 0, GetString(MID_ERRORLOADINGCONST), 0, MB_ICONERROR Or MB_SYSTEMMODAL
    6.         GoTo EndOfProcess
    7.     End If
    8.    
    9.     ' // Load project
    10.     If Not ReadProject Then
    11.         MessageBox 0, GetString(MID_ERRORREADINGPROJECT), 0, MB_ICONERROR Or MB_SYSTEMMODAL
    12.         GoTo EndOfProcess
    13.     End If
    14.    
    15.     ' // Copying from storage
    16.     If Not CopyProcess Then GoTo EndOfProcess
    17.    
    18.     ' // Execution process
    19.     If Not ExecuteProcess Then GoTo EndOfProcess
    20.    
    21.     ' // If main executable is not presented exit
    22.     If ProjectDesc.storageDescriptor.iExecutableIndex = -1 Then GoTo EndOfProcess
    23.    
    24.     ' // Run exe from memory
    25.     If Not RunProcess Then
    26.         ' // Error occrurs
    27.         MessageBox 0, GetString(MID_ERRORSTARTUPEXE), 0, MB_ICONERROR Or MB_SYSTEMMODAL
    28.     End If
    29.    
    30. EndOfProcess:
    31.    
    32.     If pProjectData Then
    33.         HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
    34.     End If
    35.    
    36.     ExitProcess 0
    37.    
    38. End Sub
    Вначале вызывается функция LoadConstants для того чтобы загрузить все необходимые константы из ресурсов:
    Код (Visual Basic):
    1. ' // modConstants.bas - main module for loading constants
    2. ' // © Krivous Anatoly Anatolevich (The trick), 2016
    3. Option Explicit
    4. Public Enum MessagesID
    5.     MID_ERRORLOADINGCONST = 100     ' // Errors
    6.     MID_ERRORREADINGPROJECT = 101   '
    7.     MID_ERRORCOPYINGFILE = 102      '
    8.     MID_ERRORWIN32 = 103            '
    9.     MID_ERROREXECUTELINE = 104      '
    10.     MID_ERRORSTARTUPEXE = 105       '
    11.     PROJECT = 200                   ' // Project resource ID
    12.     API_LIB_KERNEL32 = 300          ' // Library names
    13.     API_LIB_NTDLL = 350             '
    14.     API_LIB_USER32 = 400            '
    15.     MSG_LOADER_ERROR = 500
    16. End Enum
    17. ' // Paths
    18. Public pAppPath  As Long            ' // Path to application
    19. Public pSysPath  As Long            ' // Path to System32
    20. Public pTmpPath  As Long            ' // Path to Temp
    21. Public pWinPath  As Long            ' // Path to Windows
    22. Public pDrvPath  As Long            ' // Path to system drive
    23. Public pDtpPath  As Long            ' // Path to desktop
    24. ' // Substitution constants
    25. Public pAppRepl  As Long
    26. Public pSysRepl  As Long
    27. Public pTmpRepl  As Long
    28. Public pWinRepl  As Long
    29. Public pDrvRepl  As Long
    30. Public pDtpRepl  As Long
    31. Public pStrNull  As Long            ' // \0
    32. Public hInstance    As Long         ' // Base address
    33. Public lpCmdLine    As Long         ' // Command line
    34. Public SI           As STARTUPINFO  ' // Startup parameters
    35. Public LCID         As Long         ' // LCID
    36. ' // Load constants
    37. Function LoadConstants() As Boolean
    38.     Dim lSize   As Long
    39.     Dim pBuf    As Long
    40.     Dim index   As Long
    41.     Dim ctl     As tagINITCOMMONCONTROLSEX
    42.    
    43.     ' // Load windows classes
    44.     ctl.dwSize = Len(ctl)
    45.     ctl.dwICC = &H3FFF&
    46.     InitCommonControlsEx ctl
    47.    
    48.     ' // Get startup parameters
    49.     GetStartupInfo SI
    50.    
    51.     ' // Get command line
    52.     lpCmdLine = GetCommandLine()
    53.    
    54.     ' // Get base address
    55.     hInstance = GetModuleHandle(ByVal 0&)
    56.    
    57.     ' // Get LCID
    58.     LCID = GetUserDefaultLCID()
    59.    
    60.     ' // Alloc memory for strings
    61.     pBuf = SysAllocStringLen(0, MAX_PATH)
    62.     If pBuf = 0 Then Exit Function
    63.    
    64.     ' // Get path to process file name
    65.     If GetModuleFileName(hInstance, pBuf, MAX_PATH) = 0 Then GoTo CleanUp
    66.    
    67.     ' // Leave only directory
    68.     PathRemoveFileSpec pBuf
    69.    
    70.     ' // Save path
    71.     pAppPath = SysAllocString(pBuf)
    72.    
    73.     ' // Get Windows folder
    74.     If GetWindowsDirectory(pBuf, MAX_PATH) = 0 Then GoTo CleanUp
    75.     pWinPath = SysAllocString(pBuf)
    76.    
    77.     ' // Get System32 folder
    78.     If GetSystemDirectory(pBuf, MAX_PATH) = 0 Then GoTo CleanUp
    79.     pSysPath = SysAllocString(pBuf)
    80.    
    81.     ' // Get Temp directory
    82.     If GetTempPath(MAX_PATH, pBuf) = 0 Then GoTo CleanUp
    83.     pTmpPath = SysAllocString(pBuf)
    84.    
    85.     ' // Get system drive
    86.     PathStripToRoot pBuf
    87.     pDrvPath = SysAllocString(pBuf)
    88.    
    89.     ' // Get desktop path
    90.     If SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, 0, SHGFP_TYPE_CURRENT, pBuf) Then GoTo CleanUp
    91.     pDtpPath = SysAllocString(pBuf)
    92.    
    93.     ' // Load wildcards
    94.     For index = 1 To 6
    95.         If LoadString(hInstance, index, pBuf, MAX_PATH) = 0 Then GoTo CleanUp
    96.         Select Case index
    97.         Case 1: pAppRepl = SysAllocString(pBuf)
    98.         Case 2: pSysRepl = SysAllocString(pBuf)
    99.         Case 3: pTmpRepl = SysAllocString(pBuf)
    100.         Case 4: pWinRepl = SysAllocString(pBuf)
    101.         Case 5: pDrvRepl = SysAllocString(pBuf)
    102.         Case 6: pDtpRepl = SysAllocString(pBuf)
    103.         End Select
    104.     Next
    105.    
    106.     ' // vbNullChar
    107.     pStrNull = SysAllocStringLen(0, 0)
    108.     ' // Success
    109.     LoadConstants = True
    110.    
    111. CleanUp:
    112.    
    113.     If pBuf Then SysFreeString pBuf
    114.    
    115. End Function
    116. ' // Obtain string from resource (it should be less or equal MAX_PATH)
    117. Public Function GetString( _
    118.                 ByVal ID As MessagesID) As Long
    119.                
    120.     GetString = SysAllocStringLen(0, MAX_PATH)
    121.    
    122.     If GetString Then
    123.    
    124.         If LoadString(hInstance, ID, GetString, MAX_PATH) = 0 Then SysFreeString GetString: GetString = 0: Exit Function
    125.         If SysReAllocString(GetString, GetString) = 0 Then SysFreeString GetString: GetString = 0: Exit Function
    126.        
    127.     End If
    128.    
    129. End Function
     
  6. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Функция LoadConstants загружает все необходимые переменные и строки (hInstance, LCID, командная строка, подстановочные символы, пути по умолчанию, и т.д.). Все строки сохраняются в формате UNICODE-BSTR. Функция GetString загружает строку из ресурсов по ее идентификатору. Перечисление MessagesID содержит некоторые строковые идентификаторы нужные для работы программы (сообщения об ошибках, имена библиотек, и.т.д.). Когда все константы загрузятся вызывается функция ReadProject которая загружает проект:
    Код (Visual Basic):
    1. ' // Load project
    2. Function ReadProject() As Boolean
    3.     Dim hResource       As Long:                Dim hMememory       As Long
    4.     Dim lResSize        As Long:                Dim pRawData        As Long
    5.     Dim status          As Long:                Dim pUncompressed   As Long
    6.     Dim lUncompressSize As Long:                Dim lResultSize     As Long
    7.     Dim tmpStorageItem  As BinStorageListItem:  Dim tmpExecuteItem  As BinExecListItem
    8.     Dim pLocalBuffer    As Long
    9.    
    10.     ' // Load resource
    11.     hResource = FindResource(hInstance, GetString(PROJECT), RT_RCDATA)
    12.     If hResource = 0 Then GoTo CleanUp
    13.    
    14.     hMememory = LoadResource(hInstance, hResource)
    15.     If hMememory = 0 Then GoTo CleanUp
    16.    
    17.     lResSize = SizeofResource(hInstance, hResource)
    18.     If lResSize = 0 Then GoTo CleanUp
    19.    
    20.     pRawData = LockResource(hMememory)
    21.     If pRawData = 0 Then GoTo CleanUp
    22.    
    23.     pLocalBuffer = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lResSize)
    24.     If pLocalBuffer = 0 Then GoTo CleanUp
    25.    
    26.     ' // Copy to local buffer
    27.     CopyMemory ByVal pLocalBuffer, ByVal pRawData, lResSize
    28.    
    29.     ' // Set default size
    30.     lUncompressSize = lResSize * 2
    31.    
    32.     ' // Do decompress...
    33.     Do
    34.        
    35.         If pUncompressed Then
    36.             pUncompressed = HeapReAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, ByVal pUncompressed, lUncompressSize)
    37.         Else
    38.             pUncompressed = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lUncompressSize)
    39.         End If
    40.        
    41.         status = RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, _
    42.                                      ByVal pUncompressed, lUncompressSize, _
    43.                                      ByVal pLocalBuffer, lResSize, lResultSize)
    44.        
    45.         lUncompressSize = lUncompressSize * 2
    46.        
    47.     Loop While status = STATUS_BAD_COMPRESSION_BUFFER
    48.    
    49.     pProjectData = pUncompressed
    50.    
    51.     If status Then GoTo CleanUp
    52.     ' // Validation check
    53.     If lResultSize < LenB(ProjectDesc) Then GoTo CleanUp
    54.    
    55.     ' // Copy descriptor
    56.     CopyMemory ProjectDesc, ByVal pProjectData, LenB(ProjectDesc)
    57.    
    58.     ' // Check all members
    59.     If ProjectDesc.dwSizeOfStructure <> Len(ProjectDesc) Then GoTo CleanUp
    60.     If ProjectDesc.storageDescriptor.dwSizeOfStructure <> Len(ProjectDesc.storageDescriptor) Then GoTo CleanUp
    61.     If ProjectDesc.storageDescriptor.dwSizeOfItem <> Len(tmpStorageItem) Then GoTo CleanUp
    62.     If ProjectDesc.execListDescriptor.dwSizeOfStructure <> Len(ProjectDesc.execListDescriptor) Then GoTo CleanUp
    63.     If ProjectDesc.execListDescriptor.dwSizeOfItem <> Len(tmpExecuteItem) Then GoTo CleanUp
    64.    
    65.     ' // Initialize pointers
    66.     pStoragesTable = pProjectData + ProjectDesc.dwSizeOfStructure
    67.     pExecutesTable = pStoragesTable + ProjectDesc.storageDescriptor.dwSizeOfItem * ProjectDesc.storageDescriptor.dwNumberOfItems
    68.     pFilesTable = pExecutesTable + ProjectDesc.execListDescriptor.dwSizeOfItem * ProjectDesc.execListDescriptor.dwNumberOfItems
    69.     pStringsTable = pFilesTable + ProjectDesc.dwFileTableLen
    70.    
    71.     ' // Check size
    72.     If (pStringsTable + ProjectDesc.dwStringsTableLen - pProjectData) <> lResultSize Then GoTo CleanUp
    73.    
    74.     ' // Success
    75.     ReadProject = True
    76.    
    77. CleanUp:
    78.    
    79.     If pLocalBuffer Then HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pLocalBuffer
    80.    
    81.     If Not ReadProject And pProjectData Then
    82.         HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
    83.     End If
    84.    
    85. End Function
    Как можно увидеть я использую кучу процесса вместо массивов. Вначале загружается ресурс с проектом - PROJECT и копируется в кучу, затем производится декомпрессия используя функцию RtlDecompressBuffer. Эта функция не возвращает необходимый размер буфера поэтому мы пытаемся распаковать буфер увеличивая выходной размер буфера пока декомпрессия не будет успешно выполнена. После декомпрессии проверяются все параметры и инициализируются глобальные указатели проекта.
    Если проект успешно загружен то вызывается функция CopyProcess которая распаковывает все файлы из хранилища, согласно данным проекта:
    Код (Visual Basic):
    1. ' // Copying process
    2. Function CopyProcess() As Boolean
    3.     Dim bItem       As BinStorageListItem:  Dim index       As Long
    4.     Dim pPath       As Long:                Dim dwWritten   As Long
    5.     Dim msg         As Long:                Dim lStep       As Long
    6.     Dim isError     As Boolean:             Dim pItem       As Long
    7.     Dim pErrMsg     As Long:                Dim pTempString As Long
    8.    
    9.     ' // Set pointer
    10.     pItem = pStoragesTable
    11.    
    12.     ' // Go thru file list
    13.     For index = 0 To ProjectDesc.storageDescriptor.dwNumberOfItems - 1
    14.         ' // Copy file descriptor
    15.         CopyMemory bItem, ByVal pItem, Len(bItem)
    16.        
    17.         ' // Next item
    18.         pItem = pItem + ProjectDesc.storageDescriptor.dwSizeOfItem
    19.        
    20.         ' // If it is not main executable
    21.         If index <> ProjectDesc.storageDescriptor.iExecutableIndex Then
    22.        
    23.             ' // Normalize path
    24.             pPath = NormalizePath(pStringsTable + bItem.ofstDestPath, pStringsTable + bItem.ofstFileName)
    25.            
    26.             ' // Error occurs
    27.             If pPath = 0 Then
    28.            
    29.                 pErrMsg = GetString(MID_ERRORWIN32)
    30.                 MessageBox 0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL
    31.                 GoTo CleanUp
    32.                
    33.             Else
    34.                 Dim hFile   As Long
    35.                 Dim disp    As CREATIONDISPOSITION
    36.                
    37.                 ' // Set overwrite flags
    38.                 If bItem.dwFlags And FF_REPLACEONEXIST Then disp = CREATE_ALWAYS Else disp = CREATE_NEW
    39.                
    40.                 ' // Set number of subroutine
    41.                 lStep = 0
    42.                
    43.                 ' // Run subroutines
    44.                 Do
    45.                     ' // Disable error flag
    46.                     isError = False
    47.                    
    48.                     ' // Free string
    49.                     If pErrMsg Then SysFreeString pErrMsg: pErrMsg = 0
    50.                    
    51.                     ' // Choose subroutine
    52.                     Select Case lStep
    53.                     Case 0  ' // 0. Create folder
    54.                    
    55.                         If Not CreateSubdirectories(pPath) Then isError = True
    56.                        
    57.                     Case 1  ' // 1. Create file
    58.                    
    59.                         hFile = CreateFile(pPath, FILE_GENERIC_WRITE, 0, ByVal 0&, disp, FILE_ATTRIBUTE_NORMAL, 0)
    60.                         If hFile = INVALID_HANDLE_VALUE Then
    61.                             If GetLastError = ERROR_FILE_EXISTS Then Exit Do
    62.                             isError = True
    63.                         End If
    64.                        
    65.                     Case 2  ' // 2. Copy data to file
    66.                    
    67.                         If WriteFile(hFile, ByVal pFilesTable + bItem.ofstBeginOfData, _
    68.                                      bItem.dwSizeOfFile, dwWritten, ByVal 0&) = 0 Then isError = True
    69.                                      
    70.                         If dwWritten <> bItem.dwSizeOfFile Then
    71.                             isError = True
    72.                         Else
    73.                             CloseHandle hFile: hFile = INVALID_HANDLE_VALUE
    74.                         End If
    75.                        
    76.                     End Select
    77.                    
    78.                     ' // If error occurs show notification (retry, abort, ignore)
    79.                     If isError Then
    80.                    
    81.                         ' // Ignore error
    82.                         If bItem.dwFlags And FF_IGNOREERROR Then Exit Do
    83.                         pTempString = GetString(MID_ERRORCOPYINGFILE)
    84.                         pErrMsg = StrCat(pTempString, pPath)
    85.                        
    86.                         ' // Cleaning
    87.                         SysFreeString pTempString: pTempString = 0
    88.                        
    89.                         Select Case MessageBox(0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL Or MB_CANCELTRYCONTINUE)
    90.                         Case MESSAGEBOXRETURN.IDCONTINUE: Exit Do
    91.                         Case MESSAGEBOXRETURN.IDTRYAGAIN
    92.                         Case Else:  GoTo CleanUp
    93.                         End Select
    94.                        
    95.                     Else: lStep = lStep + 1
    96.                     End If
    97.                    
    98.                 Loop While lStep <= 2
    99.                        
    100.                 If hFile <> INVALID_HANDLE_VALUE Then
    101.                     CloseHandle hFile: hFile = INVALID_HANDLE_VALUE
    102.                 End If
    103.                
    104.                 ' // Cleaning
    105.                 SysFreeString pPath: pPath = 0
    106.                
    107.             End If
    108.            
    109.         End If
    110.        
    111.     Next
    112.    
    113.     ' // Success
    114.     CopyProcess = True
    115.    
    116. CleanUp:
    117.    
    118.     If pTempString Then SysFreeString pTempString
    119.     If pErrMsg Then SysFreeString pErrMsg
    120.     If pPath Then SysFreeString pPath
    121.    
    122.     If hFile <> INVALID_HANDLE_VALUE Then
    123.         CloseHandle hFile
    124.         hFile = INVALID_HANDLE_VALUE
    125.     End If
    126.    
    127. End Function
    128.  
     
  7. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Эта процедура проходит по всем элементам хранилища и распаковывает их одна за одной исключая главный исполняемый файл. Функция NormalizePath заменяет подстановочные знаки на реальные пути. Также существует функция CreateSubdirectories которая создает промежуточные директории (если необходимо) по переданному в качестве параметра пути. Затем вызывается функция CreateFile для создания файла затем через WriteFile данные пишутся в файл. Если происходит ошибка то выводится стандартное сообщение с предложением повторить, отменить или игнорировать.
    Код (Visual Basic):
    1. ' // Create all subdirectories by path
    2. Function CreateSubdirectories( _
    3.                 ByVal pPath As Long) As Boolean
    4.     Dim pComponent As Long
    5.     Dim tChar      As Integer
    6.    
    7.     ' // Pointer to first char
    8.     pComponent = pPath
    9.    
    10.     ' // Go thru path components
    11.     Do
    12.    
    13.         ' // Get next component
    14.         pComponent = PathFindNextComponent(pComponent)
    15.        
    16.         ' // Check if end of line
    17.         CopyMemory tChar, ByVal pComponent, 2
    18.         If tChar = 0 Then Exit Do
    19.        
    20.         ' // Write null-terminator
    21.         CopyMemory ByVal pComponent - 2, 0, 2
    22.        
    23.         ' // Check if path exists
    24.         If PathIsDirectory(pPath) = 0 Then
    25.        
    26.             ' // Create folder
    27.             If CreateDirectory(pPath, ByVal 0&) = 0 Then
    28.                 ' // Error
    29.                 CopyMemory ByVal pComponent - 2, &H5C, 2
    30.                 Exit Function
    31.             End If
    32.            
    33.         End If
    34.        
    35.         ' // Restore path delimiter
    36.         CopyMemory ByVal pComponent - 2, &H5C, 2
    37.        
    38.     Loop
    39.    
    40.     ' // Success
    41.     CreateSubdirectories = True
    42.    
    43. End Function
    44. ' // Get normalize path (replace wildcards, append file name)
    45. Function NormalizePath( _
    46.                 ByVal pPath As Long, _
    47.                 ByVal pTitle As Long) As Long
    48.     Dim lPathLen    As Long:    Dim lRelacerLen As Long
    49.     Dim lTitleLen   As Long:    Dim pRelacer    As Long
    50.     Dim lTotalLen   As Long:    Dim lPtr        As Long
    51.     Dim pTempString As Long:    Dim pRetString  As Long
    52.    
    53.     ' // Determine wildcard
    54.     Select Case True
    55.     Case IntlStrEqWorker(0, pPath, pAppRepl, 5): pRelacer = pAppPath
    56.     Case IntlStrEqWorker(0, pPath, pSysRepl, 5): pRelacer = pSysPath
    57.     Case IntlStrEqWorker(0, pPath, pTmpRepl, 5): pRelacer = pTmpPath
    58.     Case IntlStrEqWorker(0, pPath, pWinRepl, 5): pRelacer = pWinPath
    59.     Case IntlStrEqWorker(0, pPath, pDrvRepl, 5): pRelacer = pDrvPath
    60.     Case IntlStrEqWorker(0, pPath, pDtpRepl, 5): pRelacer = pDtpPath
    61.     Case Else: pRelacer = pStrNull
    62.     End Select
    63.    
    64.     ' // Get string size
    65.     lPathLen = lstrlen(ByVal pPath)
    66.     lRelacerLen = lstrlen(ByVal pRelacer)
    67.    
    68.     ' // Skip wildcard
    69.     If lRelacerLen Then
    70.         pPath = pPath + 5 * 2
    71.         lPathLen = lPathLen - 5
    72.     End If
    73.    
    74.     If pTitle Then lTitleLen = lstrlen(ByVal pTitle)
    75.    
    76.     ' // Get length all strings
    77.     lTotalLen = lPathLen + lRelacerLen + lTitleLen
    78.    
    79.     ' // Check overflow (it should be les or equal MAX_PATH)
    80.     If lTotalLen > MAX_PATH Then Exit Function
    81.    
    82.     ' // Create string
    83.     pTempString = SysAllocStringLen(0, MAX_PATH)
    84.     If pTempString = 0 Then Exit Function
    85.    
    86.     ' // Copy
    87.     lstrcpyn ByVal pTempString, ByVal pRelacer, lRelacerLen + 1
    88.     lstrcat ByVal pTempString, ByVal pPath
    89.     ' // If title is presented append
    90.     If pTitle Then
    91.         ' // Error
    92.         If PathAddBackslash(pTempString) = 0 Then GoTo CleanUp
    93.         ' // Copy file name
    94.         lstrcat ByVal pTempString, ByVal pTitle
    95.        
    96.     End If
    97.    
    98.     ' // Alloc memory for translation relative path to absolute
    99.     pRetString = SysAllocStringLen(0, MAX_PATH)
    100.     If pRetString = 0 Then GoTo CleanUp
    101.    
    102.     ' // Normalize
    103.     If PathCanonicalize(pRetString, pTempString) = 0 Then GoTo CleanUp
    104.    
    105.     NormalizePath = pRetString
    106.    
    107. CleanUp:
    108.    
    109.     If pTempString Then SysFreeString pTempString
    110.     If pRetString <> 0 And NormalizePath = 0 Then SysFreeString pRetString
    111.    
    112. End Function
    113. ' // Concatenation strings
    114. Function StrCat( _
    115.                 ByVal pStringDest As Long, _
    116.                 ByVal pStringAppended As Long) As Long
    117.     Dim l1 As Long, l2 As Long
    118.    
    119.     l1 = lstrlen(ByVal pStringDest): l2 = lstrlen(ByVal pStringAppended)
    120.     StrCat = SysAllocStringLen(0, l1 + l2)
    121.    
    122.     If StrCat = 0 Then Exit Function
    123.    
    124.     lstrcpyn ByVal StrCat, ByVal pStringDest, l1 + 1
    125.     lstrcat ByVal StrCat, ByVal pStringAppended
    126.    
    127. End Function
    После извлечения файлов вызывается функция ExecuteProcess которая запускает выполнение команд используя функцию ShellExecuteEx:
    Код (Visual Basic):
    1. ' // Execution command process
    2. Function ExecuteProcess() As Boolean
    3.     Dim index       As Long:                Dim bItem       As BinExecListItem
    4.     Dim pPath       As Long:                Dim pErrMsg     As Long
    5.     Dim shInfo      As SHELLEXECUTEINFO:    Dim pTempString As Long
    6.     Dim pItem       As Long:                Dim status      As Long
    7.     ' // Set pointer and size
    8.     shInfo.cbSize = Len(shInfo)
    9.     pItem = pExecutesTable
    10.    
    11.     ' // Go thru all items
    12.     For index = 0 To ProjectDesc.execListDescriptor.dwNumberOfItems - 1
    13.    
    14.         ' // Copy item
    15.         CopyMemory bItem, ByVal pItem, ProjectDesc.execListDescriptor.dwSizeOfItem
    16.        
    17.         ' // Set pointer to next item
    18.         pItem = pItem + ProjectDesc.execListDescriptor.dwSizeOfItem
    19.        
    20.         ' // Normalize path
    21.         pPath = NormalizePath(pStringsTable + bItem.ofstFileName, 0)
    22.        
    23.         ' // Fill SHELLEXECUTEINFO
    24.         shInfo.lpFile = pPath
    25.         shInfo.lpParameters = pStringsTable + bItem.ofstParameters
    26.         shInfo.fMask = SEE_MASK_NOCLOSEPROCESS Or SEE_MASK_FLAG_NO_UI
    27.         shInfo.nShow = SW_SHOWDEFAULT
    28.        
    29.         ' // Performing...
    30.         status = ShellExecuteEx(shInfo)
    31.        
    32.         ' // If error occurs show notification (retry, abort, ignore)
    33.         Do Until status
    34.            
    35.             If pErrMsg Then SysFreeString pErrMsg: pErrMsg = 0
    36.            
    37.             ' // Ignore error
    38.             If bItem.dwFlags And EF_IGNOREERROR Then
    39.                 Exit Do
    40.             End If
    41.                        
    42.             pTempString = GetString(MID_ERROREXECUTELINE)
    43.             pErrMsg = StrCat(pTempString, pPath)
    44.            
    45.             SysFreeString pTempString: pTempString = 0
    46.            
    47.             Select Case MessageBox(0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL Or MB_CANCELTRYCONTINUE)
    48.             Case MESSAGEBOXRETURN.IDCONTINUE: Exit Do
    49.             Case MESSAGEBOXRETURN.IDTRYAGAIN
    50.             Case Else: GoTo CleanUp
    51.             End Select
    52.             status = ShellExecuteEx(shInfo)
    53.            
    54.         Loop
    55.        
    56.         ' // Wait for process terminaton
    57.         WaitForSingleObject shInfo.hProcess, INFINITE
    58.         CloseHandle shInfo.hProcess
    59.        
    60.     Next
    61.    
    62.     ' // Success
    63.     ExecuteProcess = True
    64.    
    65. CleanUp:
    66.     If pTempString Then SysFreeString pTempString
    67.     If pErrMsg Then SysFreeString pErrMsg
    68.     If pPath Then SysFreeString pPath
    69.    
    70. End Function
     
  8. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Эта функция похожа на предыдущую за исключением того что здесь используется функция ShellExecuteEx вместо извлечения. Обратите внимание что каждая операция выполняется синхронно, т.е. каждый вызов процедуры ShellExecuteEx ждет окончания выполнения команды.
    Если предыдущая функция выполнилась успешно тогда вызывается функция RunProcess которая подготовливает данные для исполнения главного исполняемого файла из памяти:
    Код (Visual Basic):
    1. ' // Run exe from project in memory
    2. Function RunProcess() As Boolean
    3.     Dim bItem       As BinStorageListItem:  Dim Length      As Long
    4.     Dim pFileData   As Long
    5.    
    6.     ' // Get descriptor of executable file
    7.     CopyMemory bItem, ByVal pStoragesTable + ProjectDesc.storageDescriptor.dwSizeOfItem * _
    8.                       ProjectDesc.storageDescriptor.iExecutableIndex, Len(bItem)
    9.    
    10.     ' // Alloc memory within top memory addresses
    11.     pFileData = VirtualAlloc(ByVal 0&, bItem.dwSizeOfFile, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_READWRITE)
    12.     If pFileData = 0 Then Exit Function
    13.    
    14.     ' // Copy raw exe file to this memory
    15.     CopyMemory ByVal pFileData, ByVal pFilesTable + bItem.ofstBeginOfData, bItem.dwSizeOfFile
    16.    
    17.     ' // Free decompressed project data
    18.     HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
    19.     pProjectData = 0
    20.    
    21.     ' // Run exe from memory
    22.     RunExeFromMemory pFileData, bItem.dwFlags And FF_IGNOREERROR
    23.    
    24.     ' ----------------------------------------------------
    25.     ' // An error occurs
    26.     ' // Clean memory
    27.    
    28.     VirtualFree ByVal pFileData, 0, MEM_RELEASE
    29.    
    30.     ' // If ignore error then success
    31.     If bItem.dwFlags And FF_IGNOREERROR Then RunProcess = True
    32.    
    33. End Function
    Эта процедура выделяет память в верхних областях виртуального адресного пространства (поскольку большинство EXE файлов грузятся по довольно низким адресам (обычно 0x00400000). После этого очишается память данных проекта поскольку если EXE файл запустится, то эта память не будет освобождена, затем вызывается функция RunExeFromMemory которая делает следующий шаг в загрузке EXE из памяти. Если по какой-либо причине загрузка EXE файла не состоялась то освобождается выделенная память и управление передается функции Main. Итак, для того чтобы загрузить EXE файл нам нужно освободить память загрузчика, т.е. выгрузить загрузчик. Нам нужно только оставить маленькуий кусочек кода который будет загружать EXE файл и запускать его. Для этого я решил использовать шеллкод, хотя можно использовать и DLL. Шеллкод - это маленький базонезависимый код (код который не ссылается к внешним данным). Но в любом случае нам придется обеспечить доступ к API функциям из шеллкода. Мы не можем вызывать API функции непосредственно из шеллкода поскольку наш главный исполняемый файл будет выгружен и любое обращение к таблице импорта вызовет креш. Второе ограничение - это то что инструкция call использует относительное смещение (это наиболее частый случай). Из этого следует что нам нужно инициализировать некие "трамплины" которые будут перебрасывать нас на API функции. Я решил делать это посредством сплайсинга. Я просто заменяю первые 5 байт функции пусттышки на ассемблерную инструкцию jmp которая ссылается на необходимую API функцию:
    Код (Visual Basic):
    1. ' // Run EXE file by memory address
    2. Function RunExeFromMemory( _
    3.                 ByVal pExeData As Long, _
    4.                 ByVal IgnoreError As Boolean) As Boolean
    5.     Dim Length  As Long:    Dim pCode       As Long
    6.     Dim pszMsg  As Long:    Dim pMsgTable   As Long
    7.     Dim index   As Long:    Dim pCurMsg     As Long
    8.    
    9.     ' // Get size of shellcode
    10.     Length = GetAddr(AddressOf ENDSHELLLOADER) - GetAddr(AddressOf BEGINSHELLLOADER)
    11.    
    12.     ' // Alloc memory within top addresses
    13.     pCode = VirtualAlloc(ByVal 0&, Length, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_EXECUTE_READWRITE)
    14.    
    15.     ' // Copy shellcode to allocated memory
    16.     CopyMemory ByVal pCode, ByVal GetAddr(AddressOf BEGINSHELLLOADER), Length
    17.    
    18.     ' // Initialization of shellcode
    19.     If Not InitShellLoader(pCode) Then GoTo CleanUp
    20.    
    21.     ' // Splice CallLoader function in order to call shellcode
    22.     Splice AddressOf CallLoader, pCode + GetAddr(AddressOf LoadExeFromMemory) - GetAddr(AddressOf BEGINSHELLLOADER)
    23.    
    24.     ' // Check ignore errors
    25.     If Not IgnoreError Then
    26.        
    27.         ' // Alloc memory for messages table
    28.         pMsgTable = VirtualAlloc(ByVal 0&, 1024, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_READWRITE)
    29.         If pMsgTable = 0 Then GoTo CleanUp
    30.        
    31.         ' // Skip pointers
    32.         pCurMsg = pMsgTable + EM_END * 4
    33.        
    34.         For index = 0 To EM_END - 1
    35.        
    36.             ' // Load message string
    37.             pszMsg = GetString(MSG_LOADER_ERROR + index)
    38.             If pszMsg = 0 Then GoTo CleanUp
    39.            
    40.             Length = SysStringLen(pszMsg)
    41.             lstrcpyn ByVal pCurMsg, ByVal pszMsg, Length + 1
    42.            
    43.             ' // Store pointer
    44.             CopyMemory ByVal pMsgTable + index * 4, pCurMsg, Len(pCurMsg)
    45.            
    46.             ' // Next message offset
    47.             pCurMsg = pCurMsg + (Length + 1) * 2
    48.            
    49.             SysFreeString pszMsg
    50.            
    51.         Next
    52.        
    53.     End If
    54.    
    55.     ' // Call shellcode
    56.     CallLoader pExeData, pCode, pMsgTable
    57.    
    58. CleanUp:
    59.    
    60.     If pMsgTable Then
    61.         VirtualFree ByVal pMsgTable, 0, MEM_RELEASE
    62.     End If
    63.    
    64.     If pCode Then
    65.         VirtualFree ByVal pCode, 0, MEM_RELEASE
    66.     End If
    67.    
    68. End Function
    Как видно из кода он вычисляет размер шеллкода используя разницу между крайними функциями - ENDSHELLLOADER и BEGINSHELLLOADER. Эти функции должны окружать наш шеллкод и иметь разный прототип поскольку VB6 компилятор может объединять идентичные функции. Затем выделяется память для самого шеллкода и он копируется в эту область памяти.
     
  9. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    После этого вызывается функция InitShellLoader которая сплайсит все функции в шеллкоде:
    Код (Visual Basic):
    1. ' // Shellcode initialization
    2. Function InitShellLoader( _
    3.                  ByVal pShellCode As Long) As Boolean
    4.     Dim hLib    As Long:        Dim sName   As Long
    5.     Dim sFunc   As Long:        Dim lpAddr  As Long
    6.     Dim libIdx  As Long:        Dim fncIdx  As Long
    7.     Dim libName As MessagesID:  Dim fncName As MessagesID
    8.     Dim fncSpc  As Long:        Dim splAddr As Long
    9.    
    10.     ' // +----------------------------------------------------------------+
    11.     ' // |                  Fixing of API addresses                       |
    12.     ' // +----------------------------------------------------------------+
    13.     ' // | In order to call api function from shellcode i use splicing of |
    14.     ' // |    our VB functions and redirect call to corresponding api.    |
    15.     ' // |     I did same in the code that injects to other process.      |
    16.     ' // +----------------------------------------------------------------+
    17.    
    18.     splAddr = GetAddr(AddressOf tVirtualAlloc) - GetAddr(AddressOf BEGINSHELLLOADER) + pShellCode
    19.    
    20.     ' // Get size in bytes between stub functions
    21.     fncSpc = GetAddr(AddressOf tVirtualProtect) - GetAddr(AddressOf tVirtualAlloc)
    22.     ' // Use 3 library: kernel32, ntdll и user32
    23.     For libIdx = 0 To 2
    24.    
    25.         ' // Get number of imported functions depending on library
    26.         Select Case libIdx
    27.         Case 0: libName = API_LIB_KERNEL32: fncIdx = 13
    28.         Case 1: libName = API_LIB_NTDLL:    fncIdx = 1
    29.         Case 2: libName = API_LIB_USER32:   fncIdx = 1
    30.         End Select
    31.        
    32.         ' // Get library name from resources
    33.         sName = GetString(libName): If sName = 0 Then Exit Function
    34.        
    35.         ' // Get module handle
    36.         hLib = GetModuleHandle(ByVal sName): If hLib = 0 Then Exit Function
    37.         SysFreeString sName
    38.        
    39.         ' // Go thru functions
    40.         Do While fncIdx
    41.        
    42.             libName = libName + 1
    43.             ' // Get function name
    44.             sName = GetString(libName): If sName = 0 Then Exit Function
    45.            
    46.             ' // Because of GetProcAddress works with ANSI string translate it to ANSI
    47.             sFunc = ToAnsi(sName): If sFunc = 0 Then Exit Function
    48.            
    49.             ' // Get function address
    50.             lpAddr = GetProcAddress(hLib, sFunc)
    51.             SysFreeString sName: SysFreeString sFunc
    52.            
    53.             ' // Error
    54.             If lpAddr = 0 Then Exit Function
    55.            
    56.             ' // Splice stub
    57.             Splice splAddr, lpAddr
    58.            
    59.             ' // Next stub
    60.             splAddr = splAddr + fncSpc
    61.             fncIdx = fncIdx - 1
    62.            
    63.         Loop
    64.        
    65.     Next
    66.    
    67.     ' // Modify CallByPointer
    68.     lpAddr = GetAddr(AddressOf CallByPointer) - GetAddr(AddressOf BEGINSHELLLOADER) + pShellCode
    69.    
    70.     ' // pop eax    - 0x58
    71.     ' // pop ecx    - 0x59
    72.     ' // push eax   - 0x50
    73.     ' // jmp ecx    - 0xFFE1
    74.    
    75.     CopyMemory ByVal lpAddr, &HFF505958, 4
    76.     CopyMemory ByVal lpAddr + 4, &HE1, 1
    77.     ' // Success
    78.     InitShellLoader = True
    79.    
    80. End Function
    81. ' // Splice function
    82. Sub Splice( _
    83.             ByVal Func As Long, _
    84.             ByVal NewAddr As Long)
    85.     ' // Set memory permissions
    86.     VirtualProtect ByVal Func, 5, PAGE_EXECUTE_READWRITE, 0
    87.     CopyMemory ByVal Func, &HE9, 1                      ' // JMP
    88.     CopyMemory ByVal Func + 1, NewAddr - Func - 5, 4    ' // Relative address
    89. End Sub
    Вначале код вычисляет смещение первого "трамплина" (в нашем случае это функция tVirtualAlloc) относительно начала шеллкода, и вычисляет расстояние (в байтах) между функциями "трамплинами". Когда компилятор VB6 компилирует стандартный модуль он размещает функции в том же порядке в котором они определены в модуле. Необходимое условие - обеспечить уникальное возвращаемое значение для каждой функции. Затем код проходит по всем необходимым библиотекам (kernel32, ntdll, user32 - в этом порядке) и их функциям. Первая запись в ресурсах строк соответствует имени библиотеки за котором идут имена функций в этой библиотеке. Когда строка имени функции из ресурсов получена она транслируется в ANSI формат и вызывается функция GetProcAddress. Затем вызывается функция Splice которая собирает "трамплин" к необходимой функции из шеллкода. В конце модифицируется функция CallByPointer для того чтобы обеспечить прыжок из шеллкода на точку входа EXE файла. Далее функция RunExeFromMemory патчит функцию CallLoader для того чтобы обеспечить вызов шеллкода из загрузчика. После этой операции функция формирует таблицу сообщений об ошибках (если нужно) которая представляет из себя просто набор указателей на стоки сообщений. И наконец вызывается пропатченная CallLoader которая прыгает на функцию шеллкода LoadExeFromMemory которая больше не расположена внутри загрузчика, а находится в верхних адресах АП процесса.

    Внутри шеллкода.
    Итак, я сделал несколько функций внутри шеллкода:
    • LoadExeFromMemory - стартовая функция шеллкода;
    • GetImageNtHeaders - возвращает структуру IMAGE_NT_HEADERS и ее адрес по базовому адресу;
    • GetDataDirectory - возвращает структуру IMAGE_DATA_DIRECTORY и ее адрес по базовому адресу и каталоговому индексу;
    • EndProcess - показать сообщение об ошибке (если есть такое) и завершить процесс;
    • ProcessSectionsAndHeaders - выделить память под все заголовки (DOS, NT, секции) и все секции. Скопировать данные в секции;
    • ReserveMemory - зарезервировать необходимую память под EXE;
    • ProcessRelocations - настроить адреса иесли EXE был загружен не по базовому адресу;
    • ProcessImportTable - сканировать таблицу импорта EXE файла, загрузить необходимые библиотеки и заполнить таблицу адресов импорта (IAT);
    • SetMemoryPermissions - настроить разрешения памяти для каждой секции;
    • UpdateNewBaseAddress - обновить новый базовый адрес в системных структурах PEB и LDR.
    Из-за того что нельзя использовать функцию VarPtr, я сделалпохожую функцию используя функцию lstrcpyn - IntPtr. Итак, функция LoadExeFromMemory извлекает вначале заголовок NT и проверяет архитектуру процессора, является ли PE файл исполняемым и является ли он 32-битным приложением. Если проверка прошла успешно тогда шеллкод выгружает загрузчик из памяти используя функцию ZwUnmapViewOfSection. Если функция выполняется успешно EXE образ загрузчика больше не находится в памяти и занимаемая им память освобождается. Отныне мы не можем напрямую вызывать API функции, теперь мы должны использовать наши "трамплины":
    Код (Visual Basic):
    1. ' // Parse exe in memory
    2. Function LoadExeFromMemory( _
    3.                  ByVal pRawData As Long, _
    4.                  ByVal pMyBaseAddress As Long, _
    5.                  ByVal pErrMsgTable As Long) As Boolean
    6.     Dim NtHdr   As IMAGE_NT_HEADERS
    7.     Dim pBase   As Long
    8.     Dim index   As Long
    9.     Dim iError  As ERROR_MESSAGES
    10.     Dim pszMsg  As Long
    11.    
    12.     ' // Get IMAGE_NT_HEADERS
    13.     If GetImageNtHeaders(pRawData, NtHdr) = 0 Then
    14.         iError = EM_UNABLE_TO_GET_NT_HEADERS
    15.         EndProcess pErrMsgTable, iError
    16.         Exit Function
    17.     End If
    18.    
    19.     ' // Check flags
    20.     If NtHdr.FileHeader.Machine <> IMAGE_FILE_MACHINE_I386 Or _
    21.        (NtHdr.FileHeader.Characteristics And IMAGE_FILE_EXECUTABLE_IMAGE) = 0 Or _
    22.        (NtHdr.FileHeader.Characteristics And IMAGE_FILE_32BIT_MACHINE) = 0 Then Exit Function
    23.     ' // Release main EXE memory. After that main exe is unloaded from memory.
    24.     ZwUnmapViewOfSection GetCurrentProcess(), GetModuleHandle(ByVal 0&)
    25.     ' // Reserve memory for EXE
    26.     iError = ReserveMemory(pRawData, pBase)
    27.     If iError Then
    28.         EndProcess pErrMsgTable, iError
    29.         Exit Function
    30.     End If
    31.    
    32.     ' // Place data
    33.     iError = ProcessSectionsAndHeaders(pRawData, pBase)
    34.     If iError Then
    35.         EndProcess pErrMsgTable, iError
    36.         Exit Function
    37.     End If
    38.    
    39.     ' // Update new base address
    40.     iError = UpdateNewBaseAddress(pBase)
    41.     If iError Then
    42.         EndProcess pErrMsgTable, iError
    43.         Exit Function
    44.     End If
    45.    
    46.     ' // Import table processing
    47.     iError = ProcessImportTable(pBase)
    48.     If iError Then
    49.         EndProcess pErrMsgTable, iError
    50.         Exit Function
    51.     End If
    52.    
    53.     ' // Relocations processing
    54.     iError = ProcessRelocations(pBase)
    55.     If iError Then
    56.         EndProcess pErrMsgTable, iError
    57.         Exit Function
    58.     End If
    59.    
    60.     ' // Set the memory attributes
    61.     iError = SetMemoryPermissions(pBase)
    62.     If iError Then
    63.         EndProcess pErrMsgTable, iError
    64.         Exit Function
    65.     End If
    66.    
    67.     ' // Release error message table
    68.     If pErrMsgTable Then
    69.         tVirtualFree pErrMsgTable, 0, MEM_RELEASE
    70.     End If
    71.    
    72.     ' // Call entry point
    73.     CallByPointer NtHdr.OptionalHeader.AddressOfEntryPoint + pBase
    74.    
    75.     ' // End process
    76.     EndProcess
    77.    
    78. End Function
     
  10. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Затем шеллкод вызывает функцию ReserveMemory показанную ниже. Эта функция извлекает заголовок NT из загружаемого EXE и пытается зарезервировать регион памяти по адресу указанному в поле ImageBase размера SizeOfmage. Если регион по какой-то причине не был выделен функция проверяет имеет ли EXE файл таблицу релокаций. Если так, тогда функция пытается выделять память по любому адресу. Информация о релокациях позволяет загрузить EXE по любому адресу отличному от ImageBase. Она содержит все места в EXE файле где он использует абсолютную адресацию. Мы можем потом подкорректировать эти адреса используя разницу между реальным базовым адресом и адресом указанным в поле ImageBase:
    Код (Visual Basic):
    1. ' // Reserve memory for EXE
    2. Function ReserveMemory( _
    3.                  ByVal pRawExeData As Long, _
    4.                  ByRef pBase As Long) As ERROR_MESSAGES
    5.     Dim NtHdr       As IMAGE_NT_HEADERS
    6.     Dim pLocBase    As Long
    7.    
    8.     If GetImageNtHeaders(pRawExeData, NtHdr) = 0 Then
    9.         ReserveMemory = EM_UNABLE_TO_GET_NT_HEADERS
    10.         Exit Function
    11.     End If
    12.    
    13.     ' // Reserve memory for EXE
    14.     pLocBase = tVirtualAlloc(ByVal NtHdr.OptionalHeader.ImageBase, _
    15.                           NtHdr.OptionalHeader.SizeOfImage, _
    16.                           MEM_RESERVE, PAGE_EXECUTE_READWRITE)
    17.     If pLocBase = 0 Then
    18.        
    19.         ' // If relocation information not found error
    20.         If NtHdr.FileHeader.Characteristics And IMAGE_FILE_RELOCS_STRIPPED Then
    21.        
    22.             ReserveMemory = EM_UNABLE_TO_ALLOCATE_MEMORY
    23.             Exit Function
    24.            
    25.         Else
    26.             ' // Reserve memory in other region
    27.             pLocBase = tVirtualAlloc(ByVal 0&, NtHdr.OptionalHeader.SizeOfImage, _
    28.                                  MEM_RESERVE, PAGE_EXECUTE_READWRITE)
    29.            
    30.             If pLocBase = 0 Then
    31.            
    32.                 ReserveMemory = EM_UNABLE_TO_ALLOCATE_MEMORY
    33.                 Exit Function
    34.                
    35.             End If
    36.         End If
    37.        
    38.     End If
    39.    
    40.     pBase = pLocBase
    41.    
    42. End Function
    Если при вызове функции произошла ошибка то показывается сообщение о ней и приложение завершается. В противном случае вызывается функция ProcessSectionsAndHeaders. Эта функция размещает все заголовки в выделенную память, извлекает информацию о всех секциях и копирует все данные в выделенную для них память. Если какая-либо секция имеет неинициализированные данные то этот регион заполняется нулями:
    Код (Visual Basic):
    1. ' // Allocate memory for sections and copy them data to there
    2. Function ProcessSectionsAndHeaders( _
    3.                  ByVal pRawExeData As Long, _
    4.                  ByVal pBase As Long) As ERROR_MESSAGES
    5.     Dim iSec    As Long
    6.     Dim pNtHdr  As Long
    7.     Dim NtHdr   As IMAGE_NT_HEADERS
    8.     Dim sec     As IMAGE_SECTION_HEADER
    9.     Dim lpSec   As Long
    10.     Dim pData   As Long
    11.    
    12.     pNtHdr = GetImageNtHeaders(pRawExeData, NtHdr)
    13.     If pNtHdr = 0 Then
    14.         ProcessSectionsAndHeaders = EM_UNABLE_TO_GET_NT_HEADERS
    15.         Exit Function
    16.     End If
    17.    
    18.     ' // Alloc memory for headers
    19.     pData = tVirtualAlloc(ByVal pBase, NtHdr.OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE)
    20.     If pData = 0 Then
    21.         ProcessSectionsAndHeaders = EM_UNABLE_TO_ALLOCATE_MEMORY
    22.         Exit Function
    23.     End If
    24.    
    25.     ' // Copy headers
    26.     tCopyMemory pData, pRawExeData, NtHdr.OptionalHeader.SizeOfHeaders
    27.    
    28.     ' // Get address of beginnig of sections headers
    29.     pData = pNtHdr + Len(NtHdr.Signature) + Len(NtHdr.FileHeader) + NtHdr.FileHeader.SizeOfOptionalHeader
    30.    
    31.     ' // Go thru sections
    32.     For iSec = 0 To NtHdr.FileHeader.NumberOfSections - 1
    33.    
    34.         ' // Copy section descriptor
    35.         tCopyMemory IntPtr(sec.SectionName(0)), pData, Len(sec)
    36.        
    37.         ' // Alloc memory for section
    38.         lpSec = tVirtualAlloc(sec.VirtualAddress + pBase, sec.VirtualSize, MEM_COMMIT, PAGE_READWRITE)
    39.         If lpSec = 0 Then
    40.             ProcessSectionsAndHeaders = EM_UNABLE_TO_ALLOCATE_MEMORY
    41.             Exit Function
    42.         End If
    43.        
    44.         ' If there is initialized data
    45.         If sec.SizeOfRawData Then
    46.        
    47.             ' // Take into account  file alignment
    48.             If sec.SizeOfRawData > sec.VirtualSize Then sec.SizeOfRawData = sec.VirtualSize
    49.            
    50.             ' // Copy initialized data to section
    51.             tCopyMemory lpSec, pRawExeData + sec.PointerToRawData, sec.SizeOfRawData
    52.             lpSec = lpSec + sec.SizeOfRawData
    53.             sec.VirtualSize = sec.VirtualSize - sec.SizeOfRawData
    54.            
    55.         End If
    56.         ' // Fill remain part with zero
    57.         tFillMemory lpSec, sec.VirtualSize, 0
    58.        
    59.         ' // Next section
    60.         pData = pData + Len(sec)
    61.        
    62.     Next
    63.    
    64. End Function
    Затем функция LoadExeFromMemory вызывает функцию UpdateNewBaseAddress которая обновляет новый базовый адрес в user-mode системных структурах. Windows создает специальную структуру называемую PEB (Process Environment Block) для каждого процесса. Это очень полезная структура которая позволяет получить очень много информации о процессе. Множество API функций берут информацию из этой структуры. Для примера GetModuleHandle(NULL) берет возвращаемое значение из PEB.ImageBaseAddress или GetModuleHandle("MyExeName") извлекает информацию из списка загруженных модулей - PEB.Ldr. Нам нужно обновить эту информацию согласно новому базовому адресу для того чтобы API функции возвращали корректное значение. Вот небольшая часть структуры PEB:
    Код (Visual Basic):
    1. Type PEB
    2.     NotUsed                         As Long
    3.     Mutant                          As Long
    4.     ImageBaseAddress                As Long
    5.     LoaderData                      As Long ' // Pointer to PEB_LDR_DATA
    6.     ProcessParameters               As Long
    7.     ' // ....
    8. End Type
    Нам интересно только поле ImageBaseAddress и LoaderData. Первое поле содержит базовый адрес EXE файла. Второе поле содержит указатель на структуру PEB_LDR_DATA которая описывает все загруженные модули в процессе:
    Код (Visual Basic):
    1. Type PEB_LDR_DATA
    2.     Length                          As Long
    3.     Initialized                     As Long
    4.     SsHandle                        As Long
    5.     InLoadOrderModuleList           As LIST_ENTRY
    6.     InMemoryOrderModuleList         As LIST_ENTRY
    7.     InInitializationOrderModuleList As LIST_ENTRY
    8. End Type
     
  11. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Эта структура содержит три двухсвязных списка что описывают каждый модуль. Список InLoadOrderModuleList содержит ссылки на элементы в порядке загрузки, т.е. ссылки в этом списке расположены в порядке загрузки (первый модуль в начале). Список InMemoryOrderModuleList тоже самое только в порядке расположения в памяти, а InInitializationOrderModuleList в порядке инициализации. Нам нужно получить первый элемент списка InLoadOrderModuleList который является указателем на структуру LDR_MODULE:
    Код (Visual Basic):
    1. Type LDR_MODULE
    2.     InLoadOrderModuleList           As LIST_ENTRY
    3.     InMemoryOrderModuleList         As LIST_ENTRY
    4.     InInitOrderModuleList           As LIST_ENTRY
    5.     BaseAddress                     As Long
    6.     EntryPoint                      As Long
    7.     SizeOfImage                     As Long
    8.     FullDllName                     As UNICODE_STRING
    9.     BaseDllName                     As UNICODE_STRING
    10.     Flags                           As Long
    11.     LoadCount                       As Integer
    12.     TlsIndex                        As Integer
    13.     HashTableEntry                  As LIST_ENTRY
    14.     TimeDateStamp                   As Long
    15. End Type
    Эта структура описывает один модуль. Первый элемент списка InLoadOrderModuleList является описателем главного исполняемого файла. Нам нужно изменить поле BaseAddress на новый базовый адрес и сохранить изменения. Итак, для того чтобы получить адрес структуры PEB мы можем использовать функцию NtQueryInformationProcess которая извлекает множество полезной информации о процессе (узнать подробнее можно в книге 'Windows NT/2000 Native API Reference' by Gary Nebbett). Структура PEB может быть получена из структуры PROCESS_BASIC_INFORMATION которая описывает базовую информацию о процессе:
    Код (Visual Basic):
    1. Type PROCESS_BASIC_INFORMATION
    2.     ExitStatus                      As Long
    3.     PebBaseAddress                  As Long
    4.     AffinityMask                    As Long
    5.     BasePriority                    As Long
    6.     UniqueProcessId                 As Long
    7.     InheritedFromUniqueProcessId    As Long
    8. End Type
    Поле PebBaseAddress содержит адрес структуры PEB.
    Для того чтобы извлечь структуру PROCESS_BASIC_INFORMATION нам нужно передать в качестве параметра класса информации значение ProcessBasicInformation. Поскольку размер структуры может меняться в различных версиях Windows я использую кучу для извлечения структуры PROCESS_BASIC_INFORMATION. Если размер не подходит код увеличивает размер памяти для структуры PROCESS_BASIC_INFORMATION и повторяет заново пока структура не будет извлечена:
    Код (Visual Basic):
    1. Function UpdateNewBaseAddress( _
    2.                  ByVal pBase As Long) As ERROR_MESSAGES
    3.     Dim pPBI    As Long:                        Dim PBIlen  As Long
    4.     Dim PBI     As PROCESS_BASIC_INFORMATION:   Dim cPEB    As PEB
    5.     Dim ntstat  As Long
    6.     Dim ldrData As PEB_LDR_DATA
    7.     Dim ldrMod  As LDR_MODULE
    8.    
    9.     ntstat = tNtQueryInformationProcess(tGetCurrentProcess(), ProcessBasicInformation, IntPtr(PBI.ExitStatus), Len(PBI), PBIlen)
    10.    
    11.     Do While ntstat = STATUS_INFO_LENGTH_MISMATCH
    12.        
    13.         PBIlen = PBIlen * 2
    14.        
    15.         If pPBI Then
    16.             tHeapFree tGetProcessHeap(), HEAP_NO_SERIALIZE, pPBI
    17.         End If
    18.        
    19.         pPBI = tHeapAlloc(tGetProcessHeap(), HEAP_NO_SERIALIZE, PBIlen)
    20.         ntstat = tNtQueryInformationProcess(tGetCurrentProcess(), ProcessBasicInformation, pPBI, PBIlen, PBIlen)
    21.        
    22.     Loop
    23.    
    24.     If ntstat <> STATUS_SUCCESS Then
    25.         UpdateNewBaseAddress = EM_PROCESS_INFORMATION_NOT_FOUND
    26.         GoTo CleanUp
    27.     End If
    28.    
    29.     If pPBI Then
    30.         ' // Copy to PROCESS_BASIC_INFORMATION
    31.         tCopyMemory IntPtr(PBI.ExitStatus), pPBI, Len(PBI)
    32.     End If
    33.     ' // Get PEB
    34.     tCopyMemory IntPtr(cPEB.NotUsed), PBI.PebBaseAddress, Len(cPEB)
    35.    
    36.     ' // Modify image base
    37.     cPEB.ImageBaseAddress = pBase
    38.    
    39.     ' // Restore PEB
    40.     tCopyMemory PBI.PebBaseAddress, IntPtr(cPEB.NotUsed), Len(cPEB)
    41.    
    42.     ' // Fix base address in PEB_LDR_DATA list
    43.     tCopyMemory IntPtr(ldrData.Length), cPEB.LoaderData, Len(ldrData)
    44.    
    45.     ' // Get first element
    46.     tCopyMemory IntPtr(ldrMod.InLoadOrderModuleList.Flink), ldrData.InLoadOrderModuleList.Flink, Len(ldrMod)
    47.    
    48.     ' // Fix base
    49.     ldrMod.BaseAddress = pBase
    50.    
    51.     ' // Restore
    52.     tCopyMemory ldrData.InLoadOrderModuleList.Flink, IntPtr(ldrMod.InLoadOrderModuleList.Flink), Len(ldrMod)
    53.    
    54. CleanUp:
    55.    
    56.     ' // Free memory
    57.     If pPBI Then
    58.         tHeapFree tGetProcessHeap(), HEAP_NO_SERIALIZE, pPBI
    59.     End If
    60.    
    61. End Function
    После обновления базового адреса в системных структурах шеллкод вызывает функцию ProcessImportTable которая загружает необходимые библиотеки для работы EXE файла. Вначале извлекается директория IMAGE_DIRECTORY_ENTRY_IMPORT которая содержит RVA массива структур IMAGE_IMPORT_DESCRIPTOR:
    Код (Visual Basic):
    1. Type IMAGE_IMPORT_DESCRIPTOR
    2.     Characteristics                 As Long
    3.     TimeDateStamp                   As Long
    4.     ForwarderChain                  As Long
    5.     pName                           As Long
    6.     FirstThunk                      As Long
    7. End Type
    Каждая такая структура описывает одну DLL. Поле pName содержит RVA ASCIIZ строки с именем библиотеки. Поле Characteristics содержит RVA таблицы импортируемых функций, а поле FirstThunk содержит RVA таблицы адресов импорта (IAT). Таблица имен представляет из себя массив структур IMAGE_THUNK_DATA. Эта структура представляет из себя 32 битное значение в котором если установлен старший бит остальные биты представляют из себя ординал функции (импорт по ординалу), иначе остальные биты содержат RVA имени функции предваренной значением Hint. Если же структура IMAGE_THUNK_DATA содержит 0 то значит список имен закончен. Если все поля структуры IMAGE_IMPORT_DESCRIPTOR равны 0 это означает что список структур также окончен.
    Код (Visual Basic):
    1. ' // Process import table
    2. Function ProcessImportTable( _
    3.                  ByVal pBase As Long) As ERROR_MESSAGES
    4.     Dim NtHdr           As IMAGE_NT_HEADERS:        Dim datDirectory    As IMAGE_DATA_DIRECTORY
    5.     Dim dsc             As IMAGE_IMPORT_DESCRIPTOR: Dim hLib            As Long
    6.     Dim thnk            As Long:                    Dim Addr            As Long
    7.     Dim fnc             As Long:                    Dim pData           As Long
    8.        
    9.     If GetImageNtHeaders(pBase, NtHdr) = 0 Then
    10.         ProcessImportTable = EM_UNABLE_TO_GET_NT_HEADERS
    11.         Exit Function
    12.     End If
    13.    
    14.     ' // Import table processing
    15.     If NtHdr.OptionalHeader.NumberOfRvaAndSizes > 1 Then
    16.        
    17.         If GetDataDirectory(pBase, IMAGE_DIRECTORY_ENTRY_IMPORT, datDirectory) = 0 Then
    18.             ProcessImportTable = EM_INVALID_DATA_DIRECTORY
    19.             Exit Function
    20.         End If
    21.         ' // If import table exists
    22.         If datDirectory.Size > 0 And datDirectory.VirtualAddress > 0 Then
    23.        
    24.             ' // Copy import descriptor
    25.             pData = datDirectory.VirtualAddress + pBase
    26.             tCopyMemory IntPtr(dsc.Characteristics), pData, Len(dsc)
    27.            
    28.             ' // Go thru all descriptors
    29.             Do Until dsc.Characteristics = 0 And _
    30.                      dsc.FirstThunk = 0 And _
    31.                      dsc.ForwarderChain = 0 And _
    32.                      dsc.pName = 0 And _
    33.                      dsc.TimeDateStamp = 0
    34.                
    35.                 If dsc.pName > 0 Then
    36.                
    37.                     ' // Load needed library
    38.                     hLib = tLoadLibrary(dsc.pName + pBase)
    39.                    
    40.                     If hLib = 0 Then
    41.                         ProcessImportTable = EM_LOADLIBRARY_FAILED
    42.                         Exit Function
    43.                     End If
    44.                     If dsc.Characteristics Then fnc = dsc.Characteristics + pBase Else fnc = dsc.FirstThunk + pBase
    45.                    
    46.                     ' // Go to names table
    47.                     tCopyMemory IntPtr(thnk), fnc, 4
    48.                    
    49.                     ' // Go thru names table
    50.                     Do While thnk
    51.                    
    52.                         ' // Check import type
    53.                         If thnk < 0 Then
    54.                             ' // By ordinal
    55.                             Addr = tGetProcAddress(hLib, thnk And &HFFFF&)
    56.                         Else
    57.                             ' // By name
    58.                             Addr = tGetProcAddress(hLib, thnk + 2 + pBase)
    59.                         End If
    60.                        
    61.                         ' // Next function
    62.                         fnc = fnc + 4
    63.                         tCopyMemory IntPtr(thnk), fnc, 4
    64.                         tCopyMemory dsc.FirstThunk + pBase, IntPtr(Addr), 4
    65.                         dsc.FirstThunk = dsc.FirstThunk + 4
    66.                        
    67.                     Loop
    68.                    
    69.                 End If
    70.                
    71.                 ' // Next descriptor
    72.                 pData = pData + Len(dsc)
    73.                 tCopyMemory IntPtr(dsc.Characteristics), pData, Len(dsc)
    74.                
    75.             Loop
    76.            
    77.         End If
    78.        
    79.     End If
    80.                  
    81. End Function
     
  12. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Функция ProcessRelocation вызывается после обработки импорта. Эта функция настраивает все абсолютные ссылки (если таковые имеются). Извлекается каталог IMAGE_DIRECTORY_ENTRY_BASERELOC который содержит RVA массива структур IMAGE_BASE_RELOCATION. Каждый элемент этого масива содержит настройки в пределах 4Кб относительно адреса VirtualAddress:
    Код (Visual Basic):
    1. Type IMAGE_BASE_RELOCATION
    2.     VirtualAddress                  As Long
    3.     SizeOfBlock                     As Long
    4. End Type
    Поле SizeOfBlock содержит размер элемента в байтах. Массив 16-битных значений дескрипторов расположен после каждой структуры IMAGE_BASE_RELOCATION. Мы можем вычислить количество этих значений по формуле: (SizeOfBlock - Len(IMAGE_BASE_RELOCATION)) \ Len(Integer). Каждый элемент массива дескрипторов имеет следующуюю структуру:
    [​IMG]
    Верхние 4 бита содержат тип настройки. Нам интересна только настройка IMAGE_REL_BASED_HIGHLOW которая означает что нам нужно добавить разницу (RealBaseAddress - ImageBaseAddress) к значению Long которое расположено по адресу VirtualAddress + 12 младших бит дескриптора. Массив струкутр IMAGE_BASE_RELOCATION заканчивается структурой где все поля заполнены нулями:
    Код (Visual Basic):
    1. ' // Process relocations
    2. Function ProcessRelocations( _
    3.                  ByVal pBase As Long) As ERROR_MESSAGES
    4.     Dim NtHdr           As IMAGE_NT_HEADERS:        Dim datDirectory    As IMAGE_DATA_DIRECTORY
    5.     Dim relBase         As IMAGE_BASE_RELOCATION:   Dim entriesCount    As Long
    6.     Dim relType         As Long:                    Dim dwAddress       As Long
    7.     Dim dwOrig          As Long:                    Dim pRelBase        As Long
    8.     Dim delta           As Long:                    Dim pData           As Long
    9.    
    10.     ' // Check if module has not been loaded to image base value
    11.     If GetImageNtHeaders(pBase, NtHdr) = 0 Then
    12.         ProcessRelocations = EM_UNABLE_TO_GET_NT_HEADERS
    13.         Exit Function
    14.     End If
    15.    
    16.     delta = pBase - NtHdr.OptionalHeader.ImageBase
    17.    
    18.     ' // Process relocations
    19.     If delta Then
    20.        
    21.         ' // Get address of relocation table
    22.         If GetDataDirectory(pBase, IMAGE_DIRECTORY_ENTRY_BASERELOC, datDirectory) = 0 Then
    23.             ProcessRelocations = EM_INVALID_DATA_DIRECTORY
    24.             Exit Function
    25.         End If
    26.        
    27.         If datDirectory.Size > 0 And datDirectory.VirtualAddress > 0 Then
    28.        
    29.             ' // Copy relocation base
    30.             pRelBase = datDirectory.VirtualAddress + pBase
    31.             tCopyMemory IntPtr(relBase.VirtualAddress), pRelBase, Len(relBase)
    32.            
    33.             Do While relBase.VirtualAddress
    34.            
    35.                 ' // To first reloc chunk
    36.                 pData = pRelBase + Len(relBase)
    37.                
    38.                 entriesCount = (relBase.SizeOfBlock - Len(relBase)) \ 2
    39.                
    40.                 Do While entriesCount > 0
    41.                    
    42.                     tCopyMemory IntPtr(relType), pData, 2
    43.                    
    44.                     Select Case (relType \ 4096) And &HF
    45.                     Case IMAGE_REL_BASED_HIGHLOW
    46.                        
    47.                         ' // Calculate address
    48.                         dwAddress = relBase.VirtualAddress + (relType And &HFFF&) + pBase
    49.                        
    50.                         ' // Get original address
    51.                         tCopyMemory IntPtr(dwOrig), dwAddress, Len(dwOrig)
    52.                        
    53.                         ' // Add delta
    54.                         dwOrig = dwOrig + delta
    55.                        
    56.                         ' // Save
    57.                         tCopyMemory dwAddress, IntPtr(dwOrig), Len(dwOrig)
    58.                        
    59.                     End Select
    60.                    
    61.                     pData = pData + 2
    62.                     entriesCount = entriesCount - 1
    63.                    
    64.                 Loop
    65.                
    66.                 ' // Next relocation base
    67.                 pRelBase = pRelBase + relBase.SizeOfBlock
    68.                 tCopyMemory IntPtr(relBase.VirtualAddress), pRelBase, Len(relBase)
    69.                
    70.             Loop
    71.            
    72.         End If
    73.        
    74.     End If
    75. End Function
    После настройки релокаций шеллкод вызывает функцию SetMemoryPermissions которая настраивает разрешения памяти согласно полю Characteristics структуры IMAGE_SECTION_HEADER. Для этого просто вызывается функция VirtualProtect с определенными атрибутами памяти:
    Код (Visual Basic):
    1. ' // Set memory permissions
    2. Private Function SetMemoryPermissions( _
    3.                  ByVal pBase As Long) As ERROR_MESSAGES
    4.     Dim iSec    As Long:                    Dim pNtHdr  As Long
    5.     Dim NtHdr   As IMAGE_NT_HEADERS:        Dim sec     As IMAGE_SECTION_HEADER
    6.     Dim Attr    As MEMPROTECT:              Dim pSec    As Long
    7.     Dim ret     As Long
    8.    
    9.     pNtHdr = GetImageNtHeaders(pBase, NtHdr)
    10.     If pNtHdr = 0 Then
    11.         SetMemoryPermissions = EM_UNABLE_TO_GET_NT_HEADERS
    12.         Exit Function
    13.     End If
    14.     ' // Get address of first section header
    15.     pSec = pNtHdr + 4 + Len(NtHdr.FileHeader) + NtHdr.FileHeader.SizeOfOptionalHeader
    16.    
    17.     ' // Go thru section headers
    18.     For iSec = 0 To NtHdr.FileHeader.NumberOfSections - 1
    19.    
    20.         ' // Copy section descriptor
    21.         tCopyMemory IntPtr(sec.SectionName(0)), pSec, Len(sec)
    22.        
    23.         ' // Get type
    24.         If sec.Characteristics And IMAGE_SCN_MEM_EXECUTE Then
    25.             If sec.Characteristics And IMAGE_SCN_MEM_READ Then
    26.                 If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
    27.                     Attr = PAGE_EXECUTE_READWRITE
    28.                 Else
    29.                     Attr = PAGE_EXECUTE_READ
    30.                 End If
    31.             Else
    32.                 If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
    33.                     Attr = PAGE_EXECUTE_WRITECOPY
    34.                 Else
    35.                     Attr = PAGE_EXECUTE
    36.                 End If
    37.             End If
    38.         Else
    39.             If sec.Characteristics And IMAGE_SCN_MEM_READ Then
    40.                 If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
    41.                     Attr = PAGE_READWRITE
    42.                 Else
    43.                     Attr = PAGE_READONLY
    44.                 End If
    45.             Else
    46.                 If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
    47.                     Attr = PAGE_WRITECOPY
    48.                 Else
    49.                     Attr = PAGE_NOACCESS
    50.                 End If
    51.             End If
    52.         End If
    53.        
    54.         ' // Set memory permissions
    55.         If tVirtualProtect(sec.VirtualAddress + pBase, sec.VirtualSize, Attr, IntPtr(ret)) = 0 Then
    56.             SetMemoryPermissions = EM_UNABLE_TO_PROTECT_MEMORY
    57.             Exit Function
    58.         End If
    59.        
    60.         ' // Next section
    61.         pSec = pSec + Len(sec)
    62.        
    63.     Next
    64.    
    65. End Function
    В конце концов очищается таблица сообщений об ошибках (если нужно) и вызывается точка входа загруженного EXE. В предыдущей версии загрузчика я выгружал шеллкод тоже, но некоторые EXE не вызывают ExitProcess следовательно это могло вызывать креши. Загрузчик готов.
    Хотя мы написал загрузчик без использвания рантайма, компилятор VB6 добавляет его все-равно поскольку все OBJ файлы имеют ссылки на MSVBVM60 во время компиляции. Нам придется удалить рантайм из таблицы импорта загрузчика вручную. Для этого я сделал специальную утилиту - Patcher которая ищет рантайм в таблице импорта и таблице связанного импорта и удаляет его оттуда. Эта утилита также была полезна для драйвера режима ядра. Я не буду описывать ее работу поскольку она использует те же концепции PE-формата что я уже описал здесь. В общем и целом мы сделали рабочий EXE который не использует MSVBVM60 на целевой машине.
    Для того чтобы использовать загрузчик нужно скомпилировать его затем с помощью патчера пропатчить его. После этог можно использовать его в компиляторе.
    Я надеюсь вам понравилось. Спасибо за внимание!
    С уважением,
    Кривоус Анатолий (The trick).
     

    Вложения:

    • VBLoader.zip
      Размер файла:
      176,4 КБ
      Просмотров:
      761