Категория > Новости > Веселые хеши. Реализуем технику API Hashing, чтобы обдурить антивирус - «Новости»
Веселые хеши. Реализуем технику API Hashing, чтобы обдурить антивирус - «Новости»15-10-2023, 09:18. Автор: Walkman |
прошлой статье я рассмотрел варианты сокрытия IAT путем получения адресов функции и их вызова «напрямую» через Никакой скрытности Всем понятно, что вызывается Чтобы предотвратить это, добрые хакеры придумали технику API Hashing, с которой сейчас и познакомимся. Простейший API HashingРаньше мы получали адрес нужной функции через кастомный 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; } API Hashing заключается в том, что мы вместо имени функции ( if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) И станет вот такой: if (strcmp(lpProcName, HASH((const char*)hModule + addressOfNames[i])) == 0) Аналогичным образом все происходит и с Но что это вообще за хеш, откуда его взять? Хеш генерируется с помощью конкретного алгоритма хеширования, можешь выбрать любой на свой вкус. Приведу несколько примеров.
Выбор алгоритмаИтак, берем алгоритм Djb2. Это очень быстрый алгоритм хеширования. hash = ((hash << 5) + hash) + c
Вот функция, которая позволяет хешировать строку. #include <Windows.h> #include <iostream> #define INITIAL_HASH 3731 // Для рандомизации #define INITIAL_SEED 7 // ASCII DWORD HashStringDjb2A(_In_ PCHAR String) { ULONG Hash = INITIAL_HASH; INT c; while (c = *String++) Hash = ((Hash << INITIAL_SEED) + Hash) + c; return Hash; } // Unicode DWORD HashStringDjb2W(_In_ PWCHAR String) { ULONG Hash = INITIAL_HASH; INT c; while (c = *String++) Hash = ((Hash << INITIAL_SEED) + Hash) + c; return Hash; } int main() { LPWSTR str = (LPWSTR)L"HelloWorld"; std::cout << HashStringDjb2W(str); return 0; } Теперь давай попробуем алгоритм JenkinsOneAtATime32Bit. Он перебирает символы входной строки и постепенно обновляет хеш‑значение в соответствии с каждым полученным символом.
Код с функцией.
Не утомился? Давай посмотрим еще один пример. В каком‑то проекте, чуть ли не в Hell’s Gate, я встречал алгоритм LoseLose. Он вычисляет хеш‑значение входной строки, перебирая каждый символ в ней и суммируя значения ASCII каждого символа. hash = 0; hash += c; // Для каждого символа в строке Хеш‑значение, полученное в результате алгоритма LoseLose, представляет собой целое число, уникальное для входной строки. Но все равно есть вероятность коллизии. Чтобы решить эту проблему, формула алгоритма была обновлена, как показано ниже. hash = 0; hash += c; hash *= c + 2; Вот пример функции, которая генерирует подобный хеш. #include <Windows.h> #include <iostream> #define INITIAL_SEED 2 // ASCII DWORD HashStringLoseLoseA(_In_ PCHAR String) { ULONG Hash = 0; INT c; while (c = *String++) { Hash += c; Hash *= c + INITIAL_SEED; } return Hash; } // Unicode DWORD HashStringLoseLoseW(_In_ PWCHAR String) { ULONG Hash = 0; INT c; while (c = *String++) { Hash += c; Hash *= c + INITIAL_SEED; } return Hash; } int main() { LPWSTR str = (LPWSTR)L"HelloWorld"; std::cout << HashStringLoseLoseW(str); return 0; } И, думаю, последний вариант — #include <Windows.h> #include <iostream> #define INITIAL_SEED 5 UINT32 HashStringRotr32SubA(UINT32 Value, UINT Count) { DWORD Mask = (CHAR_BIT * sizeof(Value) - 1); Count &= Mask; #pragma warning( push ) #pragma warning( disable : 4146) return (Value >> Count) | (Value << ((-Count) & Mask)); #pragma warning( pop ) } INT HashStringRotr32A(_In_ LPCSTR String) { INT Value = 0; for (INT Index = 0; Index < strlen(String); Index++) Value = String[Index] + HashStringRotr32SubA(Value, 7); return Value; } UINT32 HashStringRotr32SubW(UINT32 Value, UINT Count) { DWORD Mask = (CHAR_BIT * sizeof(Value) - 1); Count &= Mask; #pragma warning( push ) #pragma warning( disable : 4146) return (Value >> Count) | (Value << ((-Count) & Mask)); #pragma warning( pop ) } INT HashStringRotr32W(_In_ LPCWSTR String) { INT Value = 0; for (INT Index = 0; Index < wcslen(String); Index++) Value = String[Index] + HashStringRotr32SubW(Value, 7); return Value; } int main() { LPWSTR str = (LPWSTR)L"HelloWorld"; std::cout << HashStringRotr32W(str); return 0; } Для API Hashing ты просто инициализируешь строку с именем функции, а затем загоняешь в любой из описанных выше алгоритмов. После получения хеша ты должен добавить его в свою программу и, используя кастомный Переходим к демонстрации.
ПримерПерейти обратно к новости |