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! ţ ȟ ʓ   ᴛ ʘ ʞ ᴣ ᴖ   ẏ Ṏ ṹ   ⓢ ⒠ ⒠ ⒦   ị ṩ   ፡   b y p a s s i t n o w 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:
- Almacenaron cada byte como un DWORD para que la cadena no se identifique inmediatamente
- 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:
b y p a s s i t n o w
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:
- Como se representa un JMP en hexadecimal (pues vamos a editar directamente el valor numerico en el archivo)
- En que posicion del archivo esta lo que queremos cambiar?
.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
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
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
Publicar un comentario