Форум программистов, компьютерный форум, киберфорум
The trick
Войти
Регистрация
Восстановить пароль
Оценить эту запись

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

Запись от The trick размещена 14.09.2016 в 19:20
Обновил(-а) The trick 14.09.2016 в 19:29

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

Компилятор.

Нажмите на изображение для увеличения
Название: Compiler.png
Просмотров: 402
Размер:	20.5 Кб
ID:	3963

Это приложение формирующее информацию для инсталляции и размещающее ее в ресурсах загрузчика. Вся информация хранится в файлах проекта. Вы можете сохранять и загружать проекты из файлов. Класс clsProject описывает такой проект. Компилятор содержит 3 секции: storage, execute, mainfest.
Секция 'storage' позволяет добавлять файлы которые будут скопированы в момент запуска приложения. Каждая запись в списке имеет флаги: 'replace if exists', 'main executable', 'ignore error'. Если выбрана 'replace if exists' то файл будет скопирован из ресурсов даже если он есть на диске. Флаг 'main executable' может быть установлен только единственного исполняемого файла который будет запущен когда все операции будут исполнены. И наконец 'ignore error' просто заставляет игнорировать все ошибки и не выводить сообщения. Порядок расположения записей в списке соответствует порядку распаковки файлов, исключая главный исполняемый файл. Главный исполняемый файл не извлекается и запускается после всех операций. Класс clsStorage описывает данную секцию. Этот класс содержит коллекцию объектов класса
clsStorageItem и дополнительные методы. Свойство MainExecutable определяет индекс главного исполняемого файла в хранилище. Когда этот параметр равен -1 значит главный исполняемый файл не задан. Класс clsStoragaItem описывает одну запись из списка хранилища, который содержит свойства определяющие поведение итема. Секция 'storage' полезна если вы хотите скопировать файлы на диск перед выполнением главного приложения (различные ресурсы/OCX/DLL и т.п.).
Следующая секция называется 'execute'. Она содержит список выполняемых команд. Эти команды просто передаются в функцию ShellExecuteEx. Таким образом можно к примеру зарегистрировать библиотеки или сделать что-то еще. Каждый элемент этого списка имеет два свойства: путь и параметры. Стоит отметить что все команды выполняються синхронно в порядке заданным в списке. Также каждый элемент списка может иметь флаг 'ignore error' который предотвращает вывод каких-либо сообщений об ошибках. Секция 'execute' представлена двумя классами clsExecute and clsExecuteItem которые очень похожи на классы хранилища.
Последняя секция - 'manifest'. Это просто текстовый файл который добавляеться в финальный файл в качестве манифеста. Для того чтобы включить манифест в EXE нужно просто выбрать флажок 'include manifest' во вкладке 'mainfest'. Это может быть полезно для использования библиотек без регистрации, визуальных стилей и т.п.
Все классы ссылаються на объект проекта (clsProject) который управляет ими. Каждый класс который ссылается на проект может быть сохранен или заружен используя PropertyBag в качестве контейнера. Все ссылки сохраняються с относительными путями (как в .vbp файле) поэтому можно перемещать папку с проектом без проблем с путями. Для того чтобы транслировать из/то относительного/абсолютного пути я использовал функции PathRelativePathTo и PathCanonicalize.
Итак, это была базовая информация о проекте Compiler. Сейчас я расскажу о процедуре компиляции. Как я уже сказал вся информация об инсталляции сохраняется в ресурсы загрузчика. Вначале на нужно определить формат данных:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
' // 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 содержит индекс исполняемого файла в хранилище. Общая структура показана на рисунке:

Нажмите на изображение для увеличения
Название: BinProject_rus.png
Просмотров: 279
Размер:	65.1 Кб
ID:	3964

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

Нажмите на изображение для увеличения
Название: DOs.png
Просмотров: 426
Размер:	1.4 Кб
ID:	3965

Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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:
Visual Basic
1
2
3
4
5
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). Следующее поле описывает исполняемый файл и имеет следующий формат:
Visual Basic
1
2
3
4
5
6
7
8
9
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 имеет следующий формат:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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. Рассмотрим формат заголовка секции:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
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 смещение в файле:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
' // 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 файл не использует их все. Давайте рассмотрим структуру элемента каталога:
Visual Basic
1
2
3
4
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 таблицы ресурсов которая имеет следующий формат:
Visual Basic
1
2
3
4
5
6
7
8
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 и имеют следующую структуру:
Visual Basic
1
2
3
4
Type IMAGE_RESOURCE_DIRECTORY_ENTRY
    NameId                      As Long
    OffsetToData                As Long
End Type
Первое поле этой структуры определяет является ли это именованной запись либо это запись с числовым идентификатором в зависимости от старшего бита. Если этот бит установлен то остальные биты определяют смещение от начала ресурсов к структуре IMAGE_RESOURCE_DIR_STRING_U которая имет следующий формат:
Visual Basic
1
2
3
4
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:
Visual Basic
1
2
3
4
5
6
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:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
' // 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 для того чтобы загрузить все необходимые константы из ресурсов:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
' // 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 которая загружает проект:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
' // 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 которая распаковывает все файлы из хранилища, согласно данным проекта:
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
' // 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 данные пишутся в файл. Если происходит ошибка то выводится стандартное сообщение с предложением повторить, отменить или игнорировать.
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
' // 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
Вторая часть ->.
Размещено в Без категории
Показов 4395 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.