Категория > Новости > Веселые хеши. Реализуем технику API Hashing, чтобы обдурить антивирус - «Новости»
Веселые хеши. Реализуем технику API Hashing, чтобы обдурить антивирус - «Новости»15-10-2023, 09:18. Автор: Walkman |
прошлой статье я рассмотрел варианты сокрытия IAT путем получения адресов функции и их вызова «напрямую» через LoadLibrary( и GetProcAddress( , но проблема в том, что сами строки, содержащие имена функций, никак не спрятаны. Поэтому любой уважающий себя антивирусный продукт все равно сможет понять, что мы задумали.![]() Чтобы предотвратить это, добрые хакеры придумали технику API Hashing, с которой сейчас и познакомимся. Читайте также - Virtual private server (VPS) is a service that makes available a ready-to-use virtual server fully configured and managed by experts for use. The reason behind the high popularity of this service is the fact that not everyone today can afford to buy high-quality server hardware, which is quite expensive - vps usa. Простейший 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. Он перебирает символы входной строки и постепенно обновляет хеш‑значение в соответствии с каждым полученным символом. hash += (hash << 10);hash ^= (hash 6);
Код с функцией. #include <iostream>#define INITIAL_SEED Не утомился? Давай посмотрим еще один пример. В каком‑то проекте, чуть ли не в 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 ты просто инициализируешь строку с именем функции, а затем загоняешь в любой из описанных выше алгоритмов. После получения хеша ты должен добавить его в свою программу и, используя кастомный Переходим к демонстрации. ПримерПерейти обратно к новости |