Важная информация
RSS лента

The trick

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

Оценить эту запись

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


Компилятор.


Это приложение формирующее информацию для инсталляции и размещающее ее в ресурсах загрузчика. Вся информация хранится в файлах проекта. Вы можете сохранять и загружать проекты из файлов. Класс 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. Сейчас я расскажу о процедуре компиляции. Как я уже сказал вся информация об инсталляции сохраняется в ресурсы загрузчика. Вначале на нужно определить формат данных:
VB Code:
  1. ' // Storage list item
  2. Private Type BinStorageListItem
  3.     ofstFileName        As Long            ' // Offset of file name
  4.     ofstDestPath        As Long            ' // Offset of file path
  5.     dwSizeOfFile        As Long            ' // Size of file
  6.     ofstBeginOfData     As Long            ' // Offset of beginning data
  7.     dwFlags             As FileFlags       ' // Flags
  8. End Type
  9.  
  10. ' // Execute list item
  11. Private Type BinExecListItem
  12.     ofstFileName        As Long            ' // Offset of file name
  13.     ofstParameters      As Long            ' // Offset of parameters
  14.     dwFlags             As ExeFlags        ' // Flags
  15. End Type
  16.  
  17. ' // Storage descriptor
  18. Private Type BinStorageList
  19.     dwSizeOfStructure   As Long            ' // Size of structure
  20.     iExecutableIndex    As Long            ' // Index of main executable
  21.     dwSizeOfItem        As Long            ' // Size of BinaryStorageItem structure
  22.     dwNumberOfItems     As Long            ' // Number of files in storage
  23. End Type
  24.  
  25. ' // Execute list descriptor
  26. Private Type BinExecList
  27.     dwSizeOfStructure   As Long            ' // Size of structure
  28.     dwSizeOfItem        As Long            ' // Size of BinaryExecuteItem structure
  29.     dwNumberOfItems     As Long            ' // Number of items
  30. End Type
  31.  
  32. ' // Base information about project
  33. Private Type BinProject
  34.     dwSizeOfStructure   As Long            ' // Size of structure
  35.     storageDescriptor   As BinStorageList  ' // Storage descriptor
  36.     execListDescriptor  As BinExecList     ' // Command descriptor
  37.     dwStringsTableLen   As Long            ' // Size of strings table
  38.     dwFileTableLen      As Long            ' // Size of data table
  39. End Type

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


Любой элемент может ссылаться на таблицу строк и таблицу файлов. Для этой цели используется смещение относительно начала таблицы. Все итемы расположены одна за другой. Теперь мы знаем внутренний формат проекта и можем поговорить о том как постороить загрузчик который будет содержать эти данные. Как я уже сказал мы сохраняем данные в ресурсы загрузчика. О самом загрузчике я расскажу позднее, а сейчас я хотел бы заметить одну важную особенность. Когда мы ложим данные проекта в 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", но мы можем написать там любую программу:


VB Code:
  1. Type IMAGE_DOS_HEADER
  2.     e_magic                     As Integer
  3.     e_cblp                      As Integer
  4.     e_cp                        As Integer
  5.     e_crlc                      As Integer
  6.     e_cparhdr                   As Integer
  7.     e_minalloc                  As Integer
  8.     e_maxalloc                  As Integer
  9.     e_ss                        As Integer
  10.     e_sp                        As Integer
  11.     e_csum                      As Integer
  12.     e_ip                        As Integer
  13.     e_cs                        As Integer
  14.     e_lfarlc                    As Integer
  15.     e_ovno                      As Integer
  16.     e_res(0 To 3)               As Integer
  17.     e_oemid                     As Integer
  18.     e_oeminfo                   As Integer
  19.     e_res2(0 To 9)              As Integer
  20.     e_lfanew                    As Long
  21. End Type

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

Первое поле этой структуры содержит сигнатуру 'PE\0\0' (aka. IMAGE_NT_SIGNATURE). Следующее поле описывает исполняемый файл и имеет следующий формат:
VB Code:
  1. Type IMAGE_FILE_HEADER
  2.     Machine                         As Integer
  3.     NumberOfSections                As Integer
  4.     TimeDateStamp                   As Long
  5.     PointerToSymbolTable            As Long
  6.     NumberOfSymbols                 As Long
  7.     SizeOfOptionalHeader            As Integer
  8.     Characteristics                 As Integer
  9. End Type

Поле Machine определяет архитектуру процессора и должно иметь значение IMAGE_FILE_MACHINE_I386 в нашем случае. Поле NumberOfSections определяет количество секций в PE файле.

  • Любой EXE файл содержит секции. Каждая секция занимает место в адресном пространстве процесса и опционально в файле. Секция может содержать как код так и данные (инизиализированные или не), а также имеет имя. Наиболее распространенные имена: .text, .data, .rsrc. Обычно секция .text содержит код, .data инициализированные данные, а .rsrc - ресурсы. Можно изменять это поведение используя дериктивы линкера. Каждая секция имеет адрес называемый виртуальным адресом. В общем в PE формате существует несколько типов адресации. Первый - относительный виртуальный адрес (RVA). Из-за того что PE фал может быть загружен по любому адресу все ссылки внутри PE файла имеют относительную адресацию. RVA - это смещение относительно базового адреса (адреса первого байта PE-образа в памяти). Сумма RVA и базового адреса называется виртуальным адресом (VA). Также существует RAW-смещение которое показывает смещение относительно начала файла относительно RVA. Заметьте что RVA <> RAW. Когда модуль загружается каждая секция размещается по виртуальному адресу. Для примера модуль может иметь секцию что не имеет инициализированных данных. Такая секция не будет занимать место в PE-файле, но будет в памяти. Это очень важный момент поскольку мы будем работать с сырым EXE файлом.

Поле TimeDateStamp содержит дату создания PE модуля в формате UTC. Поля PointerToSymbolTable and NumberOfSymbols содержат информацию о символах в PE файлах. В общем эти поля содержат нули, но эти поля всегда используються в объектных файлах (*.OBJ, *.LIB) для разрешения ссылок во время линковки а также содержат отладочную информацию для PE модуля. Следующее поле SizeOfOptionalHeader содержит размер структуры расположенной после IMAGE_FILE_HEADER так называемой IMAGE_OPTIONAL_HEADER которая всегда присутствует в PE файлах (хотя может отсутствовать в OBJ файлах). Эта структура являеться очень важной для загрузки PE модуля в память. Заметьте что эта структура различается в 32 битных и 64 битных PE-модулях. И наконец поле Characteristics содержит PE-аттрибуты.
Структура IMAGE_OPTIONAL_HEADER имеет следующий формат:
VB Code:
  1. Type IMAGE_OPTIONAL_HEADER
  2.     Magic                           As Integer
  3.     MajorLinkerVersion              As Byte
  4.     MinorLinkerVersion              As Byte
  5.     SizeOfCode                      As Long
  6.     SizeOfInitializedData           As Long
  7.     SizeOfUnitializedData           As Long
  8.     AddressOfEntryPoint             As Long
  9.     BaseOfCode                      As Long
  10.     BaseOfData                      As Long
  11.     ImageBase                       As Long
  12.     SectionAlignment                As Long
  13.     FileAlignment                   As Long
  14.     MajorOperatingSystemVersion     As Integer
  15.     MinorOperatingSystemVersion     As Integer
  16.     MajorImageVersion               As Integer
  17.     MinorImageVersion               As Integer
  18.     MajorSubsystemVersion           As Integer
  19.     MinorSubsystemVersion           As Integer
  20.     W32VersionValue                 As Long
  21.     SizeOfImage                     As Long
  22.     SizeOfHeaders                   As Long
  23.     CheckSum                        As Long
  24.     SubSystem                       As Integer
  25.     DllCharacteristics              As Integer
  26.     SizeOfStackReserve              As Long
  27.     SizeOfStackCommit               As Long
  28.     SizeOfHeapReserve               As Long
  29.     SizeOfHeapCommit                As Long
  30.     LoaderFlags                     As Long
  31.     NumberOfRvaAndSizes             As Long
  32.     DataDirectory(15)               As IMAGE_DATA_DIRECTORY
  33. End Type

Первое поле содержит тип образа (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. Рассмотрим формат заголовка секции:
VB Code:
  1. Type IMAGE_SECTION_HEADER
  2.     SectionName(7)              As Byte
  3.     VirtualSize                 As Long
  4.     VirtualAddress              As Long
  5.     SizeOfRawData               As Long
  6.     PointerToRawData            As Long
  7.     PointerToRelocations        As Long
  8.     PointerToLinenumbers        As Long
  9.     NumberOfRelocations         As Integer
  10.     NumberOfLinenumbers         As Integer
  11.     Characteristics             As Long
  12. End Type

Первое поле содержит имя секции в формате UTF-8 c завершающим нуль-терминалом. Это имя ограничено 8-ю символами (если имя секции имеет размер 8 символов то нуль-терминатор игнорируется). COFF файл может иметь имя больше чем 8 символов в этом случае имя начинается с символа '/' за которым следует ASCII строка с десятичным значением смещения в строковой таблице (поле IMAGE_FILE_HEADER). PE файл не поддерживает длинные имена секций. Поля VirtualSize и VirtualAddress содержат размер секции в памяти и адрес (RVA). Поля SizeOfRawData и PointerToRawData содержат RAW адрес данных в файле (если секция содержит инициализированные данные). Это ключевой момент потому что мы можем вычислить RAW адрес с помощью относительного виртуального адреса используя информацию из заголовка секций. Я написал функцию для перевода RVA адресации в RAW смещение в файле:
VB Code:
  1. ' // RVA to RAW
  2. Function RVA2RAW( _
  3.                  ByVal rva As Long, _
  4.                  ByRef sec() As IMAGE_SECTION_HEADER) As Long
  5.     Dim index As Long
  6.     
  7.     For index = 0 To UBound(sec)
  8.         
  9.         If rva >= sec(index).VirtualAddress And _
  10.            rva < sec(index).VirtualAddress + sec(index).VirtualSize Then
  11.             RVA2RAW = sec(index).PointerToRawData + (rva - sec(index).VirtualAddress)
  12.             Exit Function
  13.         End If
  14.         
  15.     Next
  16.     
  17.     RVA2RAW = rva
  18.     
  19. End Function

Эта функция перечисляет все секции и проверяет если переданный адрес находится в пределах секции. Следующие 5 полей используються только в COFF файлах и не важны в PE файлах. Поле Characteristics содержит атрибуты секции такие как права доступа к памяти и управление. Мы будем использовать это поле для защиты памяти exe файла в загрузчике.
Давайте теперь вернемся к каталогам данных. Как мы видели существует 16 элементов в данном каталоге. Обычно PE файл не использует их все. Давайте рассмотрим структуру элемента каталога:
VB Code:
  1. Private Type IMAGE_DATA_DIRECTORY
  2.     VirtualAddress                  As Long
  3.     Size                            As Long
  4. End Type

Эта структура содержит два поля. Первое поле содержит RVA адрес данных каталога, воторое - размер. Когда элемент каталога не представлен в PE файле то оба поля содержат нули. Вообще большинство VB6-компилируемых приложений имеют только 4 каталога: таблица импорта, таблица ресурсов, таблица связанного импорта и таблица адресов импорта (IAT). Сейчас мы рассмотрим таблицу ресурсов которая имеет индекс IMAGE_DIRECTORY_ENTRY_RESOURCE потому что мы работаем с этой информацией в проекте Compiler.
Все ресурсы в EXE файле представлены в виде трехуровнего дерева. Первый уровень определяет тип ресурса (RT_BITMAP, RT_MANIFEST, RT_RCDATA, и т.д.), следующий - идентификатор ресурса и наконец третий - язык. В стандартном редакторе ресурсов VB Resource Editor можно изменять только первые 2 уровня. Все ресурсы размещаются таблице ресурсов расположенной в секции .rsrc EXE файла. Благодаря такой структуре мы можем изменять ресурсы даже в готовом EXE файле. Для того чтобы добраться до самих данных в секции ресурсов нам сначала нужно прочитать IMAGE_DIRECTORY_ENTRY_RESOURCE из опционального хидера. Поле VirtualAddress содержит RVA таблицы ресурсов которая имеет следующий формат:
VB Code:
  1. Type IMAGE_RESOURCE_DIRECTORY
  2.     Characteristics             As Long
  3.     TimeDateStamp               As Long
  4.     MajorVersion                As Integer
  5.     MinorVersion                As Integer
  6.     NumberOfNamedEntries        As Integer
  7.     NumberOfIdEntries           As Integer
  8. End Type

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

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

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

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

Компиляция.

Итак, когда мы начинаем компиляцию проекта то вызывается метод 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:
VB Code:
  1. ' // Startup subroutine
  2. Sub Main()
  3.  
  4.     ' // Load constants
  5.     If Not LoadConstants Then
  6.         MessageBox 0, GetString(MID_ERRORLOADINGCONST), 0, MB_ICONERROR Or MB_SYSTEMMODAL
  7.         GoTo EndOfProcess
  8.     End If
  9.     
  10.     ' // Load project
  11.     If Not ReadProject Then
  12.         MessageBox 0, GetString(MID_ERRORREADINGPROJECT), 0, MB_ICONERROR Or MB_SYSTEMMODAL
  13.         GoTo EndOfProcess
  14.     End If
  15.     
  16.     ' // Copying from storage
  17.     If Not CopyProcess Then GoTo EndOfProcess
  18.     
  19.     ' // Execution process
  20.     If Not ExecuteProcess Then GoTo EndOfProcess
  21.     
  22.     ' // If main executable is not presented exit
  23.     If ProjectDesc.storageDescriptor.iExecutableIndex = -1 Then GoTo EndOfProcess
  24.     
  25.     ' // Run exe from memory
  26.     If Not RunProcess Then
  27.         ' // Error occrurs
  28.         MessageBox 0, GetString(MID_ERRORSTARTUPEXE), 0, MB_ICONERROR Or MB_SYSTEMMODAL
  29.     End If
  30.     
  31. EndOfProcess:
  32.     
  33.     If pProjectData Then
  34.         HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
  35.     End If
  36.     
  37.     ExitProcess 0
  38.     
  39. End Sub

Вначале вызывается функция LoadConstants для того чтобы загрузить все необходимые константы из ресурсов:
VB Code:
  1. ' // modConstants.bas - main module for loading constants
  2. ' // © Krivous Anatoly Anatolevich (The trick), 2016
  3.  
  4. Option Explicit
  5.  
  6. Public Enum MessagesID
  7.     MID_ERRORLOADINGCONST = 100     ' // Errors
  8.     MID_ERRORREADINGPROJECT = 101   '
  9.     MID_ERRORCOPYINGFILE = 102      '
  10.     MID_ERRORWIN32 = 103            '
  11.     MID_ERROREXECUTELINE = 104      '
  12.     MID_ERRORSTARTUPEXE = 105       '
  13.     PROJECT = 200                   ' // Project resource ID
  14.     API_LIB_KERNEL32 = 300          ' // Library names
  15.     API_LIB_NTDLL = 350             '
  16.     API_LIB_USER32 = 400            '
  17.     MSG_LOADER_ERROR = 500
  18. End Enum
  19.  
  20. ' // Paths
  21.  
  22. Public pAppPath  As Long            ' // Path to application
  23. Public pSysPath  As Long            ' // Path to System32
  24. Public pTmpPath  As Long            ' // Path to Temp
  25. Public pWinPath  As Long            ' // Path to Windows
  26. Public pDrvPath  As Long            ' // Path to system drive
  27. Public pDtpPath  As Long            ' // Path to desktop
  28.  
  29. ' // Substitution constants
  30.  
  31. Public pAppRepl  As Long
  32. Public pSysRepl  As Long
  33. Public pTmpRepl  As Long
  34. Public pWinRepl  As Long
  35. Public pDrvRepl  As Long
  36. Public pDtpRepl  As Long
  37. Public pStrNull  As Long            ' // \0
  38.  
  39. Public hInstance    As Long         ' // Base address
  40. Public lpCmdLine    As Long         ' // Command line
  41. Public SI           As STARTUPINFO  ' // Startup parameters
  42. Public LCID         As Long         ' // LCID
  43.  
  44. ' // Load constants
  45. Function LoadConstants() As Boolean
  46.     Dim lSize   As Long
  47.     Dim pBuf    As Long
  48.     Dim index   As Long
  49.     Dim ctl     As tagINITCOMMONCONTROLSEX
  50.     
  51.     ' // Load windows classes
  52.     ctl.dwSize = Len(ctl)
  53.     ctl.dwICC = &H3FFF&
  54.     InitCommonControlsEx ctl
  55.     
  56.     ' // Get startup parameters
  57.     GetStartupInfo SI
  58.     
  59.     ' // Get command line
  60.     lpCmdLine = GetCommandLine()
  61.     
  62.     ' // Get base address
  63.     hInstance = GetModuleHandle(ByVal 0&)
  64.     
  65.     ' // Get LCID
  66.     LCID = GetUserDefaultLCID()
  67.     
  68.     ' // Alloc memory for strings
  69.     pBuf = SysAllocStringLen(0, MAX_PATH)
  70.     If pBuf = 0 Then Exit Function
  71.     
  72.     ' // Get path to process file name
  73.     If GetModuleFileName(hInstance, pBuf, MAX_PATH) = 0 Then GoTo CleanUp
  74.     
  75.     ' // Leave only directory
  76.     PathRemoveFileSpec pBuf
  77.     
  78.     ' // Save path
  79.     pAppPath = SysAllocString(pBuf)
  80.     
  81.     ' // Get Windows folder
  82.     If GetWindowsDirectory(pBuf, MAX_PATH) = 0 Then GoTo CleanUp
  83.     pWinPath = SysAllocString(pBuf)
  84.     
  85.     ' // Get System32 folder
  86.     If GetSystemDirectory(pBuf, MAX_PATH) = 0 Then GoTo CleanUp
  87.     pSysPath = SysAllocString(pBuf)
  88.     
  89.     ' // Get Temp directory
  90.     If GetTempPath(MAX_PATH, pBuf) = 0 Then GoTo CleanUp
  91.     pTmpPath = SysAllocString(pBuf)
  92.     
  93.     ' // Get system drive
  94.     PathStripToRoot pBuf
  95.     pDrvPath = SysAllocString(pBuf)
  96.     
  97.     ' // Get desktop path
  98.     If SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, 0, SHGFP_TYPE_CURRENT, pBuf) Then GoTo CleanUp
  99.     pDtpPath = SysAllocString(pBuf)
  100.     
  101.     ' // Load wildcards
  102.     For index = 1 To 6
  103.         If LoadString(hInstance, index, pBuf, MAX_PATH) = 0 Then GoTo CleanUp
  104.         Select Case index
  105.         Case 1: pAppRepl = SysAllocString(pBuf)
  106.         Case 2: pSysRepl = SysAllocString(pBuf)
  107.         Case 3: pTmpRepl = SysAllocString(pBuf)
  108.         Case 4: pWinRepl = SysAllocString(pBuf)
  109.         Case 5: pDrvRepl = SysAllocString(pBuf)
  110.         Case 6: pDtpRepl = SysAllocString(pBuf)
  111.         End Select
  112.     Next
  113.     
  114.     ' // vbNullChar
  115.     pStrNull = SysAllocStringLen(0, 0)
  116.  
  117.     ' // Success
  118.     LoadConstants = True
  119.     
  120. CleanUp:
  121.     
  122.     If pBuf Then SysFreeString pBuf
  123.     
  124. End Function
  125.  
  126. ' // Obtain string from resource (it should be less or equal MAX_PATH)
  127. Public Function GetString( _
  128.                 ByVal ID As MessagesID) As Long
  129.                 
  130.     GetString = SysAllocStringLen(0, MAX_PATH)
  131.     
  132.     If GetString Then
  133.     
  134.         If LoadString(hInstance, ID, GetString, MAX_PATH) = 0 Then SysFreeString GetString: GetString = 0: Exit Function
  135.         If SysReAllocString(GetString, GetString) = 0 Then SysFreeString GetString: GetString = 0: Exit Function
  136.         
  137.     End If
  138.     
  139. End Function

Функция LoadConstants загружает все необходимые переменные и строки (hInstance, LCID, командная строка, подстановочные символы, пути по умолчанию, и т.д.). Все строки сохраняются в формате UNICODE-BSTR. Функция GetString загружает строку из ресурсов по ее идентификатору. Перечисление MessagesID содержит некоторые строковые идентификаторы нужные для работы программы (сообщения об ошибках, имена библиотек, и.т.д.). Когда все константы загрузятся вызывается функция ReadProject которая загружает проект:
VB Code:
  1. ' // Load project
  2. Function ReadProject() As Boolean
  3.     Dim hResource       As Long:                Dim hMememory       As Long
  4.     Dim lResSize        As Long:                Dim pRawData        As Long
  5.     Dim status          As Long:                Dim pUncompressed   As Long
  6.     Dim lUncompressSize As Long:                Dim lResultSize     As Long
  7.     Dim tmpStorageItem  As BinStorageListItem:  Dim tmpExecuteItem  As BinExecListItem
  8.     Dim pLocalBuffer    As Long
  9.     
  10.     ' // Load resource
  11.     hResource = FindResource(hInstance, GetString(PROJECT), RT_RCDATA)
  12.     If hResource = 0 Then GoTo CleanUp
  13.     
  14.     hMememory = LoadResource(hInstance, hResource)
  15.     If hMememory = 0 Then GoTo CleanUp
  16.     
  17.     lResSize = SizeofResource(hInstance, hResource)
  18.     If lResSize = 0 Then GoTo CleanUp
  19.     
  20.     pRawData = LockResource(hMememory)
  21.     If pRawData = 0 Then GoTo CleanUp
  22.     
  23.     pLocalBuffer = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lResSize)
  24.     If pLocalBuffer = 0 Then GoTo CleanUp
  25.     
  26.     ' // Copy to local buffer
  27.     CopyMemory ByVal pLocalBuffer, ByVal pRawData, lResSize
  28.     
  29.     ' // Set default size
  30.     lUncompressSize = lResSize * 2
  31.     
  32.     ' // Do decompress...
  33.     Do
  34.         
  35.         If pUncompressed Then
  36.             pUncompressed = HeapReAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, ByVal pUncompressed, lUncompressSize)
  37.         Else
  38.             pUncompressed = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lUncompressSize)
  39.         End If
  40.         
  41.         status = RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, _
  42.                                      ByVal pUncompressed, lUncompressSize, _
  43.                                      ByVal pLocalBuffer, lResSize, lResultSize)
  44.         
  45.         lUncompressSize = lUncompressSize * 2
  46.         
  47.     Loop While status = STATUS_BAD_COMPRESSION_BUFFER
  48.     
  49.     pProjectData = pUncompressed
  50.     
  51.     If status Then GoTo CleanUp
  52.  
  53.     ' // Validation check
  54.     If lResultSize < LenB(ProjectDesc) Then GoTo CleanUp
  55.     
  56.     ' // Copy descriptor
  57.     CopyMemory ProjectDesc, ByVal pProjectData, LenB(ProjectDesc)
  58.     
  59.     ' // Check all members
  60.     If ProjectDesc.dwSizeOfStructure <> Len(ProjectDesc) Then GoTo CleanUp
  61.     If ProjectDesc.storageDescriptor.dwSizeOfStructure <> Len(ProjectDesc.storageDescriptor) Then GoTo CleanUp
  62.     If ProjectDesc.storageDescriptor.dwSizeOfItem <> Len(tmpStorageItem) Then GoTo CleanUp
  63.     If ProjectDesc.execListDescriptor.dwSizeOfStructure <> Len(ProjectDesc.execListDescriptor) Then GoTo CleanUp
  64.     If ProjectDesc.execListDescriptor.dwSizeOfItem <> Len(tmpExecuteItem) Then GoTo CleanUp
  65.     
  66.     ' // Initialize pointers
  67.     pStoragesTable = pProjectData + ProjectDesc.dwSizeOfStructure
  68.     pExecutesTable = pStoragesTable + ProjectDesc.storageDescriptor.dwSizeOfItem * ProjectDesc.storageDescriptor.dwNumberOfItems
  69.     pFilesTable = pExecutesTable + ProjectDesc.execListDescriptor.dwSizeOfItem * ProjectDesc.execListDescriptor.dwNumberOfItems
  70.     pStringsTable = pFilesTable + ProjectDesc.dwFileTableLen
  71.     
  72.     ' // Check size
  73.     If (pStringsTable + ProjectDesc.dwStringsTableLen - pProjectData) <> lResultSize Then GoTo CleanUp
  74.     
  75.     ' // Success
  76.     ReadProject = True
  77.     
  78. CleanUp:
  79.     
  80.     If pLocalBuffer Then HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pLocalBuffer
  81.     
  82.     If Not ReadProject And pProjectData Then
  83.         HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
  84.     End If
  85.     
  86. End Function

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

Эта процедура проходит по всем элементам хранилища и распаковывает их одна за одной исключая главный исполняемый файл. Функция NormalizePath заменяет подстановочные знаки на реальные пути. Также существует функция CreateSubdirectories которая создает промежуточные директории (если необходимо) по переданному в качестве параметра пути. Затем вызывается функция CreateFile для создания файла затем через WriteFile данные пишутся в файл. Если происходит ошибка то выводится стандартное сообщение с предложением повторить, отменить или игнорировать.
VB Code:
  1. ' // Create all subdirectories by path
  2. Function CreateSubdirectories( _
  3.                 ByVal pPath As Long) As Boolean
  4.     Dim pComponent As Long
  5.     Dim tChar      As Integer
  6.     
  7.     ' // Pointer to first char
  8.     pComponent = pPath
  9.     
  10.     ' // Go thru path components
  11.     Do
  12.     
  13.         ' // Get next component
  14.         pComponent = PathFindNextComponent(pComponent)
  15.         
  16.         ' // Check if end of line
  17.         CopyMemory tChar, ByVal pComponent, 2
  18.         If tChar = 0 Then Exit Do
  19.         
  20.         ' // Write null-terminator
  21.         CopyMemory ByVal pComponent - 2, 0, 2
  22.         
  23.         ' // Check if path exists
  24.         If PathIsDirectory(pPath) = 0 Then
  25.         
  26.             ' // Create folder
  27.             If CreateDirectory(pPath, ByVal 0&) = 0 Then
  28.                 ' // Error
  29.                 CopyMemory ByVal pComponent - 2, &H5C, 2
  30.                 Exit Function
  31.             End If
  32.             
  33.         End If
  34.         
  35.         ' // Restore path delimiter
  36.         CopyMemory ByVal pComponent - 2, &H5C, 2
  37.         
  38.     Loop
  39.     
  40.     ' // Success
  41.     CreateSubdirectories = True
  42.     
  43. End Function
  44.  
  45. ' // Get normalize path (replace wildcards, append file name)
  46. Function NormalizePath( _
  47.                 ByVal pPath As Long, _
  48.                 ByVal pTitle As Long) As Long
  49.     Dim lPathLen    As Long:    Dim lRelacerLen As Long
  50.     Dim lTitleLen   As Long:    Dim pRelacer    As Long
  51.     Dim lTotalLen   As Long:    Dim lPtr        As Long
  52.     Dim pTempString As Long:    Dim pRetString  As Long
  53.     
  54.     ' // Determine wildcard
  55.     Select Case True
  56.     Case IntlStrEqWorker(0, pPath, pAppRepl, 5): pRelacer = pAppPath
  57.     Case IntlStrEqWorker(0, pPath, pSysRepl, 5): pRelacer = pSysPath
  58.     Case IntlStrEqWorker(0, pPath, pTmpRepl, 5): pRelacer = pTmpPath
  59.     Case IntlStrEqWorker(0, pPath, pWinRepl, 5): pRelacer = pWinPath
  60.     Case IntlStrEqWorker(0, pPath, pDrvRepl, 5): pRelacer = pDrvPath
  61.     Case IntlStrEqWorker(0, pPath, pDtpRepl, 5): pRelacer = pDtpPath
  62.     Case Else: pRelacer = pStrNull
  63.     End Select
  64.     
  65.     ' // Get string size
  66.     lPathLen = lstrlen(ByVal pPath)
  67.     lRelacerLen = lstrlen(ByVal pRelacer)
  68.     
  69.     ' // Skip wildcard
  70.     If lRelacerLen Then
  71.         pPath = pPath + 5 * 2
  72.         lPathLen = lPathLen - 5
  73.     End If
  74.     
  75.     If pTitle Then lTitleLen = lstrlen(ByVal pTitle)
  76.     
  77.     ' // Get length all strings
  78.     lTotalLen = lPathLen + lRelacerLen + lTitleLen
  79.     
  80.     ' // Check overflow (it should be les or equal MAX_PATH)
  81.     If lTotalLen > MAX_PATH Then Exit Function
  82.     
  83.     ' // Create string
  84.     pTempString = SysAllocStringLen(0, MAX_PATH)
  85.     If pTempString = 0 Then Exit Function
  86.     
  87.     ' // Copy
  88.     lstrcpyn ByVal pTempString, ByVal pRelacer, lRelacerLen + 1
  89.     lstrcat ByVal pTempString, ByVal pPath
  90.  
  91.     ' // If title is presented append
  92.     If pTitle Then
  93.  
  94.         ' // Error
  95.         If PathAddBackslash(pTempString) = 0 Then GoTo CleanUp
  96.  
  97.         ' // Copy file name
  98.         lstrcat ByVal pTempString, ByVal pTitle
  99.         
  100.     End If
  101.     
  102.     ' // Alloc memory for translation relative path to absolute
  103.     pRetString = SysAllocStringLen(0, MAX_PATH)
  104.     If pRetString = 0 Then GoTo CleanUp
  105.     
  106.     ' // Normalize
  107.     If PathCanonicalize(pRetString, pTempString) = 0 Then GoTo CleanUp
  108.     
  109.     NormalizePath = pRetString
  110.     
  111. CleanUp:
  112.     
  113.     If pTempString Then SysFreeString pTempString
  114.     If pRetString <> 0 And NormalizePath = 0 Then SysFreeString pRetString
  115.     
  116. End Function
  117.  
  118. ' // Concatenation strings
  119. Function StrCat( _
  120.                 ByVal pStringDest As Long, _
  121.                 ByVal pStringAppended As Long) As Long
  122.     Dim l1 As Long, l2 As Long
  123.     
  124.     l1 = lstrlen(ByVal pStringDest): l2 = lstrlen(ByVal pStringAppended)
  125.     StrCat = SysAllocStringLen(0, l1 + l2)
  126.     
  127.     If StrCat = 0 Then Exit Function
  128.     
  129.     lstrcpyn ByVal StrCat, ByVal pStringDest, l1 + 1
  130.     lstrcat ByVal StrCat, ByVal pStringAppended
  131.     
  132. End Function


Вторая часть ->.

Обновлено 14.09.2016 в 21:29 The trick

Категории
Visual Basic 6.0

Комментарии

  1. Аватар для yiiconf2017
    Можно поподробнее, пожалуйтса
  2. Аватар для The trick
    Цитата Сообщение от yiiconf2017
    Можно поподробнее, пожалуйтса
    Думаю я достаточно подробно все описал. Если есть конкретный вопрос - задавайте.