Здравствуйте, многоуважаемые форумчане.
Сегодня я хочу поведать вам о банальной технологии обфускации API вызвов. Эта методика сильно усложнит жизнь начинающему реверсеру и немного уменьшит вес вашего софта.
Для того, что бы скрыть вызовы WinApi функций из под нашей программы - мы будем парсить таблицу экспорта нужных нам библиотек своими ручками.
Думаю, у вас сейчас есть один вопрос - а как мы получим дескриптор нужной нам библиотеки?
Всё просто. В каждый процесс в ОС Windows по дефолту грузятся несколько библиотек. Основные - Kernel32.dll и Ntdll.dll, в зависимости от нахождения в эмуляции ( Wow64 ) могут грузиться и другие,
однако это нам не нужно в контексте данного гайда.
В системе Windows есть структура под названием PEB. ( Process Environment Block, блок информации о процессе. Подробнее - https://en.wikipedia.org/wiki/Process_Environment_Block )
Оттуда мы сможем взять дескриптор модуля Kernel32.dll, подтянуть из его таблицы экспорта LoadLibrary, а затем грузить нужные нам библиотеки, доставая из них нужные функции.
Для некоторых вышеописанное может показаться сложной задачей, однако поверьте, всё банально проосто. Сейчас я вам это наглядно покажу.
Давайте напишем функцию, которая вернёт нам дескриптор модуля Kernel32.dll.
Теперь давайте приступим к реализации самой функции для парсинга таблицы экспорта нужных нам модулей.
В диком виде в основном функции ищут по хешам от их названий, однако я не буду усложнять задачу и дабы всем был понятен посыл статьи - сделаю проще. Искать будем по их названиям.
Садимся кодить, в процессе написания функции буду оставлять в ней коментарии, дабы смысл был понятен всем :
И теперь давайте попробуем практически использовать вышеприведённый мной код, неявно вызовем месседжбокс :
Сегодня я хочу поведать вам о банальной технологии обфускации API вызвов. Эта методика сильно усложнит жизнь начинающему реверсеру и немного уменьшит вес вашего софта.
Для того, что бы скрыть вызовы WinApi функций из под нашей программы - мы будем парсить таблицу экспорта нужных нам библиотек своими ручками.
Думаю, у вас сейчас есть один вопрос - а как мы получим дескриптор нужной нам библиотеки?
Всё просто. В каждый процесс в ОС Windows по дефолту грузятся несколько библиотек. Основные - Kernel32.dll и Ntdll.dll, в зависимости от нахождения в эмуляции ( Wow64 ) могут грузиться и другие,
однако это нам не нужно в контексте данного гайда.
В системе Windows есть структура под названием PEB. ( Process Environment Block, блок информации о процессе. Подробнее - https://en.wikipedia.org/wiki/Process_Environment_Block )
Оттуда мы сможем взять дескриптор модуля Kernel32.dll, подтянуть из его таблицы экспорта LoadLibrary, а затем грузить нужные нам библиотеки, доставая из них нужные функции.
Для некоторых вышеописанное может показаться сложной задачей, однако поверьте, всё банально проосто. Сейчас я вам это наглядно покажу.
Давайте напишем функцию, которая вернёт нам дескриптор модуля Kernel32.dll.
Код:
HMODULE GetKernel32()
{
__asm
{
mov ebx, FS:[0x30] // PEB для 32-битных приложений всегда лежит в регистре FS по смещению 0x30, получаем его.
mov ebx, [ebx + 0x0C] // Получаем указатель на структуру PEB_LDR_DATA.
mov ebx, [ebx + 0x14] // Получаем указатель на первую запись в InMemoryOrderModuleList.
mov ebx, [ebx] // Получаем указатель на вторую запись в InMemoryOrderModuleList. ( ntdll.dll )
mov ebx, [ebx] // Получаем указатель на третью запись в InMemoryOrderModuleList. ( kernel32.dll )
mov eax, [ebx + 0x10] // Получаем адрес нужного нам модуля.
}
}
Теперь давайте приступим к реализации самой функции для парсинга таблицы экспорта нужных нам модулей.
В диком виде в основном функции ищут по хешам от их названий, однако я не буду усложнять задачу и дабы всем был понятен посыл статьи - сделаю проще. Искать будем по их названиям.
Садимся кодить, в процессе написания функции буду оставлять в ней коментарии, дабы смысл был понятен всем :
Код:
LPVOID pGetProcAddress(
IN HMODULE hModule,
IN LPSTR lpFunctionName
)
{
if (hModule == NULL || lpFunctionName == NULL) return NULL; // Если один из параметров равен нулю - возвращаем ноль.
PIMAGE_DOS_HEADER IDH = (PIMAGE_DOS_HEADER)hModule; // Получаем указатель на DOS заголовок модуля. Подробнее - тут : https://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html
PIMAGE_NT_HEADERS INH = (PIMAGE_NT_HEADERS)((DWORD)IDH + IDH->e_lfanew); // Получаем указатель на NT заголовок модуля. Подробнее - тут : https://www.nirsoft.net/kernel_struct/vista/IMAGE_NT_HEADERS.html
// P.S : e_lfanew - зарахдкоренное смещение до NT заголовка.
PIMAGE_EXPORT_DIRECTORY IED = (PIMAGE_EXPORT_DIRECTORY)((DWORD)IDH + INH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // Получаем указатель на таблицу экспорта модуля.
PDWORD AddressOfFunctions = (PDWORD)((DWORD)IDH + IED->AddressOfFunctions); // Получаем указатель на таблицу с адресами функций.
PDWORD AddressOfNames = (PDWORD)((DWORD)IDH + IED->AddressOfNames); // Получаем указатель на таблицу с именами функций.
PWORD AddressOfNameOrdinals = (PWORD)((DWORD)IDH + IED->AddressOfNameOrdinals); // Получаем указатель на таблицу с порядковыми номерами функций. ( Ординалами ).
DWORD dwOrdinal = 0; // Сюда будет ложится порядковый номер функции.
// Теперь проходимся в цикле по списку имён функций, экспортируемых данным модулем.
for (DWORD i = 0; i < IED->NumberOfNames; i++)
{
LPSTR lpApiName = (LPSTR)((DWORD)IDH + AddressOfNames[i]); // Получаем имя текущей функции.
if (strcmp(lpFunctionName, lpApiName) == 0) // Если имя текущей идентично имени функции, которую мы ищем - сохраняем её порядковый номер и выходим из цикла.
{
dwOrdinal = AddressOfNameOrdinals[i];
break;
}
}
LPBYTE lpFunctionVA = (LPBYTE)((DWORD)IDH + AddressOfFunctions[dwOrdinal]); // Получаем адрес функции.
// Вот незадача. Функция может переадресовыватся в другую библиотеку. Такое понятие называется - Function Forwarding. Это сделано для обратной совместимости между старым ПО и новыми системами.
// Однако мы сейчас заставим нашу функцию обрабатывать функции, которые форвардятся.
// Если функция форвардится - переменная lpFunctionVA будет содержать значение следующего вида : Название библиотеки, куда ссылается функция.Название функции
// Например : HeapAlloc из Kernel32.dll даст нам результат ntdll.RtlAllocateHeap и так далее.
if (lpFunctionVA > (LPBYTE)IED && lpFunctionVA < (LPBYTE)IED + INH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) // Определяем, форвардится ли функция.
{
char szDllName[MAX_PATH + 1] = { 0 }; // Буффер для получения названия динамической библиотеки, в которую ссылается функция.
DWORD dwCounter = 0; // Счётчик, нужный нам для цикла.
while (lpFunctionVA[dwCounter] != '.')
{
szDllName[dwCounter] = lpFunctionVA[dwCounter];
dwCounter++;
}
LPSTR lpForwardedFunctionName = (LPSTR)(lpFunctionVA + (dwCounter + 1)); // Извлекаем название функции. + 1 - дабы обойти точку.
HMODULE hKernel32 = GetKernel32(); // Получаем дескриптор модуля Kernel32.
typedef HMODULE(WINAPI* xLoadLibraryA)(LPCSTR lpLibFileName); // Обьявляем прототип функции LoadLibraryA.
xLoadLibraryA pLoadLibraryA = (xLoadLibraryA)pGetProcAddress(hKernel32, "LoadLibraryA"); // Получаем адрес LoadLibraryA.
HMODULE hForwardedLibrary = pLoadLibraryA(szDllName); // Загружаем библиотеку, на которую ссылается функция.
return pGetProcAddress(hForwardedLibrary, lpForwardedFunctionName); // Возвращаем адрес уже настоящей функции.
}
return lpFunctionVA; // Возвращаем адрес функции, если она не форвардится.
}
И теперь давайте попробуем практически использовать вышеприведённый мной код, неявно вызовем месседжбокс :
Код:
HMODULE hKernel32 = GetKernel32();
typedef HMODULE(WINAPI* xLoadLibraryW)(LPCWSTR lpLibFileName);
typedef int(WINAPI* xMessageBoxW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);
xLoadLibraryW pLoadLibraryW = (xLoadLibraryW)pGetProcAddress(hKernel32, "LoadLibraryW");
HMODULE hUser32 = pLoadLibraryW(L"user32.dll");
xMessageBoxW pMessageBoxW = (xMessageBoxW)pGetProcAddress(hUser32, "MessageBoxW");
pMessageBoxW(NULL, L"Hello, my nickname is dopeware!", NULL, MB_OK);