- Статус
- Оффлайн
- Регистрация
- 2 Июл 2020
- Сообщения
- 140
- Реакции
- 285
Вступление:
⠀⠀⠀⠀⠀Всех приветствую и желаю хорошего настроения, да и хорошего дня. В этой статье(из серии) будет рассмотрено написание простого протектора, поэтому начнём с самого простого т.е создание SDK. Я решил разбить на несколько частей фактический 1 проект по следующим причинам:
1) Если публиковать всё в 1 момент, то часть информации при написания протектора с большим шансом будет упущена. Я больше придерживаюсь принципа step by step or bit by bit.
2) Из-за нехватки времени(личная жизнь, учёба и т.п. и т.д.) и иногда переменного настроения не получится написать всё сразу.
3) На мой взгляд, лучше попытаться написать своё решение(даже если решение проблемы будет слегка неудачное).
4) Код и пример больше подразумевает использование под Windows и при компиляции через Visual Studio Community т.к брать несколько компиляторов и платформ – крайне сомнительное дело. В крайнем случае изучите разницу между PE файлами.
⠀⠀⠀⠀⠀Считайте это больше примером, а не готовым кодом т.к сделать одному за условно несколько неполных дней у вас не особо получится.
Так же при работе с PE файлом вы должны понимать,что вы делаете ±, поскольку иногда вставка кода из интернета может привести иногда к забавным ситуациям. Так же рекомендую изучить работу импорта или тех же релоков, чтобы понять их работу т.к если в чужом коде будут траблы, и вы не понимаете, как это фиксить => вы должны сначала это будете изучить и желательно написать свою реализацию кода. В связи с этим я предпочту больше описать работу тех же импортов, релоков, нежели кидать код в виде результата или работу кода без серьёзного объяснения.
⠀⠀⠀⠀⠀Я не буду с нуля описывать те же все структуры секции и в конце просто оставлю ссылки на определённый материал + я не могу писать бесконечное количество символов в посте.
⠀⠀⠀⠀⠀Пару моментов, и тот же парсинг я решил взять готовый,но вы в любом случае должны понимать что делаете. Я долго не занимался кодингом нормально, поэтому во многих местах мог сделать сомнительные действия. Надеюсь, читатель подскажет и скажет мои ошибки. Так же основная реализация будет касаться x64 кода,а x32 будет опубликован под постом слегка позже. Протектор предназначен для наивных файлов и для языка c/c++(можно, конечно, и другие языки аля femboy Rust, но легче будет сами сделать реализацию).
При написании анализа использовались следующие инструменты:
PE-Bear, CFF explorer, Visual Studio Community, Ida Pro & x64dbg.
Так же понадобятся библиотеки:
Пожалуйста, авторизуйтесь для просмотра ссылки.
(генерация кода) &
Пожалуйста, авторизуйтесь для просмотра ссылки.
(дизассемблирование кода). Можете использовать и другие, всё зависит от вас 
Part 1: Import
⠀⠀⠀⠀⠀Здесь будет представлен пример изучения работы IMAGE_DIRECTORY_ENTRY_IMPORT и IMAGE_DIRECTORY_ENTRY_IAT. IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT и IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT будут рассмотрены слегка позже и дописаны в комментарии к посту(или в самом посте с примечанием),когда появится время.⠀⠀⠀⠀⠀С первым,с чем вы столкнётесь в IMAGE_DIRECTORY_ENTRY_IMPORT. VirtualAddress – массив структуры IMAGE_IMPORT_DESCRIPTOR,сама структура документирована(лежит в header-файле winnt.h):

⠀⠀⠀⠀⠀Если описывать простыми словами:
Name – имя DLL в которой находится нужный экспорт.
FirstThunk – указатель на диапазон из IMAGE_DIRECTORY_ENTRY_IAT,где содержится указатель на rva(но фактический размер будет PVOID из-за совмещения с oridinal) PIMAGE_IMPORT_BY_NAME или находится oridinal экспорта(фактический номер экспорта в нужной DLL, но с флагом и как я понял, начинается с 1,а не с нуля).
OriginalFirstThunk – указатель на rva(размер так же PVOID) таблицу PIMAGE_IMPORT_BY_NAME или oridinal(могут совместно существовать).
⠀⠀⠀⠀⠀Чтобы стало более наглядно, продемонстрирую небольшой пример с анализом(пример с импортом из KERNEL32.DLL):

⠀⠀⠀⠀⠀Как упоминалось выше, фактический это указатели на rva, но размер IMAGE_IMPORT_DESCRIPTOR ещё завершается обнулённым sizeof(IMAGE_IMPORT_DESCRIPTOR).
⠀⠀⠀⠀⠀OriginalFirstThunk – фактический rva list заканчивающийся NULL и указатель rva имеет размер (PVOID). Пример с oridinal будет слегка попозже и из системной библиотеки.

⠀⠀⠀⠀⠀Name – rva имя DLL в которой находится нужный экспорт.

⠀⠀⠀⠀⠀Тут небольшой мем с Ida Pro(по крайней мере, в версии 8.3) и как загрузчик PE будет работать.Фактический, если переменная из IMAGE_DIRECTORY_ENTRY_IAT не будет содержать в себе rva на PIMAGE_IMPORT_BY_NAME или содержать в себе oridinal, то импорт в Ida Pro будет показан, но переменная не инициализируется при загрузке PE.
⠀⠀⠀⠀⠀Для наглядности я воспользуюсь PE Bear, как это будет выглядеть при обычной компиляции кода:

⠀⠀⠀⠀⠀Oridinal будет выглядеть так(пример с KernelBase.dll и импортом из ntdll.dll)

⠀⠀⠀⠀⠀Вместо rva на PIMAGE_IMPORT_BY_NAME,будет содержаться export id | IMAGE_ORDINAL_FLAG(x64 - IMAGE_ORDINAL_FLAG64, x32 - IMAGE_ORDINAL_FLAG32).
⠀⠀⠀⠀⠀В зависимости от сборки они могут меняться,но тут от сборке windows к сборке.
⠀⠀⠀⠀⠀Единственное с чем я решил не разбираться - выравниванием в PIMAGE_IMPORT_BY_NAME и Name(IMAGE_IMPORT_DESCRIPTOR), и поэтому выравниваю по sizeof(PVOID).
Пример реализации создания можете посмотреть
Пожалуйста, авторизуйтесь для просмотра ссылки.
(слегка громоздкий вариант).⠀⠀⠀⠀⠀При создании новой IMAGE_DIRECTORY_ENTRY_IMPORT и IMAGE_DIRECTORY_ENTRY_IAT вам нужно будет найти все вызовы/чтение/получение адреса, вам потребуется пройтись дизассемблером и пофиксить вызовы на новый адрес(в x64 из-за rip- relative инструкций это будет легко и вот пример реализации). В x32 ещё потребуется пересоздавать IMAGE_DIRECTORY_ENTRY_BASERELOC(в примере он удаляется и заново создаётся ппри фиксе вызовов/чтения/получения) т.к во всех вызовах/чтении/получении адреса используется релоки из-за отсутствия rip- relative инструкций(фактический, тоже самое,но нужно ещё прибавлять ImageBase из PIMAGE_OPTIONAL_HEADER).
Part 2: relocathion
⠀⠀⠀⠀⠀Здесь будет описана сама структура, поскольку описание работы релоков можете найти в списке используемых источников. Сама структура документирована(лежит в header-файле winnt.h):
⠀⠀⠀⠀⠀Если объяснять простыми словами как устроена структура: Есть несколько массивов IMAGE_BASE_RELOCATION, где содержится VirtualAddress выровненный по PAGE_SIZE(0x1000) и много смещений, содержащие типы релока и его rva(максимальное PAGE_SIZE-1). Между массивами нет никаких выравниваний(есть только не брать IMAGE_REL_BASED_ABSOLUTE,но он не обязательный).
Создание релока не супер сложное(пример из кода):
create relocathion:
builded_reloce = pe->reloce_info.list[i + j].type; //IMAGE_REL_BASED_DIR64 и т.п
builded_reloce <<= SHIFT_RELOCE;//установка типа
builded_reloce |= pe->reloce_info.list[i + j].rva;
Для примера возьму x32 программу и слегка разберёмся как устроены релоки в ней.


Само
Пожалуйста, авторизуйтесь для просмотра ссылки.
не супер сложное, но нужно будет отсортировать и правильно отнести релок к нужному с нужным VirtualAddressPart 3: create section
⠀⠀⠀⠀⠀Как говорится: самый лучший код – код, взятый с интернета. Правда , вы должны понимать,что нет гарантий,что код будет выполняться как вы хотите. Я решил взять реализация создания секции с
Пожалуйста, авторизуйтесь для просмотра ссылки.
.Однако, есть пару подлянок и разберём самую забавную:
bad fix size:
optional_header->SizeOfImage = align(optional_header->SizeOfImage + size + sizeof(uint32_t) + 1 + sizeof(IMAGE_SECTION_HEADER), optional_header->SectionAlignment);



⠀⠀⠀⠀⠀Фактический будет взят код/данные размером 0x200 из 1 секции, благодаря чему в выделенном регионе для PE будет лежать ещё ваш код…
Здесь пришлось пофиксить такие прикольные моменты и не уверен в написании кода только для подсчёта размера SizeOfImage, но расчёт должен быть правильный(я не создавал супер много секций для теста и зная себя в некоторых моментах мог кое-что пропустить, но размеры по типу 0x1000, 0x1337 инициализируются и выравниваются правильно). Пример слегка исправленного кода можете посмотреть
Пожалуйста, авторизуйтесь для просмотра ссылки.
.Единственный не совсем обязательный момент – выравнивание VirtualSize,хотя это не является обязательным, но мой глаз слегка дёргается при SizeOfRawData > VirtualSize.
Part 4: create SDK import или почему @Kodama сожрал мои радужные колечки
⠀⠀⠀⠀⠀В этом моменте слегка можно затупить, но здесь ничего сложного.Вам понадобится просто создать DLL-проект и добавить,например следующий код:
DLL-export:
#ifndef _WIN64
#define MEH_API __stdcall
#else
#define MEH_API __fastcall
#endif // !_WIN64
EXTERN_C __declspec(dllexport) bool MEH_API MehIsDebuggerDetect( )
{
return false;
}
EXTERN_C __declspec(dllexport) bool MEH_API MehIsCRCValid()
{
return true;
}
EXTERN_C __declspec(dllexport) bool MEH_API MehIsVMDetect()
{
return false;
}
В добавок вам нужно создать .def-файл и включить его в проект для корректного отображения символов в x32 при компиляции. Пример:
export_def_file:
EXPORTS
MehIsDebuggerDetect
MehIsCRCValid
MehIsVMDetect
После компиляции нужно подключить появившийся после компиляции dll .lib-файл и добавить,где вы планиурете использовать и создать header с SDK(я пока не добавлял для драйверов полную реализацию и не делал проверки). Пример:
SDK_IMPORT:
#pragma once
#ifndef _WIN64
#define MEH_API __stdcall
#else
#define MEH_API __fastcall
#endif // !_WIN64
EXTERN_C __declspec(dllimport) bool MEH_API MehIsDebuggerDetect();
EXTERN_C __declspec(dllimport) bool MEH_API MehIsCRCValid();
EXTERN_C __declspec(dllimport) bool MEH_API MehIsVMDetect();
Существуют и другие варианты добавления SDK,но этот является самым удобным.
После использовании .lib-файла и компиляции проекта у вас должен появиться этот импорт в файле:

⠀⠀⠀⠀⠀Теперь мы должны просто отдельно обработать и удалить импорт,если это является нашим SDK(в моём случае я пихаю в отдельный std::vector для удобства SDK и не инициализирую его) и вместо вызова/чтения должны впихнуть на наш код в секции.
⠀⠀⠀⠀⠀Простыми словами о состояние SDK и пару моментов при написании:
Anti-debug: установка и проверка ThreadHideFromDebugger, простой вызов ProcessDebugObjectHandle и с bug check.Это отдельно будет детектить,например, ScyllaHide и TitanHide, но не при совместной работе.
Anti-CRC patch:Идёт проверка только секций + дополнительно проверка на изменения массива с crc данными(кроме последних данных) + проверка на созданию новой секции за пределами SizeOfImage т.к можно восстановить данные в PE заголовке и детекта не будет,но новая секция,например, будет существовать. Сама проверка реквизирована у VMP.
Anti-vm: вызов cpuid(eax = 1) и проверка бита hypervisor present(31) в ecx.
P.S. Я не делал сложные проверки т.к требовалось бы больше времени и код стал бы объёмным.
Из моментов, которые нужно исправить или изменить в будущем:
1) Нужно сделать заранее подсчёт размера для импортов и релоков, чтобы выделять нужную память для секции с этими данными.
2) Добавить постепенно поддержку x32 и драйверов.
3) Подумать над парсингом кода из своего сокмпилированного протектора с использованием
Пожалуйста, авторизуйтесь для просмотра ссылки.
&
Пожалуйста, авторизуйтесь для просмотра ссылки.
+ self PE pars.Список используемых источников при написании статьи
1)
Пожалуйста, авторизуйтесь для просмотра ссылки.
2)
Пожалуйста, авторизуйтесь для просмотра ссылки.
3)
Пожалуйста, авторизуйтесь для просмотра ссылки.
4)
Пожалуйста, авторизуйтесь для просмотра ссылки.
5)
Пожалуйста, авторизуйтесь для просмотра ссылки.
6)
Пожалуйста, авторизуйтесь для просмотра ссылки.