Pages

sábado, 21 de julio de 2018

Nebula CTF - level13

El usuario flag13 nos ha preparado otro binario para desafiarnos:

level13@nebula:/home/flag13$ ls -al
total 13
drwxr-x--- 2 flag13 level13   80 2011-11-20 21:22 .
drwxr-xr-x 1 root   root     100 2012-08-27 07:18 ..
-rw-r--r-- 1 flag13 flag13   220 2011-05-18 02:54 .bash_logout
-rw-r--r-- 1 flag13 flag13  3353 2011-05-18 02:54 .bashrc
-rwsr-x--- 1 flag13 level13 7321 2011-11-20 21:22 flag13
-rw-r--r-- 1 flag13 flag13   675 2011-05-18 02:54 .profile
level13@nebula:/home/flag13$ file ./flag13
./flag13: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped

Un pequeño examen superficial:

level13@nebula:/home/flag13$ ./flag13
Security failure detected. UID 1014 started us, we expect 1000
The system administrators will be notified of this violation
level13@nebula:/home/flag13$ ltrace ./flag13
__libc_start_main(0x80484c4, 1, 0xbfc396c4, 0x8048600, 0x8048670 <unfinished ...>
getuid()                                         = 1014
getuid()                                         = 1014
printf("Security failure detected. UID %"..., 1014Security failure detected. UID 1014 started us, we expect 1000
) = 63
puts("The system administrators will b"...The system administrators will be notified of this violation
)      = 61
exit(1 <unfinished ...>
+++ exited (status 1) +++

Obviamente no pretendemos ni podemos cambiar nuestro real user ID, pero sí podemos utilizar un debugger como gdb para modificar el valor devuelto por getuid() y ver como se comporta ./flag11:

level13@nebula:/home/flag13$ gdb -q ./flag13 -ex 'set disassembly-flavor intel'
Reading symbols from /home/flag13/flag13...(no debugging symbols found)...done.
(gdb) disass main
Dump of assembler code for function main:
   0x080484c4 <+0>:  push   ebp
   0x080484c5 <+1>:  mov    ebp,esp
   0x080484c7 <+3>:  push   edi
   0x080484c8 <+4>:  push   ebx
   0x080484c9 <+5>:  and    esp,0xfffffff0
   0x080484cc <+8>:  sub    esp,0x130
   0x080484d2 <+14>: mov    eax,DWORD PTR [ebp+0xc]
   0x080484d5 <+17>: mov    DWORD PTR [esp+0x1c],eax
   0x080484d9 <+21>: mov    eax,DWORD PTR [ebp+0x10]
   0x080484dc <+24>: mov    DWORD PTR [esp+0x18],eax
   0x080484e0 <+28>: mov    eax,gs:0x14
   0x080484e6 <+34>: mov    DWORD PTR [esp+0x12c],eax
   0x080484ed <+41>: xor    eax,eax
   0x080484ef <+43>: call   0x80483c0 <getuid@plt>
   0x080484f4 <+48>: cmp    eax,0x3e8
   0x080484f9 <+53>: je     0x8048531 <main+109>
   0x080484fb <+55>: call   0x80483c0 <getuid@plt>
   0x08048500 <+60>: mov    edx,0x80486d0
   0x08048505 <+65>: mov    DWORD PTR [esp+0x8],0x3e8
   0x0804850d <+73>: mov    DWORD PTR [esp+0x4],eax
   0x08048511 <+77>: mov    DWORD PTR [esp],edx
   0x08048514 <+80>: call   0x80483a0 <printf@plt>
   0x08048519 <+85>: mov    DWORD PTR [esp],0x804870c

Pondremos un breakpoint en main+48, que es la instrucción donde se compara el valor devuelto por getuid() con 0x3e8 (1000):

(gdb) break *main+48
Breakpoint 1 at 0x80484f4
(gdb) run
Starting program: /home/flag13/flag13 

Breakpoint 1, 0x080484f4 in main ()

Una vez el programa se detiene, modificamos el valor del registro EAX antes de que se efectúe la comparación:

(gdb) i r eax
eax            0x3f6 1014
(gdb) set $eax=0x3e8
(gdb) i r eax
eax            0x3e8 1000
(gdb) c
Continuing.
your token is b705702b-76a8-42b0-8844-3adabbe5ac58
[Inferior 1 (process 4403) exited with code 063]

Lo utilizamos como password para flag13:

level13@nebula:/home/flag13$ su flag13
Password: 
sh-4.2$ getflag
You have successfully executed getflag on a target account

Pwned!

Lo que sigue es una sesión de reversing con radare2 más detallada:

dpc@kernelinside:~$ r2 ./flag13
 -- This is an unregistered copy.
[0x08048410]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[0x08048410]> iz
000 0x000006d0 0x080486d0  59  60 (.rodata) ascii Security failure detected. UID %d started us, we expect %d\n
001 0x0000070c 0x0804870c  60  61 (.rodata) ascii The system administrators will be notified of this violation
002 0x0000074c 0x0804874c  36  37 (.rodata) ascii 8mjomjh8wml;bwnh8jwbbnnwi;>;88?o;9ob
003 0x00000771 0x08048771  17  18 (.rodata) ascii your token is %s\n

El string 8mjomjh8wml;bwnh8jwbbnnwi;>;88?o;9ob es sospechoso de ser el token cifrado, curiosamente tiene la misma longitud que aquellos que obtuvimos de flag04 y flag10. El comando afl no arroja más funciones locales que main(), veamos si es allí dónde se referencia el token:

[0x08048410]> axt 0x0804874c
sym.main 0x8048547 [DATA] mov edx, str.8mjomjh8wml_bwnh8jwbbnnwi___88_o_9ob

Correcto. Procedemos a hacer reversing con main():

[0x080484c4]> pdf
|           ;-- main:
/ (fcn) sym.main 304
|   sym.main (int arg_ch, int arg_10h);
|           ; var int local_8h @ ebp-0x8
|           ; arg int arg_ch @ ebp+0xc
|           ; arg int arg_10h @ ebp+0x10
|           ; var uid_t local_4h @ esp+0x4
|           ; var int local_8h_2 @ esp+0x8
|           ; var int local_18h @ esp+0x18
|           ; var int local_1ch @ esp+0x1c
|           ; var int local_28h @ esp+0x28
|           ; var int local_2ch @ esp+0x2c
|           ; var int local_12ch @ esp+0x12c
|           ; DATA XREF from entry0 (0x8048427)
|           0x080484c4      push ebp
|           0x080484c5      mov ebp, esp
|           0x080484c7      push edi
|           0x080484c8      push ebx
|           0x080484c9      and esp, 0xfffffff0
|           0x080484cc      sub esp, 0x130
|           0x080484d2      mov eax, dword [arg_ch]                    ; [0xc:4]=-1 ; 12
|           0x080484d5      mov dword [local_1ch], eax
|           0x080484d9      mov eax, dword [arg_10h]                   ; [0x10:4]=-1 ; 16
|           0x080484dc      mov dword [local_18h], eax
|           0x080484e0      mov eax, dword gs:[0x14]                   ; [0x14:4]=-1 ; 20
|           0x080484e6      mov dword [local_12ch], eax
|           0x080484ed      xor eax, eax
|           0x080484ef      call sym.imp.getuid                        ; uid_t getuid(void)
|           0x080484f4      cmp eax, 0x3e8                             ; 1000

Como ya hemos visto, se llama a getuid() y se compara con el valor 0x3e8(1000), en caso de error se imprime la violación de acceso y ./flag13 invoca a exit(). En caso contrario prosigue con el siguiente fragmento de código:

|           0x08048531      lea eax, [local_2ch]                       ; 0x2c ; ',' ; 44
|           0x08048535      mov ebx, eax
|           0x08048537      mov eax, 0
|           0x0804853c      mov edx, 0x40                              ; '@' ; 64
|           0x08048541      mov edi, ebx
|           0x08048543      mov ecx, edx
|           0x08048545      rep stosd dword es:[edi], eax

Este patrón es reconocible al instante y representa la función:

memset(buffer, 0, 0x40)

Cambiemos el nombre local_2ch por algo más descriptivo:

[0x080484c4]> afvn local_2ch buffer

Continuamos:

|           0x08048547      mov edx, str.8mjomjh8wml_bwnh8jwbbnnwi___88_o_9ob ; 0x804874c ; "8mjomjh8wml;bwnh8jwbbnnwi;>;88?o;9ob"
|           0x0804854c      lea eax, [buffer]                          ; 0x2c ; ',' ; 44
|           0x08048550      mov ecx, dword [edx]
|           0x08048552      mov dword [eax], ecx
|           0x08048554      mov ecx, dword [edx + 4]                   ; [0x4:4]=-1 ; 4
|           0x08048557      mov dword [eax + 4], ecx
|           0x0804855a      mov ecx, dword [edx + 8]                   ; [0x8:4]=-1 ; 8
|           0x0804855d      mov dword [eax + 8], ecx
|           0x08048560      mov ecx, dword [edx + 0xc]                 ; [0xc:4]=-1 ; 12
|           0x08048563      mov dword [eax + 0xc], ecx
|           0x08048566      mov ecx, dword [edx + 0x10]                ; [0x10:4]=-1 ; 16
|           0x08048569      mov dword [eax + 0x10], ecx
|           0x0804856c      mov ecx, dword [edx + 0x14]                ; [0x14:4]=-1 ; 20
|           0x0804856f      mov dword [eax + 0x14], ecx
|           0x08048572      mov ecx, dword [edx + 0x18]                ; [0x18:4]=-1 ; 24
|           0x08048575      mov dword [eax + 0x18], ecx
|           0x08048578      mov ecx, dword [edx + 0x1c]                ; [0x1c:4]=-1 ; 28
|           0x0804857b      mov dword [eax + 0x1c], ecx
|           0x0804857e      mov ecx, dword [edx + 0x20]                ; [0x20:4]=-1 ; 32
|           0x08048581      mov dword [eax + 0x20], ecx
|           0x08048584      movzx edx, byte [edx + 0x24]               ; [0x24:1]=255 ; '$' ; 36
|           0x08048588      mov byte [eax + 0x24], dl

Este código es muy simple, realiza una copia exacta en bloques de 4 bytes del string 8mjomjh8wml;bwnh8jwbbnnwi;>;88?o;9ob en buffer. A continuación un bucle importante:

|           0x0804858b      mov dword [local_28h], 0
|       ,=< 0x08048593      jmp 0x80485b4
|       |   ; CODE XREF from sym.main (0x80485c1)
|      .--> 0x08048595      lea eax, [buffer]                          ; 0x2c ; ',' ; 44
|      :|   0x08048599      add eax, dword [local_28h]
|      :|   0x0804859d      movzx eax, byte [eax]
|      :|   0x080485a0      mov edx, eax
|      :|   0x080485a2      xor edx, 0x5a
|      :|   0x080485a5      lea eax, [buffer]                          ; 0x2c ; ',' ; 44
|      :|   0x080485a9      add eax, dword [local_28h]
|      :|   0x080485ad      mov byte [eax], dl
|      :|   0x080485af      add dword [local_28h], 1
|      :|   ; CODE XREF from sym.main (0x8048593)
|      :`-> 0x080485b4      lea eax, [buffer]                          ; 0x2c ; ',' ; 44
|      :    0x080485b8      add eax, dword [local_28h]
|      :    0x080485bc      movzx eax, byte [eax]
|      :    0x080485bf      test al, al
|      `==< 0x080485c1      jne 0x8048595

Ya hemos visto algo similar en ./flag11, buffer (que ahora contiene el token cifrado), se recorre byte por byte y se cifra con la clave 0x5a ('Z') mediante una operación XOR, almacenando el resultado en el mismo buffer. Seguidamente este se imprime por pantalla:

|           0x080485c3      mov eax, str.your_token_is__s              ; 0x8048771 ; "your token is %s\n"
|           0x080485c8      lea edx, [buffer]                          ; 0x2c ; ',' ; 44
|           0x080485cc      mov dword [local_4h], edx
|           0x080485d0      mov dword [esp], eax                       ; const char *format
|           0x080485d3      call sym.imp.printf                        ; int printf(const char *format)
|           0x080485d8      mov edx, dword [local_12ch]                ; [0x12c:4]=-1 ; 300
|           0x080485df      xor edx, dword gs:[0x14]
|       ,=< 0x080485e6      je 0x80485ed
|       |   0x080485e8      call sym.imp.__stack_chk_fail              ; void __stack_chk_fail(void)
|       |   ; CODE XREF from sym.main (0x80485e6)
|       `-> 0x080485ed      lea esp, [local_8h]
|           0x080485f0      pop ebx
|           0x080485f1      pop edi
|           0x080485f2      pop ebp
\           0x080485f3      ret

Podemos hacer un script para descifrar el token, o ya que tratamos con caracteres ASCII utilizar una web que haga el trabajo por nosotros:

XOR Calculator


Pwned!

No hay comentarios:

Publicar un comentario

Protostar CTF - stack5

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