Insomni’hack 2011 – Reverse 2

Pour ce challenge de reverse engineering, il nous fallait connaître la bonne clef à envoyer au serveur pour qu’il puisse ouvrir un fichier en local et nous renvoyer le flag.

Pour commencer on va se renseigner sur le binaire.

jonathan@ArchLinux [reverse] $ file server
server: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for
GNU/Linux 2.6.15, not stripped

Ok, jusqu’ici tout va bien, c’est un binaire linux/x86-32 bits.

Ensuite nous allons créer un fichier nommé key.txt pour simuler le même scénario que celui de notre cible.
Le serveur nous renverra le contenu de key.txt si la clef qu’on lui envoie est la bonne.

jonathan@ArchLinux [reverse] $ echo "Bravo, le flag est: xxxxx" >> key.txt
jonathan@ArchLinux [reverse] $ cat key.txt
Bravo, le flag est: xxxxx
jonathan@ArchLinux [reverse] $

Pour commencer, le binaire fait appel à une fonction (net_init) qui va initialiser le socket.
La partie ici qui m’interesse est de savoir sur quel port le service écoute.

gdb$ disass net_init
Dump of assembler code for function net_init:
[...]
0x08048aea <+138>:   movw   $0x2,-0x1c(%ebp)
0x08048af0 <+144>:   mov    0x8(%ebp),%eax
0x08048af3 <+147>:   movzwl %ax,%eax
0x08048af6 <+150>:   mov    %eax,(%esp)
0x08048af9 <+153>:   call   0x80487e8 <htons@plt>
0x08048afe <+158>:   mov    %ax,-0x1a(%ebp)
0x08048b02 <+162>:   movl   $0x0,-0x18(%ebp)
0x08048b09 <+169>:   movl   $0x8,0x8(%esp)
0x08048b11 <+177>:   movl   $0x0,0x4(%esp)
[...]
gdb$ b *0x08048af9
Breakpoint 1 at 0x8048af9
gdb$ r
[Thread debugging using libthread_db enabled]
--------------------------------------------------------------------------[regs]
EAX: 000022B8  EBX: B7F9CFF4  ECX: 00000004
EDX: 00000802  ESI: 00000000  EDI: 00000000
EBP: BFFFF7B8  ESP: BFFFF770  EIP: 08048AF9
 
--------------------------------------------------------------------------[code]
=> 0x8048af9 <net_init+153>:    call   0x80487e8 <htons@plt>
   0x8048afe <net_init+158>:    mov    %ax,-0x1a(%ebp)
   0x8048b02 <net_init+162>:    movl   $0x0,-0x18(%ebp)
   0x8048b09 <net_init+169>:    movl   $0x8,0x8(%esp)
   0x8048b11 <net_init+177>:    movl   $0x0,0x4(%esp)
   0x8048b19 <net_init+185>:    lea    -0x1c(%ebp),%eax
   0x8048b1c <net_init+188>:    add    $0x8,%eax
   0x8048b1f <net_init+191>:    mov    %eax,(%esp)
--------------------------------------------------------------------------------
 
Breakpoint 1, 0x08048af9 in net_init ()
gdb$

On a donc placé un breakpoint sur l’appel de la fonction htons pour avoir l’état du registre eax.
EAX = 0x22b8 ce qui est 8888 en décimale.

Vous allez me dire, “On aurait pu très bien lancer le service, puis voir avec nmap le port qui est ouvert”.
Oui, c’est vrai ça aurait été une solution simple, mais c’est une épreuve de reverse donc on reverse :p

Maintenant, continunons pour voir ce que fait le service après réception d’une connection.
La fonction qui est intéressante c’est talk_with_client

gdb$ disass talk_with_client
Dump of assembler code for function talk_with_client:
0x08048e93 <+0>:    push   %ebp
0x08048e94 <+1>:    mov    %esp,%ebp
0x08048e96 <+3>:    sub    $0x38,%esp
0x08048e99 <+6>:    movl   $0x0,-0x10(%ebp)
0x08048ea0 <+13>:   movl   $0x0,-0x18(%ebp)
0x08048ea7 <+20>:   movl   $0x0,-0x1c(%ebp)
0x08048eae <+27>:   mov    0x8(%ebp),%eax
0x08048eb1 <+30>:   mov    (%eax),%eax
0x08048eb3 <+32>:   mov    %eax,-0xc(%ebp)
0x08048eb6 <+35>:   movl   $0x9,(%esp)
0x08048ebd <+42>:   call   0x80488b8 <malloc@plt>
0x08048ec2 <+47>:   mov    %eax,-0x1c(%ebp)
0x08048ec5 <+50>:   movl   $0x9,0x8(%esp)
0x08048ecd <+58>:   mov    -0x1c(%ebp),%eax
0x08048ed0 <+61>:   mov    %eax,0x4(%esp)
0x08048ed4 <+65>:   mov    -0xc(%ebp),%eax
0x08048ed7 <+68>:   mov    %eax,(%esp)
0x08048eda <+71>:   call   0x80487f8 <read@plt>
0x08048edf <+76>:   mov    %eax,-0x14(%ebp)
0x08048ee2 <+79>:   cmpl   $0x9,-0x14(%ebp)
0x08048ee6 <+83>:   je     0x8048efe <talk_with_client+107>
0x08048ee8 <+85>:   mov    $0x80490cf,%eax
0x08048eed <+90>:   mov    -0x14(%ebp),%edx
0x08048ef0 <+93>:   mov    %edx,0x4(%esp)
0x08048ef4 <+97>:   mov    %eax,(%esp)
0x08048ef7 <+100>:  call   0x8048878 <printf@plt>
0x08048efc <+105>:  jmp    0x8048f65 <talk_with_client+210>
0x08048efe <+107>:  mov    -0x1c(%ebp),%eax
0x08048f01 <+110>:  mov    %eax,(%esp)
0x08048f04 <+113>:  call   0x8048e27 <validate_input>
0x08048f09 <+118>:  mov    %eax,-0x10(%ebp)
0x08048f0c <+121>:  cmpl   $0x0,-0x10(%ebp)
0x08048f10 <+125>:  je     0x8048f4f <talk_with_client+188>
0x08048f12 <+127>:  call   0x8048d58 <read_key>
0x08048f17 <+132>:  mov    %eax,-0x18(%ebp)
0x08048f1a <+135>:  cmpl   $0x0,-0x18(%ebp)
0x08048f1e <+139>:  je     0x8048f4f <talk_with_client+188>
0x08048f20 <+141>:  mov    -0x18(%ebp),%eax
0x08048f23 <+144>:  mov    %eax,(%esp)
0x08048f26 <+147>:  call   0x8048858 <strlen@plt>
0x08048f2b <+152>:  add    $0x1,%eax
0x08048f2e <+155>:  mov    %eax,0x8(%esp)
0x08048f32 <+159>:  mov    -0x18(%ebp),%eax
0x08048f35 <+162>:  mov    %eax,0x4(%esp)
0x08048f39 <+166>:  mov    -0xc(%ebp),%eax
0x08048f3c <+169>:  mov    %eax,(%esp)
0x08048f3f <+172>:  call   0x80487a8 <write@plt>
0x08048f44 <+177>:  mov    -0x18(%ebp),%eax
0x08048f47 <+180>:  mov    %eax,(%esp)
0x08048f4a <+183>:  call   0x8048808 <free@plt>
0x08048f4f <+188>:  mov    -0x1c(%ebp),%eax
0x08048f52 <+191>:  mov    %eax,(%esp)
0x08048f55 <+194>:  call   0x8048808 <free@plt>
0x08048f5a <+199>:  mov    -0xc(%ebp),%eax
0x08048f5d <+202>:  mov    %eax,(%esp)
0x08048f60 <+205>:  call   0x80488a8 <close@plt>
0x08048f65 <+210>:  leave
0x08048f66 <+211>:  ret
End of assembler dump.
gdb$

Cette fonction fait appel à deux fonctions très intéressantes:

- validate_input
- read_key

Clairement, c’est la fonction validate_input qu’il va falloir décortiquer.

gdb$ disass validate_input
Dump of assembler code for function validate_input:
0x08048e27 <+0>:    push   %ebp
0x08048e28 <+1>:    mov    %esp,%ebp
0x08048e2a <+3>:    sub    $0x10,%esp
0x08048e2d <+6>:    mov    0x8(%ebp),%eax
0x08048e30 <+9>:    movzbl (%eax),%eax
0x08048e33 <+12>:   cmp    $0x53,%al
0x08048e35 <+14>:   je     0x8048e3e <validate_input+23>
0x08048e37 <+16>:   mov    $0x0,%eax
0x08048e3c <+21>:   jmp    0x8048e91 <validate_input+106>
0x08048e3e <+23>:   movl   $0x0,-0x4(%ebp)
0x08048e45 <+30>:   jmp    0x8048e86 <validate_input+95>
0x08048e47 <+32>:   mov    -0x4(%ebp),%eax
0x08048e4a <+35>:   add    0x8(%ebp),%eax
0x08048e4d <+38>:   movzbl (%eax),%eax
0x08048e50 <+41>:   movsbl %al,%eax
0x08048e53 <+44>:   movzbl %al,%ecx
0x08048e56 <+47>:   mov    -0x4(%ebp),%eax
0x08048e59 <+50>:   add    $0x1,%eax
0x08048e5c <+53>:   add    0x8(%ebp),%eax
0x08048e5f <+56>:   movzbl (%eax),%eax
0x08048e62 <+59>:   movsbl %al,%edx
0x08048e65 <+62>:   mov    -0x4(%ebp),%eax
0x08048e68 <+65>:   add    $0x2,%eax
0x08048e6b <+68>:   add    0x8(%ebp),%eax
0x08048e6e <+71>:   movzbl (%eax),%eax
0x08048e71 <+74>:   movsbl %al,%eax
0x08048e74 <+77>:   lea    (%edx,%eax,1),%eax
0x08048e77 <+80>:   cmp    %eax,%ecx
0x08048e79 <+82>:   je     0x8048e82 <validate_input+91>
0x08048e7b <+84>:   mov    $0x0,%eax
0x08048e80 <+89>:   jmp    0x8048e91 <validate_input+106>
0x08048e82 <+91>:   addl   $0x3,-0x4(%ebp)
0x08048e86 <+95>:   cmpl   $0x8,-0x4(%ebp)
0x08048e8a <+99>:   jle    0x8048e47 <validate_input+32>
0x08048e8c <+101>:  mov    $0x1,%eax
0x08048e91 <+106>:  leave
0x08048e92 <+107>:  ret
End of assembler dump.
gdb$

Commençons par analyser tout ça.

0x08048e2d <+6>:    mov    0x8(%ebp),%eax
0x08048e30 <+9>:    movzbl (%eax),%eax
0x08048e33 <+12>:   cmp    $0x53,%al

Ici, le programme va placer l’adresse de notre argument dans eax, puis va prendre le premier caractère de cette adresse pour le mettre dans eax.

Ensuite il fait une comparaison du caractère avec 0×53 soit le caractère ‘S’.

0x08048e37 <+16>:    mov    $0x0,%eax
0x08048e3c <+21>:    jmp    0x8048e91 <validate_input+106>

Si ça n’est pas le cas il place 0 dans eax puis jump sur leave ret.

0x08048e3e <+23>:    movl   $0x0,-0x4(%ebp)
0x08048e45 <+30>:    jmp    0x8048e86 <validate_input+95>

Ensuite le programme initialise une variable à 0 qu’on va appeler i.
Donc i = 0, puis il jump en validate_input+95

0x08048e86 <+95>:    cmpl   $0x8,-0x4(%ebp)
0x08048e8a <+99>:    jle    0x8048e47 <validate_input+32>

Il compare si i = 8 on peut donc transformer ça en while(i != 8)
Si i n’est pas égale à 8 on saute en validate_input+32

Donc, déjà dès maintenant on peut savoir que notre chaîne doit faire 9 caractères.

0x08048e47 <+32>:    mov    -0x4(%ebp),%eax
0x08048e4a <+35>:    add    0x8(%ebp),%eax
0x08048e4d <+38>:    movzbl (%eax),%eax
0x08048e50 <+41>:    movsbl %al,%eax
0x08048e53 <+44>:    movzbl %al,%ecx
0x08048e56 <+47>:    mov    -0x4(%ebp),%eax
0x08048e59 <+50>:    add    $0x1,%eax
0x08048e5c <+53>:    add    0x8(%ebp),%eax
0x08048e5f <+56>:    movzbl (%eax),%eax
0x08048e62 <+59>:    movsbl %al,%edx
0x08048e65 <+62>:    mov    -0x4(%ebp),%eax
0x08048e68 <+65>:    add    $0x2,%eax
0x08048e6b <+68>:    add    0x8(%ebp),%eax
0x08048e6e <+71>:    movzbl (%eax),%eax
0x08048e71 <+74>:    movsbl %al,%eax
0x08048e74 <+77>:    lea    (%edx,%eax,1),%eax
0x08048e77 <+80>:    cmp    %eax,%ecx
0x08048e79 <+82>:    je     0x8048e82 <validate_input+91>
0x08048e7b <+84>:    mov    $0x0,%eax
0x08048e80 <+89>:    jmp    0x8048e91 <validate_input+106>
0x08048e82 <+91>:    addl   $0x3,-0x4(%ebp)
0x08048e86 <+95>:    cmpl   $0x8,-0x4(%ebp)
0x08048e8a <+99>:    jle    0x8048e47 <validate_input+32>

Sur cette section ce qui se passe c’est qu’il additionne la valeur du deuxième caractère avec le
troisième puis compare si le tout est égale à 0×53 (‘S’) et il repète ce scénario tant que i != 8

Donc notre clef doit être constituée comme ceci: “S!2S!2S!2″

‘!’ + ’2′ = 0×21 + 0×32 = 0×53 = S

Bon, maintenant pour voir un peu comment tout cela se passe dans le programme, on va mettre un breakpoint
puis tout faire step by step :)

Je pose mon breakpoint en validate_input + 6

gdb$ b *0x08048e2d
Breakpoint 1 at 0x8048e2d
gdb$ r
[Thread 0xb7e53b70 (LWP 30300) exited]
[New Thread 0xb7e53b70 (LWP 30308)]
[Switching to Thread 0xb7e53b70 (LWP 30308)]
--------------------------------------------------------------------------[regs]
EAX: 0804C0B0  EBX: B7FB6FF4  ECX: 00000000
EDX: 00000000  ESI: B7E53B70  EDI: 003D0F00
EBP: B7E53358  ESP: B7E53348  EIP: 08048E2D
 
--------------------------------------------------------------------------[code]
=> 0x8048e2d <validate_input+6>:    mov    0x8(%ebp),%eax
   0x8048e30 <validate_input+9>:    movzbl (%eax),%eax
   0x8048e33 <validate_input+12>:   cmp    $0x53,%al
   0x8048e35 <validate_input+14>:   je     0x8048e3e <validate_input+23>
   0x8048e37 <validate_input+16>:   mov    $0x0,%eax
   0x8048e3c <validate_input+21>:   jmp    0x8048e91 <validate_input+106>
   0x8048e3e <validate_input+23>:   movl   $0x0,-0x4(%ebp)
   0x8048e45 <validate_input+30>:   jmp    0x8048e86 <validate_input+95>
--------------------------------------------------------------------------------
 
Breakpoint 1, 0x08048e2d in validate_input ()
gdb$ x/s 0x0804C0B0
0x804c0b0:     "S!2S!2S!2"
gdb$

On a bien notre chaîne.

gdb$
--------------------------------------------------------------------------[regs]
EAX: 00000053  EBX: B7FB6FF4  ECX: 00000000
EDX: 00000000  ESI: B7E53B70  EDI: 003D0F00
EBP: B7E53358  ESP: B7E53348  EIP: 08048E33
 
--------------------------------------------------------------------------[code]
=> 0x8048e33 <validate_input+12>:    cmp    $0x53,%al
   0x8048e35 <validate_input+14>:    je     0x8048e3e <validate_input+23>
   0x8048e37 <validate_input+16>:    mov    $0x0,%eax
   0x8048e3c <validate_input+21>:    jmp    0x8048e91 <validate_input+106>
   0x8048e3e <validate_input+23>:    movl   $0x0,-0x4(%ebp)
   0x8048e45 <validate_input+30>:    jmp    0x8048e86 <validate_input+95>
   0x8048e47 <validate_input+32>:    mov    -0x4(%ebp),%eax
   0x8048e4a <validate_input+35>:    add    0x8(%ebp),%eax
--------------------------------------------------------------------------------
0x08048e33 in validate_input ()

Notre registre eax est bien à 0×53 donc la comparaison est bonne.

gdb$
--------------------------------------------------------------------------[regs]
EAX: 00000053  EBX: B7FB6FF4  ECX: 00000000
EDX: 00000000  ESI: B7E53B70  EDI: 003D0F00
EBP: B7E53358  ESP: B7E53348  EIP: 08048E86
 
--------------------------------------------------------------------------[code]
=> 0x8048e86 <validate_input+95>:    cmpl   $0x8,-0x4(%ebp)
   0x8048e8a <validate_input+99>:    jle    0x8048e47 <validate_input+32>
   0x8048e8c <validate_input+101>:   mov    $0x1,%eax
   0x8048e91 <validate_input+106>:   leave
   0x8048e92 <validate_input+107>:   ret
   0x8048e93 <talk_with_client>:     push   %ebp
   0x8048e94 <talk_with_client+1>:   mov    %esp,%ebp
   0x8048e96 <talk_with_client+3>:   sub    $0x38,%esp
--------------------------------------------------------------------------------
0x08048e86 in validate_input ()
gdb$ x/x $ebp - 0x4
0xb7e53354:    0x00

i n’est pas égale à 8 donc il jump en validate_input+32.

gdb$
--------------------------------------------------------------------------[regs]
EAX: 00000021  EBX: B7FB6FF4  ECX: 00000053
EDX: 00000000  ESI: B7E53B70  EDI: 003D0F00
EBP: B7E53358  ESP: B7E53348  EIP: 08048E62
 
--------------------------------------------------------------------------[code]
=> 0x8048e62 <validate_input+59>:    movsbl %al,%edx
   0x8048e65 <validate_input+62>:    mov    -0x4(%ebp),%eax
   0x8048e68 <validate_input+65>:    add    $0x2,%eax
   0x8048e6b <validate_input+68>:    add    0x8(%ebp),%eax
   0x8048e6e <validate_input+71>:    movzbl (%eax),%eax
   0x8048e71 <validate_input+74>:    movsbl %al,%eax
   0x8048e74 <validate_input+77>:    lea    (%edx,%eax,1),%eax
   0x8048e77 <validate_input+80>:    cmp    %eax,%ecx
--------------------------------------------------------------------------------
0x08048e62 in validate_input ()

ecx égale l’ancien eax, puis il place notre deuxième caractère dans eax soit ‘!’

gdb$
--------------------------------------------------------------------------[regs]
EAX: 00000032  EBX: B7FB6FF4  ECX: 00000053
EDX: 00000021  ESI: B7E53B70  EDI: 003D0F00
EBP: B7E53358  ESP: B7E53348  EIP: 08048E71
 
--------------------------------------------------------------------------[code]
=> 0x8048e71 <validate_input+74>:    movsbl %al,%eax
   0x8048e74 <validate_input+77>:    lea    (%edx,%eax,1),%eax
   0x8048e77 <validate_input+80>:    cmp    %eax,%ecx
   0x8048e79 <validate_input+82>:    je     0x8048e82 <validate_input+91>
   0x8048e7b <validate_input+84>:    mov    $0x0,%eax
   0x8048e80 <validate_input+89>:    jmp    0x8048e91 <validate_input+106>
   0x8048e82 <validate_input+91>:    addl   $0x3,-0x4(%ebp)
   0x8048e86 <validate_input+95>:    cmpl   $0x8,-0x4(%ebp)
--------------------------------------------------------------------------------
0x08048e71 in validate_input ()

Il place ensuite eax dans edx, puis place notre troisième caractère dans eax soit ’2′

gdb$
--------------------------------------------------------------------------[regs]
EAX: 00000053  EBX: B7FB6FF4  ECX: 00000053
EDX: 00000021  ESI: B7E53B70  EDI: 003D0F00
EBP: B7E53358  ESP: B7E53348  EIP: 08048E77
 
--------------------------------------------------------------------------[code]
=> 0x8048e77 <validate_input+80>:    cmp    %eax,%ecx
   0x8048e79 <validate_input+82>:    je     0x8048e82 <validate_input+91>
   0x8048e7b <validate_input+84>:    mov    $0x0,%eax
   0x8048e80 <validate_input+89>:    jmp    0x8048e91 <validate_input+106>
   0x8048e82 <validate_input+91>:    addl   $0x3,-0x4(%ebp)
   0x8048e86 <validate_input+95>:    cmpl   $0x8,-0x4(%ebp)
   0x8048e8a <validate_input+99>:    jle    0x8048e47 <validate_input+32>
   0x8048e8c <validate_input+101>:   mov    $0x1,%eax
--------------------------------------------------------------------------------
0x08048e77 in validate_input ()

Puis il additionne eax et edx et il place le résultat dans eax et ensuite il compare eax et ecx,
donc il teste si l’addition est bien égale à 0×53.

Et il répète ça tant que i n’est pas égale à 8, si tout se passe bien il read_key.

Voici le sript python qui envoie la clef au serveur sur le port 8888

jonathan@ArchLinux [write-up] $ cat connection.py
#!/usr/bin/env python2
 
import socket
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect(('192.168.0.3', 8888))
s.send("S!2S!2S!2")
answer = s.recv(1024)
print answer
s.close
jonathan@ArchLinux [write-up] $ ./connection.py
Bravo, le flag est: xxxxx
 
jonathan@ArchLinux [write-up] $

Et voilà le serveur nous renvoie bien la clef :)

 

Djo & Agix,

Tags: ctf

2 Comments

Leave a Reply

XHTML: You can use these tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">