На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела Visual Basic: Общие вопросы
Здесь обсуждаются вопросы по языку Visual Basic 1-6 (а так же по схожим языкам, как, например, PowerBASIC).
Вопросы по Visual Basic .NET (это который входит в состав Visual Studio 2002/2003/2005/2008+, для тех, кто не в курсе) обсуждаются в разделе .NET.

Обратите внимание:
1. Прежде чем начать новую тему или отправить сообщение, убедитесь, что Вы не нарушаете правил форума!
2. Обязательно воспользуйтесь поиском. Возможно, Ваш вопрос уже обсуждали. Полезные ссылки приведены ниже.
3. Темы с просьбой выполнить какую-либо работу за автора в этом разделе не обсуждаются. Студенты, вам сюда: ПОМОЩЬ СТУДЕНТАМ!
4. Используйте теги [ code=vba ] ...текст программы... [ /code ] для выделения текста программы подсветкой.
5. Помните, здесь телепатов нет. Формулируйте свой вопрос максимально грамотно и чётко: Как правильно задавать вопросы
6. Запрещено отвечать в темы месячной (и более) давности, без веских на то причин.

Полезные ссылки:
user posted image FAQ Сайта user posted image FAQ Раздела user posted image Кладовка user posted image Наши Исходники user posted image API-Guide user posted image Поиск по Разделу user posted image MSDN Library Online user posted image Google

Ваше мнение о модераторах: user posted image SCINER, user posted image B.V.
Модераторы: SCINER, B.V.
  
> Загрузчик, шеллкод, без рантайма... , Статья которая подробно описывает этапы создания EXE загрузчика на VB6.

    Всем привет! Когда-то давно я исследовал 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> - рабочий стол.

    Компилятор.

    user posted image


    Это приложение формирующее информацию для инсталляции и размещающее ее в ресурсах загрузчика. Вся информация хранится в файлах проекта. Вы можете сохранять и загружать проекты из файлов. Класс 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. Сейчас я расскажу о процедуре компиляции. Как я уже сказал вся информация об инсталляции сохраняется в ресурсы загрузчика. Вначале на нужно определить формат данных:
    ExpandedWrap disabled
      ' // Storage list item
      Private Type BinStorageListItem
          ofstFileName        As Long            ' // Offset of file name
          ofstDestPath        As Long            ' // Offset of file path
          dwSizeOfFile        As Long            ' // Size of file
          ofstBeginOfData     As Long            ' // Offset of beginning data
          dwFlags             As FileFlags       ' // Flags
      End Type
       
      ' // Execute list item
      Private Type BinExecListItem
          ofstFileName        As Long            ' // Offset of file name
          ofstParameters      As Long            ' // Offset of parameters
          dwFlags             As ExeFlags        ' // Flags
      End Type
       
      ' // Storage descriptor
      Private Type BinStorageList
          dwSizeOfStructure   As Long            ' // Size of structure
          iExecutableIndex    As Long            ' // Index of main executable
          dwSizeOfItem        As Long            ' // Size of BinaryStorageItem structure
          dwNumberOfItems     As Long            ' // Number of files in storage
      End Type
       
      ' // Execute list descriptor
      Private Type BinExecList
          dwSizeOfStructure   As Long            ' // Size of structure
          dwSizeOfItem        As Long            ' // Size of BinaryExecuteItem structure
          dwNumberOfItems     As Long            ' // Number of items
      End Type
       
      ' // Base information about project
      Private Type BinProject
          dwSizeOfStructure   As Long            ' // Size of structure
          storageDescriptor   As BinStorageList  ' // Storage descriptor
          execListDescriptor  As BinExecList     ' // Command descriptor
          dwStringsTableLen   As Long            ' // Size of strings table
          dwFileTableLen      As Long            ' // Size of data table
      End Type

    Структура BinProject размещается в начале ресурсов. Заметьте что проект сохраняется как RT_RCDATA с именем PROJECT. Поле dwSizeOfStructure определяет размер структуры BinProject. storageDescriptor и execListDescriptor определяют описатели хранилища и команд соответственно. Поле dwStringsTableLen показывает размер строковой таблицы. Строковая таблица содержит все имена и команды в формате UNICODE. Поле dwFileTableLen определяет размер всех данных в хранилище. И хранилище BinStorageList и списки команд BinExecList также имеют поля dwSizeOfItem и dwSizeOfStructure которые определяют размер структуры описателя и размер одного элемента в списке. Эти структуры также содержат поле dwNumberOfItems которое показывает количество элементов в списке. Поле iExecutableIndex содержит индекс исполняемого файла в хранилище. Общая структура показана на рисунке:

    user posted image


    Любой элемент может ссылаться на таблицу строк и таблицу файлов. Для этой цели используется смещение относительно начала таблицы. Все итемы расположены одна за другой. Теперь мы знаем внутренний формат проекта и можем поговорить о том как постороить загрузчик который будет содержать эти данные. Как я уже сказал мы сохраняем данные в ресурсы загрузчика. О самом загрузчике я расскажу позднее, а сейчас я хотел бы заметить одну важную особенность. Когда мы ложим данные проекта в EXE файл загрузчика то это не затрагивает другие данные в ресурсах. Для примера, если запустить такой EXE то информация хранящаяся в ресурсах внутреннего EXE не будет загружена. Тоже самое относится к иконкам и версии приложения. Для избежания данных проблем нужно скопировать все ресурсы из внутреннего EXE в загрузчик. WinAPI предоставляет набор функций для замены ресурсов. Для того чтобы получить список ресурсов нам нужно распарсить EXE файл и извлечь данные. Я написал функцию LoadResources которая извлекает все ресурсы EXE файла в массив.

    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", но мы можем написать там любую программу:

    user posted image


    ExpandedWrap disabled
      Type IMAGE_DOS_HEADER
          e_magic                     As Integer
          e_cblp                      As Integer
          e_cp                        As Integer
          e_crlc                      As Integer
          e_cparhdr                   As Integer
          e_minalloc                  As Integer
          e_maxalloc                  As Integer
          e_ss                        As Integer
          e_sp                        As Integer
          e_csum                      As Integer
          e_ip                        As Integer
          e_cs                        As Integer
          e_lfarlc                    As Integer
          e_ovno                      As Integer
          e_res(0 To 3)               As Integer
          e_oemid                     As Integer
          e_oeminfo                   As Integer
          e_res2(0 To 9)              As Integer
          e_lfanew                    As Long
      End Type

    Но поскольку мы не пишем DOS программы для нас эта структура не важна. Нам интересно только поля e_magic и e_lfanew. Первое поле должно содержать сигнатуру 'MZ' aka. IMAGE_DOS_SIGNATURE а второе смещение до очень важной структуры IMAGE_NT_HEADERS:
    ExpandedWrap disabled
      Type IMAGE_NT_HEADERS
          Signature                       As Long
          FileHeader                      As IMAGE_FILE_HEADER
          OptionalHeader                  As IMAGE_OPTIONAL_HEADER
      End Type

    Первое поле этой структуры содержит сигнатуру 'PE\0\0' (aka. IMAGE_NT_SIGNATURE). Следующее поле описывает исполняемый файл и имеет следующий формат:
    ExpandedWrap disabled
      Type IMAGE_FILE_HEADER
          Machine                         As Integer
          NumberOfSections                As Integer
          TimeDateStamp                   As Long
          PointerToSymbolTable            As Long
          NumberOfSymbols                 As Long
          SizeOfOptionalHeader            As Integer
          Characteristics                 As Integer
      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 имеет следующий формат:
    ExpandedWrap disabled
      Type IMAGE_OPTIONAL_HEADER
          Magic                           As Integer
          MajorLinkerVersion              As Byte
          MinorLinkerVersion              As Byte
          SizeOfCode                      As Long
          SizeOfInitializedData           As Long
          SizeOfUnitializedData           As Long
          AddressOfEntryPoint             As Long
          BaseOfCode                      As Long
          BaseOfData                      As Long
          ImageBase                       As Long
          SectionAlignment                As Long
          FileAlignment                   As Long
          MajorOperatingSystemVersion     As Integer
          MinorOperatingSystemVersion     As Integer
          MajorImageVersion               As Integer
          MinorImageVersion               As Integer
          MajorSubsystemVersion           As Integer
          MinorSubsystemVersion           As Integer
          W32VersionValue                 As Long
          SizeOfImage                     As Long
          SizeOfHeaders                   As Long
          CheckSum                        As Long
          SubSystem                       As Integer
          DllCharacteristics              As Integer
          SizeOfStackReserve              As Long
          SizeOfStackCommit               As Long
          SizeOfHeapReserve               As Long
          SizeOfHeapCommit                As Long
          LoaderFlags                     As Long
          NumberOfRvaAndSizes             As Long
          DataDirectory(15)               As IMAGE_DATA_DIRECTORY
      End Type

    Первое поле содержит тип образа (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. Рассмотрим формат заголовка секции:
    ExpandedWrap disabled
      Type IMAGE_SECTION_HEADER
          SectionName(7)              As Byte
          VirtualSize                 As Long
          VirtualAddress              As Long
          SizeOfRawData               As Long
          PointerToRawData            As Long
          PointerToRelocations        As Long
          PointerToLinenumbers        As Long
          NumberOfRelocations         As Integer
          NumberOfLinenumbers         As Integer
          Characteristics             As Long
      End Type

    Первое поле содержит имя секции в формате UTF-8 c завершающим нуль-терминалом. Это имя ограничено 8-ю символами (если имя секции имеет размер 8 символов то нуль-терминатор игнорируется). COFF файл может иметь имя больше чем 8 символов в этом случае имя начинается с символа '/' за которым следует ASCII строка с десятичным значением смещения в строковой таблице (поле IMAGE_FILE_HEADER). PE файл не поддерживает длинные имена секций. Поля VirtualSize и VirtualAddress содержат размер секции в памяти и адрес (RVA). Поля SizeOfRawData и PointerToRawData содержат RAW адрес данных в файле (если секция содержит инициализированные данные). Это ключевой момент потому что мы можем вычислить RAW адрес с помощью относительного виртуального адреса используя информацию из заголовка секций. Я написал функцию для перевода RVA адресации в RAW смещение в файле:
    ExpandedWrap disabled
      ' // RVA to RAW
      Function RVA2RAW( _
                       ByVal rva As Long, _
                       ByRef sec() As IMAGE_SECTION_HEADER) As Long
          Dim index As Long
          
          For index = 0 To UBound(sec)
              
              If rva >= sec(index).VirtualAddress And _
                 rva < sec(index).VirtualAddress + sec(index).VirtualSize Then
                  RVA2RAW = sec(index).PointerToRawData + (rva - sec(index).VirtualAddress)
                  Exit Function
              End If
              
          Next
          
          RVA2RAW = rva
          
      End Function

    Эта функция перечисляет все секции и проверяет если переданный адрес находится в пределах секции. Следующие 5 полей используються только в COFF файлах и не важны в PE файлах. Поле Characteristics содержит атрибуты секции такие как права доступа к памяти и управление. Мы будем использовать это поле для защиты памяти exe файла в загрузчике.
    Давайте теперь вернемся к каталогам данных. Как мы видели существует 16 элементов в данном каталоге. Обычно PE файл не использует их все. Давайте рассмотрим структуру элемента каталога:
    ExpandedWrap disabled
      Private Type IMAGE_DATA_DIRECTORY
          VirtualAddress                  As Long
          Size                            As Long
      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 таблицы ресурсов которая имеет следующий формат:
    ExpandedWrap disabled
      Type IMAGE_RESOURCE_DIRECTORY
          Characteristics             As Long
          TimeDateStamp               As Long
          MajorVersion                As Integer
          MinorVersion                As Integer
          NumberOfNamedEntries        As Integer
          NumberOfIdEntries           As Integer
      End Type

    Эта структура описывает все ресурсы в PE файле. Первые 4 поля не важны для нас; поле NumberOfNamedEntries и NumberOfIdEntries содержат количество именованных записей и записей с числовыми идентификаторами соответственно. Для примера, когда мы добавляем картинку в стандартном редакторе это добавит запись с числовым идентификатором равным 2 (RT_BITMAP). Сами записи расположены сразу после IMAGE_RESOURCE_DIRECTORY и имеют следующую структуру:
    ExpandedWrap disabled
      Type IMAGE_RESOURCE_DIRECTORY_ENTRY
          NameId                      As Long
          OffsetToData                As Long
      End Type

    Первое поле этой структуры определяет является ли это именованной запись либо это запись с числовым идентификатором в зависимости от старшего бита. Если этот бит установлен то остальные биты определяют смещение от начала ресурсов к структуре IMAGE_RESOURCE_DIR_STRING_U которая имет следующий формат:
    ExpandedWrap disabled
      Type IMAGE_RESOURCE_DIR_STRING_U
          Length                      As Integer
          NameString                  As String
      End Type

    Заметьте что это не правильная VB-структура и показана для наглядности. Первые два байта являются беззнаковым целым которые показывают длину строки в формате UNICODE (в символах) которая следует за ними. Таким образом для того чтобы получить строку нам нужно прочитать первые два байта с размером, выделить память для строки согласно этого размера и прочитать данные в строковую переменную. Напротив, если старший бит поля NameId сброшен то оно содержит числовой идентификатор ресурса (RT_BITMAP в примере). Поле OffsetToData имеет также двойную интерпретацию. Если старший бит установлен то это смещение (от начала ресурсов) до следующего уровня дерева ресурсов, т.е. до структуры IMAGE_RESOURCE_DIRECTORY. Иначе - это смещение до структуры IMAGE_RESOURCE_DATA_ENTRY:
    ExpandedWrap disabled
      Type IMAGE_RESOURCE_DATA_ENTRY
          OffsetToData                As Long
          Size                        As Long
          CodePage                    As Long
          Reserved                    As Long
      End Type

    Наиболее важными для нас являются поля OffsetToData and Size которые содержат RVA и размер сырых данных ресурса. Теперь мы можем извлечь все данные из ресурсов любого PE файла.

    Компиляция.


    Итак, когда мы начинаем компиляцию проекта то вызывается метод 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:
    ExpandedWrap disabled
      ' // Startup subroutine
      Sub Main()
       
          ' // Load constants
          If Not LoadConstants Then
              MessageBox 0, GetString(MID_ERRORLOADINGCONST), 0, MB_ICONERROR Or MB_SYSTEMMODAL
              GoTo EndOfProcess
          End If
          
          ' // Load project
          If Not ReadProject Then
              MessageBox 0, GetString(MID_ERRORREADINGPROJECT), 0, MB_ICONERROR Or MB_SYSTEMMODAL
              GoTo EndOfProcess
          End If
          
          ' // Copying from storage
          If Not CopyProcess Then GoTo EndOfProcess
          
          ' // Execution process
          If Not ExecuteProcess Then GoTo EndOfProcess
          
          ' // If main executable is not presented exit
          If ProjectDesc.storageDescriptor.iExecutableIndex = -1 Then GoTo EndOfProcess
          
          ' // Run exe from memory
          If Not RunProcess Then
              ' // Error occrurs
              MessageBox 0, GetString(MID_ERRORSTARTUPEXE), 0, MB_ICONERROR Or MB_SYSTEMMODAL
          End If
          
      EndOfProcess:
          
          If pProjectData Then
              HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
          End If
          
          ExitProcess 0
          
      End Sub

    Вначале вызывается функция LoadConstants для того чтобы загрузить все необходимые константы из ресурсов:
    ExpandedWrap disabled
      ' // modConstants.bas - main module for loading constants
      ' // © Krivous Anatoly Anatolevich (The trick), 2016
       
      Option Explicit
       
      Public Enum MessagesID
          MID_ERRORLOADINGCONST = 100     ' // Errors
          MID_ERRORREADINGPROJECT = 101   '
          MID_ERRORCOPYINGFILE = 102      '
          MID_ERRORWIN32 = 103            '
          MID_ERROREXECUTELINE = 104      '
          MID_ERRORSTARTUPEXE = 105       '
          PROJECT = 200                   ' // Project resource ID
          API_LIB_KERNEL32 = 300          ' // Library names
          API_LIB_NTDLL = 350             '
          API_LIB_USER32 = 400            '
          MSG_LOADER_ERROR = 500
      End Enum
       
      ' // Paths
       
      Public pAppPath  As Long            ' // Path to application
      Public pSysPath  As Long            ' // Path to System32
      Public pTmpPath  As Long            ' // Path to Temp
      Public pWinPath  As Long            ' // Path to Windows
      Public pDrvPath  As Long            ' // Path to system drive
      Public pDtpPath  As Long            ' // Path to desktop
       
      ' // Substitution constants
       
      Public pAppRepl  As Long
      Public pSysRepl  As Long
      Public pTmpRepl  As Long
      Public pWinRepl  As Long
      Public pDrvRepl  As Long
      Public pDtpRepl  As Long
      Public pStrNull  As Long            ' // \0
       
      Public hInstance    As Long         ' // Base address
      Public lpCmdLine    As Long         ' // Command line
      Public SI           As STARTUPINFO  ' // Startup parameters
      Public LCID         As Long         ' // LCID
       
      ' // Load constants
      Function LoadConstants() As Boolean
          Dim lSize   As Long
          Dim pBuf    As Long
          Dim index   As Long
          Dim ctl     As tagINITCOMMONCONTROLSEX
          
          ' // Load windows classes
          ctl.dwSize = Len(ctl)
          ctl.dwICC = &H3FFF&
          InitCommonControlsEx ctl
          
          ' // Get startup parameters
          GetStartupInfo SI
          
          ' // Get command line
          lpCmdLine = GetCommandLine()
          
          ' // Get base address
          hInstance = GetModuleHandle(ByVal 0&)
          
          ' // Get LCID
          LCID = GetUserDefaultLCID()
          
          ' // Alloc memory for strings
          pBuf = SysAllocStringLen(0, MAX_PATH)
          If pBuf = 0 Then Exit Function
          
          ' // Get path to process file name
          If GetModuleFileName(hInstance, pBuf, MAX_PATH) = 0 Then GoTo CleanUp
          
          ' // Leave only directory
          PathRemoveFileSpec pBuf
          
          ' // Save path
          pAppPath = SysAllocString(pBuf)
          
          ' // Get Windows folder
          If GetWindowsDirectory(pBuf, MAX_PATH) = 0 Then GoTo CleanUp
          pWinPath = SysAllocString(pBuf)
          
          ' // Get System32 folder
          If GetSystemDirectory(pBuf, MAX_PATH) = 0 Then GoTo CleanUp
          pSysPath = SysAllocString(pBuf)
          
          ' // Get Temp directory
          If GetTempPath(MAX_PATH, pBuf) = 0 Then GoTo CleanUp
          pTmpPath = SysAllocString(pBuf)
          
          ' // Get system drive
          PathStripToRoot pBuf
          pDrvPath = SysAllocString(pBuf)
          
          ' // Get desktop path
          If SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, 0, SHGFP_TYPE_CURRENT, pBuf) Then GoTo CleanUp
          pDtpPath = SysAllocString(pBuf)
          
          ' // Load wildcards
          For index = 1 To 6
              If LoadString(hInstance, index, pBuf, MAX_PATH) = 0 Then GoTo CleanUp
              Select Case index
              Case 1: pAppRepl = SysAllocString(pBuf)
              Case 2: pSysRepl = SysAllocString(pBuf)
              Case 3: pTmpRepl = SysAllocString(pBuf)
              Case 4: pWinRepl = SysAllocString(pBuf)
              Case 5: pDrvRepl = SysAllocString(pBuf)
              Case 6: pDtpRepl = SysAllocString(pBuf)
              End Select
          Next
          
          ' // vbNullChar
          pStrNull = SysAllocStringLen(0, 0)
       
          ' // Success
          LoadConstants = True
          
      CleanUp:
          
          If pBuf Then SysFreeString pBuf
          
      End Function
       
      ' // Obtain string from resource (it should be less or equal MAX_PATH)
      Public Function GetString( _
                      ByVal ID As MessagesID) As Long
                      
          GetString = SysAllocStringLen(0, MAX_PATH)
          
          If GetString Then
          
              If LoadString(hInstance, ID, GetString, MAX_PATH) = 0 Then SysFreeString GetString: GetString = 0: Exit Function
              If SysReAllocString(GetString, GetString) = 0 Then SysFreeString GetString: GetString = 0: Exit Function
              
          End If
          
      End Function

    Функция LoadConstants загружает все необходимые переменные и строки (hInstance, LCID, командная строка, подстановочные символы, пути по умолчанию, и т.д.). Все строки сохраняются в формате UNICODE-BSTR. Функция GetString загружает строку из ресурсов по ее идентификатору. Перечисление MessagesID содержит некоторые строковые идентификаторы нужные для работы программы (сообщения об ошибках, имена библиотек, и.т.д.). Когда все константы загрузятся вызывается функция ReadProject которая загружает проект:
    ExpandedWrap disabled
      ' // Load project
      Function ReadProject() As Boolean
          Dim hResource       As Long:                Dim hMememory       As Long
          Dim lResSize        As Long:                Dim pRawData        As Long
          Dim status          As Long:                Dim pUncompressed   As Long
          Dim lUncompressSize As Long:                Dim lResultSize     As Long
          Dim tmpStorageItem  As BinStorageListItem:  Dim tmpExecuteItem  As BinExecListItem
          Dim pLocalBuffer    As Long
          
          ' // Load resource
          hResource = FindResource(hInstance, GetString(PROJECT), RT_RCDATA)
          If hResource = 0 Then GoTo CleanUp
          
          hMememory = LoadResource(hInstance, hResource)
          If hMememory = 0 Then GoTo CleanUp
          
          lResSize = SizeofResource(hInstance, hResource)
          If lResSize = 0 Then GoTo CleanUp
          
          pRawData = LockResource(hMememory)
          If pRawData = 0 Then GoTo CleanUp
          
          pLocalBuffer = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lResSize)
          If pLocalBuffer = 0 Then GoTo CleanUp
          
          ' // Copy to local buffer
          CopyMemory ByVal pLocalBuffer, ByVal pRawData, lResSize
          
          ' // Set default size
          lUncompressSize = lResSize * 2
          
          ' // Do decompress...
          Do
              
              If pUncompressed Then
                  pUncompressed = HeapReAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, ByVal pUncompressed, lUncompressSize)
              Else
                  pUncompressed = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lUncompressSize)
              End If
              
              status = RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, _
                                           ByVal pUncompressed, lUncompressSize, _
                                           ByVal pLocalBuffer, lResSize, lResultSize)
              
              lUncompressSize = lUncompressSize * 2
              
          Loop While status = STATUS_BAD_COMPRESSION_BUFFER
          
          pProjectData = pUncompressed
          
          If status Then GoTo CleanUp
       
          ' // Validation check
          If lResultSize < LenB(ProjectDesc) Then GoTo CleanUp
          
          ' // Copy descriptor
          CopyMemory ProjectDesc, ByVal pProjectData, LenB(ProjectDesc)
          
          ' // Check all members
          If ProjectDesc.dwSizeOfStructure <> Len(ProjectDesc) Then GoTo CleanUp
          If ProjectDesc.storageDescriptor.dwSizeOfStructure <> Len(ProjectDesc.storageDescriptor) Then GoTo CleanUp
          If ProjectDesc.storageDescriptor.dwSizeOfItem <> Len(tmpStorageItem) Then GoTo CleanUp
          If ProjectDesc.execListDescriptor.dwSizeOfStructure <> Len(ProjectDesc.execListDescriptor) Then GoTo CleanUp
          If ProjectDesc.execListDescriptor.dwSizeOfItem <> Len(tmpExecuteItem) Then GoTo CleanUp
          
          ' // Initialize pointers
          pStoragesTable = pProjectData + ProjectDesc.dwSizeOfStructure
          pExecutesTable = pStoragesTable + ProjectDesc.storageDescriptor.dwSizeOfItem * ProjectDesc.storageDescriptor.dwNumberOfItems
          pFilesTable = pExecutesTable + ProjectDesc.execListDescriptor.dwSizeOfItem * ProjectDesc.execListDescriptor.dwNumberOfItems
          pStringsTable = pFilesTable + ProjectDesc.dwFileTableLen
          
          ' // Check size
          If (pStringsTable + ProjectDesc.dwStringsTableLen - pProjectData) <> lResultSize Then GoTo CleanUp
          
          ' // Success
          ReadProject = True
          
      CleanUp:
          
          If pLocalBuffer Then HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pLocalBuffer
          
          If Not ReadProject And pProjectData Then
              HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
          End If
          
      End Function

    Как можно увидеть я использую кучу процесса вместо массивов. Вначале загружается ресурс с проектом - PROJECT и копируется в кучу, затем производится декомпрессия используя функцию RtlDecompressBuffer. Эта функция не возвращает необходимый размер буфера поэтому мы пытаемся распаковать буфер увеличивая выходной размер буфера пока декомпрессия не будет успешно выполнена. После декомпрессии проверяются все параметры и инициализируются глобальные указатели проекта.
    Если проект успешно загружен то вызывается функция CopyProcess которая распаковывает все файлы из хранилища, согласно данным проекта:
    ExpandedWrap disabled
      ' // Copying process
      Function CopyProcess() As Boolean
          Dim bItem       As BinStorageListItem:  Dim index       As Long
          Dim pPath       As Long:                Dim dwWritten   As Long
          Dim msg         As Long:                Dim lStep       As Long
          Dim isError     As Boolean:             Dim pItem       As Long
          Dim pErrMsg     As Long:                Dim pTempString As Long
          
          ' // Set pointer
          pItem = pStoragesTable
          
          ' // Go thru file list
          For index = 0 To ProjectDesc.storageDescriptor.dwNumberOfItems - 1
       
              ' // Copy file descriptor
              CopyMemory bItem, ByVal pItem, Len(bItem)
              
              ' // Next item
              pItem = pItem + ProjectDesc.storageDescriptor.dwSizeOfItem
              
              ' // If it is not main executable
              If index <> ProjectDesc.storageDescriptor.iExecutableIndex Then
              
                  ' // Normalize path
                  pPath = NormalizePath(pStringsTable + bItem.ofstDestPath, pStringsTable + bItem.ofstFileName)
                  
                  ' // Error occurs
                  If pPath = 0 Then
                  
                      pErrMsg = GetString(MID_ERRORWIN32)
                      MessageBox 0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL
                      GoTo CleanUp
                      
                  Else
                      Dim hFile   As Long
                      Dim disp    As CREATIONDISPOSITION
                      
                      ' // Set overwrite flags
                      If bItem.dwFlags And FF_REPLACEONEXIST Then disp = CREATE_ALWAYS Else disp = CREATE_NEW
                      
                      ' // Set number of subroutine
                      lStep = 0
                      
                      ' // Run subroutines
                      Do
                          ' // Disable error flag
                          isError = False
                          
                          ' // Free string
                          If pErrMsg Then SysFreeString pErrMsg: pErrMsg = 0
                          
                          ' // Choose subroutine
                          Select Case lStep
                          Case 0  ' // 0. Create folder
                          
                              If Not CreateSubdirectories(pPath) Then isError = True
                              
                          Case 1  ' // 1. Create file
                          
                              hFile = CreateFile(pPath, FILE_GENERIC_WRITE, 0, ByVal 0&, disp, FILE_ATTRIBUTE_NORMAL, 0)
                              If hFile = INVALID_HANDLE_VALUE Then
                                  If GetLastError = ERROR_FILE_EXISTS Then Exit Do
                                  isError = True
                              End If
                              
                          Case 2  ' // 2. Copy data to file
                          
                              If WriteFile(hFile, ByVal pFilesTable + bItem.ofstBeginOfData, _
                                           bItem.dwSizeOfFile, dwWritten, ByVal 0&) = 0 Then isError = True
                                          
                              If dwWritten <> bItem.dwSizeOfFile Then
                                  isError = True
                              Else
                                  CloseHandle hFile: hFile = INVALID_HANDLE_VALUE
                              End If
                              
                          End Select
                          
                          ' // If error occurs show notification (retry, abort, ignore)
                          If isError Then
                          
                              ' // Ignore error
                              If bItem.dwFlags And FF_IGNOREERROR Then Exit Do
       
                              pTempString = GetString(MID_ERRORCOPYINGFILE)
                              pErrMsg = StrCat(pTempString, pPath)
                              
                              ' // Cleaning
                              SysFreeString pTempString: pTempString = 0
                              
                              Select Case MessageBox(0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL Or MB_CANCELTRYCONTINUE)
                              Case MESSAGEBOXRETURN.IDCONTINUE: Exit Do
                              Case MESSAGEBOXRETURN.IDTRYAGAIN
                              Case Else:  GoTo CleanUp
                              End Select
                              
                          Else: lStep = lStep + 1
                          End If
                          
                      Loop While lStep <= 2
                              
                      If hFile <> INVALID_HANDLE_VALUE Then
                          CloseHandle hFile: hFile = INVALID_HANDLE_VALUE
                      End If
                      
                      ' // Cleaning
                      SysFreeString pPath: pPath = 0
                      
                  End If
                  
              End If
              
          Next
          
          ' // Success
          CopyProcess = True
          
      CleanUp:
          
          If pTempString Then SysFreeString pTempString
          If pErrMsg Then SysFreeString pErrMsg
          If pPath Then SysFreeString pPath
          
          If hFile <> INVALID_HANDLE_VALUE Then
              CloseHandle hFile
              hFile = INVALID_HANDLE_VALUE
          End If
          
      End Function

    Эта процедура проходит по всем элементам хранилища и распаковывает их одна за одной исключая главный исполняемый файл. Функция NormalizePath заменяет подстановочные знаки на реальные пути. Также существует функция CreateSubdirectories которая создает промежуточные директории (если необходимо) по переданному в качестве параметра пути. Затем вызывается функция CreateFile для создания файла затем через WriteFile данные пишутся в файл. Если происходит ошибка то выводится стандартное сообщение с предложением повторить, отменить или игнорировать.
    ExpandedWrap disabled
      ' // Create all subdirectories by path
      Function CreateSubdirectories( _
                      ByVal pPath As Long) As Boolean
          Dim pComponent As Long
          Dim tChar      As Integer
          
          ' // Pointer to first char
          pComponent = pPath
          
          ' // Go thru path components
          Do
          
              ' // Get next component
              pComponent = PathFindNextComponent(pComponent)
              
              ' // Check if end of line
              CopyMemory tChar, ByVal pComponent, 2
              If tChar = 0 Then Exit Do
              
              ' // Write null-terminator
              CopyMemory ByVal pComponent - 2, 0, 2
              
              ' // Check if path exists
              If PathIsDirectory(pPath) = 0 Then
              
                  ' // Create folder
                  If CreateDirectory(pPath, ByVal 0&) = 0 Then
                      ' // Error
                      CopyMemory ByVal pComponent - 2, &H5C, 2
                      Exit Function
                  End If
                  
              End If
              
              ' // Restore path delimiter
              CopyMemory ByVal pComponent - 2, &H5C, 2
              
          Loop
          
          ' // Success
          CreateSubdirectories = True
          
      End Function
       
      ' // Get normalize path (replace wildcards, append file name)
      Function NormalizePath( _
                      ByVal pPath As Long, _
                      ByVal pTitle As Long) As Long
          Dim lPathLen    As Long:    Dim lRelacerLen As Long
          Dim lTitleLen   As Long:    Dim pRelacer    As Long
          Dim lTotalLen   As Long:    Dim lPtr        As Long
          Dim pTempString As Long:    Dim pRetString  As Long
          
          ' // Determine wildcard
          Select Case True
          Case IntlStrEqWorker(0, pPath, pAppRepl, 5): pRelacer = pAppPath
          Case IntlStrEqWorker(0, pPath, pSysRepl, 5): pRelacer = pSysPath
          Case IntlStrEqWorker(0, pPath, pTmpRepl, 5): pRelacer = pTmpPath
          Case IntlStrEqWorker(0, pPath, pWinRepl, 5): pRelacer = pWinPath
          Case IntlStrEqWorker(0, pPath, pDrvRepl, 5): pRelacer = pDrvPath
          Case IntlStrEqWorker(0, pPath, pDtpRepl, 5): pRelacer = pDtpPath
          Case Else: pRelacer = pStrNull
          End Select
          
          ' // Get string size
          lPathLen = lstrlen(ByVal pPath)
          lRelacerLen = lstrlen(ByVal pRelacer)
          
          ' // Skip wildcard
          If lRelacerLen Then
              pPath = pPath + 5 * 2
              lPathLen = lPathLen - 5
          End If
          
          If pTitle Then lTitleLen = lstrlen(ByVal pTitle)
          
          ' // Get length all strings
          lTotalLen = lPathLen + lRelacerLen + lTitleLen
          
          ' // Check overflow (it should be les or equal MAX_PATH)
          If lTotalLen > MAX_PATH Then Exit Function
          
          ' // Create string
          pTempString = SysAllocStringLen(0, MAX_PATH)
          If pTempString = 0 Then Exit Function
          
          ' // Copy
          lstrcpyn ByVal pTempString, ByVal pRelacer, lRelacerLen + 1
          lstrcat ByVal pTempString, ByVal pPath
       
          ' // If title is presented append
          If pTitle Then
       
              ' // Error
              If PathAddBackslash(pTempString) = 0 Then GoTo CleanUp
       
              ' // Copy file name
              lstrcat ByVal pTempString, ByVal pTitle
              
          End If
          
          ' // Alloc memory for translation relative path to absolute
          pRetString = SysAllocStringLen(0, MAX_PATH)
          If pRetString = 0 Then GoTo CleanUp
          
          ' // Normalize
          If PathCanonicalize(pRetString, pTempString) = 0 Then GoTo CleanUp
          
          NormalizePath = pRetString
          
      CleanUp:
          
          If pTempString Then SysFreeString pTempString
          If pRetString <> 0 And NormalizePath = 0 Then SysFreeString pRetString
          
      End Function
       
      ' // Concatenation strings
      Function StrCat( _
                      ByVal pStringDest As Long, _
                      ByVal pStringAppended As Long) As Long
          Dim l1 As Long, l2 As Long
          
          l1 = lstrlen(ByVal pStringDest): l2 = lstrlen(ByVal pStringAppended)
          StrCat = SysAllocStringLen(0, l1 + l2)
          
          If StrCat = 0 Then Exit Function
          
          lstrcpyn ByVal StrCat, ByVal pStringDest, l1 + 1
          lstrcat ByVal StrCat, ByVal pStringAppended
          
      End Function

    После извлечения файлов вызывается функция ExecuteProcess которая запускает выполнение команд используя функцию ShellExecuteEx:
    ExpandedWrap disabled
      ' // Execution command process
      Function ExecuteProcess() As Boolean
          Dim index       As Long:                Dim bItem       As BinExecListItem
          Dim pPath       As Long:                Dim pErrMsg     As Long
          Dim shInfo      As SHELLEXECUTEINFO:    Dim pTempString As Long
          Dim pItem       As Long:                Dim status      As Long
       
          ' // Set pointer and size
          shInfo.cbSize = Len(shInfo)
          pItem = pExecutesTable
          
          ' // Go thru all items
          For index = 0 To ProjectDesc.execListDescriptor.dwNumberOfItems - 1
          
              ' // Copy item
              CopyMemory bItem, ByVal pItem, ProjectDesc.execListDescriptor.dwSizeOfItem
              
              ' // Set pointer to next item
              pItem = pItem + ProjectDesc.execListDescriptor.dwSizeOfItem
              
              ' // Normalize path
              pPath = NormalizePath(pStringsTable + bItem.ofstFileName, 0)
              
              ' // Fill SHELLEXECUTEINFO
              shInfo.lpFile = pPath
              shInfo.lpParameters = pStringsTable + bItem.ofstParameters
              shInfo.fMask = SEE_MASK_NOCLOSEPROCESS Or SEE_MASK_FLAG_NO_UI
              shInfo.nShow = SW_SHOWDEFAULT
              
              ' // Performing...
              status = ShellExecuteEx(shInfo)
              
              ' // If error occurs show notification (retry, abort, ignore)
              Do Until status
                  
                  If pErrMsg Then SysFreeString pErrMsg: pErrMsg = 0
                  
                  ' // Ignore error
                  If bItem.dwFlags And EF_IGNOREERROR Then
                      Exit Do
                  End If
                              
                  pTempString = GetString(MID_ERROREXECUTELINE)
                  pErrMsg = StrCat(pTempString, pPath)
                  
                  SysFreeString pTempString: pTempString = 0
                  
                  Select Case MessageBox(0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL Or MB_CANCELTRYCONTINUE)
                  Case MESSAGEBOXRETURN.IDCONTINUE: Exit Do
                  Case MESSAGEBOXRETURN.IDTRYAGAIN
                  Case Else: GoTo CleanUp
                  End Select
       
                  status = ShellExecuteEx(shInfo)
                  
              Loop
              
              ' // Wait for process terminaton
              WaitForSingleObject shInfo.hProcess, INFINITE
              CloseHandle shInfo.hProcess
              
          Next
          
          ' // Success
          ExecuteProcess = True
          
      CleanUp:
       
          If pTempString Then SysFreeString pTempString
          If pErrMsg Then SysFreeString pErrMsg
          If pPath Then SysFreeString pPath
          
      End Function
    Сообщение отредактировано: TheTrik -
      Эта функция похожа на предыдущую за исключением того что здесь используется функция ShellExecuteEx вместо извлечения. Обратите внимание что каждая операция выполняется синхронно, т.е. каждый вызов процедуры ShellExecuteEx ждет окончания выполнения команды.
      Если предыдущая функция выполнилась успешно тогда вызывается функция RunProcess которая подготовливает данные для исполнения главного исполняемого файла из памяти:
      ExpandedWrap disabled
        ' // Run exe from project in memory
        Function RunProcess() As Boolean
            Dim bItem       As BinStorageListItem:  Dim Length      As Long
            Dim pFileData   As Long
            
            ' // Get descriptor of executable file
            CopyMemory bItem, ByVal pStoragesTable + ProjectDesc.storageDescriptor.dwSizeOfItem * _
                              ProjectDesc.storageDescriptor.iExecutableIndex, Len(bItem)
            
         
            ' // Alloc memory within top memory addresses
            pFileData = VirtualAlloc(ByVal 0&, bItem.dwSizeOfFile, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_READWRITE)
            If pFileData = 0 Then Exit Function
            
            ' // Copy raw exe file to this memory
            CopyMemory ByVal pFileData, ByVal pFilesTable + bItem.ofstBeginOfData, bItem.dwSizeOfFile
            
            ' // Free decompressed project data
            HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
            pProjectData = 0
            
            ' // Run exe from memory
            RunExeFromMemory pFileData, bItem.dwFlags And FF_IGNOREERROR
            
            ' ----------------------------------------------------
            ' // An error occurs
            ' // Clean memory
            
            VirtualFree ByVal pFileData, 0, MEM_RELEASE
            
            ' // If ignore error then success
            If bItem.dwFlags And FF_IGNOREERROR Then RunProcess = True
            
        End Function

      Эта процедура выделяет память в верхних областях виртуального адресного пространства (поскольку большинство EXE файлов грузятся по довольно низким адресам (обычно 0x00400000). После этого очишается память данных проекта поскольку если EXE файл запустится, то эта память не будет освобождена, затем вызывается функция RunExeFromMemory которая делает следующий шаг в загрузке EXE из памяти. Если по какой-либо причине загрузка EXE файла не состоялась то освобождается выделенная память и управление передается функции Main. Итак, для того чтобы загрузить EXE файл нам нужно освободить память загрузчика, т.е. выгрузить загрузчик. Нам нужно только оставить маленькуий кусочек кода который будет загружать EXE файл и запускать его. Для этого я решил использовать шеллкод, хотя можно использовать и DLL. Шеллкод - это маленький базонезависимый код (код который не ссылается к внешним данным). Но в любом случае нам придется обеспечить доступ к API функциям из шеллкода. Мы не можем вызывать API функции непосредственно из шеллкода поскольку наш главный исполняемый файл будет выгружен и любое обращение к таблице импорта вызовет креш. Второе ограничение - это то что инструкция call использует относительное смещение (это наиболее частый случай). Из этого следует что нам нужно инициализировать некие "трамплины" которые будут перебрасывать нас на API функции. Я решил делать это посредством сплайсинга. Я просто заменяю первые 5 байт функции пусттышки на ассемблерную инструкцию jmp которая ссылается на необходимую API функцию:
      ExpandedWrap disabled
        ' // Run EXE file by memory address
        Function RunExeFromMemory( _
                        ByVal pExeData As Long, _
                        ByVal IgnoreError As Boolean) As Boolean
            Dim Length  As Long:    Dim pCode       As Long
            Dim pszMsg  As Long:    Dim pMsgTable   As Long
            Dim index   As Long:    Dim pCurMsg     As Long
            
            ' // Get size of shellcode
            Length = GetAddr(AddressOf ENDSHELLLOADER) - GetAddr(AddressOf BEGINSHELLLOADER)
            
            ' // Alloc memory within top addresses
            pCode = VirtualAlloc(ByVal 0&, Length, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_EXECUTE_READWRITE)
            
            ' // Copy shellcode to allocated memory
            CopyMemory ByVal pCode, ByVal GetAddr(AddressOf BEGINSHELLLOADER), Length
            
            ' // Initialization of shellcode
            If Not InitShellLoader(pCode) Then GoTo CleanUp
            
            ' // Splice CallLoader function in order to call shellcode
            Splice AddressOf CallLoader, pCode + GetAddr(AddressOf LoadExeFromMemory) - GetAddr(AddressOf BEGINSHELLLOADER)
            
            ' // Check ignore errors
            If Not IgnoreError Then
                
                ' // Alloc memory for messages table
                pMsgTable = VirtualAlloc(ByVal 0&, 1024, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_READWRITE)
                If pMsgTable = 0 Then GoTo CleanUp
                
                ' // Skip pointers
                pCurMsg = pMsgTable + EM_END * 4
                
                For index = 0 To EM_END - 1
                
                    ' // Load message string
                    pszMsg = GetString(MSG_LOADER_ERROR + index)
                    If pszMsg = 0 Then GoTo CleanUp
                    
                    Length = SysStringLen(pszMsg)
         
                    lstrcpyn ByVal pCurMsg, ByVal pszMsg, Length + 1
                    
                    ' // Store pointer
                    CopyMemory ByVal pMsgTable + index * 4, pCurMsg, Len(pCurMsg)
                    
                    ' // Next message offset
                    pCurMsg = pCurMsg + (Length + 1) * 2
                    
                    SysFreeString pszMsg
                    
                Next
                
            End If
            
            ' // Call shellcode
            CallLoader pExeData, pCode, pMsgTable
            
        CleanUp:
            
            If pMsgTable Then
                VirtualFree ByVal pMsgTable, 0, MEM_RELEASE
            End If
            
            If pCode Then
                VirtualFree ByVal pCode, 0, MEM_RELEASE
            End If
            
        End Function

      Как видно из кода он вычисляет размер шеллкода используя разницу между крайними функциями - ENDSHELLLOADER и BEGINSHELLLOADER. Эти функции должны окружать наш шеллкод и иметь разный прототип поскольку VB6 компилятор может объединять идентичные функции. Затем выделяется память для самого шеллкода и он копируется в эту область памяти. После этого вызывается функция InitShellLoader которая сплайсит все функции в шеллкоде:
      ExpandedWrap disabled
        ' // Shellcode initialization
        Function InitShellLoader( _
                         ByVal pShellCode As Long) As Boolean
            Dim hLib    As Long:        Dim sName   As Long
            Dim sFunc   As Long:        Dim lpAddr  As Long
            Dim libIdx  As Long:        Dim fncIdx  As Long
            Dim libName As MessagesID:  Dim fncName As MessagesID
            Dim fncSpc  As Long:        Dim splAddr As Long
            
            ' // +----------------------------------------------------------------+
            ' // |                  Fixing of API addresses                       |
            ' // +----------------------------------------------------------------+
            ' // | In order to call api function from shellcode i use splicing of |
            ' // |    our VB functions and redirect call to corresponding api.    |
            ' // |     I did same in the code that injects to other process.      |
            ' // +----------------------------------------------------------------+
            
            splAddr = GetAddr(AddressOf tVirtualAlloc) - GetAddr(AddressOf BEGINSHELLLOADER) + pShellCode
            
            ' // Get size in bytes between stub functions
            fncSpc = GetAddr(AddressOf tVirtualProtect) - GetAddr(AddressOf tVirtualAlloc)
         
            ' // Use 3 library: kernel32, ntdll и user32
            For libIdx = 0 To 2
            
                ' // Get number of imported functions depending on library
                Select Case libIdx
                Case 0: libName = API_LIB_KERNEL32: fncIdx = 13
                Case 1: libName = API_LIB_NTDLL:    fncIdx = 1
                Case 2: libName = API_LIB_USER32:   fncIdx = 1
                End Select
                
                ' // Get library name from resources
                sName = GetString(libName): If sName = 0 Then Exit Function
                
                ' // Get module handle
                hLib = GetModuleHandle(ByVal sName): If hLib = 0 Then Exit Function
                SysFreeString sName
                
                ' // Go thru functions
                Do While fncIdx
                
                    libName = libName + 1
                    ' // Get function name
                    sName = GetString(libName): If sName = 0 Then Exit Function
                    
                    ' // Because of GetProcAddress works with ANSI string translate it to ANSI
                    sFunc = ToAnsi(sName): If sFunc = 0 Then Exit Function
                    
                    ' // Get function address
                    lpAddr = GetProcAddress(hLib, sFunc)
                    SysFreeString sName: SysFreeString sFunc
                    
                    ' // Error
                    If lpAddr = 0 Then Exit Function
                    
                    ' // Splice stub
                    Splice splAddr, lpAddr
                    
                    ' // Next stub
                    splAddr = splAddr + fncSpc
                    fncIdx = fncIdx - 1
                    
                Loop
                
            Next
            
            ' // Modify CallByPointer
            lpAddr = GetAddr(AddressOf CallByPointer) - GetAddr(AddressOf BEGINSHELLLOADER) + pShellCode
            
            ' // pop eax    - 0x58
            ' // pop ecx    - 0x59
            ' // push eax   - 0x50
            ' // jmp ecx    - 0xFFE1
            
            CopyMemory ByVal lpAddr, &HFF505958, 4
            CopyMemory ByVal lpAddr + 4, &HE1, 1
         
            ' // Success
            InitShellLoader = True
            
        End Function
         
        ' // Splice function
        Sub Splice( _
                    ByVal Func As Long, _
                    ByVal NewAddr As Long)
            ' // Set memory permissions
            VirtualProtect ByVal Func, 5, PAGE_EXECUTE_READWRITE, 0
            CopyMemory ByVal Func, &HE9, 1                      ' // JMP
            CopyMemory ByVal Func + 1, NewAddr - Func - 5, 4    ' // Relative address
        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 функции, теперь мы должны использовать наши "трамплины":
      ExpandedWrap disabled
        ' // Parse exe in memory
        Function LoadExeFromMemory( _
                         ByVal pRawData As Long, _
                         ByVal pMyBaseAddress As Long, _
                         ByVal pErrMsgTable As Long) As Boolean
            Dim NtHdr   As IMAGE_NT_HEADERS
            Dim pBase   As Long
            Dim index   As Long
            Dim iError  As ERROR_MESSAGES
            Dim pszMsg  As Long
            
            ' // Get IMAGE_NT_HEADERS
            If GetImageNtHeaders(pRawData, NtHdr) = 0 Then
                iError = EM_UNABLE_TO_GET_NT_HEADERS
                EndProcess pErrMsgTable, iError
                Exit Function
            End If
            
            ' // Check flags
            If NtHdr.FileHeader.Machine <> IMAGE_FILE_MACHINE_I386 Or _
               (NtHdr.FileHeader.Characteristics And IMAGE_FILE_EXECUTABLE_IMAGE) = 0 Or _
               (NtHdr.FileHeader.Characteristics And IMAGE_FILE_32BIT_MACHINE) = 0 Then Exit Function
         
            ' // Release main EXE memory. After that main exe is unloaded from memory.
            ZwUnmapViewOfSection GetCurrentProcess(), GetModuleHandle(ByVal 0&)
         
            ' // Reserve memory for EXE
            iError = ReserveMemory(pRawData, pBase)
            If iError Then
                EndProcess pErrMsgTable, iError
                Exit Function
            End If
            
            ' // Place data
            iError = ProcessSectionsAndHeaders(pRawData, pBase)
            If iError Then
                EndProcess pErrMsgTable, iError
                Exit Function
            End If
            
            ' // Update new base address
            iError = UpdateNewBaseAddress(pBase)
            If iError Then
                EndProcess pErrMsgTable, iError
                Exit Function
            End If
            
            ' // Import table processing
            iError = ProcessImportTable(pBase)
            If iError Then
                EndProcess pErrMsgTable, iError
                Exit Function
            End If
            
            ' // Relocations processing
            iError = ProcessRelocations(pBase)
            If iError Then
                EndProcess pErrMsgTable, iError
                Exit Function
            End If
            
            ' // Set the memory attributes
            iError = SetMemoryPermissions(pBase)
            If iError Then
                EndProcess pErrMsgTable, iError
                Exit Function
            End If
            
            ' // Release error message table
            If pErrMsgTable Then
                tVirtualFree pErrMsgTable, 0, MEM_RELEASE
            End If
            
            ' // Call entry point
            CallByPointer NtHdr.OptionalHeader.AddressOfEntryPoint + pBase
            
            ' // End process
            EndProcess
            
        End Function

      Затем шеллкод вызывает функцию ReserveMemory показанную ниже. Эта функция извлекает заголовок NT из загружаемого EXE и пытается зарезервировать регион памяти по адресу указанному в поле ImageBase размера SizeOfmage. Если регион по какой-то причине не был выделен функция проверяет имеет ли EXE файл таблицу релокаций. Если так, тогда функция пытается выделять память по любому адресу. Информация о релокациях позволяет загрузить EXE по любому адресу отличному от ImageBase. Она содержит все места в EXE файле где он использует абсолютную адресацию. Мы можем потом подкорректировать эти адреса используя разницу между реальным базовым адресом и адресом указанным в поле ImageBase:
      ExpandedWrap disabled
        ' // Reserve memory for EXE
        Function ReserveMemory( _
                         ByVal pRawExeData As Long, _
                         ByRef pBase As Long) As ERROR_MESSAGES
            Dim NtHdr       As IMAGE_NT_HEADERS
            Dim pLocBase    As Long
            
            If GetImageNtHeaders(pRawExeData, NtHdr) = 0 Then
                ReserveMemory = EM_UNABLE_TO_GET_NT_HEADERS
                Exit Function
            End If
            
            ' // Reserve memory for EXE
            pLocBase = tVirtualAlloc(ByVal NtHdr.OptionalHeader.ImageBase, _
                                  NtHdr.OptionalHeader.SizeOfImage, _
                                  MEM_RESERVE, PAGE_EXECUTE_READWRITE)
            If pLocBase = 0 Then
                
                ' // If relocation information not found error
                If NtHdr.FileHeader.Characteristics And IMAGE_FILE_RELOCS_STRIPPED Then
                
                    ReserveMemory = EM_UNABLE_TO_ALLOCATE_MEMORY
                    Exit Function
                    
                Else
                    ' // Reserve memory in other region
                    pLocBase = tVirtualAlloc(ByVal 0&, NtHdr.OptionalHeader.SizeOfImage, _
                                         MEM_RESERVE, PAGE_EXECUTE_READWRITE)
                    
                    If pLocBase = 0 Then
                    
                        ReserveMemory = EM_UNABLE_TO_ALLOCATE_MEMORY
                        Exit Function
                        
                    End If
         
                End If
                
            End If
            
            pBase = pLocBase
            
        End Function

      Если при вызове функции произошла ошибка то показывается сообщение о ней и приложение завершается. В противном случае вызывается функция ProcessSectionsAndHeaders. Эта функция размещает все заголовки в выделенную память, извлекает информацию о всех секциях и копирует все данные в выделенную для них память. Если какая-либо секция имеет неинициализированные данные то этот регион заполняется нулями:
      ExpandedWrap disabled
        ' // Allocate memory for sections and copy them data to there
        Function ProcessSectionsAndHeaders( _
                         ByVal pRawExeData As Long, _
                         ByVal pBase As Long) As ERROR_MESSAGES
         
            Dim iSec    As Long
            Dim pNtHdr  As Long
            Dim NtHdr   As IMAGE_NT_HEADERS
            Dim sec     As IMAGE_SECTION_HEADER
            Dim lpSec   As Long
            Dim pData   As Long
            
            pNtHdr = GetImageNtHeaders(pRawExeData, NtHdr)
            If pNtHdr = 0 Then
                ProcessSectionsAndHeaders = EM_UNABLE_TO_GET_NT_HEADERS
                Exit Function
            End If
            
            ' // Alloc memory for headers
            pData = tVirtualAlloc(ByVal pBase, NtHdr.OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE)
            If pData = 0 Then
                ProcessSectionsAndHeaders = EM_UNABLE_TO_ALLOCATE_MEMORY
                Exit Function
            End If
            
            ' // Copy headers
            tCopyMemory pData, pRawExeData, NtHdr.OptionalHeader.SizeOfHeaders
            
            ' // Get address of beginnig of sections headers
            pData = pNtHdr + Len(NtHdr.Signature) + Len(NtHdr.FileHeader) + NtHdr.FileHeader.SizeOfOptionalHeader
            
            ' // Go thru sections
            For iSec = 0 To NtHdr.FileHeader.NumberOfSections - 1
            
                ' // Copy section descriptor
                tCopyMemory IntPtr(sec.SectionName(0)), pData, Len(sec)
                
                ' // Alloc memory for section
                lpSec = tVirtualAlloc(sec.VirtualAddress + pBase, sec.VirtualSize, MEM_COMMIT, PAGE_READWRITE)
                If lpSec = 0 Then
                    ProcessSectionsAndHeaders = EM_UNABLE_TO_ALLOCATE_MEMORY
                    Exit Function
                End If
                
                ' If there is initialized data
                If sec.SizeOfRawData Then
                
                    ' // Take into account  file alignment
                    If sec.SizeOfRawData > sec.VirtualSize Then sec.SizeOfRawData = sec.VirtualSize
                    
                    ' // Copy initialized data to section
                    tCopyMemory lpSec, pRawExeData + sec.PointerToRawData, sec.SizeOfRawData
                    lpSec = lpSec + sec.SizeOfRawData
                    sec.VirtualSize = sec.VirtualSize - sec.SizeOfRawData
                    
                End If
         
                ' // Fill remain part with zero
                tFillMemory lpSec, sec.VirtualSize, 0
                
                ' // Next section
                pData = pData + Len(sec)
                
            Next
            
        End Function

      Затем функция LoadExeFromMemory вызывает функцию UpdateNewBaseAddress которая обновляет новый базовый адрес в user-mode системных структурах. Windows создает специальную структуру называемую PEB (Process Environment Block) для каждого процесса. Это очень полезная структура которая позволяет получить очень много информации о процессе. Множество API функций берут информацию из этой структуры. Для примера GetModuleHandle(NULL) берет возвращаемое значение из PEB.ImageBaseAddress или GetModuleHandle("MyExeName") извлекает информацию из списка загруженных модулей - PEB.Ldr. Нам нужно обновить эту информацию согласно новому базовому адресу для того чтобы API функции возвращали корректное значение. Вот небольшая часть структуры PEB:
      ExpandedWrap disabled
        Type PEB
            NotUsed                         As Long
            Mutant                          As Long
            ImageBaseAddress                As Long
            LoaderData                      As Long ' // Pointer to PEB_LDR_DATA
            ProcessParameters               As Long
            ' // ....
        End Type

      Нам интересно только поле ImageBaseAddress и LoaderData. Первое поле содержит базовый адрес EXE файла. Второе поле содержит указатель на структуру PEB_LDR_DATA которая описывает все загруженные модули в процессе:
      ExpandedWrap disabled
        Type PEB_LDR_DATA
            Length                          As Long
            Initialized                     As Long
            SsHandle                        As Long
            InLoadOrderModuleList           As LIST_ENTRY
            InMemoryOrderModuleList         As LIST_ENTRY
            InInitializationOrderModuleList As LIST_ENTRY
        End Type

      Эта структура содержит три двухсвязных списка что описывают каждый модуль. Список InLoadOrderModuleList содержит ссылки на элементы в порядке загрузки, т.е. ссылки в этом списке расположены в порядке загрузки (первый модуль в начале). Список InMemoryOrderModuleList тоже самое только в порядке расположения в памяти, а InInitializationOrderModuleList в порядке инициализации. Нам нужно получить первый элемент списка InLoadOrderModuleList который является указателем на структуру LDR_MODULE:
      ExpandedWrap disabled
        Type LDR_MODULE
            InLoadOrderModuleList           As LIST_ENTRY
            InMemoryOrderModuleList         As LIST_ENTRY
            InInitOrderModuleList           As LIST_ENTRY
            BaseAddress                     As Long
            EntryPoint                      As Long
            SizeOfImage                     As Long
            FullDllName                     As UNICODE_STRING
            BaseDllName                     As UNICODE_STRING
            Flags                           As Long
            LoadCount                       As Integer
            TlsIndex                        As Integer
            HashTableEntry                  As LIST_ENTRY
            TimeDateStamp                   As Long
        End Type

      Эта структура описывает один модуль. Первый элемент списка InLoadOrderModuleList является описателем главного исполняемого файла. Нам нужно изменить поле BaseAddress на новый базовый адрес и сохранить изменения. Итак, для того чтобы получить адрес структуры PEB мы можем использовать функцию NtQueryInformationProcess которая извлекает множество полезной информации о процессе (узнать подробнее можно в книге 'Windows NT/2000 Native API Reference' by Gary Nebbett). Структура PEB может быть получена из структуры PROCESS_BASIC_INFORMATION которая описывает базовую информацию о процессе:
      ExpandedWrap disabled
        Type PROCESS_BASIC_INFORMATION
            ExitStatus                      As Long
            PebBaseAddress                  As Long
            AffinityMask                    As Long
            BasePriority                    As Long
            UniqueProcessId                 As Long
            InheritedFromUniqueProcessId    As Long
        End Type

      Поле PebBaseAddress содержит адрес структуры PEB.
      Для того чтобы извлечь структуру PROCESS_BASIC_INFORMATION нам нужно передать в качестве параметра класса информации значение ProcessBasicInformation. Поскольку размер структуры может меняться в различных версиях Windows я использую кучу для извлечения структуры PROCESS_BASIC_INFORMATION. Если размер не подходит код увеличивает размер памяти для структуры PROCESS_BASIC_INFORMATION и повторяет заново пока структура не будет извлечена:
      ExpandedWrap disabled
        Function UpdateNewBaseAddress( _
                         ByVal pBase As Long) As ERROR_MESSAGES
            Dim pPBI    As Long:                        Dim PBIlen  As Long
            Dim PBI     As PROCESS_BASIC_INFORMATION:   Dim cPEB    As PEB
            Dim ntstat  As Long
            Dim ldrData As PEB_LDR_DATA
            Dim ldrMod  As LDR_MODULE
            
            ntstat = tNtQueryInformationProcess(tGetCurrentProcess(), ProcessBasicInformation, IntPtr(PBI.ExitStatus), Len(PBI), PBIlen)
            
            Do While ntstat = STATUS_INFO_LENGTH_MISMATCH
                
                PBIlen = PBIlen * 2
                
                If pPBI Then
                    tHeapFree tGetProcessHeap(), HEAP_NO_SERIALIZE, pPBI
                End If
                
                pPBI = tHeapAlloc(tGetProcessHeap(), HEAP_NO_SERIALIZE, PBIlen)
                ntstat = tNtQueryInformationProcess(tGetCurrentProcess(), ProcessBasicInformation, pPBI, PBIlen, PBIlen)
                
            Loop
            
            If ntstat <> STATUS_SUCCESS Then
                UpdateNewBaseAddress = EM_PROCESS_INFORMATION_NOT_FOUND
                GoTo CleanUp
            End If
            
            If pPBI Then
                ' // Copy to PROCESS_BASIC_INFORMATION
                tCopyMemory IntPtr(PBI.ExitStatus), pPBI, Len(PBI)
            End If
         
            ' // Get PEB
            tCopyMemory IntPtr(cPEB.NotUsed), PBI.PebBaseAddress, Len(cPEB)
            
            ' // Modify image base
            cPEB.ImageBaseAddress = pBase
            
            ' // Restore PEB
            tCopyMemory PBI.PebBaseAddress, IntPtr(cPEB.NotUsed), Len(cPEB)
            
            ' // Fix base address in PEB_LDR_DATA list
            tCopyMemory IntPtr(ldrData.Length), cPEB.LoaderData, Len(ldrData)
            
            ' // Get first element
            tCopyMemory IntPtr(ldrMod.InLoadOrderModuleList.Flink), ldrData.InLoadOrderModuleList.Flink, Len(ldrMod)
            
            ' // Fix base
            ldrMod.BaseAddress = pBase
            
            ' // Restore
            tCopyMemory ldrData.InLoadOrderModuleList.Flink, IntPtr(ldrMod.InLoadOrderModuleList.Flink), Len(ldrMod)
            
        CleanUp:
            
            ' // Free memory
            If pPBI Then
                tHeapFree tGetProcessHeap(), HEAP_NO_SERIALIZE, pPBI
            End If
            
        End Function

      После обновления базового адреса в системных структурах шеллкод вызывает функцию ProcessImportTable которая загружает необходимые библиотеки для работы EXE файла. Вначале извлекается директория IMAGE_DIRECTORY_ENTRY_IMPORT которая содержит RVA массива структур IMAGE_IMPORT_DESCRIPTOR:
      ExpandedWrap disabled
        Type IMAGE_IMPORT_DESCRIPTOR
            Characteristics                 As Long
            TimeDateStamp                   As Long
            ForwarderChain                  As Long
            pName                           As Long
            FirstThunk                      As Long
        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 это означает что список структур также окончен.
      ExpandedWrap disabled
        ' // Process import table
        Function ProcessImportTable( _
                         ByVal pBase As Long) As ERROR_MESSAGES
            Dim NtHdr           As IMAGE_NT_HEADERS:        Dim datDirectory    As IMAGE_DATA_DIRECTORY
            Dim dsc             As IMAGE_IMPORT_DESCRIPTOR: Dim hLib            As Long
            Dim thnk            As Long:                    Dim Addr            As Long
            Dim fnc             As Long:                    Dim pData           As Long
                
            If GetImageNtHeaders(pBase, NtHdr) = 0 Then
                ProcessImportTable = EM_UNABLE_TO_GET_NT_HEADERS
                Exit Function
            End If
            
            ' // Import table processing
            If NtHdr.OptionalHeader.NumberOfRvaAndSizes > 1 Then
                
                If GetDataDirectory(pBase, IMAGE_DIRECTORY_ENTRY_IMPORT, datDirectory) = 0 Then
                    ProcessImportTable = EM_INVALID_DATA_DIRECTORY
                    Exit Function
                End If
         
                ' // If import table exists
                If datDirectory.Size > 0 And datDirectory.VirtualAddress > 0 Then
                
                    ' // Copy import descriptor
                    pData = datDirectory.VirtualAddress + pBase
                    tCopyMemory IntPtr(dsc.Characteristics), pData, Len(dsc)
                    
                    ' // Go thru all descriptors
                    Do Until dsc.Characteristics = 0 And _
                             dsc.FirstThunk = 0 And _
                             dsc.ForwarderChain = 0 And _
                             dsc.pName = 0 And _
                             dsc.TimeDateStamp = 0
                        
                        If dsc.pName > 0 Then
                        
                            ' // Load needed library
                            hLib = tLoadLibrary(dsc.pName + pBase)
                            
                            If hLib = 0 Then
                                ProcessImportTable = EM_LOADLIBRARY_FAILED
                                Exit Function
                            End If
         
                            If dsc.Characteristics Then fnc = dsc.Characteristics + pBase Else fnc = dsc.FirstThunk + pBase
                            
                            ' // Go to names table
                            tCopyMemory IntPtr(thnk), fnc, 4
                            
                            ' // Go thru names table
                            Do While thnk
                            
                                ' // Check import type
                                If thnk < 0 Then
                                    ' // By ordinal
                                    Addr = tGetProcAddress(hLib, thnk And &HFFFF&)
                                Else
                                    ' // By name
                                    Addr = tGetProcAddress(hLib, thnk + 2 + pBase)
                                End If
                                
                                ' // Next function
                                fnc = fnc + 4
                                tCopyMemory IntPtr(thnk), fnc, 4
                                tCopyMemory dsc.FirstThunk + pBase, IntPtr(Addr), 4
                                dsc.FirstThunk = dsc.FirstThunk + 4
                                
                            Loop
                            
                        End If
                        
                        ' // Next descriptor
                        pData = pData + Len(dsc)
                        tCopyMemory IntPtr(dsc.Characteristics), pData, Len(dsc)
                        
                    Loop
                    
                End If
                
            End If
                        
        End Function

      Функция ProcessRelocation вызывается после обработки импорта. Эта функция настраивает все абсолютные ссылки (если таковые имеются). Извлекается каталог IMAGE_DIRECTORY_ENTRY_BASERELOC который содержит RVA массива структур IMAGE_BASE_RELOCATION. Каждый элемент этого масива содержит настройки в пределах 4Кб относительно адреса VirtualAddress:
      ExpandedWrap disabled
        Type IMAGE_BASE_RELOCATION
            VirtualAddress                  As Long
            SizeOfBlock                     As Long
        End Type

      Поле SizeOfBlock содержит размер элемента в байтах. Массив 16-битных значений дескрипторов расположен после каждой структуры
      IMAGE_BASE_RELOCATION. Мы можем вычислить количество этих значений по формуле: (SizeOfBlock - Len(IMAGE_BASE_RELOCATION)) \ Len(Integer). Каждый элемент массива дескрипторов имеет следующуюю структуру:
      user posted image

      Верхние 4 бита содержат тип настройки. Нам интересна только настройка IMAGE_REL_BASED_HIGHLOW которая означает что нам нужно добавить разницу (RealBaseAddress - ImageBaseAddress) к значению Long которое расположено по адресу VirtualAddress + 12 младших бит дескриптора. Массив струкутр IMAGE_BASE_RELOCATION заканчивается структурой где все поля заполнены нулями:
      ExpandedWrap disabled
        ' // Process relocations
        Function ProcessRelocations( _
                         ByVal pBase As Long) As ERROR_MESSAGES
            Dim NtHdr           As IMAGE_NT_HEADERS:        Dim datDirectory    As IMAGE_DATA_DIRECTORY
            Dim relBase         As IMAGE_BASE_RELOCATION:   Dim entriesCount    As Long
            Dim relType         As Long:                    Dim dwAddress       As Long
            Dim dwOrig          As Long:                    Dim pRelBase        As Long
            Dim delta           As Long:                    Dim pData           As Long
            
            ' // Check if module has not been loaded to image base value
            If GetImageNtHeaders(pBase, NtHdr) = 0 Then
                ProcessRelocations = EM_UNABLE_TO_GET_NT_HEADERS
                Exit Function
            End If
            
            delta = pBase - NtHdr.OptionalHeader.ImageBase
            
            ' // Process relocations
            If delta Then
                
                ' // Get address of relocation table
                If GetDataDirectory(pBase, IMAGE_DIRECTORY_ENTRY_BASERELOC, datDirectory) = 0 Then
                    ProcessRelocations = EM_INVALID_DATA_DIRECTORY
                    Exit Function
                End If
                
                If datDirectory.Size > 0 And datDirectory.VirtualAddress > 0 Then
                
                    ' // Copy relocation base
                    pRelBase = datDirectory.VirtualAddress + pBase
                    tCopyMemory IntPtr(relBase.VirtualAddress), pRelBase, Len(relBase)
                    
                    Do While relBase.VirtualAddress
                    
                        ' // To first reloc chunk
                        pData = pRelBase + Len(relBase)
                        
                        entriesCount = (relBase.SizeOfBlock - Len(relBase)) \ 2
                        
                        Do While entriesCount > 0
                            
                            tCopyMemory IntPtr(relType), pData, 2
                            
                            Select Case (relType \ 4096) And &HF
                            Case IMAGE_REL_BASED_HIGHLOW
                                
                                ' // Calculate address
                                dwAddress = relBase.VirtualAddress + (relType And &HFFF&) + pBase
                                
                                ' // Get original address
                                tCopyMemory IntPtr(dwOrig), dwAddress, Len(dwOrig)
                                
                                ' // Add delta
                                dwOrig = dwOrig + delta
                                
                                ' // Save
                                tCopyMemory dwAddress, IntPtr(dwOrig), Len(dwOrig)
                                
                            End Select
                            
                            pData = pData + 2
                            entriesCount = entriesCount - 1
                            
                        Loop
                        
                        ' // Next relocation base
                        pRelBase = pRelBase + relBase.SizeOfBlock
                        tCopyMemory IntPtr(relBase.VirtualAddress), pRelBase, Len(relBase)
                        
                    Loop
                    
                End If
                
            End If
         
        End Function

      После настройки релокаций шеллкод вызывает функцию SetMemoryPermissions которая настраивает разрешения памяти согласно полю Characteristics структуры IMAGE_SECTION_HEADER. Для этого просто вызывается функция VirtualProtect с определенными атрибутами памяти:
      ExpandedWrap disabled
        ' // Set memory permissions
        Private Function SetMemoryPermissions( _
                         ByVal pBase As Long) As ERROR_MESSAGES
            Dim iSec    As Long:                    Dim pNtHdr  As Long
            Dim NtHdr   As IMAGE_NT_HEADERS:        Dim sec     As IMAGE_SECTION_HEADER
            Dim Attr    As MEMPROTECT:              Dim pSec    As Long
            Dim ret     As Long
            
            pNtHdr = GetImageNtHeaders(pBase, NtHdr)
            If pNtHdr = 0 Then
                SetMemoryPermissions = EM_UNABLE_TO_GET_NT_HEADERS
                Exit Function
            End If
         
            ' // Get address of first section header
            pSec = pNtHdr + 4 + Len(NtHdr.FileHeader) + NtHdr.FileHeader.SizeOfOptionalHeader
            
            ' // Go thru section headers
            For iSec = 0 To NtHdr.FileHeader.NumberOfSections - 1
            
                ' // Copy section descriptor
                tCopyMemory IntPtr(sec.SectionName(0)), pSec, Len(sec)
                
                ' // Get type
                If sec.Characteristics And IMAGE_SCN_MEM_EXECUTE Then
                    If sec.Characteristics And IMAGE_SCN_MEM_READ Then
                        If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                            Attr = PAGE_EXECUTE_READWRITE
                        Else
                            Attr = PAGE_EXECUTE_READ
                        End If
                    Else
                        If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                            Attr = PAGE_EXECUTE_WRITECOPY
                        Else
                            Attr = PAGE_EXECUTE
                        End If
                    End If
                Else
                    If sec.Characteristics And IMAGE_SCN_MEM_READ Then
                        If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                            Attr = PAGE_READWRITE
                        Else
                            Attr = PAGE_READONLY
                        End If
                    Else
                        If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                            Attr = PAGE_WRITECOPY
                        Else
                            Attr = PAGE_NOACCESS
                        End If
                    End If
                End If
                
                ' // Set memory permissions
                If tVirtualProtect(sec.VirtualAddress + pBase, sec.VirtualSize, Attr, IntPtr(ret)) = 0 Then
                    SetMemoryPermissions = EM_UNABLE_TO_PROTECT_MEMORY
                    Exit Function
                End If
                
                ' // Next section
                pSec = pSec + Len(sec)
                
            Next
            
        End Function

      В конце концов очищается таблица сообщений об ошибках (если нужно) и вызывается точка входа загруженного EXE. В предыдущей версии загрузчика я выгружал шеллкод тоже, но некоторые EXE не вызывают ExitProcess следовательно это могло вызывать креши. Загрузчик готов.
      Хотя мы написал загрузчик без использвания рантайма, компилятор VB6 добавляет его все-равно поскольку все OBJ файлы имеют ссылки на MSVBVM60 во время компиляции. Нам придется удалить рантайм из таблицы импорта загрузчика вручную. Для этого я сделал специальную утилиту - Patcher которая ищет рантайм в таблице импорта и таблице связанного импорта и удаляет его оттуда. Эта утилита также была полезна для драйвера режима ядра. Я не буду описывать ее работу поскольку она использует те же концепции PE-формата что я уже описал здесь. В общем и целом мы сделали рабочий EXE который не использует MSVBVM60 на целевой машине.
      Для того чтобы использовать загрузчик нужно скомпилировать его затем с помощью патчера пропатчить его. После этог можно использовать его в компиляторе.

      Я надеюсь вам понравилось. Спасибо за внимание!
      С уважением,
      Кривоус Анатолий (The trick).
      Прикреплённый файлПрикреплённый файлVBLoader.zip (176,44 Кбайт, скачиваний: 118)
        Зачетно.
        Однако, насчет запуска без рантайма -- давно уже было изучено, что можно, но только если отказаться от классов, автоматизации, строк и прочего в коде проекта. Отсюда вытекает сомнительность пользы отлучения от рантайма.
          Цитата B.V. @
          Однако, насчет запуска без рантайма -- давно уже было изучено, что можно, но только если отказаться от классов, автоматизации, строк и прочего в коде проекта. Отсюда вытекает сомнительность пользы отлучения от рантайма.

          Не видел ничего подобного кроме этого проекта и драйвера.
            А я и не говорил, что что-то подобное было. Под изучено я имел ввиду изыскания Хакера с VBS
            0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
            0 пользователей:


            Рейтинг@Mail.ru
            [ Script execution time: 0,0935 ]   [ 18 queries used ]   [ Generated: 28.04.24, 00:11 GMT ]