Категория > Новости > Веселые хеши. Реализуем технику 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// ASCIIDWORD HashStringDjb2A(_In_ PCHAR String){ ULONG Hash = INITIAL_HASH; INT c; while (c = *String++)Hash = ((Hash << INITIAL_SEED) + Hash) + c; return Hash;}// UnicodeDWORD 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// ASCIIDWORD HashStringLoseLoseA(_In_ PCHAR String){ ULONG Hash = 0; INT c; while (c = *String++) {Hash += c;Hash *= c + INITIAL_SEED; } return Hash;}// UnicodeDWORD 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 5UINT32 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 ты просто инициализируешь строку с именем функции, а затем загоняешь в любой из описанных выше алгоритмов. После получения хеша ты должен добавить его в свою программу и, используя кастомный Переходим к демонстрации.
ПримерПерейти обратно к новости |