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
























[...] $ file server server: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically [...] Nibbles microblog This entry was posted in Sécurité Informatique and tagged 2011, Insomni’hack, [...]
[...] Some solutions of challenges from Junod itself and other competitors (severals challenges here, the GPGPU reverse and the reverse 2) [...]