
Writeup de desafio CTF pwn explorando CVE-2021-4034 (pkexec) através de manipulação de heap, com engenharia reversa baseada em Ghidra e um binário auxiliar customizado shelly.so.
Este é um desafio CTF do tipo pwn que escrevi em C e que exige que o usuário explore a vulnerabilidade CVE-2021-4034. Os jogadores recebem 2 binários no diretório challenge deste repositório. O binário chal implementa o desafio CTF e o shelly.so é um binário auxiliar.
No momento em que este writeup foi escrito, o Dockerfile ainda não está completo. O Dockerfile é necessário para implantar este desafio durante um CTF ao vivo, mas não localmente: Você ainda pode emular este desafio localmente configurando as permissões de usuário e instalando os pacotes vulneráveis da seguinte forma:
libpolkit-gobject-1-0=0.105-26ubuntu1 libpolkit-agent-1-0=0.105-26ubuntu1 policykit-1=0.105-26ubuntu1.flag.txt pertencente a root:root no diretório atual.challenge para o diretório atual.chal como o usuário não privilegiado.Executar o binário chal nos dá uma ideia vaga do que esse binário faz:```
WELCOME TO THE HUB CTRL+ALT+DELICIOUS
We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every byte
Brincar com o tamanho da entrada na função `Add` e os índices das funções `Cancel` não nos dá nada especial (sem overflow ou segmentation fault). No entanto, algumas coisas interessantes:
- Parece que os pedidos são colocados numa lista (talvez uma lista ligada) e são indexados a partir de 0?
- Há este `Order number: 0x7ffde93681f0` que parece imprimir alguma localização na pilha?
- Além disso, o programa cria um diretório com o nome que introduzimos juntamente com 3 executáveis, um dos quais é o ficheiro binário auxiliar que nos é dado:```sh
peasant@Tin-VM:~/Desktop$ ls
chal chal.c Dockerfile shelly.so solve.py tin
peasant@Tin-VM:~/Desktop$ ls -l tin/
total 28
-rwxrwx--- 1 peasant vboxsf 5 Feb 4 14:33 notes
-rwxrwx--- 1 peasant vboxsf 20 Feb 4 14:33 recipe
-rwxr-x--- 1 peasant vboxsf 16488 Feb 4 14:33 shelly.so
peasant@Tin-VM:~/Desktop$ cat tin/notes
0000
peasant@Tin-VM:~/Desktop$ cat tin/recipe
aaaa
bbbb
cccc
dddc
Os executáveis contêm nossa entrada.
Você pode brincar com as outras funções e torcer para encontrar alguns bugs (o que é provável), mas vou direto ao ponto e abrir o programa no Ghidra.
Comparando as strings que aparecem durante a execução do programa e as strings presentes no Ghidra, podemos renomear algumas das funções FUN_* para nomes familiares:```C
undefined8 main(void)
{ int iVar1; size_t sVar2; undefined2 *puVar3; long in_FS_OFFSET; int opt; int local_1c; char *local_18; long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28); local_18 = "/recipe"; print("WELCOME TO THE HUB CTRL+ALT+DELICIOUS\n"); print( "We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every by te\n\n" ); while( true ) { print("1. ENTER THE HUB\n"); print("2. QUIT\n"); __isoc99_scanf(&DAT_001030c5,&opt); getc(stdin); if (opt != 1) break; printf("Order number: %p\n",&local_18); print("Enter your name: "); __isoc99_scanf(&DAT_0010334f,&DAT_00105120); sVar2 = strlen(&DAT_00105120); puVar3 = (undefined2 *)malloc(sVar2 + 2); DAT_00105100 = puVar3; *puVar3 = 0x2f2e; *(undefined *)(puVar3 + 1) = 0; strcpy((char *)(DAT_00105100 + 1),&DAT_00105120); iVar1 = FUN_001022f0(DAT_00105100,&DAT_00105060); if (iVar1 == -1) { mkdir((char *)DAT_00105100,0x1c0); } DAT_00105140 = 0; order_cnt = 0; for (local_1c = 0; local_1c < 10; local_1c = local_1c + 1) { *(undefined8 *)(&ptr_array + (long)local_1c * 8) = 0; } main_menu(); } print("Come again :)\n");
O `printf("Order number: %p\n",&local_18);` **imprime a localização de uma variável local na pilha.**
Uma verificação rápida no gdb mostra-nos que o endereço vazado é o endereço do ponteiro para a string constante `/recipe`:```gdb
...
Order number: 0x7fffffffdfc0
...
gef➤ x/gx 0x7fffffffdfc0
0x7fffffffdfc0: 0x0000555555559020
gef➤ x/s 0x0000555555559020
0x555555559020: "/recipe"
Vemos uma chamada mkdir, que cria um diretório com o nome da nossa entrada no diretório atual.
Isso é consistente com nossa observação quando executamos o programa. Em seguida, ele inicializa alguma variável antes de chamar a função main_menu, que se parece com algo como:```C
while( true ) {
while( true ) {
while( true ) {
while( true ) {
while( true ) {
while( true ) {
print("1. ADD NEW ORDER\n");
print("2. EDIT ORDER\n");
print("3. SHOW ORDER\n");
print("4. CANCEL ORDER\n");
print("5. CHECKOUT\n");
print("6. DONE\n");
__isoc99_scanf(&DAT_001030c5,&local_40);
getc(stdin);
if (local_40 != 1) break;
add_order();
}
if (local_40 != 2) break;
edit_order();
}
if (local_40 != 3) break;
show_order();
}
if (local_40 != 4) break;
cancel_order();
}
if (local_40 != 5) break;
checkout();
}
if (local_40 == 6) break;
if (local_40 == 0x539) {
print(
"\nGORDON RAMSAY: Finally, a worthy opponent, our battle will be legendary! I BET YOU CAN 'T GUESS THE SECRET RECIPE.\n"
);
fgets(inp,0x20,stdin);
getrandom(random-bytes,0x10,0);
for (local_3c = 0; local_3c < 0x10; local_3c = local_3c + 1) {
if (inp[local_3c] != random-bytes[local_3c]) {
print("...Nuh Uh!...\n");
/* WARNING: Subroutine does not return */
exit(0);
}
print("...Ooh Yes.. sCruMpTioUs...");
}
print("Fine... I'll give you a taste.\n");
FUN_00101504();
}
Se você não está familiarizado com REV, esta é a descompilação para a declaração `switch` em C. Há uma opção interessante, também conhecida como `0x539`. Ela permite que os jogadores adivinhem `0x10` bytes aleatórios. Se todos os bytes forem iguais, então chama `FUN_00101504();` que chama `system("cat flag.txt");`. Caso contrário, o programa encerra.