Категория > Новости > Веселые хеши. Реализуем технику API Hashing, чтобы обдурить антивирус - «Новости»

Веселые хеши. Реализуем технику API Hashing, чтобы обдурить антивирус - «Новости»


15-10-2023, 09:18. Автор: Walkman
прош­лой статье я рас­смот­рел вари­анты сок­рытия IAT путем получе­ния адре­сов фун­кции и их вызова «нап­рямую» через LoadLibrary() и GetProcAddress(), но проб­лема в том, что сами стро­ки, содер­жащие име­на фун­кций, никак не спря­таны. Поэто­му любой ува­жающий себя анти­вирус­ный про­дукт все рав­но смо­жет понять, что мы задума­ли.
Веселые хеши. Реализуем технику API Hashing, чтобы обдурить антивирус - «Новости»
Ни­какой скрыт­ности
Всем понят­но, что вызыва­ется

Что­бы пре­дот­вра­тить это, доб­рые хакеры при­дума­ли тех­нику 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


Рань­ше мы получа­ли адрес нуж­ной фун­кции через кас­томный GetProcAddress(). Вот как это выг­лядит.


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 зак­люча­ется в том, что мы вмес­то име­ни фун­кции (lpProcName) будем переда­вать хеш от нее. Фак­тичес­ки изме­нит­ся лишь сле­дующая стро­ка.


if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0)

И ста­нет вот такой:


if (strcmp(lpProcName, HASH((const char*)hModule + addressOfNames[i])) == 0)

Ана­логич­ным обра­зом все про­исхо­дит и с GetModuleHandle().


Но что это вооб­ще за хеш, отку­да его взять? Хеш генери­рует­ся с помощью кон­крет­ного алго­рит­ма хеширо­вания, можешь выб­рать любой на свой вкус. При­веду нес­коль­ко при­меров.


 

Выбор алгоритма


Итак, берем алго­ритм Djb2. Это очень быс­трый алго­ритм хеширо­вания.


hash = ((hash << 5) + hash) + c


  • 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);


  • hash — текущее зна­чение хеша;


  • c — текущее зна­чение сим­вола в стро­ке.


Код с фун­кци­ей.


#include <iostream>#define INITIAL_SEED
7// ASCIIUINT32 HashStringJenkinsOneAtATime32BitA(_In_ PCHAR String){
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenA(String);
while (Index != Length)
{Hash += String[Index++];Hash += Hash << INITIAL_SEED;Hash ^= Hash 6;
}
Hash += Hash << 3;
Hash ^= Hash 11;
Hash += Hash << 15;
return Hash;}// UnicodeUINT32 HashStringJenkinsOneAtATime32BitW(_In_ PWCHAR String){
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenW(String);
while (Index != Length)
{Hash += String[Index++];Hash += Hash << INITIAL_SEED;Hash ^= Hash 6;
}
Hash += Hash << 3;
Hash ^= Hash 11;
Hash += Hash << 15;
return Hash;}int main() {
LPWSTR str = (LPWSTR)L"HelloWorld";
std::cout << HashStringJenkinsOneAtATime32BitW(str);
return 0;}

Не уто­мил­ся? Давай пос­мотрим еще один при­мер. В каком‑то про­екте, чуть ли не в 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;
}

И, думаю, пос­ледний вари­ант — Rotr32().


#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 ты прос­то ини­циали­зиру­ешь стро­ку с име­нем фун­кции, а затем загоня­ешь в любой из опи­сан­ных выше алго­рит­мов. Пос­ле получе­ния хеша ты дол­жен добавить его в свою прог­рамму и, исполь­зуя кас­томный GetProcAddress(), искать адрес нуж­ной фун­кции.


Пе­рехо­дим к демонс­тра­ции.


 

Пример



Перейти обратно к новости