
Scrittura di una sfida CTF pwn che sfrutta CVE-2021-4034 (pkexec) tramite manipolazione dell'heap, con reverse engineering basato su Ghidra e un binario helper personalizzato shelly.so.
Questa è una sfida CTF pwn che ho scritto in C e che richiede all'utente di sfruttare la vulnerabilità CVE-2021-4034. Ai giocatori vengono forniti 2 binari nella directory challenge di questo repository. Il binario chal implementa la sfida CTF e shelly.so è un binario di supporto.
Al momento della stesura di questo writeup, il Dockerfile non è ancora completo. Il Dockerfile è necessario per distribuire questa sfida durante un CTF dal vivo, ma non localmente: Puoi comunque emulare questa sfida localmente impostando i permessi utente e installando i pacchetti vulnerabili come segue:
libpolkit-gobject-1-0=0.105-26ubuntu1 libpolkit-agent-1-0=0.105-26ubuntu1 policykit-1=0.105-26ubuntu1.flag.txt di proprietà di root:root nella directory corrente.challenge nella directory corrente.chal come utente non privilegiato.Eseguire il binario chal ci dà un'idea vaga di cosa fa questo binario:```
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
Giocando con la dimensione dell'input nella funzione `Add` e con gli indici delle funzioni `Cancel` non otteniamo nulla di speciale (nessun overflow o segmentation fault). Tuttavia, alcune cose interessanti:
- Sembra che gli ordini siano inseriti in una lista (forse una lista concatenata) e che siano indicizzati a partire da 0?
- C'è questo `Order number: 0x7ffde93681f0` che sembra stampare una posizione nello stack?
- Inoltre, il programma crea una directory con il nostro nome di input insieme a 3 eseguibili, uno dei quali è il file binario helper che ci viene fornito:```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
The executables contain our input.
You can play around with the other functions and hope that you might run into some bugs (which is probable), but I'm gonna cut to the chase and open the program in Ghidra.
Comparing the strings that appear while running the program and the strings that are present in Ghidra, we can rename some of the FUN_* functions into familiar names:```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");
L'istruzione `printf("Order number: %p\n",&local_18);` **stampa la posizione di una variabile locale nello stack.**
Un rapido controllo in gdb mostra che l'indirizzo divulgato è l'indirizzo del puntatore alla stringa costante `/recipe`:```gdb
...
Order number: 0x7fffffffdfc0
...
gef➤ x/gx 0x7fffffffdfc0
0x7fffffffdfc0: 0x0000555555559020
gef➤ x/s 0x0000555555559020
0x555555559020: "/recipe"
Vediamo una chiamata a mkdir, che crea una directory con il nostro nome di input nella directory corrente.
Questo è coerente con quanto osservato quando abbiamo eseguito il programma. Poi inizializza una variabile prima di chiamare la funzione main_menu, che assomiglia a:```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 non hai familiarità con REV, questa è la decompilazione per l'istruzione `switch` in C. C'è un'opzione interessante, a.k.a `0x539`. Permette ai giocatori di indovinare `0x10` byte casuali. Se tutti i byte sono uguali, allora chiama `FUN_00101504();` che chiama `system("cat flag.txt");`. Altrimenti, il programma termina.