Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5
Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости
Справочник от А до Я
HTML, CSS, JavaScript
Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы
Помогли мы вам |
прошлой статье я рассмотрел варианты сокрытия IAT путем получения адресов функции и их вызова «напрямую» через LoadLibrary(
и GetProcAddress(
, но проблема в том, что сами строки, содержащие имена функций, никак не спрятаны. Поэтому любой уважающий себя антивирусный продукт все равно сможет понять, что мы задумали.
Никакой скрытности
Всем понятно, что вызывается
Чтобы предотвратить это, добрые хакеры придумали технику 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(
, искать адрес нужной функции.
Переходим к демонстрации.
|
|