Зоркий глаз антивируса так и норовит обнаружить наши пейлоады! Давай покажу, как скрыть все импорты, чтобы спрятаться от назойливого внимания антивирусных программ. Мы попробуем мимикрировать под легитимное приложение с помощью IAT Camouflage.
Все написанные под Windows программы имеют IAT (Import Address Table) — это таблица, которая содержит функции, импортируемые программой из DLL-библиотек. Зачастую нам, как атакующим, следует скрывать импорты, чтобы антивирусным приложениям было сложнее идентифицировать используемые в программе функции. В этой статье разобраны некоторые способы скрытия таблицы импортов.
Если мы посмотрим на то, что лежит у нее в IAT, то будем неприятно удивлены. Здесь и CRT, и какие‑то левые функции, которые мы даже не вызываем.
Предлагаю сразу же избавиться от CRT. Common Language Runtime — набор функций и макросов для программ на языке С. Функции обычно связаны с управлением памятью (memcpy()), открытием и закрытием файлов (fopen()) и работой со строками (strcpy()).
Библиотеки DLL, которые реализуют CRT, называются vcruntimeXXX.dll, где XXX — номер версии используемой библиотеки CRT. Это правило применяется не ко всем библиотекам CRT, встречаются также DLL c именами
Чтобы заставить компилятор сделать статическую линковку (в этом случае библиотека не импортируется, а уже как бы зашита в программу), следует изменить свойства проекта. Сначала открываем раздел в «Проект → Свойства».
Оттуда переходим в «C/C++ → Создание кода → Библиотека времени выполнения → Многопоточная (/MT) → Применить → ОК».
Затем перекомпилируем проект и проверяем IAT.
Отлично, от CRT в IAT мы избавились. Теперь пришло время поговорить о скрытии импорта функции MessageBoxW(). Сама функция представлена в библиотеке user32.dll, поэтому мы можем использовать API GetProcAddress() для получения адреса этой функции и ее вызова.
Здесь hModule — хендл на DLL, в которой реализована функция, а lpProcName — имя этой функции. Для успешного вызова MessageBoxW() нужно лишь создать прототип функции в нашем коде.
Компилируем, проверяем IAT и видим, что функция пропала.
Казалось бы, неплохо? Всем спасибо, конец статьи.
Но не тут‑то было. Давай настроим компилятор так, чтобы он импортировал не все функции из kernel32.dll, а лишь нужные, то есть те, которые в явном виде присутствуют в коде. Для этого сначала отключаем SDL.
Затем отключаем оптимизацию программы.
Отключаем исключения C++, делаем статическую линковку, отключаем проверку безопасности.
Включаем игнорирование стандартных библиотек.
Отключаем файл манифеста, убираем создание дебаг‑информации.
Затем устанавливаем точку входа.
Перекомпилируем проект, проверяем IAT и видим, что любой желающий сможет определить использование
От этих функций никак не избавиться (через
Просмотреть экспорты позволяет тот же dumpbin, но с флагом /exports.
Нам нужно лишь как‑то получить базовый адрес загрузки библиотеки, а затем от него можно добраться до EAT. Пока для простоты эксперимента базовый адрес загрузки предлагаю получать через LoadLibrary(). Эта функция позволяет загрузить библиотеку в текущий процесс, а затем получить на нее хендл.
Этот хендл является базовым адресом загрузки DLL в память процесса. После получения базового адреса следует начать парсить EAT, до него можно добраться по пути IMAGE_DOS_HEADER → IMAGE_NT_HEADERS → IMAGE_OPTIONAL_HEADER → IMAGE_DATA_DIRECTORY → IMAGE_EXPORT_DIRECTORY. Подробнее о парсинге PE можно прочитать в различных публикациях, вот несколько ссылок.
Функция достаточно проста: принимает базовый адрес загрузки библиотеки, а также имя функции, адрес которой надо получить. Работает так, как нам и нужно, — путем парсинга EAT.
Помнишь, мы отключили CRT? Поэтому использование функции strcmp() из этого кода невозможно. К счастью, сравнение двух строк — базовый алгоритм, который пишут еще на паскале в седьмом классе. На C++ я вынес его в отдельную функцию custom_strcmp().
Разобравшись, как и что делать, получаем следующий код.
Компилируем, запускаем. Видим, что остался лишь один импорт.
Чем же заменить LoadLibrary()? Если мы глянем на последовательность вызовов функций, то увидим, что LoadLibrary() вызывает LdrLoadDll(), она — LdrpLoadDll(), а та — еще одну функцию... Процесс, скажу честно, достаточно сложный. Вот картинка, которая хорошо описывает последовательность вызовов при загрузке DLL.
Предлагаю не распутывать весь этот клубок, а просто вызвать LdrLoadDll(). Функция представлена в ntdll.dll, а эта библиотека присутствует в любом процессе в Windows. Абсолютно в любом. Теперь проблема другая — откуда взять базовый адрес загрузки ntdll.dll? Здесь нам на помощь придет функция GetModuleHandle(). Эта функция принимает лишь один параметр — имя DLL, а возвращает хендл. Хендл как раз таки будет базовым адресом загрузки библиотеки.
Что ж, сказано — сделано, прототип функции LdrLoadDll() небольшой, параметры практически те же, что и у LoadLibrary(). Их просто чуть больше.
В этом коде мы сначала получаем базовый адрес ntdll.dll, а затем через наш кастомный GetProcAddress() находим адреса нужных функций, после чего загружаем DLL в процесс. Итоговый код программы стал чуть больше.
Вновь компилируем, запускаем.
Вот незадача, я снова забыл, что нельзя напрямую вызывать никакие функции. Продолжаем мозговой штурм. Что делает GetModuleHandle()? Функция принимает лишь имя библиотеки, а возвращает ее базовый адрес. Такую информацию можно достать из PEB (Process Environment Block). Вновь обращаемся к интернету и находим там нужный код.
Проблема в том, что здесь используются функции либо из CRT, либо те, которые будут отражаться в IAT. Заменяем memset().
Код моей кастомной функции прост: идет копирование в память нужного значения. Теперь заменяем wcscpy_s().
Здесь чуть сложнее: сначала идет проверка на размер, после чего подсчет длины (увеличение счетчика до тех пор, пока не будет встречен null byte — конец строки), а затем копирование в память.
Для сравнения двух Unicode-строк автор использует функцию из Shlwapi.
Мы такой роскошью не обладаем, поэтому вновь пишем код самостоятельно. Предлагаю просто по аналогии со strcmp() заменить wcscmp().
Эта функция вернет 0, если строки равны. Немного подебажив, я увидел, что в PEB ntdll представлен как C:\Windows\SYSTEM32\ntdll.dll, поэтому такое же значение и передаем нашей функции.
Итоговый код проекта стал еще больше. Теперь для стандартного MessageBox() нужно целых 176 строк.
Наконец в IAT стало пусто.
Причем обрати внимание, что у тебя компилятор может выдавать ошибку LNK1120.
Если это произошло, открывай раздел «Проект → Свойства» и отключай оптимизацию.
Осталось лишь навести немного красоты. Ведь это очень подозрительно, если у программы пустой IAT. Займемся IAT Camouflage.
Добавим, например, код, который никогда не выполнится.
Обрати внимание, что компиляторы нынче достаточно умные, поэтому нужно будет отключить оптимизацию, как описано выше. В противном случае компилятор увидит, что этот кусок кода никогда не срабатывает, и исключит его из исполняемого файла.
Компилируем проект и видим, что IAT стал похож на IAT обычной ничем не примечательной программы.
Можно даже в проекте создать маленькие макросы, которые будут выполнять эту работу автоматически.
Автор @MichelleVermishelle | TG: @Michaelzhm
Источник xakep.ru
Все написанные под Windows программы имеют IAT (Import Address Table) — это таблица, которая содержит функции, импортируемые программой из DLL-библиотек. Зачастую нам, как атакующим, следует скрывать импорты, чтобы антивирусным приложениям было сложнее идентифицировать используемые в программе функции. В этой статье разобраны некоторые способы скрытия таблицы импортов.
ПРОСТЕЙШЕЕ СКРЫТИЕ
Итак, пусть у нас есть простенькая программа, которая, например, выводит MessageBox.
C++:
#include <Windows.h>
int main() {
MessageBox(NULL, L"HI", L"HI", MB_OK);
return 0;
}
Если мы посмотрим на то, что лежит у нее в IAT, то будем неприятно удивлены. Здесь и CRT, и какие‑то левые функции, которые мы даже не вызываем.
Код:
dumpbin /imports .\Article.exe
Предлагаю сразу же избавиться от CRT. Common Language Runtime — набор функций и макросов для программ на языке С. Функции обычно связаны с управлением памятью (memcpy()), открытием и закрытием файлов (fopen()) и работой со строками (strcpy()).
Библиотеки DLL, которые реализуют CRT, называются vcruntimeXXX.dll, где XXX — номер версии используемой библиотеки CRT. Это правило применяется не ко всем библиотекам CRT, встречаются также DLL c именами
api-ms-win-crt-stdio-l1-1-0.dll, api-ms-win-crt-runtime-l1-1-0.dll и api-ms-win-crt-locale-l1-1-0.dll. Каждая DLL служит для определенной цели и экспортирует несколько функций. Эти библиотеки DLL компонуются компилятором во время компиляции и поэтому находятся в IAT сгенерированных программ.Чтобы заставить компилятор сделать статическую линковку (в этом случае библиотека не импортируется, а уже как бы зашита в программу), следует изменить свойства проекта. Сначала открываем раздел в «Проект → Свойства».
Оттуда переходим в «C/C++ → Создание кода → Библиотека времени выполнения → Многопоточная (/MT) → Применить → ОК».
Затем перекомпилируем проект и проверяем IAT.
Отлично, от CRT в IAT мы избавились. Теперь пришло время поговорить о скрытии импорта функции MessageBoxW(). Сама функция представлена в библиотеке user32.dll, поэтому мы можем использовать API GetProcAddress() для получения адреса этой функции и ее вызова.
Код:
FARPROC GetProcAddress(
[in] HMODULE hModule,
[in] LPCSTR lpProcName
);
C++:
#include <Windows.h>
typedef int (WINAPI* MessageBoxWFunc)(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
);
int main() {
HMODULE user32Module = LoadLibrary(L"user32.dll");
MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(GetProcAddress(user32Module, "MessageBoxW"));
MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
return 0;
}
Казалось бы, неплохо? Всем спасибо, конец статьи.
Но не тут‑то было. Давай настроим компилятор так, чтобы он импортировал не все функции из kernel32.dll, а лишь нужные, то есть те, которые в явном виде присутствуют в коде. Для этого сначала отключаем SDL.
Затем отключаем оптимизацию программы.
Отключаем исключения C++, делаем статическую линковку, отключаем проверку безопасности.
Включаем игнорирование стандартных библиотек.
Отключаем файл манифеста, убираем создание дебаг‑информации.
Затем устанавливаем точку входа.
Перекомпилируем проект, проверяем IAT и видим, что любой желающий сможет определить использование
LoadLibrary() и GetProcAddress() в нашем коде.
От этих функций никак не избавиться (через
GetProcAddress() тем более не вызвать — рекурсия), поэтому придется придумать какую‑то альтернативу.СОБСТВЕННЫЕ LOADLIBRARY() И GETPROCADDRESS()
Начнем с написания собственного GetProcAddress(). У каждой DLL-библиотеки есть раздел EAT (Export Address Table), в котором содержатся экспортируемые из этой библиотеки функции. Буквально — методы, которые могут быть вызваны при включении этой DLL в программу.Просмотреть экспорты позволяет тот же dumpbin, но с флагом /exports.
Код:
dumpbin /exports C:\Windows\System32\user32.dll
Нам нужно лишь как‑то получить базовый адрес загрузки библиотеки, а затем от него можно добраться до EAT. Пока для простоты эксперимента базовый адрес загрузки предлагаю получать через LoadLibrary(). Эта функция позволяет загрузить библиотеку в текущий процесс, а затем получить на нее хендл.
Код:
HMODULE LoadLibraryA(
[in] LPCSTR lpLibFileName
);
WWW
- Parsing PE File Headers with C++ (ired.team)
- PE (Portable Executable): На странных берегах («Хабрахабр»)
- PE-формат. Часть 1 — Базовая информация (kaimi.io)
GetProcAddress(). Я выбрал самый аккуратный и милый вариант.
C++:
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
}
}
return NULL;
}
Помнишь, мы отключили CRT? Поэтому использование функции strcmp() из этого кода невозможно. К счастью, сравнение двух строк — базовый алгоритм, который пишут еще на паскале в седьмом классе. На C++ я вынес его в отдельную функцию custom_strcmp().
C++:
int custom_strcmp(const char* str1, const char* str2) {
while (*str1 || *str2) {
if (*str1 < *str2) {
return -1;
}
else if (*str1 > *str2) {
return 1;
}
str1++;
str2++;
}
return 0;
}
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
);
int custom_strcmp(const char* str1, const char* str2) {
while (*str1 || *str2) {
if (*str1 < *str2) {
return -1;
}
else if (*str1 > *str2) {
return 1;
}
str1++;
str2++;
}
return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
}
}
return NULL;
}
int main() {
HMODULE user32Module = LoadLibrary(L"user32.dll");
MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
return 0;
}
Чем же заменить LoadLibrary()? Если мы глянем на последовательность вызовов функций, то увидим, что LoadLibrary() вызывает LdrLoadDll(), она — LdrpLoadDll(), а та — еще одну функцию... Процесс, скажу честно, достаточно сложный. Вот картинка, которая хорошо описывает последовательность вызовов при загрузке DLL.
Предлагаю не распутывать весь этот клубок, а просто вызвать LdrLoadDll(). Функция представлена в ntdll.dll, а эта библиотека присутствует в любом процессе в Windows. Абсолютно в любом. Теперь проблема другая — откуда взять базовый адрес загрузки ntdll.dll? Здесь нам на помощь придет функция GetModuleHandle(). Эта функция принимает лишь один параметр — имя DLL, а возвращает хендл. Хендл как раз таки будет базовым адресом загрузки библиотеки.
Код:
HMODULE GetModuleHandleA(
[in, optional] LPCSTR lpModuleName
);
Код:
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
UNICODE_STRING ustrModule;
HANDLE hModule = NULL;
HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
RtlInitUnicodeString(&ustrModule, lpFileName);
pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
if (!myLdrLoadDll) {
return NULL;
}
NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
return (HMODULE)hModule;
}
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
);
typedef NTSTATUS(NTAPI* pLdrLoadDll) (
PWCHAR PathToFile,
ULONG Flags,
PUNICODE_STRING ModuleFileName,
PHANDLE ModuleHandle
);
typedef VOID(NTAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
int custom_strcmp(const char* str1, const char* str2) {
while (*str1 || *str2) {
if (*str1 < *str2) {
return -1;
}
else if (*str1 > *str2) {
return 1;
}
str1++;
str2++;
}
return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
}
}
return NULL;
}
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
UNICODE_STRING ustrModule;
HANDLE hModule = NULL;
HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
RtlInitUnicodeString(&ustrModule, lpFileName);
pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
if (!myLdrLoadDll) {
return NULL;
}
NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
return (HMODULE)hModule;
}
int main() {
HMODULE user32Module = MyLoadLibrary(L"user32.dll");
MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
return 0;
}
Вот незадача, я снова забыл, что нельзя напрямую вызывать никакие функции. Продолжаем мозговой штурм. Что делает GetModuleHandle()? Функция принимает лишь имя библиотеки, а возвращает ее базовый адрес. Такую информацию можно достать из PEB (Process Environment Block). Вновь обращаемся к интернету и находим там нужный код.
C++:
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {
PEB* pPeb = (PEB*)__readgsqword(0x60);
// For x86
// PEB* pPeb = (PEB*)__readgsqword(0x30);
PEB_LDR_DATA* Ldr = pPeb->Ldr;
LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;
LIST_ENTRY* pStartListEntry = ModuleList->Flink;
WCHAR mystr[MAX_PATH] = { 0 };
WCHAR substr[MAX_PATH] = { 0 };
for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList; pListEntry = pListEntry->Flink) {
LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));
memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
memset(substr, 0, MAX_PATH * sizeof(WCHAR));
wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
wcscpy_s(substr, MAX_PATH, lModuleName);
if (cmpUnicodeStr(substr, mystr)) {
return (HMODULE)pEntry->DllBase;
}
}
// The needed DLL wasn’t found
printf("failed to get a handle to %s\n", lModuleName);
return NULL;
}
C++:
void* __cdecl custom_memset(void* Destination, int Value, size_t Size) {
unsigned char* p = (unsigned char*)Destination;
while (Size > 0) {
*p = (unsigned char)Value;
p++;
Size--;
}
return Destination;
}
C++:
wchar_t* custom_wcsncpy(wchar_t* dest, const wchar_t* src, size_t count) {
wchar_t* originalDest = dest;
while (count > 0) {
*dest = *src;
if (*src == L'\0') {
break;
}
dest++;
src++;
count--;
}
while (count > 0) {
*dest = L'\0';
dest++;
count--;
}
return originalDest;
}
size_t custom_wcslen(const wchar_t* str) {
if (str == nullptr) {
return 0;
}
size_t length = 0;
while (*str != L'\0') {
length++;
str++;
}
return length;
}
errno_t custom_wcscpy_s(wchar_t* dest, size_t destSize, const wchar_t* src) {
if (dest == nullptr || src == nullptr) {
return EINVAL;
}
size_t srcLength = custom_wcslen(src);
if (destSize < srcLength + 1) {
return ERANGE;
}
custom_wcsncpy(dest, src, destSize);
dest[srcLength] = L'\0';
return 0;
}
Для сравнения двух Unicode-строк автор использует функцию из Shlwapi.
C++:
int cmpUnicodeStr(WCHAR substr[], WCHAR mystr[]) {
_wcslwr_s(substr, MAX_PATH);
_wcslwr_s(mystr, MAX_PATH);
int result = 0;
if (StrStrW(mystr, substr) != NULL) {
result = 1;
}
return result;
}
C++:
int custom_wcscmp(const wchar_t* str1, const wchar_t* str2) {
while (*str1 == *str2 && *str1 != L'\0') {
str1++;
str2++;
}
return *str1 - *str2;
}
Код:
HMODULE hNtdll = myGetModuleHandle(L"C:\\Windows\\SYSTEM32\\ntdll.dll");
Итоговый код проекта стал еще больше. Теперь для стандартного MessageBox() нужно целых 176 строк.
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
);
typedef NTSTATUS(NTAPI* pLdrLoadDll) (
PWCHAR PathToFile,
ULONG Flags,
PUNICODE_STRING ModuleFileName,
PHANDLE ModuleHandle
);
typedef VOID(NTAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
wchar_t* custom_wcsncpy(wchar_t* dest, const wchar_t* src, size_t count) {
wchar_t* originalDest = dest;
while (count > 0) {
*dest = *src;
if (*src == L'\0') {
break;
}
dest++;
src++;
count--;
}
while (count > 0) {
*dest = L'\0';
dest++;
count--;
}
return originalDest;
}
size_t custom_wcslen(const wchar_t* str) {
if (str == nullptr) {
return 0;
}
size_t length = 0;
while (*str != L'\0') {
length++;
str++;
}
return length;
}
int custom_wcscmp(const wchar_t* str1, const wchar_t* str2) {
while (*str1 == *str2 && *str1 != L'\0') {
str1++;
str2++;
}
return *str1 - *str2;
}
errno_t custom_wcscpy_s(wchar_t* dest, size_t destSize, const wchar_t* src) {
if (dest == nullptr || src == nullptr) {
return EINVAL;
}
size_t srcLength = custom_wcslen(src);
if (destSize < srcLength + 1) {
return ERANGE;
}
custom_wcsncpy(dest, src, destSize);
dest[srcLength] = L'\0';
return 0;
}
int custom_strcmp(const char* str1, const char* str2) {
while (*str1 || *str2) {
if (*str1 < *str2) {
return -1;
}
else if (*str1 > *str2) {
return 1;
}
str1++;
str2++;
}
return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
}
}
return NULL;
}
void* __cdecl custom_memset(void* Destination, int Value, size_t Size) {
unsigned char* p = (unsigned char*)Destination;
while (Size > 0) {
*p = (unsigned char)Value;
p++;
Size--;
}
return Destination;
}
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {
PEB* pPeb = (PEB*)__readgsqword(0x60);
// For x86
// PEB* pPeb = (PEB*)__readgsqword(0x30);
PEB_LDR_DATA* Ldr = pPeb->Ldr;
LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;
LIST_ENTRY* pStartListEntry = ModuleList->Flink;
WCHAR mystr[MAX_PATH] = { 0 };
WCHAR substr[MAX_PATH] = { 0 };
for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList; pListEntry = pListEntry->Flink) {
LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));
custom_memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
custom_memset(substr, 0, MAX_PATH * sizeof(WCHAR));
custom_wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
custom_wcscpy_s(substr, MAX_PATH, lModuleName);
if (custom_wcscmp(substr, mystr) == 0) {
// Returning the DLL base address
return (HMODULE)pEntry->DllBase;
}
}
}
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
UNICODE_STRING ustrModule;
HANDLE hModule = NULL;
HMODULE hNtdll = myGetModuleHandle(L"C:\\Windows\\SYSTEM32\\ntdll.dll");
pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
RtlInitUnicodeString(&ustrModule, lpFileName);
pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
if (!myLdrLoadDll) {
return NULL;
}
NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
return (HMODULE)hModule;
}
int main() {
HMODULE user32Module = MyLoadLibrary(L"user32.dll");
MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
return 0;
}
Наконец в IAT стало пусто.
Причем обрати внимание, что у тебя компилятор может выдавать ошибку LNK1120.
Код:
1>Source.obj : error LNK2001: неразрешенный внешний символ memset.
1>A:\SSD\ProjectsVS\Article\x64\Release\Article.exe : fatal error LNK1120: неразрешенных внешних элементов: 1
Осталось лишь навести немного красоты. Ведь это очень подозрительно, если у программы пустой IAT. Займемся IAT Camouflage.
IAT CAMOUFLAGE
Некоторые антивирусы могут повесить на нас детект просто потому, что мы не вызываем никаких функций, поскольку у нас пустой IAT. Предлагаю добавить парочку фиктивных вызовов функций, которые ни к чему не приводят. При этом в IAT, само собой, появятся записи о том, что используются те или иные функции.Добавим, например, код, который никогда не выполнится.
C++:
int main() {
HMODULE user32Module = MyLoadLibrary(L"user32.dll");
MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
int m = 4;
if (m > 5) {
unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
i = GetLastError();
i = SetCriticalSectionSpinCount(NULL, NULL);
i = GetWindowContextHelpId(NULL);
i = GetWindowLongPtrW(NULL, NULL);
i = RegisterClassW(NULL);
i = IsWindowVisible(NULL);
i = ConvertDefaultLocale(NULL);
i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
i = IsDialogMessageW(NULL, NULL);
}
return 0;
}
Компилируем проект и видим, что IAT стал похож на IAT обычной ничем не примечательной программы.
ПСЕВДОХЕНДЛЫ
Наконец, на десерт хочется упомянуть псевдохендлы. Псевдохендлами считаются фиктивные хендлы на какой‑либо текущий ресурс. Например, вызов GetCurrentProcess() не получится ничем заменить, но вместо него можно использовать псевдохендл. Система Windows, получив такой хендл, поймет, что ей нужно обратиться к процессу, из которого была вызвана функция. Таким образом, для получения хендла, например, на текущий процесс, достаточно передавать значение -1. И Windows определит, что это хендл на текущий процесс.Можно даже в проекте создать маленькие макросы, которые будут выполнять эту работу автоматически.
Код:
#define NtCurrentProcess() ((HANDLE)-1) // Получить хендл на текущий процесс
#define NtCurrentThread() ((HANDLE)-2) // Получить хендл на текущий поток
ВЫВОДЫ
Все‑таки очень увлекательно играть в кошки‑мышки с антивирусами. Каждый раз думаешь, что же там такое они навертели и почему на мой пейлоад вешается детект. В этой статье мы обсудили один из вариантов ответа на этот вопрос.Автор @MichelleVermishelle | TG: @Michaelzhm
Источник xakep.ru