Решил написать маленькую заметку. Она сохранит вам нервов. На самом деле тут мало чего нового, так что да, имейте ввиду. Почему рантайм линковка? Рантайм линковка позволяет полностью (ну, или почти) избавиться от IAT (Import Address Table), а также затруднит анализ кода аналитиками. Им придется покопаться в листингах, чтоб понять, что делает тот или иной вызов. В этой «статье» мы оставим несколько записей в IAT, а в выводе, я скажу, как можно и от них избавиться, так что оставлю это на вас. Хорошо, а как реализовать-то? Весьма просто, в принципе особых знаний и не нужно, просто имейте под своей рукой документацию по винде (aka MSDN). Я уже создал проект, он не содержит в себе CRT, а следовательно, вес финального PE (релиз версии, если точнее) будет очень мал, да и CRT-функций в IAT уже не будет, как вы могли догадаться. Создаем проект, по классике включаем Windows.h и processthreadsapi.h, пишем входную точку (предварительно необходимо настроить компоновщик, если вы собрались использовать кастомную входную точку). #include <Windows.h> #include <processthreadsapi.h> VOID APIENTRY ENTRY() { } C #include <Windows.h> #include <processthreadsapi.h> VOID APIENTRY ENTRY() { } processthreadsapi.h пригодиться нам для нескольких функций, как я и говорил ранее, это всего лишь заметка, поэтому я все еще в праве оставить несколько функций в IAT. Теперь, нам необходимо определить функцию, которую мы хотим вызвать. Так как это просто заметка, я остановился на Unicode версии Message Box – MessageBoxW. Открываем MSDN, смотрим по документации какие параметры принимает функция. В моем случае: HWND, LPCWSTR и UINT. Определим это как указатель на функцию через ключевое слово typedef: typedef int(__stdcall *MSGBOXPRC)(HWND, LPCWSTR, LPCWSTR, UINT); //MessageBoxW это функция, возвращающая целое число, поэтому и int. C typedef int(__stdcall *MSGBOXPRC)(HWND, LPCWSTR, LPCWSTR, UINT); //MessageBoxW это функция, возвращающая целое число, поэтому и int. __stdcall – конвенция вызова, в данном случае обычный вызов, который передает аргументы через стек справа налево. Он равносилен конвенции WINAPI. На самом деле, лучше WINAPI и писать, на мой взгляд, но да ладно. О конвенциях можно подробнее почитать на MSDN, у них под это выделен топик. Наведем пару штрихов в точке входа, а конкретней добавим функцию LoadLibrary, которая подгрузит нам user32.dll: #include <Windows.h> #include <processthreadsapi.h> typedef int(__stdcall *MSGBOXPRC)(HWND, LPCWSTR, LPCWSTR, UINT); VOID APIENTRY ENTRY() { HMODULE hModule; //HMODULE – базовый адрес DLL MSGBOXPRC msgbox; //Наша функция hModule = LoadLibrary(L"user32.dll"); } C #include <Windows.h> #include <processthreadsapi.h> typedef int(__stdcall *MSGBOXPRC)(HWND, LPCWSTR, LPCWSTR, UINT); VOID APIENTRY ENTRY() { HMODULE hModule; //HMODULE – базовый адрес DLL MSGBOXPRC msgbox; //Наша функция hModule = LoadLibrary(L"user32.dll"); } Теперь в hModule содержится адрес user32.dll. Проверим, что он не равен NULL, если вдруг он окажется равен NULL, то сделаем что-нибудь (я ничего решил не делать): #include <Windows.h> #include <processthreadsapi.h> VOID APIENTRY ENTRY() { HMODULE hModule; MSGBOXPRC msgbox; hModule = LoadLibrary(L"user32.dll"); if(hModule != NULL) { } else { //err. } } C #include <Windows.h> #include <processthreadsapi.h> VOID APIENTRY ENTRY() { HMODULE hModule; MSGBOXPRC msgbox; hModule = LoadLibrary(L"user32.dll"); if(hModule != NULL) { } else { //err. } } Получим адрес функции через GetProcAddress: msgbox = (MSGBOXPRC)GetProcAddress(hModule, "MessageBoxW"); C msgbox = (MSGBOXPRC)GetProcAddress(hModule, "MessageBoxW"); Она принимает два аргумента: наш HMODULE и LPCSTR (строка с именем функции). Заранее проверим, валидный ли он: #include <Windows.h> #include <processthreadsapi.h> #pragma region runtimedefs typedef int(__stdcall *MSGBOXPRC)(HWND, LPCWSTR, LPCWSTR, UINT); #pragma endregion VOID APIENTRY ENTRY() { HMODULE hModule; MSGBOXPRC msgbox; hModule = LoadLibrary(L"user32.dll"); if(hModule != NULL) { msgbox = (MSGBOXPRC)GetProcAddress(hModule, "MessageBoxW"); //Получаем адрес функции if(msgbox != NULL) { } } else { //err. } } C #include <Windows.h> #include <processthreadsapi.h> #pragma region runtimedefs typedef int(__stdcall *MSGBOXPRC)(HWND, LPCWSTR, LPCWSTR, UINT); #pragma endregion VOID APIENTRY ENTRY() { HMODULE hModule; MSGBOXPRC msgbox; hModule = LoadLibrary(L"user32.dll"); if(hModule != NULL) { msgbox = (MSGBOXPRC)GetProcAddress(hModule, "MessageBoxW"); //Получаем адрес функции if(msgbox != NULL) { } } else { //err. } } Осталось только вызвать функцию. Делается через указатель на функцию. (msgbox)(NULL, TEXT("Loaded Message Box!"), TEXT("Runtime Linking"), MB_OK); C (msgbox)(NULL, TEXT("Loaded Message Box!"), TEXT("Runtime Linking"), MB_OK); Также не забываем освободить библиотеку через FreeLibrary: FreeLibrary(hModule); C FreeLibrary(hModule); Запустим наш код. Работает, как видите. Hatred #include <Windows.h> #include <processthreadsapi.h> #pragma region runtimedefs typedef int(__stdcall *MSGBOXPRC)(HWND, LPCWSTR, LPCWSTR, UINT); #pragma endregion VOID APIENTRY ENTRY() { HMODULE hModule; MSGBOXPRC msgbox; hModule = LoadLibrary(L"user32.dll"); if(hModule != NULL) { msgbox = (MSGBOXPRC)GetProcAddress(hModule, L"MessageBoxW"); if(msgbox != NULL) { (msgbox)(NULL, TEXT("Loaded Message Box!"), TEXT("Runtime Linking"), MB_OK); } FreeLibrary(hModule); } else { // err. } } C #include <Windows.h> #include <processthreadsapi.h> #pragma region runtimedefs typedef int(__stdcall *MSGBOXPRC)(HWND, LPCWSTR, LPCWSTR, UINT); #pragma endregion VOID APIENTRY ENTRY() { HMODULE hModule; MSGBOXPRC msgbox; hModule = LoadLibrary(L"user32.dll"); if(hModule != NULL) { msgbox = (MSGBOXPRC)GetProcAddress(hModule, L"MessageBoxW"); if(msgbox != NULL) { (msgbox)(NULL, TEXT("Loaded Message Box!"), TEXT("Runtime Linking"), MB_OK); } FreeLibrary(hModule); } else { // err. } } Рефлексия/Вывод Да, ваш код будет менее чистым, с ним будет муторнее работать, но это того стоит. Однако, у нас все равно осталось несколько записей в IAT. Я говорил ранее, что и от них можно избавиться. Да, можно, для этого вам необходимо получить PEB-структуру, затем извлечить из нее дескриптор модуля ядра. К PEB вы можете обратиться через регистр fs, по смещению 30h: HMODULE GetKernel(){ __asm { mov eax, dword ptr fs : [30h] mov eax, dword ptr[eax + 0ch] mov esi, dword ptr[eax + 1ch] lodsd //Загрузить строковый операнд в регистр EAX mov eax, dword ptr[eax + 08h] } } C HMODULE GetKernel(){ __asm { mov eax, dword ptr fs : [30h] mov eax, dword ptr[eax + 0ch] mov esi, dword ptr[eax + 1ch] lodsd //Загрузить строковый операнд в регистр EAX mov eax, dword ptr[eax + 08h] } } Учтите, что вам также придется писать свой аналог функции GetProcAddress. Через свой аналог GetProcAddress получить адрес на LoadLibrary и пошло поехало. Также, несмотря на всю эту прелесть, строки в коде все же остаются на зло нам. Поэтому, правильнее будет реализовать поиск функций по хешу. Плюсовикам тут будет играть на руку (я все же обычный сишник), они могут реализовать хеш-функцию (мне, в принципе это тоже не мешало), затем, чтоб не переопределять каждый раз функции, создать несколько перегруженных шаблонов, с нужным количеством аргументов и там искать адрес функции через кастомный аналог GetProcAddress по хешу. Как-то так.
пиздатая статья незаслуженно оставленная без внимания, читать интересно, все довольно просто, давай побольше темок Особенно интересно как испортить жизнь реверсерам, чтоб их ида ваще пососала большой негритоской докторской
AIexa, thank you. Название темы скорее всего просто отпугивает людей, хоть и несмотря на простую реализацию идеи, которую можно увидеть во многих более профессиональных продуктах. Чтож, я не особо гонюсь за признанием и прочим. Не особо есть возможность разбежаться, ибо тут просто работают крипторы (однако можно написать про стабы). Технически написать о чем-то подобном можно. Как будет возможность что-то рассказать :^)
Тема хорошая, но почему бы не использовать Lazy Importer, например. Из плюсов: код чище, проще для использования новичкам. Из плюсов только что можно использовать в nocrt проектах, но есть ли от них смысл?(lazy вроде нельзя в nocrt юзать, требуется переписывать его).
GitHub_inactive5428188, Я никого не ограничиваю же, я показал, как можно избавиться ручками от IAT (или от большего его куска). Если в проекте нежелательно использование сторонних библиотек, можно самому динамически резолвить функции (RVA их), как показано здесь. А еще я не знаю, работает ли эта штука для сишечки (не для крестов). Ну, почему только в No-CRT? Сейчас отойду немного от кейса с "вырезанием IAT": вполне бывает так, что нужно резолвить какие-нибудь специфические NTAPI (и другие API) функции в юзермоде или кернелмоде, делается это точно также. В остальном, это больше пособие для юных кодеров зверьков, которые хотят избавиться от лишнего и слегка усложнить анализ PE-файла.