Global Cyberlympics 2016

Esta semana fue el Global Cyberlympics 2016. Un CTF de sólo 12 horas para equipos de 5 personas. Nos pasamos la noche jugando, porque en estos lados del mundo venía siendo de 7 p.m. a 7 a.m. Al final, logramos marcar 1400 puntos (el equipo ganador hizo 3275) con lo que terminamos en el puesto 32 (de entre unos 120 equipos).
Lo interesante es que todo el juego tenía un aire muy ochentero, pues estaba enmarcado en un MUD de espionaje. Entonces, para poder resolver los retos primero había que encontrarlos en medio del MUD. Y había algunos retos intermedios que no daban puntos pero que se necesitaban para pasar de una sala a la siguiente.
Como siempre que se intenta algo nuevo, no faltaron los problemas (desde empezar a jugar sin scoreboard hasta reenviar las flags correctas varias veces porque no eran aceptadas), pero en general la experiencia fue muy buena. Además, este juego incluyó bastante reversing y eso lo hizo aun mas interesante desde mi punto de vista.
Como en mi equipo algunos nunca han intentado los retos de reversing, al final del juego pidieron los write-ups para esos niveles. Y eso es lo que presento en este post. Y como la idea es que sirva de introducción, he intentado resolver cada reto de varias formas. Espero que se entienda al menos una forma en cada reto :P

Primer binario: tang0

El primer binario que reversamos fue "tang0". No era un reto para puntos, sólo era usado para conseguir un password con el que se pasaba un punto de control en el MUD. Este es el enlace de descarga y el password del .7z (ambos los sacamos del MUD):

"tang0" via analisis dinámico (para resolver las cosas rápido)

Aquí esta la salida de "file":
user@ubuntu:~$ file tang0
tang0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3fd192f8069383d84a6038955454a3a67ce63ecd, not stripped
Nos interesa que es un ELF64, que esta enlazado dinámicamente (lo que significa, que tendremos que reversar menos código, pues las funciones estándar se quedan en al librería compartida) y que no esta "stripped" (no se como decirlo en español, pero significa que la información de depuración, e.g. los nombres de las funciones, no fue removida del binario, y por tanto sera mas fácil reversarlo).
Aqui esta la salida de "strings":
user@ubuntu:~$ strings tang0
/lib64/ld-linux-x86-64.so.2
libc.so.6
fopen
puts
printf
strlen
__isoc99_fscanf
fclose
strcmp
__libc_start_main
__gmon_start__
GLIBC_2.2.5
GLIBC_2.7
fffff.
M.a.r.k.H
8.3.f
t0k3n.txH
[]A\A]A^A_
&#x%x;
So close...but it's not gonna happen
How did you get here
Access denied
Congratulations, you've found the GGoCySEA guard station password.
This password came at a great cost, use it wisely!
Access denied.
This file does not contain the correct t0k3n.
Unable to locate the t0k3n file.
;*3$"
La he truncado porque continua con algunos otros strings estandar, pero aquí ya hay informacion suficiente para pasar el reto. Estas cadenas nos dicen que hace el binario:
So close...but it's not gonna happen
How did you get here
Access denied
Congratulations, you've found the GGoCySEA guard station password.
This password came at a great cost, use it wisely!
Access denied.
This file does not contain the correct t0k3n.
Unable to locate the t0k3n file.
Por el mensaje "Congratulations, you've (…)" inferimos que el binario va a darnos un password. Por el mensaje "Unable to (…)" inferimos que necesitamos darle un "t0k3n file". Finalmente, por el mensaje "This file (…)" inferimos que en en ese t0k3n file debemos suministrarle un "t0k3n" correcto.
Entre los strings tambien vemos estas funciones estándar:
fopen
puts
printf
strlen
__isoc99_fscanf
fclose
strcmp-
Ese "strcmp" es nuestro mejor target, Pues asumimos que lo usara para conmparar el t0k3n que ingresemos con el t0k3n correcto.
Finalmente, tenemos estos strings interesantes, pero los ignoraremos para hacer la sesión mas productiva :)
M.a.r.k.H
8.3.f
t0k3n.txH
Vamos a crear un "token_file" en el que almacenaremos un "token" y veremos como se comporta el binario:
user@ubuntu:~$ echo "token" > token_file
user@ubuntu:~$ ./tang0
Access denied
user@ubuntu:~$ ./tang0 token_file
Unable to locate the t0k3n file.
OK, usaremos "ltrace" para capturar los parametros de strcmp:
user@ubuntu:~$ ltrace ./tang0 token_file
__libc_start_main(0x4007ce, 2, 0x7ffcaa531a08, 0x400910 <unfinished ...>
strcmp("token_file", "t0k3n.txt") = 63
puts("Unable to locate the t0k3n file."...Unable to locate the t0k3n file.
) = 33
+++ exited (status 0) +++
Y es obvio que compara nuestro "token_file" con "t0k3n.txt", asi que ya tenemos el nombre del archivo correcto: "t0k3n.txt"
user@ubuntu:~$ echo "token" > t0k3n.txt
user@ubuntu:~$ ./tang0 t0k3n.txt
Access denied.
This file does not contain the correct t0k3n.
Ya reconoce el archivo, pero el t0k3n es incorrecto. Usamos de nuevo "ltrace":
user@ubuntu:~$ ltrace ./tang0 t0k3n.txt
__libc_start_main(0x4007ce, 2, 0x7ffde02afc58, 0x400910 <unfinished ...>
strcmp("t0k3n.txt", "t0k3n.txt") = 0
fopen("t0k3n.txt", "r") = 0xe01010
__isoc99_fscanf(0xe01010, 0x400af1, 0x7ffde02afb40, 0x7fad54bdc790) = 1
strcmp("token", "M.a.r.k.8.3.a") = 5
puts("Access denied.\nThis file does no"...Access denied.
This file does not contain the correct t0k3n.
) = 61
fclose(0xe01010) = 0
+++ exited (status 0) +++
Y esta alli muy claro que compara nuestro "token" con "M.a.r.k.8.3.a" :)
user@ubuntu:~$ echo "M.a.r.k.8.3.a" > t0k3n.txt
user@ubuntu:~$ ./tang0 t0k3n.txt
Congratulations, you've found the GGoCySEA guard station password.
This password came at a great cost, use it wisely!
&#x163;
&#x21f;
&#x293;
&#x20;
&#x1d1b;
&#x298;
&#x29e;
&#x1d23;
&#x1d16;
&#x20;
&#x1e8f;
&#x1e4e;
&#x1e79;
&#x20;
&#x24e2;
&#x24a0;
&#x24a0;
&#x24a6;
&#x20;
&#x1ecb;
&#x1e69;
&#x20;
&#x1361;
&#x20;
&#x62;
&#x79;
&#x70;
&#x61;
&#x73;
&#x73;
&#x69;
&#x74;
&#x6e;
&#x6f;
&#x77;
user@ubuntu:~$
Que obviamente significa: ţȟʓ ᴛʘʞᴣᴖ ẏṎṹ ⓢ⒠⒠⒦ ịṩ ፡ bypassitnow, pero esto ya no es reversing y les queda de tarea entender de donde sale :)

"tang0" via análisis estático (para aprender un poco mas de reversing)

Si cargamos el binario en IDA Pro (si, si van a usar IDA tendra que ser el Pro, pues la version Free no soporta binarios de 64 bits) tenemos varias opciones para empezar:
  • podemos analizar los strings (como hicimos en el anterior, pero desde IDA)
  • podemos analizar los imports (y encontrariamos ese "strcmp" magico)
  • podemos analizar desde el principio: la funcion "main" (solo porque es un reto de un CTF y por lo tanto es un binario suficientemente corto como para entenderlo completo).
  • podemos hacer alguna otra cosa, seguramente hay mas formas de resolverlo, pero se me ocurren esas tres…

Analisis estático de "tang0" iniciando via strings

Vamos a "View" → "Open subview" → "Strings" (El shortcut es Shift+F12) y sale esto:
.rodata:0000000000400A00 00000008 C &#x%x;\n
.rodata:0000000000400A08 00000025 C So close...but it's not gonna happen
.rodata:0000000000400ACC 00000015 C How did you get here
.rodata:0000000000400AE1 0000000E C Access denied
.rodata:0000000000400AF8 00000076 C Congratulations, you've found the GGoCySEA guard station password.\nThis password came at a great cost, use it wisely!
.rodata:0000000000400B70 0000003D C Access denied.\nThis file does not contain the correct t0k3n.
.rodata:0000000000400BB0 00000021 C Unable to locate the t0k3n file.
.eh_frame:0000000000400C7F 00000006 C ;*3$\"
Alli solo necesitamos darle doble-click al string que nos interese para ir a la seccion relevante. Por ejemplo, si queremos saber el nombre del t0k3n file, podemos darle a "Unable to locate the t0k3n file" y nos lleva a esto:
.rodata:0000000000400B70 ; char aAccessDenied_T[]
.rodata:0000000000400B70 aAccessDenied_T db 'Access denied.',0Ah ; DATA XREF: main:loc_4008C5↑o
.rodata:0000000000400B70 db 'This file does not contain the correct t0k3n.',0
.rodata:0000000000400BAD align 10h
Lo que necesitamos es ese "DATA XREF", que es una referencia cruzada a esta porcion de datos. Esto es, una referencia al lugar desde donde se estan usando estos datos.
Podemos darle click a ese "loc_4008C5↑o" y presionar la tecla "X", o darle click derecho para ver el menu contextual que nos da la opcion "Jump to xref to operand" con el shortcut "X".
Esto nos lleva a este bloque de codigo:
.text:00000000004008C5 ; ---------------------------------------------------------------------------
.text:00000000004008C5
.text:00000000004008C5 loc_4008C5: ; CODE XREF: main+C4↑j
.text:00000000004008C5 mov edi, offset aAccessDenied_T ; "Access denied.\nThis file does not cont"...
.text:00000000004008CA call _puts
.text:00000000004008CF mov rax, [rbp+stream]
.text:00000000004008D3 mov rdi, rax ; stream
.text:00000000004008D6 call _fclose
.text:00000000004008DB jmp short loc_4008E7
La referencia al texto que vamos a mostrar va en el registro edi y luego hacemos un "call _puts". Obviamente estamos en la parte donde se imprime el mensaje. Pero lo que queremos es evitar llegar hasta aquí! Nuevamente podemos usar el XREF (esta vez es un CODE XREF porque la referencia viene de la sección de código, no de la sección de datos). Si seguimos ese "CODE XREF: main+C4↑j" llegamos a unas pocas lineas mas arriba (esta vez pegare también unas lineas anteriores para tener contexto):
.text:0000000000400885 mov rsi, rdx ; s2
.text:0000000000400888 mov rdi, rax ; s1
.text:000000000040088B call _strcmp
.text:0000000000400890 test eax, eax
.text:0000000000400892 jnz short loc_4008C5 ; LLEGAMOS AQUI
.text:0000000000400894 lea rax, [rbp+s]
.text:0000000000400898 mov rdi, rax ; s
.text:000000000040089B call _strlen
.text:00000000004008A0 mov [rbp+var_14], eax
.text:00000000004008A3 mov edi, offset aCongratulation ; "Congratulations, you've found the GGoCy"...
.text:00000000004008A8 call _puts
.text:00000000004008AD mov eax, [rbp+var_14]
.text:00000000004008B0 mov edi, eax
.text:00000000004008B2 call t0k3n_g3n
.text:00000000004008B7 mov rax, [rbp+stream]
.text:00000000004008BB mov rdi, rax ; stream
.text:00000000004008BE call _fclose
.text:00000000004008C3 jmp short loc_4008E7
Entonces, en las dos primeras lineas cargamos dos parámetros para strcmp en los registros rdi y rsi y en la tercera linea llamamos a strcmp. Luego, usamos ese test eax, eax para verificar el valor que retorna strcmp. Hasta ahora, pueden imaginarlo como "eax = strcmp(rdi, rsi);" Recuerden que strcmp retorna 0 si los strings son iguales, entonces la siguiente linea es un JNZ = Jump-if-Not-Zero, esto es saltaremos a imprimir el mensaje de error si el strcmp no retorna cero.
Y si no saltamos? significa que los strings son iguales y en las siguientes lineas se muestra el mensaje "Congratulations" con puts. Y luego se llama a la funcion t0k3n_g3n que seguramente significa "token generator" o algo asi. Esta es la función que queremos reversar.
Podemos resumirlo como:
if (!strcmp(s1, s2)) {
    puts("Congratulations, you've (…)");
    t0k3n_g3n();
}
Realmente, justo antes del puts() se llamo tambien a strlen(), que se necesita pasar como parametro a t0k3n_g3n, pero eso no es importante en estos momentos…
Veamos t0k3n_g3n (podemos seguir el XREF o buscarla en la ventana de funciones de IDA. Por cierto, estos nombres de funciones los sabemos porque el binario no esta "stripped", si hubieran usado el comando "strip, ISA le daria algun nombre generico):
.text:000000000040077C ; =============== S U B R O U T I N E =======================================
.text:000000000040077C
.text:000000000040077C ; Attributes: bp-based frame
.text:000000000040077C
.text:000000000040077C                 public t0k3n_g3n
.text:000000000040077C t0k3n_g3n       proc near               ; CODE XREF: main+E4↓p
.text:000000000040077C
.text:000000000040077C var_4           = dword ptr -4
.text:000000000040077C
.text:000000000040077C                 push    rbp
.text:000000000040077D                 mov     rbp, rsp
.text:0000000000400780                 sub     rsp, 10h
.text:0000000000400784                 mov     [rbp+var_4], edi
.text:0000000000400787                 mov     eax, [rbp+var_4]
.text:000000000040078A                 imul    eax, [rbp+var_4]
.text:000000000040078E                 mov     edx, eax
.text:0000000000400790                 mov     eax, edx
.text:0000000000400792                 add     eax, eax
.text:0000000000400794                 add     eax, edx
.text:0000000000400796                 shl     eax, 2
.text:0000000000400799                 lea     ecx, [rax+rdx]
.text:000000000040079C                 mov     edx, 60F25DEBh
.text:00000000004007A1                 mov     eax, ecx
.text:00000000004007A3                 imul    edx
.text:00000000004007A5                 sar     edx, 6
.text:00000000004007A8                 mov     eax, ecx
.text:00000000004007AA                 sar     eax, 1Fh
.text:00000000004007AD                 sub     edx, eax
.text:00000000004007AF                 mov     eax, edx
.text:00000000004007B1                 cmp     eax, [rbp+var_4]
.text:00000000004007B4                 jnz     short loc_4007C2
.text:00000000004007B6                 mov     eax, [rbp+var_4]
.text:00000000004007B9                 mov     edi, eax
.text:00000000004007BB                 call    tang0
.text:00000000004007C0                 jmp     short locret_4007CC
.text:00000000004007C2 ; ---------------------------------------------------------------------------
.text:00000000004007C2
.text:00000000004007C2 loc_4007C2:                             ; CODE XREF: t0k3n_g3n+38↑j
.text:00000000004007C2                 mov     edi, offset aHowDidYouGetHe ; "How did you get here"
.text:00000000004007C7                 call    _puts
.text:00000000004007CC
.text:00000000004007CC locret_4007CC:                          ; CODE XREF: t0k3n_g3n+44↑j
.text:00000000004007CC                 leave
.text:00000000004007CD                 retn
.text:00000000004007CD t0k3n_g3n       endp
Esta función parece que solo hace algunos cálculos y verificaciones para luego llamar a la función "tang0". Vemos sumas, restas, multiplicaciones, corrimientos de bits… También hay un valor mágico: "60F25DEBh"… Podríamos analizarla, pero como aun no estamos imprimiendo el password, parece que lo importante esta en tang0, así que mejor veremos esa:
.text:00000000004006D6 ; =============== S U B R O U T I N E =======================================
.text:00000000004006D6
.text:00000000004006D6 ; Attributes: bp-based frame
.text:00000000004006D6
.text:00000000004006D6                 public tang0
.text:00000000004006D6 tang0           proc near               ; CODE XREF: t0k3n_g3n+3F↓p
.text:00000000004006D6
.text:00000000004006D6 var_94          = dword ptr -94h
.text:00000000004006D6 var_90          = dword ptr -90h
.text:00000000004006D6 var_4           = dword ptr -4
.text:00000000004006D6
.text:00000000004006D6                 push    rbp
.text:00000000004006D7                 mov     rbp, rsp
.text:00000000004006DA                 sub     rsp, 0A0h
.text:00000000004006E1                 mov     [rbp+var_94], edi
.text:00000000004006E7                 mov     [rbp+var_4], 0A9h
.text:00000000004006EE                 mov     eax, [rbp+var_94]
.text:00000000004006F4                 imul    eax, 0A9h
.text:00000000004006FA                 cdq
.text:00000000004006FB                 idiv    [rbp+var_4]
.text:00000000004006FE                 cmp     eax, [rbp+var_94]
.text:0000000000400704                 jnz     short loc_400770
.text:0000000000400706                 lea     rax, [rbp+var_90]
.text:000000000040070D                 mov     edx, offset unk_400A40
.text:0000000000400712                 mov     ecx, 11h
.text:0000000000400717                 mov     rdi, rax
.text:000000000040071A                 mov     rsi, rdx
.text:000000000040071D                 rep movsq
.text:0000000000400720                 mov     rdx, rsi
.text:0000000000400723                 mov     rax, rdi
.text:0000000000400726                 mov     ecx, [rdx]
.text:0000000000400728                 mov     [rax], ecx
.text:000000000040072A                 lea     rax, [rax+4]
.text:000000000040072E                 lea     rdx, [rdx+4]
.text:0000000000400732                 mov     [rbp+var_94], 0
.text:000000000040073C                 jmp     short loc_400765
.text:000000000040073E ; ---------------------------------------------------------------------------
.text:000000000040073E
.text:000000000040073E loc_40073E:                             ; CODE XREF: tang0+96↓j
.text:000000000040073E                 mov     eax, [rbp+var_94]
.text:0000000000400744                 cdqe
.text:0000000000400746                 mov     eax, [rbp+rax*4+var_90]
.text:000000000040074D                 mov     esi, eax
.text:000000000040074F                 mov     edi, offset format ; "&#x%x;\n"
.text:0000000000400754                 mov     eax, 0
.text:0000000000400759                 call    _printf
.text:000000000040075E                 add     [rbp+var_94], 1
.text:0000000000400765
.text:0000000000400765 loc_400765:                             ; CODE XREF: tang0+66↑j
.text:0000000000400765                 cmp     [rbp+var_94], 22h
.text:000000000040076C                 jle     short loc_40073E
.text:000000000040076E                 jmp     short locret_40077A
.text:0000000000400770 ; ---------------------------------------------------------------------------
.text:0000000000400770
.text:0000000000400770 loc_400770:                             ; CODE XREF: tang0+2E↑j
.text:0000000000400770                 mov     edi, offset s   ; "So close...but it's not gonna happen"
.text:0000000000400775                 call    _puts
.text:000000000040077A
.text:000000000040077A locret_40077A:                          ; CODE XREF: tang0+98↑j
.text:000000000040077A                 leave
.text:000000000040077B                 retn
.text:000000000040077B tang0           endp
.text:000000000040077B
.text:000000000040077C
Esta es la función importante. Ayuda bastante el hecho de que se llama igual que el binario, pero ademas de eso, vemos que imprime unos valores con printf (resaltado en verde), y como puse el análisis dinámico primero, ya sabemos que ese "&#x%x;\n" es el formato con el que nos da el password, así que estamos seguros de que esta es la función importante :)
En EDI esta el format string, y en ESI la referencia al string. Unas lineas antes vemos que lo carga desde [rbp+rax*4+var_90]. Lo he resaltado en amarillo.  Y si buscamos mas referencias a ese "var_90" lo encontramos unas lineas mas arriba (también en amarillo).   Y justo ahi tenemos ese "unk_400A40" (resaltado en cyan) que resulta ser es un buffer con datos estáticos que tenemos que mirar:
.rodata:0000000000400A40 unk_400A40      db  63h ; c             ; DATA XREF: tang0+37 o
.rodata:0000000000400A41                 db    1
.rodata:0000000000400A42                 db    0
.rodata:0000000000400A43                 db    0
.rodata:0000000000400A44                 db  1Fh
.rodata:0000000000400A45                 db    2
.rodata:0000000000400A46                 db    0
.rodata:0000000000400A47                 db    0
.rodata:0000000000400A48                 db  93h ; ô
.rodata:0000000000400A49                 db    2
.rodata:0000000000400A4A                 db    0
.rodata:0000000000400A4B                 db    0
.rodata:0000000000400A4C                 db  20h
.rodata:0000000000400A4D                 db    0
.rodata:0000000000400A4E                 db    0
.rodata:0000000000400A4F                 db    0
.rodata:0000000000400A50                 db  1Bh
.rodata:0000000000400A51                 db  1Dh
.rodata:0000000000400A52                 db    0
.rodata:0000000000400A53                 db    0
.rodata:0000000000400A54                 db  98h ; ÿ
.rodata:0000000000400A55                 db    2
.rodata:0000000000400A56                 db    0
.rodata:0000000000400A57                 db    0
.rodata:0000000000400A58                 db  9Eh ; ×
.rodata:0000000000400A59                 db    2
.rodata:0000000000400A5A                 db    0
.rodata:0000000000400A5B                 db    0
.rodata:0000000000400A5C                 db  23h ; #
.rodata:0000000000400A5D                 db  1Dh
.rodata:0000000000400A5E                 db    0
.rodata:0000000000400A5F                 db    0
.rodata:0000000000400A60                 db  16h
.rodata:0000000000400A61                 db  1Dh
.rodata:0000000000400A62                 db    0
.rodata:0000000000400A63                 db    0
.rodata:0000000000400A64                 db  20h
.rodata:0000000000400A65                 db    0
.rodata:0000000000400A66                 db    0
.rodata:0000000000400A67                 db    0
.rodata:0000000000400A68                 db  8Fh ; Å
.rodata:0000000000400A69                 db  1Eh
.rodata:0000000000400A6A                 db    0
.rodata:0000000000400A6B                 db    0
.rodata:0000000000400A6C                 db  4Eh ; N
.rodata:0000000000400A6D                 db  1Eh
.rodata:0000000000400A6E                 db    0
.rodata:0000000000400A6F                 db    0
.rodata:0000000000400A70                 db  79h ; y
.rodata:0000000000400A71                 db  1Eh
.rodata:0000000000400A72                 db    0
.rodata:0000000000400A73                 db    0
.rodata:0000000000400A74                 db  20h
.rodata:0000000000400A75                 db    0
.rodata:0000000000400A76                 db    0
.rodata:0000000000400A77                 db    0
.rodata:0000000000400A78                 db 0E2h ; Ô
.rodata:0000000000400A79                 db  24h ; $
.rodata:0000000000400A7A                 db    0
.rodata:0000000000400A7B                 db    0
.rodata:0000000000400A7C                 db 0A0h ; á
.rodata:0000000000400A7D                 db  24h ; $
.rodata:0000000000400A7E                 db    0
.rodata:0000000000400A7F                 db    0
.rodata:0000000000400A80                 db 0A0h ; á
.rodata:0000000000400A81                 db  24h ; $
.rodata:0000000000400A82                 db    0
.rodata:0000000000400A83                 db    0
.rodata:0000000000400A84                 db 0A6h ; ª
.rodata:0000000000400A85                 db  24h ; $
.rodata:0000000000400A86                 db    0
.rodata:0000000000400A87                 db    0
.rodata:0000000000400A88                 db  20h
.rodata:0000000000400A89                 db    0
.rodata:0000000000400A8A                 db    0
.rodata:0000000000400A8B                 db    0
.rodata:0000000000400A8C                 db 0CBh ; -
.rodata:0000000000400A8D                 db  1Eh
.rodata:0000000000400A8E                 db    0
.rodata:0000000000400A8F                 db    0
.rodata:0000000000400A90                 db  69h ; i
.rodata:0000000000400A91                 db  1Eh
.rodata:0000000000400A92                 db    0
.rodata:0000000000400A93                 db    0
.rodata:0000000000400A94                 db  20h
.rodata:0000000000400A95                 db    0
.rodata:0000000000400A96                 db    0
.rodata:0000000000400A97                 db    0
.rodata:0000000000400A98                 db  61h ; a
.rodata:0000000000400A99                 db  13h
.rodata:0000000000400A9A                 db    0
.rodata:0000000000400A9B                 db    0
.rodata:0000000000400A9C                 db  20h
.rodata:0000000000400A9D                 db    0
.rodata:0000000000400A9E                 db    0
.rodata:0000000000400A9F                 db    0
.rodata:0000000000400AA0                 db  62h ; b
.rodata:0000000000400AA1                 db    0
.rodata:0000000000400AA2                 db    0
.rodata:0000000000400AA3                 db    0
.rodata:0000000000400AA4                 db  79h ; y
.rodata:0000000000400AA5                 db    0
.rodata:0000000000400AA6                 db    0
.rodata:0000000000400AA7                 db    0
.rodata:0000000000400AA8                 db  70h ; p
.rodata:0000000000400AA9                 db    0
.rodata:0000000000400AAA                 db    0
.rodata:0000000000400AAB                 db    0
.rodata:0000000000400AAC                 db  61h ; a
.rodata:0000000000400AAD                 db    0
.rodata:0000000000400AAE                 db    0
.rodata:0000000000400AAF                 db    0
.rodata:0000000000400AB0                 db  73h ; s
.rodata:0000000000400AB1                 db    0
.rodata:0000000000400AB2                 db    0
.rodata:0000000000400AB3                 db    0
.rodata:0000000000400AB4                 db  73h ; s
.rodata:0000000000400AB5                 db    0
.rodata:0000000000400AB6                 db    0
.rodata:0000000000400AB7                 db    0
.rodata:0000000000400AB8                 db  69h ; i
.rodata:0000000000400AB9                 db    0
.rodata:0000000000400ABA                 db    0
.rodata:0000000000400ABB                 db    0
.rodata:0000000000400ABC                 db  74h ; t
.rodata:0000000000400ABD                 db    0
.rodata:0000000000400ABE                 db    0
.rodata:0000000000400ABF                 db    0
.rodata:0000000000400AC0                 db  6Eh ; n
.rodata:0000000000400AC1                 db    0
.rodata:0000000000400AC2                 db    0
.rodata:0000000000400AC3                 db    0
.rodata:0000000000400AC4                 db  6Fh ; o
.rodata:0000000000400AC5                 db    0
.rodata:0000000000400AC6                 db    0
.rodata:0000000000400AC7                 db    0
.rodata:0000000000400AC8                 db  77h ; w
.rodata:0000000000400AC9                 db    0
.rodata:0000000000400ACA                 db    0
.rodata:0000000000400ACB                 db    0
Y allí vemos el password que queriamos :) ¿No? OK, ignoren los "db 0" y todo el ruido inicial:
.rodata:0000000000400AA0 db 62h ; b
.rodata:0000000000400AA4 db 79h ; y
.rodata:0000000000400AA8 db 70h ; p
.rodata:0000000000400AAC db 61h ; a
.rodata:0000000000400AB0 db 73h ; s
.rodata:0000000000400AB4 db 73h ; s
.rodata:0000000000400AB8 db 69h ; i
.rodata:0000000000400ABC db 74h ; t
.rodata:0000000000400AC0 db 6Eh ; n
.rodata:0000000000400AC4 db 6Fh ; o
.rodata:0000000000400AC8 db 77h ; w
Aqui combinaron dos trucos:
  1. Almacenaron cada byte como un DWORD para que la cadena no se identifique inmediatamente
  2. Escribieron el texto inicial en Unicodes raros, por para que uno pase por encima del buffer y no los identifique como texto.
Solo para que lo recuerden, esta era la salida que veíamos en análisis dinámico si ignoramos el mensaje inicial que estaba representado en Unicode:
&#x62;
&#x79;
&#x70;
&#x61;
&#x73;
&#x73;
&#x69;
&#x74;
&#x6e;
&#x6f;
&#x77;
Y confirmamos que son los mismos valores :)

Análisis estático de "tang0" iniciando via imports

La segunda opción que mencioné antes para hacer el análisis estático era usar imports. Vamos a "View" → "Open subview" → "Imports" (No tiene shortcut) y sale esto:
Address          Ordinal Name                           Library
-------          ------- ----                           -------
0000000000600FB8         __isoc99_fscanf@@GLIBC_2.7          
0000000000600FBC         puts@@GLIBC_2.2.5                    
0000000000600FC0         fclose@@GLIBC_2.2.5                  
0000000000600FC4         strlen@@GLIBC_2.2.5                  
0000000000600FC8         printf@@GLIBC_2.2.5                  
0000000000600FCC         __libc_start_main@@GLIBC_2.2.5      
0000000000600FD0         strcmp@@GLIBC_2.2.5                  
0000000000600FD4         fopen@@GLIBC_2.2.5                  
0000000000600FD8         __isoc99_fscanf                      
0000000000600FDC         puts                                
0000000000600FE0         fclose                              
0000000000600FE4         strlen                              
0000000000600FE8         printf                              
0000000000600FEC         __libc_start_main                    
0000000000600FF0         strcmp                              
0000000000600FF4         fopen                                
0000000000600FF8         _ITM_deregisterTMCloneTable          
0000000000600FFC         __gmon_start__                      
0000000000601000         _Jv_RegisterClasses                  
0000000000601004         _ITM_registerTMCloneTable          
El "strcmp" nos llama de inmediato la atención (ya explicamos porque) así que buscamos referencias a esta función (recuerden, primero doble-clic y luego "x".
Aparece esta:
Up o .got.plt:off_600F88 dq offset strcmp
Lo que encontramos fue una referencia a otra referencia (esto es común con los imports, asi funcionan pero no vamos a parar a explicar porque).  Solo es darle "x" de nuevo:
Direction Type Address Text          
--------- ---- ------- ----          
Up        p    main+68 call    _strcmp
Up        p    main+BD call    _strcmp
Estas son las dos comparaciones que hace el binario para validar el nombre de archivo y el token. Podemos seguir cada una y el procedimiento seria el mismo de la técnica anterior con "strings".
También podríamos devolvernos, y en lugar de intentar sacar el password nosotros mismos, podríamos intentar obtener los valores necesarios para ejecutar el binario… en ese caso solo tendríamos que seguir el XREF anterior a cada strcmp para regresar un poco… pero esto se parece mucho a la opción 3. Así que veamos esa directamente:

Análisis estático de "tang0" iniciando en el main()

Otra buena opción, especialmente en binarios cortos, es empezar desde el "principio". Esta es la función main():
.text:00000000004007CE ; =============== S U B R O U T I N E =======================================
.text:00000000004007CE
.text:00000000004007CE ; Attributes: bp-based frame
.text:00000000004007CE
.text:00000000004007CE ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00000000004007CE                 public main
.text:00000000004007CE main            proc near               ; DATA XREF: _start+1D↑o
.text:00000000004007CE
.text:00000000004007CE var_60          = qword ptr -60h
.text:00000000004007CE var_54          = dword ptr -54h
.text:00000000004007CE s2              = byte ptr -50h
.text:00000000004007CE var_48          = byte ptr -48h
.text:00000000004007CE var_40          = byte ptr -40h
.text:00000000004007CE var_38          = dword ptr -38h
.text:00000000004007CE var_34          = word ptr -34h
.text:00000000004007CE var_32          = byte ptr -32h
.text:00000000004007CE s               = byte ptr -30h
.text:00000000004007CE var_14          = dword ptr -14h
.text:00000000004007CE stream          = qword ptr -10h
.text:00000000004007CE s1              = qword ptr -8
.text:00000000004007CE
.text:00000000004007CE                 push    rbp
.text:00000000004007CF                 mov     rbp, rsp
.text:00000000004007D2                 sub     rsp, 60h
.text:00000000004007D6                 mov     [rbp+var_54], edi
.text:00000000004007D9                 mov     [rbp+var_60], rsi
.text:00000000004007DD                 cmp     [rbp+var_54], 2
.text:00000000004007E1                 jz      short loc_4007F2
.text:00000000004007E3                 mov     edi, offset aAccessDenied ; "Access denied"
.text:00000000004007E8                 call    _puts
.text:00000000004007ED                 jmp     locret_400902
.text:00000000004007F2 ; ---------------------------------------------------------------------------
.text:00000000004007F2
.text:00000000004007F2 loc_4007F2:                             ; CODE XREF: main+13↑j
.text:00000000004007F2                 mov     rax, 2E6B2E722E612E4Dh
.text:00000000004007FC                 mov     qword ptr [rbp+var_40], rax
.text:0000000000400800                 mov     [rbp+var_38], 2E332E38h
.text:0000000000400807                 mov     [rbp+var_34], 61h
.text:000000000040080D                 mov     [rbp+var_32], 0
.text:0000000000400811                 mov     rax, 78742E6E336B3074h
.text:000000000040081B                 mov     qword ptr [rbp+s2], rax
.text:000000000040081F                 mov     [rbp+var_48], 74h
.text:0000000000400823                 jmp     loc_4008E7
.text:0000000000400828 ; ---------------------------------------------------------------------------
.text:0000000000400828
.text:0000000000400828 loc_400828:                             ; CODE XREF: main+12E↓j
.text:0000000000400828                 lea     rdx, [rbp+s2]
.text:000000000040082C                 mov     rax, [rbp+s1]
.text:0000000000400830                 mov     rsi, rdx        ; s2
.text:0000000000400833                 mov     rdi, rax        ; s1
.text:0000000000400836                 call    _strcmp
.text:000000000040083B                 test    eax, eax
.text:000000000040083D                 jnz     loc_4008DD
.text:0000000000400843                 mov     rax, [rbp+s1]
.text:0000000000400847                 mov     esi, offset modes ; "r"
.text:000000000040084C                 mov     rdi, rax        ; filename
.text:000000000040084F                 call    _fopen
.text:0000000000400854                 mov     [rbp+stream], rax
.text:0000000000400858                 cmp     [rbp+stream], 0
.text:000000000040085D                 jz      loc_4008E7
.text:0000000000400863                 lea     rdx, [rbp+s]
.text:0000000000400867                 mov     rax, [rbp+stream]
.text:000000000040086B                 mov     esi, offset aS  ; "%s"
.text:0000000000400870                 mov     rdi, rax
.text:0000000000400873                 mov     eax, 0
.text:0000000000400878                 call    ___isoc99_fscanf
.text:000000000040087D                 lea     rdx, [rbp+var_40]
.text:0000000000400881                 lea     rax, [rbp+s]
.text:0000000000400885                 mov     rsi, rdx        ; s2
.text:0000000000400888                 mov     rdi, rax        ; s1
.text:000000000040088B                 call    _strcmp
.text:0000000000400890                 test    eax, eax
.text:0000000000400892                 jnz     short loc_4008C5
.text:0000000000400894                 lea     rax, [rbp+s]
.text:0000000000400898                 mov     rdi, rax        ; s
.text:000000000040089B                 call    _strlen
.text:00000000004008A0                 mov     [rbp+var_14], eax
.text:00000000004008A3                 mov     edi, offset aCongratulation ; "Congratulations, you've found the GGoCy"...
.text:00000000004008A8                 call    _puts
.text:00000000004008AD                 mov     eax, [rbp+var_14]
.text:00000000004008B0                 mov     edi, eax
.text:00000000004008B2                 call    t0k3n_g3n
.text:00000000004008B7                 mov     rax, [rbp+stream]
.text:00000000004008BB                 mov     rdi, rax        ; stream
.text:00000000004008BE                 call    _fclose
.text:00000000004008C3                 jmp     short loc_4008E7
.text:00000000004008C5 ; ---------------------------------------------------------------------------
.text:00000000004008C5
.text:00000000004008C5 loc_4008C5:                             ; CODE XREF: main+C4↑j
.text:00000000004008C5                 mov     edi, offset aAccessDenied_T ; "Access denied.\nThis file does not cont"...
.text:00000000004008CA                 call    _puts
.text:00000000004008CF                 mov     rax, [rbp+stream]
.text:00000000004008D3                 mov     rdi, rax        ; stream
.text:00000000004008D6                 call    _fclose
.text:00000000004008DB                 jmp     short loc_4008E7
.text:00000000004008DD ; ---------------------------------------------------------------------------
.text:00000000004008DD
.text:00000000004008DD loc_4008DD:                             ; CODE XREF: main+6F↑j
.text:00000000004008DD                 mov     edi, offset aUnableToLocate ; "Unable to locate the t0k3n file."
.text:00000000004008E2                 call    _puts
.text:00000000004008E7
.text:00000000004008E7 loc_4008E7:                             ; CODE XREF: main+55↑j
.text:00000000004008E7                                         ; main+8F↑j ...
.text:00000000004008E7                 add     [rbp+var_60], 8
.text:00000000004008EC                 mov     rax, [rbp+var_60]
.text:00000000004008F0                 mov     rax, [rax]
.text:00000000004008F3                 mov     [rbp+s1], rax
.text:00000000004008F7                 cmp     [rbp+s1], 0
.text:00000000004008FC                 jnz     loc_400828
.text:0000000000400902
.text:0000000000400902 locret_400902:                          ; CODE XREF: main+1F↑j
.text:0000000000400902                 leave
.text:0000000000400903                 retn
.text:0000000000400903 main            endp
.text:0000000000400903
.text:0000000000400903 ; ---------------------------------------------------------------------------
.text:0000000000400904                 align 10h
.text:0000000000400910
Una buena parte ya la hemos visto antes, pues es donde se hacen los strcmp y se imprimen los mensajes de éxito o fracaso… La parte interesante y nueva esta justo al principio de la función: primero vemos el bloque con la validación del número de argumentos. Recuerden que salía "Access denied" si no le pasamos un nombre de archivo como argumento… El segundo bloque es muy interesante porque tiene cinco valores mágicos (resaltados en verde):
  • 2E6B2E722E612E4Dh
  • 2E332E38h
  • 61h
  • 78742E6E336B3074h
  • 74h
Cuando nos encontramos este tipo de constantes (también llamadas "inmediatos"), lo ideal es entender que son.  A veces podemos buscarlos e identificar el algoritmos solo a partir de los valores inmediatos (por ejemplo, MD5, CRC32, SHA1, todos tienen sus constantes caracteristicas, eso ayuda mucho).  En este caso, la cosa es mas facil: si le damos click derecho a cualquiera de estas constantes, IDA nos muestra representaciones alternativas (es decir, como se veria esto como octal, como binario, como decimal, como string…) y entre ellas vemos que estos valores pueden representarse como strings (tambien pueden darle click a cada constante y usar el shortcut "r"):
  • 2E6B2E722E612E4Dh  =  '.k.r.a.M'
  • 2E332E38h  =  '.3.8'
  • 61h  =  'a'
  • 78742E6E336B3074h  =  'xt.n3k0t'
  • 74h  =  't'
Entonces en un qword, un dword y un byte esta el string "a.3.8.k.r.a.M" que es el token que necesitamos (visto al revés por asuntos del endianess). Y en un qword y un dword esta el string "txt.n3k0t" que es el nombre del archivo (nuevamente, al revés por el endianess). Aquí solo sería ejecutar con estos valores como hicimos inicialmente.

Segundo binario: reverseme

El segundo binario fue reverseme. Otro ELF64. Este si daba un t0k3n para la plataforma por 200 puntos. Este es el enlace y el password del 7z:

 https://www.dropbox.com/s/wsr8y2uioga4fv5/reverseme.7z?dl=1 R3verS3ME!

Si cargamos en IDA Pro y vemos el desensamblado solo encontraremos una funcion. Ningún import. Esto es señal de que este binario se escribió directamente en assembly, no en C como el anterior.

Este es el desensamblado de la función. No hace falta que traten de entenderla, solo la pongo para tener la referencia completa y asi hacernos a una idea de la clase de función que tendríamos que analizar (una serie larguisima de operaciones aritmeticas o logicas que parecen un trabalenguas).

Es interesante que solo incluye cuatro saltos: 3 condicionales (lineas 13, 21, y 32) y uno incondicional (linea 26).  Los dos primeras saltos condicionales parecen ser algunas verificaciones iniciales, pero el tercera es una bifurcacion interesante porque comparamos el registro ecx con un valor magico 0EFBA893Fh (linea 31)y si son iguales (ese JZ = Jump-if-Zero es equivalente a JE = Jump-if-Equal) saltamos hacia b30e55a8 (para ejecutar todo ese trabalenguas), pero si no son iguales llamamos a "sys_exit" y el programa termina de inmediato.

Asi que para que se ejecute todo ese bloque larguisimo de operaciones logicas y aritmeticas ecx tiene que llegar con el valor 0EFBA893Fh.

"reverseme" via parchado del binario

Esta vez veremos otra tecnica distinta. Modificaremos el binario para cambiar su comportamiento.  Lo interesante es que aplicar parches es generalmente la tecnica mas rapida posible.

La idea principal es que quisieramos que ese bloque largo de instrucciones se ejecute siempre, no solamente cuando se cumple la condicion de ECX = 0xEFBA893F.

Lo que haremos sera modificar el salto, cambiando este:

.text:080480B9                 jz      short b30e55a8

Por un salto incondicional:

.text:080480B9                 jmp     short b30e55a8

Y todo queda resuelto :)

Para esto necesitamos saber dos cosas:
  1. Como se representa un JMP en hexadecimal (pues vamos a editar directamente el valor numerico en el archivo)
  2. En que posicion del archivo esta lo que queremos cambiar?
La primera es relativamente facil. La verdad es que los JMP pueden escribirse de varias formas diferentes dependiendo de que tan lejos tenemos que saltar (algunos desensambladores muestran diferentes opciones como JMP SHORT, JMP NEAR, JMP FAR, otros solo los llamaran JMP).  Para saber que tan lejos estamos saltando una opcion es ver como se codifico el salto condicional (si tambien hay varios valores posibles en hexa para cada salto condicional).  Para esto, le damos clic a la linea que queremos estudiar:



.text:080480B9                 jz      short b30e55a8

Y vamos  "View" → "Open subviews" → "Hex Dump"

y aparece un volcado hexadecimal con dos valores resaltados "74 09":

080480B9  74 09 B8 01 00 00 00 31  DB CD 80 31 C0 31 DB 31
080480C9  C9 31 D2 50 89 C1 29 C8  BB 41 4D 2E 2A B9 37 4D
080480D9  2E 2A 01 D8 29 C8 89 C6  89 CB 29 D9 B8 82 BF 24
080480E9  47 BB 4B BF 24 47 01 C1  29 D9 C1 E6 08 09 CE 89
080480F9  DA 29 D3 B8 5D BB A9 7F  BA 29 BB A9 7F 01 C3 29
08048109  D3 C1 E6 08 09 DE 89 CA  29 D1 B8 1D 01 9F 7D BA
08048119  EA 00 9F 7D 01 C1 29 D1  C1 E6 08 09 CE 56 31 F6
08048129  89 C2 29 D0 B9 F0 DB 8E  3B BA 7E DB 8E 3B 01 C8
08048139  29 D0 89 C6 89 DA 29 D3  B8 0A 24 97 20 BA C3 23
08048149  97 20 01 C3 29 D3 C1 E6  08 09 DE 89 DA 29 D3 B9
08048159  75 7A 3F 70 BA 55 7A 3F  70 01 CB 29 D3 C1 E6 08
08048169  09 DE 89 C3 29 D8 BA A4  C2 84 16 BB 80 C2 84 16
08048179  01 D0 29 D8 C1 E6 08 09  C6 56 31 F6 89 C3 29 D8
08048189  BA D3 E7 D0 68 BB B2 E7  D0 68 01 D0 29 D8 89 C6

Aqui 0x74 representa el salto condicional y 0x09 representa el destino. Veamos de nuevo el salto y algunas instrucciones mas:


.text:080480B9                 jz      short b30e55a8
.text:080480BB                 mov     eax, 1
.text:080480C0                 xor     ebx, ebx        ; status
.text:080480C2                 int     80h             ; LINUX - sys_exit
.text:080480C4 ; ---------------------------------------------------------------------------
.text:080480C4
.text:080480C4 b30e55a8:                               ; CODE XREF: a7f143da+39↓j

El salto esta en la primera linea (que corresponde a la direccion 0x080480B9) y su destino es la etiqueta "b30e55a8" (que esta en la ultima linea de ese trozo y corresponde a la instruccion 0x080480C4).  Si calculamos la diferencia entre las direcciones vemos que  0x080480C4 - 0x080480B9 es igual a 11 bytes.

¿11 bytes? pero el salto esta codificado como "09", ¿no deberían ser 9 bytes? si, son 9. Solo tenemos que restar la longitud de la instruccion del salto, recuerden que ese "jz short b30e55a8" se codifica como "74 09". entonces son 11 bytes - 2 bytes de la instruccion = 9 bytes.

Lo importante es que tenemos un salto condicional corto "JZ SHORT" (ya IDA nos lo decía desde el principio) y que el destino esta a solo 9 bytes de distancia.

Cambiaremos el salto condicional corto por un salto incondicional corto. ¿Como se representa un salto incondicional corto? 0xEB.  Creánme.

Bueno, no me crean.  En el binario ya hay un salto incondicional corto, podemos usarlo para verificar:

.text:080480AC                 jmp     short a650fe19

Si verifican el volcado hexadecimal veran que se codifica como: "EB F1" ( y en este caso el F1 representa un valor negativo, es un salto para devolverse, pero esto es irrelevante. lo importante es que el 0xEB es JMP SHORT).

Para mas info solo busquen algo como: "x86 assembly jmp short" en google. Esta primera referencia lo explica bien: http://thestarman.pcministry.com/asm/2bytejumps.htm

OK.  Tenemos el cambio que queremos: necesitamos cambiar un 0x74 por un 0xEB :)  o tambien podriamos cambiarlo por un 0x75 que significa JNZ o JNE (Jump-if-Not-Equal).  Asi saltaria en cualquier caso menos cuando le llegue el valor esperado.  Pero el JMP es mas limpio.

Podemos verificar el cambio directamente en IDA.  En la vista Hex Dump le damos click derecho y "Edit", cambiamos 74 por EB, click derecho y "Apply changes". Al volver al desensamblado tendremos un JMP en lugar de un JZ :)

Sin embargo, esto no guarda los cambios en el binario. Solo en la representación que hace IDA del mismo… La verdad no se si IDA tiene la opción para guardar también el cambio en el binario. tal vez si, nunca he buscado.

Para hacer eso uso siempre algún editor hexadecimal. En esta maquina tengo instalado HexWorkshop. Pero sirve cualquiera…

Aquí volvemos al segundo problema: "En que posición del archivo esta lo que queremos cambiar?"  Obviamente no es solo buscar un 74 porque puede estar muchas veces por ejemplo 0x74 también representaría el carácter 't'.

Lo normal es identificar unos 10 bytes que sirvan de patrón para ubicarnos. Bueno, 10 o 20 o los que se requieran hasta encontrar un patrón único. Podríamos por ejemplo buscar la linea entera que muestra IDA en el dump: "74 09 B8 01 00 00 00 31  DB CD 80 31 C0 31 DB 31".  Aunque  en este caso solo hay un "74 09" en todo el binario. Así que con eso podemos ubicarnos.

Vamos a HexWorkshop, cambiamos el binario, vamos a "Edit" → "Find". Y le ingresamos "7409" (o algún patrón mas largo)

Eso nos ubica en la region correcta. Hacemos el cambio del "74" a "EB". Guardamos los cambios en un nuevo binario (yo lo llame "reverseme-patched") y lo ejecutamos:

user@ubuntu:~$ ./reverseme-patched
Can you reverse me?
th3 k3y y0u s33k !s: R3ver5!nG !$ Gr347
Ese era el token que buscábamos :) y ademas es muy cierto.

"reverseme" vía parchado en memoria

Les dije que parchar era una opción muy rápida y aun así los demoré mucho… esta es solo una alternativa para hacer las cosas mas rápido: en lugar de generar un nuevo binario, parcharemos en memoria el binario original.
Empezamos por lanzar el binario en Linux con gdbserver (vamos a usar IDA desde Windows via GDB en Linux)
user@ubuntu:~$ gdbserver localhost:23946 ./reverseme
Process ./reverseme created; pid = 2142
Listening on port 23946
Luego nos vamos a IDA y en "Debugger" → "Switch debugger" seleccionamos: "Remote GDB debugger". Cuando nos pregunte Hostname le damos la IP del Linux y en puerto, el puerto en el que esta escuchando gbd: 23946
Nos preguntara "Do you want to attach?" y le decimos que si. Esto nos llevara a IDA como depurador, usando un esquema de color terriblemente feo.
Ahora, le damos clic en la linea donde verifica el valor mágico:
.text:080480B3 cmp     ecx, 0EFBA893Fh
Y vamos a "Debugger" → "Breakpoints" → "Add breakpoint" (el shortcut es F2). Al hacer esto, la linea se resalta en rojo. Vamos a "Debugger" → "Continue process" (el shortcut es F9) y el programa corre hasta la parte donde se va a realizar la comparación.
Aquí podemos editar el valor del registro ECX para ingresarle el numero que esta esperando (recuerden que era 0EFBA893Fh). Para hacer esto, vamos a la ventana "General registers"  (por defecto esta en la esquina superior derecha) y le damos doble-clic al registro RCX (así se llama el registro de 64 bits, ECX es el nombre del registro de 32 bits, CX el de 16 bits CL el de 8 bits…) e ingresamos el valor 0xEFBA893F.
Pulsamos F9 (o "Debugger" → "Continue process") y miramos de nuevo la consola en Linux:
user@ubuntu:~$ gdbserver localhost:23946 ./reverseme
Process ./reverseme created; pid = 2142
Listening on port 23946
Remote debugging from host 192.168.1.19
Remote side has terminated connection.  GDBserver will reopen the connection.
Listening on port 23946
Remote debugging from host 192.168.1.19
Can you reverse me?
th3 k3y y0u s33k !s: R3ver5!nG !$ Gr347

Child exited with status 0
Remote side has terminated connection.  GDBserver will reopen the connection.
Listening on port 23946
Efectivamente, nos dio el token y termino el proceso :)  Mucho mas rapido, ¿no?
Otra opcion habria sido poner el breakpoint en el JZ en lugar de ponerlo en el CMP.  Entonces, en lugar de modificar el registo RCX, tendriamos que modificar la flag ZF.
Finalmente, esto de parchar en memoria tambien podria hacerse directamente en GDB, sin enlazarse con IDA:
user@ubuntu:~$ gdb ./reverseme
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./reverseme...(no debugging symbols found)...done.

(gdb) break *0x080480B3
Breakpoint 1 at 0x80480b3

(gdb) run
Starting program: /home/user/reverseme
Can you reverse me?

Breakpoint 1, 0x080480b3 in b8653e9a ()

(gdb) set $ecx = 0xEFBA893F

(gdb) continue
Continuing.

th3 k3y y0u s33k !s: R3ver5!nG !$ Gr347
[Inferior 1 (process 2259) exited normally]

(gdb) quit

user@ubuntu:~$

Tercer binario: codeName.exe

El tercer binario que reversamos fue codeName.exe, un PE32. Otros 200 puntos. Aqui esta el enlace y la contraseña que teniamos para el 7z:

https://www.dropbox.com/s/g0n8vona55pfdcu/codeName.7z?dl=1 cur74lcun4bul4

Creanlo o no este era todo un regalo pero se volvio dificil por no incluir las dll necesarias (y luego por algunos otros males).  Mucha gente en el IRC lloraba por eso. Y nosotros tambien lo sufrimos.

Por ahora lo haremos de la forma dificil, sin ejecutar nunca el binario:

Esta vez les ahorrare el desensamblado.  Pero la idea es similar a los anteriores.  Hay algunos strcmp atractivos que nos ayudan a entender lo que esta pasando.  De hecho, esto es mas o menos lo que pasa:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  std::string *v3; // ST00_4@13
  std::string *v4; // ST00_4@13
  const char *v5; // eax@13
  int v6; // eax@14
  std::string *v7; // ST00_4@16
  std::string *v9; // [sp+0h] [bp-108h]@13
  std::string *v10; // [sp+0h] [bp-108h]@14
  const std::string *MaxCount; // [sp+8h] [bp-100h]@11
  char Buf[4]; // [sp+1Ch] [bp-ECh]@1
  char v13; // [sp+20h] [bp-E8h]@1
  char v14; // [sp+80h] [bp-88h]@1
  char v15; // [sp+C1h] [bp-47h]@1
  __int16 v16; // [sp+C2h] [bp-46h]@1
  int v17; // [sp+C4h] [bp-44h]@1
  char v18; // [sp+E4h] [bp-24h]@13
  char v19; // [sp+E8h] [bp-20h]@13
  char v20; // [sp+ECh] [bp-1Ch]@13
  int *v21; // [sp+FCh] [bp-Ch]@1

  v21 = &argc;
  __main();
  *(_DWORD *)Buf = 0;
  memset(&v13, 0, 0x60u);
  printf("Enter the uber secret operation code name: ");
  fgets(Buf, 100, __iob);
  qmemcpy(&v14, "Operation Ghost Car: https://www.youtube.com/watch?v=Wz1W_omigwg", 0x41u);
  v15 = 0;
  v16 = 0;
  memset(&v17, 0, 0x20u);
  if ( !strcmp(Buf, "Supersonic Snake\n") )
  {
    puts("Operation has expired");
  }
  else if ( !strcmp(Buf, "Hot Lobster\n") )
  {
    puts("Subjects have been pinched");
  }
  else if ( !strcmp(Buf, "Burnt Potato\n") )
  {
    puts("Operation has expired");
  }
  else if ( !strcmp(Buf, "Burp Gun\n") )
  {
    puts("Operation has expired");
  }
  else if ( !strcmp(Buf, "Duck Meat\n") )
  {
    puts("Operation has expired");
  }
  else if ( !strcmp(Buf, (const char *)&"Dirty Finger\n") )
  {
    puts("Operation has expired");
  }
  else
  {
    generateOpName((int)&v19);
    std::string::string((std::string *)&v19, MaxCount);
    sha256(&v18, &v20);
    std::string::~string(v3);
    v5 = (const char *)std::string::c_str(v4);
    if ( strcmp(Buf, v5) == 0 )
    {
      v6 = std::string::c_str(v9);
      printf("Operation in effect, th3 t0k3n you s33k is: %s\n", v6);
    }
    else
    {
      puts((const char *)&"That operation does not exist");
    }
    std::string::~string(v10);
    std::string::~string(v7);
  }
  return 0;
}

¿Que? ¿de donde salio eso?

Si. IDA Pro no solo desensambla y depura, también decompila… No habia querido decirles para que no hicieran trampa en los dos niveles anteriores…

Ese es el decompilado que genera HexRays (el decompilador de IDA Pro), que soporta x86, x86-64,  ARM, y PowerPC (pero cada uno se vende por separado).

Es suficientemente claro que solicita el nombre una "operacion" y lo valida. Algunas ya estan expiradas, por ejemplo: "Supersonic Snake" o "Duck Meat", pero hay una que esta activa y nos dara el mensaje: "Operation in effect, th3 t0k3n you s33k is: %s\n"

Vemos que hay una funcion que se encarga de generar el nombre de la operacion activa: generateOpName(), vemos tambien que poco despues de ella se llama a tambian a la funcion sha256().

Veamos que hace generateOpName():

int __cdecl generateOpName(int a1)
{
  int v1; // ST78_4@1
  int v2; // eax@1
  char v3; // al@1
  int v4; // eax@1
  char v5; // al@1
  int v6; // eax@1
  char v7; // al@1
  int v8; // eax@1
  char v9; // al@1
  int v10; // eax@1
  char v11; // al@1
  int v12; // eax@1
  char v13; // al@1
  int v14; // eax@1
  char v15; // al@1
  int v16; // eax@1
  char v17; // al@1
  int v18; // eax@1
  char v19; // al@1
  int v20; // eax@1
  char v21; // al@1
  int v22; // eax@1
  char v23; // al@1
  int v24; // eax@1
  char v25; // al@1
  int v26; // eax@1
  char v27; // al@1
  int v28; // eax@1
  char v30; // [sp+93h] [bp-9h]@1

  std::allocator<char>::allocator(&v30);
  std::string::string(a1);
  std::allocator<char>::~allocator(&v30);
  v1 = (char)a();
  v2 = std::string::operator+=(a1);
  std::string::operator=(a1, v2);
  v3 = b();
  v4 = std::string::operator+=(a1);
  std::string::operator=(a1, v4);
  v5 = c();
  v6 = std::string::operator+=(a1);
  std::string::operator=(a1, v6);
  v7 = d();
  v8 = std::string::operator+=(a1);
  std::string::operator=(a1, v8);
  v9 = e();
  v10 = std::string::operator+=(a1);
  std::string::operator=(a1, v10);
  v11 = f();
  v12 = std::string::operator+=(a1);
  std::string::operator=(a1, v12);
  v13 = g();
  v14 = std::string::operator+=(a1);
  std::string::operator=(a1, v14);
  v15 = h();
  v16 = std::string::operator+=(a1);
  std::string::operator=(a1, v16);
  v17 = a();
  v18 = std::string::operator+=(a1);
  std::string::operator=(a1, v18);
  v19 = j();
  v20 = std::string::operator+=(a1);
  std::string::operator=(a1, v20);
  v21 = k();
  v22 = std::string::operator+=(a1);
  std::string::operator=(a1, v22);
  v23 = l();
  v24 = std::string::operator+=(a1);
  std::string::operator=(a1, v24);
  v25 = m();
  v26 = std::string::operator+=(a1);
  std::string::operator=(a1, v26);
  v27 = n();
  v28 = std::string::operator+=(a1);
  std::string::operator=(a1, v28);
  return a1;
}

No es tan claro como el anterior por que C++ no decompila tan bien como C, pero lo que esta pasando es que se llama a varias funciones con nombres de letras: a(), b(), c(), … y asi hasta la n().
Entre cada llamado se mete tambien ese "operator+=" y ´para los que nunca aprendimos C++, siemrpe esta la documentacion online: http://www.cplusplus.com/reference/string/string/operator+=/
asi que, basicamente, estamos concatenando las salidas de esas funciones…

Si analizamos cada una de esas mini-funciones veremos que solo retornan una letra y ya :P

Por ejemplo, este es el desensamblado de la funcion a():

signed int a(void)
{
  return 'T';
}

facil!

y la funcion b()?

signed int b(void)
{
  return 'i';
}

facil :)

Si seguimos asi llegamos a la funcion n():

signed int n(void)
{
  return 10;
}

que retorna 0x10 o '\n' :)

Concatenando todo se forma el string: 'Time to Tang0\n'

Y si calculamos el sha256 tenemos el token correcto:

user@ubuntu:~$ echo "Time to Tang0" | sha256sum
e48e631e8ffeee32a377541d50ec4f2080ca6020d4d224770ed9e6687b1ed7b0  -

Pero al principio no me funcionaba.  Primero porque el primer binario que subieron estaba malo y tuvieron que cambiarlo, y luego porque al parecer el scoreboard no soportaba tokens tan largos :S

En fin, ese si es el token correcto, pero para estar seguros lo ideal es ejecutar el binario y dejar que el lo calcule :)

Como les dije antes, ejecutar el binario era un todo reto aparte. A nosotros tambien nos hizo sufrir. Alguien del equipo encontro unas dll que parecian funcionar, pero no… Se rompia mas adelante :S

A decir verdad, solo logre ejecutarlo cuando alguien dijo en el IRC que logro hacerlo correr con las dll que vienen en apkstudio.  Sabiendo esto el reto no tomaba ni un segundo :(

Descargue apkstudio-2.0.3b-windows.zip y fui sacando las DLL que me pedia el binario. Al final se necesitan tres: libgcc_s_dw2-1.dll, libstdc++-6.dll, y libwinpthread-1.dll

[Anaconda2] E:\Users\ruben\Desktop>codeName.exe
Enter the uber secret operation code name: Time to Tang0
Operation in effect, th3 t0k3n you s33k is: e48e631e8ffeee32a377541d50ec4f2080ca
6020d4d224770ed9e6687b1ed7b0

Si, efectivamente ese es el nombre de la operacion, y ese es el token correcto :)

Ahora bien, ¿que habria pasado si hubieramos encontrado las dll correctas desde el principio?

Hay varias formas de afrontarlo, pero podriamos por ejemplo usar el mismo truco del reto anterior (mas o menos):

.text:00401924                 mov     [esp+108h+Format], eax ; Str2
.text:00401928                 lea     eax, [ebp+Buf]
.text:0040192E                 mov     [esp+108h+var_108], eax ; this
.text:00401931                 call    _strcmp
.text:00401936                 test    eax, eax
.text:00401938                 setz    al
.text:0040193B                 test    al, al
.text:0040193D                 jz      short loc_40195B
.text:0040193F                 lea     eax, [ebp+var_24]
.text:00401942                 mov     ecx, eax
.text:00401944                 call    __ZNKSs5c_strEv ; std::string::c_str(void)
.text:00401949                 mov     [esp+108h+Format], eax
.text:0040194D                 mov     [esp+108h+var_108], offset aOperationInEff ; "Operation in effect, th3 t0k3n you s33k"...
.text:00401954                 call    _printf
.text:00401959                 jmp     short loc_401967

Tenemos un JZ. Pero esta vez no queremos cambiarlo por un JMP pues siempre fallaría, aun con el nombre de la operación correcta.  Lo que se hace en estos casos es cambiarlo por NOP (Por tantos NOP como sea necesario).

Ya sabemos del reto anterior que un JZ SHORT requiere dos bytes.  Pues bien la operacion NOP (que no hace nada, solo ocupa espacio) requiere un byte y se representa por 0x90 entonces podríamos cambiar el "74 1C" que representa ese salto por un "90 90" que no hace nada, y el nuevo binario siempre nos dará el token.  Bueno, no siempre.  Si le damos algún nombre de operación expirada como "Supersonic Snake" aun nos dirá que esta expirada, pero cualquier otra cosa, como "abc" o "123" nos dará el token correcto :)

Ademas, podriamos no parchar nada y leer en la memoria el nombre de la operacion correcta:

Si ponemos un breakpoint aqui:

.text:00401924 mov     [esp+108h+Format], eax          ; Str2

Al ejecutar podemos poner cualquier nombre de operacion. Le damos ENTER y el para en el breakpoint.

Si en esa linea le damos doble-click a "eax" nos muestra el valor que hay almacenado alli:

debug018:003418B4 db  54h ; T
debug018:003418B5 db  69h ; i
debug018:003418B6 db  6Dh ; m
debug018:003418B7 db  65h ; e
debug018:003418B8 db  20h
debug018:003418B9 db  74h ; t
debug018:003418BA db  6Fh ; o
debug018:003418BB db  20h
debug018:003418BC db  54h ; T
debug018:003418BD db  61h ; a
debug018:003418BE db  6Eh ; n
debug018:003418BF db  67h ; g
debug018:003418C0 db  30h ; 0
debug018:003418C1 db  0Ah
debug018:003418C2 db    0

Tambien podemos pulsar "a" para verlo como un string en ascii:

debug018:003418B4 aTimeToTang0 db 'Time to Tang0',0Ah,0

Y desde aqui volvemos a ejecutar con el nombre correcto y obtenemos el token.

Moraleja

La moraleja es que aunque las cosas pueden resolverse solo con análisis estático o solo con análisis dinámico, generalmente es mas fácil mezclar las dos cosas.  En un CTF esto es especialmente útil porque la idea es marcar los puntos tan rápido como sea posible, pero ya a la hora de los write-up, vale la pena tomarse un tiempo adicional para mejorar otras habilidades, especialmente las relacionadas con el análisis estático ;)

Y para no terminar el post sin una imagen, que les parece esta:

Es el reto mas loco que he visto en mucho tiempo…
De alguna forma c4fdez logró sacar de ahí el numero "762351".

Comentarios