636 lines
22 KiB
Markdown
636 lines
22 KiB
Markdown
---
|
|
title: "Sécurité logicielle : TD 9 Hackme"
|
|
date: 2023-04-14
|
|
tags: ["Assembleur", "x86"]
|
|
categories: ["Sécurité logicielle", "TD"]
|
|
author:
|
|
- Yorick Barbanneau
|
|
---
|
|
|
|
## Level 0
|
|
|
|
### Première exécution du programme
|
|
|
|
Le programme demande à l'utilisateur une saisie, lorsque je rentre un texte, il
|
|
répond `Nope`:
|
|
|
|
```
|
|
./hackme
|
|
This is level 0, welcome! What do you have to say?
|
|
bonjour
|
|
Nope!
|
|
```
|
|
|
|
Il doit donc s'attendre à un mot de passe bien précis.
|
|
|
|
|
|
### Avec strace
|
|
|
|
Lors de l'exécution du programme à l'aide de `strace`, nous pouvons d'abord voir
|
|
-- après les projections mémoire avec `mmap` et d'autres éléments -- des appels
|
|
systèmes `write` pour afficher le message invitant à la saisie. Cet affichage
|
|
est découpé en 4 parties (3 de 16 octets et une de 3)
|
|
|
|
Vient ensuite un appel système `read` pour lire la saisie sur l'entrée standard
|
|
puis un `write()` de 6 octets pour écrire `Nope!` sur la sortie standard.
|
|
|
|
Enfin un appel système `exit_group` est lancé avec 1 en paramètre (terminer tous
|
|
les threads du processus). La commande `echo $?` lancé dans le terminal ayant
|
|
exécuté `hack` confirme que le processus a quitté avec 1 comme code de
|
|
retour.
|
|
|
|
### avec strings
|
|
|
|
L'exécution de `strings hackme` montre des choses intéressantes. On y voit des
|
|
traces des fonctions `wprintf` (qui pourrait être utile pour la suite),
|
|
`strlen`. `getline`. On voit aussi tout un tas d'autres chaines qui semble être
|
|
des mots de passe:
|
|
|
|
```
|
|
[...]
|
|
GLIBC_2.2
|
|
GLIBC_2.0
|
|
__gmon_start__
|
|
ZYh<
|
|
ZYh0
|
|
8Eureu%
|
|
UWVS
|
|
[^_]
|
|
R`jw}]nj
|
|
ezsf
|
|
;*2$"(
|
|
QnuuxMjm
|
|
IAmSuperSecure
|
|
GCC: (Debian 8.3.0-6) 8.3.0
|
|
[...]
|
|
```
|
|
|
|
`IAmSuperSecure` parait relativement intéressant. Mais à ce stade rien n'est
|
|
sûr. Mais juste comme ça essayons tout de même un `lstrace` sur notre programme:
|
|
|
|
```
|
|
ltrace ./hackme
|
|
__libc_start_main(0x80490a0, 1, 0xffb45f24, 0x8049590 <unfinished ...>
|
|
wprintf(0x804a064, 0xf7f988cb, 0xf7c1ca2f, 0xf7f804a0This is level 0, welcome! What do you have to say?
|
|
) = 51
|
|
getline(0xffb45e18, 0xffb45e1c, 0xf7e1d620, 0xf7c76ca5MyBadPassword
|
|
) = 14
|
|
strcmp("MyBadPassword", "IAmSuperSecure") = 1
|
|
wprintf(0x804a048, 0xf7fbca40, 0, 0x80492c2Nope!
|
|
) = 6
|
|
exit(1 <no return ...>
|
|
+++ exited (status 1) +++
|
|
```
|
|
|
|
La sortie de `ltrace` une fois la saisie effectuée, montre qu'elle est comparée
|
|
avec `IAmSuperSecure`, un essai le confirme. Comme dirait Bernard dans <u>Day of
|
|
the Tentacle</u>: *"This is all too easy!"* :
|
|
|
|
```
|
|
./hackme
|
|
This is level 0, welcome! What do you have to say?
|
|
IAmSuperSecure
|
|
Ok, that was easy!
|
|
```
|
|
|
|
## Level 1
|
|
|
|
Une fois pframe installé et le point d'arrêt positionné sur `strcmp@plt`, la
|
|
*pile d'appel* montre que la fonction appelante est `r1()`. Une fois passé dans
|
|
son contexte, analysons le code assembleur:
|
|
|
|
```
|
|
[...]
|
|
(gdb) bt
|
|
#0 0x08049030 in strcmp@plt ()
|
|
#1 0x08049372 in r1 ()
|
|
#2 0x080490c8 in main ()
|
|
(gdb) frame 1
|
|
#1 0x08049372 in r1 ()
|
|
(gdb) disass
|
|
Dump of assembler code for function r1:
|
|
0x08049350 <+0>: push %ebx
|
|
0x08049351 <+1>: sub $0x14,%esp
|
|
0x08049354 <+4>: push $0x804a184
|
|
0x08049359 <+9>: call 0x8049060 <wprintf@plt>
|
|
0x0804935e <+14>: call 0x8049220 <r>
|
|
0x08049363 <+19>: pop %edx
|
|
0x08049364 <+20>: pop %ecx
|
|
0x08049365 <+21>: push $0x804d030 <- voici l'adresse de la chaine source
|
|
0x0804936a <+26>: push %eax <- voici l'adresse de ma saisie
|
|
0x0804936b <+27>: mov %eax,%ebx
|
|
0x0804936d <+29>: call 0x8049030 <strcmp@plt>
|
|
=> 0x08049372 <+34>: add $0x10,%esp
|
|
0x08049375 <+37>: test %eax,%eax
|
|
```
|
|
|
|
Nous pouvons voir que les adresses vers les chaines comparées par `strcmp()`
|
|
sont positionnées sur la pile comme le montre les commentaires ci-dessus.
|
|
Affichons le contenu pointés par celles-ci avec `gdb`:
|
|
|
|
```
|
|
(gdb) p (char*)($eax)
|
|
$1 = 0x804f580 "ThisIsMyTest
|
|
(gdb) p (char*)0x804d030
|
|
$2 = 0x804d030 <p> "HelloDad"
|
|
```
|
|
|
|
Nous avons notre mot de passe `HelloDad`. Mais cette chaine ne figure pas dans
|
|
la liste des chaines données par la commande `strings`, elle est donc obscurcie.
|
|
Essayons donc de comprendre comment. Recommençons l'exécution avec un `watch`
|
|
la chaine contenue sur le programme:
|
|
|
|
```
|
|
(gdb) wa * (char*)0x804d030
|
|
Hardware watchpoint 1: * (char*)0x804d030
|
|
[...]
|
|
|
|
Hardware watchpoint 1: *(char*)0x804d030
|
|
|
|
Old value = 81 'Q'
|
|
New value = 72 'H'
|
|
0x08049339 in z ()
|
|
(gdb) bt
|
|
#0 0x08049339 in z ()
|
|
#1 0x080490c3 in main ()
|
|
```
|
|
|
|
La fonction permettant le déchiffrage est `z()`, passons à son désassemblage:
|
|
|
|
```
|
|
...
|
|
(gdb) disass
|
|
Dump of assembler code for function z:
|
|
0x08049320 <+0>: mov 0x4(%esp),%edx
|
|
0x08049324 <+4>: movzbl (%edx),%eax
|
|
0x08049327 <+7>: test %al,%al
|
|
0x08049329 <+9>: je 0x8049340 <z+32>
|
|
0x0804932b <+11>: lea 0x0(%esi,%eiz,1),%esi
|
|
0x0804932f <+15>: nop
|
|
|
|
0x08049330 <+16>: sub $0x9,%eax ; retire 0x9 à %eax
|
|
|
|
0x08049333 <+19>: add $0x1,%edx ; incrémente %edx
|
|
|
|
0x08049336 <+22>: mov %al,-0x1(%edx) ; remets le caractère déchiffré
|
|
; dans sa chaine
|
|
|
|
=> 0x08049339 <+25>: movzbl (%edx),%eax ; copie le caractère courant
|
|
; dans %eax (bits 1 à 8)
|
|
|
|
0x0804933c <+28>: test %al,%al ; boucle tant que le caractère
|
|
0x0804933e <+30>: jne 0x8049330 <z+16> ; \0 n'est pas trouvé
|
|
```
|
|
|
|
En observant le contenu de cette fonction, on comprend alors que le
|
|
déchiffrement du mot de passe du niveau 2 se fait en retirant `0x9` à chaque
|
|
caractère de la chaine contenue dans le programme.
|
|
|
|
## Level 2
|
|
|
|
C'est reparti pour un tour! Nouvelle exécution du programme avec *gdb* en
|
|
plaçant un point d'arrêt sur `srtcmp@plt` et on arrive jusqu'au niveau 2:
|
|
|
|
```
|
|
On to level 2! So what do you want?
|
|
ThisIsTest
|
|
|
|
Breakpoint 1, 0x08049030 in strcmp@plt ()
|
|
(gdb) bt
|
|
#0 0x08049030 in strcmp@plt ()
|
|
#1 0x080493ca in r2 ()
|
|
#2 0x080490cd in main ()
|
|
```
|
|
|
|
Cette fois-ci c'est la fonction `r2()` qui s'occupe du "déchiffrement", voici
|
|
son code désassemblé :
|
|
|
|
```
|
|
(gdb) frame 1
|
|
#1 0x080493ca in r2 ()
|
|
(gdb) disass
|
|
Dump of assembler code for function r2:
|
|
0x080493a0 <+0> push %ebx
|
|
0x080493a1 <+1>: sub $0x14,%esp
|
|
0x080493a4 <+4>: push $0x804a280
|
|
0x080493a9 <+9>: call 0x8049060 <wprintf@plt>
|
|
0x080493ae <+14>: call 0x8049220 <r>
|
|
0x080493b3 <+19>: mov %eax,%ebx
|
|
0x080493b5 <+21>: mov %eax,(%esp)
|
|
0x080493b8 <+24>: call 0x80492f0 <x1>
|
|
0x080493bd <+29>: pop %eax
|
|
0x080493be <+30>: pop %edx
|
|
0x080493bf <+31>: push $0x804adec <- adresse de la chaine
|
|
0x080493c4 <+36>: push %ebx <- adresse de notre saisie
|
|
0x080493c5 <+37>: call 0x8049030 <strcmp@plt>
|
|
=> 0x080493ca <+42>: add $0x10,%esp
|
|
0x080493cd <+45>: test %eax,%eax
|
|
0x080493cf <+47>: jne 0x80493eb <r2+75>
|
|
0x080493d1 <+49>: sub $0xc,%esp
|
|
0x080493d4 <+52>: push $0x804a318
|
|
0x080493d9 <+57>: call 0x8049060 <wprintf@plt>
|
|
0x080493de <+62>: mov %ebx,(%esp)
|
|
0x080493e1 <+65>: call 0x8049050 <free@plt>
|
|
0x080493e6 <+70>: add $0x18,%esp
|
|
0x080493e9 <+73>: pop %ebx
|
|
0x080493ea <+74>: ret
|
|
0x080493eb <+75>: call 0x8049280 <f>
|
|
End of assembler dump.
|
|
```
|
|
|
|
Profitons-en pour afficher le contenu de nos deux chaines:
|
|
|
|
```
|
|
(gdb) p (char*)0x804adec
|
|
$10 = 0x804adec "R`jw}]nj"
|
|
(gdb) p (char*)$ebx
|
|
$11 = 0x804f5c0 "]qr|R|]n|}"
|
|
```
|
|
|
|
Code de `x1()` qui "chiffre" la saisie utilisateur du troisième mot de passe.
|
|
C'est le chiffrement inverse de celui vu au niveau 1, ici on ajoute `0x9` à
|
|
chaque caractère.
|
|
|
|
Pour déchiffrer le mot de passe écrit dans le programme, j'ai écris un script
|
|
Python :
|
|
|
|
```python
|
|
cyphertext = "R`jw}]nj"
|
|
cleartext = ""
|
|
for l in range(len(cyphertext)):
|
|
c += (chr(ord(cyphertext[l])-0x9))
|
|
print(cleartext)
|
|
```
|
|
|
|
ce qui nous donne `IWantTea`
|
|
|
|
### Level 3
|
|
|
|
Après avoir lancé le programme et atteint la saisie du niveau 3, `Ctrl+c` envoi
|
|
le signal `SIGINT` au programme, donne la main à l'invite de commande. Voici le
|
|
résultat de la commande `bt`:
|
|
|
|
```
|
|
[...]
|
|
#5 0x08049248 in r ()
|
|
#6 0x08049403 in wut ()
|
|
#7 0x080490d2 in main ()
|
|
```
|
|
|
|
`r()` se se charge de la saisie, mais que fait `wut()`? Intéressons nous
|
|
à cette fonction d'abord passant dans sa frame puis en la désassemblant:
|
|
|
|
```
|
|
(gdb) frame 6
|
|
#6 0x08049403 in wut ()
|
|
(gdb) disass
|
|
Dump of assembler code for function wut:
|
|
0x080493f0 <+0>: push %ebx
|
|
0x080493f1 <+1>: sub $0x14,%esp
|
|
0x080493f4 <+4>: push $0x804a37c
|
|
0x080493f9 <+9>: call 0x8049060 <wprintf@plt>
|
|
0x080493fe <+14>: call 0x8049220 <r>
|
|
=> 0x08049403 <+19>: add $0x10,%esp
|
|
0x08049406 <+22>: cmpl $0x65727545,(%eax) <- intéressant
|
|
0x0804940c <+28>: jne 0x8049433 <wut+67>
|
|
0x0804940e <+30>: cmpl $0x21614b,0x4(%eax) <- et encore interessant
|
|
0x08049415 <+37>: mov %eax,%ebx
|
|
0x08049417 <+39>: jne 0x8049433 <wut+67>
|
|
0x08049419 <+41>: sub $0xc,%esp
|
|
0x0804941c <+44>: push $0x804a424
|
|
0x08049421 <+49>: call 0x8049060 <wprintf@plt>
|
|
0x08049426 <+54>: mov %ebx,(%esp)
|
|
0x08049429 <+57>: call 0x8049050 <free@plt>
|
|
0x0804942e <+62>: add $0x18,%esp
|
|
0x08049431 <+65>: pop %ebx
|
|
0x08049432 <+66>: ret
|
|
0x08049433 <+67>: call 0x8049280 <f>
|
|
End of assembler dump.
|
|
```
|
|
|
|
Voici deux comparaisons intéressantes. La première s'effectue sur les 4 octets à
|
|
l'adresse contenue dans `%eax`. La suivante sur le contenu à l'adresse de
|
|
`%eax + 0x4`.
|
|
|
|
Un script Python permet encore une fois de transformer ces deux valeurs en
|
|
texte, le voici:
|
|
|
|
```python
|
|
#!/bin/env python3
|
|
|
|
hextext = "6572754521614b"
|
|
finaltext = ""
|
|
cleartext = ""
|
|
for i in range(0, len(hextext) - 1, 2):
|
|
c = '{}{}'.format(hextext[i], hextext[i+1])
|
|
cleartext += (chr(int(c, 16)))
|
|
cur_size=0
|
|
bits_processed=0
|
|
for i in range(0, len(cleartext) - 1, 4):
|
|
if (len(cleartext) - 4 * bits_processed) > 4:
|
|
cur_size = 4
|
|
else:
|
|
cur_size = len(cleartext) - 4 * bits_processed
|
|
for j in range(i+cur_size,i,-1):
|
|
finaltext += cleartext[j-1]
|
|
bits_processed+=1
|
|
print('Level 3 text: {}'.format(finaltext))
|
|
```
|
|
|
|
La seconde série de boucles de ce script sert à remettre les octets dans le bon
|
|
ordre. En effet les données sont mises sur la pile donc chaque élément de 4
|
|
octets doit être inversé.
|
|
|
|
Le mot de passe est `EureKa!`
|
|
|
|
## Level 4
|
|
|
|
Comme pour le niveau 3, il faut exécuter notre programme dans gdb, utiliser la
|
|
technique du `ctrl+c` puis `bt`:
|
|
|
|
```
|
|
...
|
|
#5 0x08049248 in r ()
|
|
#6 0x080494e3 in aa ()
|
|
#7 0x080490d7 in main ()
|
|
```
|
|
|
|
C'est la fonction `aa()` qui appelle `r()` (fonction de saisie), intéressons
|
|
nous à elle en la désassemblant :
|
|
|
|
```
|
|
(gdb) frame 6
|
|
[...]
|
|
(gdb) disass
|
|
Dump of assembler code for function aa:
|
|
0x080494d0 <+0>: push %ebx
|
|
0x080494d1 <+1>: sub $0x14,%esp
|
|
0x080494d4 <+4>: push $0x804a474
|
|
0x080494d9 <+9>: call 0x8049060 <wprintf@plt>
|
|
0x080494de <+14>: call 0x8049220 <r>
|
|
=> 0x080494e3 <+19>: mov %eax,(%esp)
|
|
0x080494e6 <+22>: mov %eax,%ebx
|
|
0x080494e8 <+24>: call 0x8049080 <strlen@plt>
|
|
0x080494ed <+29>: add $0x10,%esp
|
|
0x080494f0 <+32>: cmp $0x4,%eax
|
|
0x080494f3 <+35>: jne 0x8049524 <aa+84>
|
|
0x080494f5 <+37>: sub $0x8,%esp
|
|
0x080494f8 <+40>: push $0x804adf5
|
|
0x080494fd <+45>: push %ebx
|
|
0x080494fe <+46>: call 0x8049470 <bb> <- cet appel est intéressant non?
|
|
0x08049503 <+51>: add $0x10,%esp
|
|
0x08049506 <+54>: test %eax,%eax
|
|
0x08049508 <+56>: je 0x8049524 <aa+84>
|
|
0x0804950a <+58>: sub $0xc,%esp
|
|
0x0804950d <+61>: push $0x804a4ec
|
|
0x08049512 <+66>: call 0x8049060 <wprintf@plt>
|
|
0x08049517 <+71>: mov %ebx,(%esp)
|
|
0x0804951a <+74>: call 0x8049050 <free@plt>
|
|
0x0804951f <+79>: add $0x18,%esp
|
|
0x08049522 <+82>: pop %ebx
|
|
0x08049523 <+83>: ret
|
|
0x08049524 <+84>: call 0x8049280 <f>
|
|
End of assembler dump.
|
|
```
|
|
|
|
Cette fonction appelle une autre : `bb()`. C'est elle qui semble se charger de
|
|
la vérification de la saisie. Avant de l'étudier, voyons comment `aa()` met les
|
|
éléments en place avant de l'appeler :
|
|
|
|
* mets l'adresse vers la zone mémoire contenant la saisie utilisateur dans
|
|
l'adresse contenue dans `%esp`
|
|
* puis copie cette adresse dans %ebx, afin de préparer l'appel à `strlen()`
|
|
* cet appel positionnera le résultat dans `%eax` à partir de la chaine dans
|
|
`%ebx`
|
|
* `%esp` est incrémenté de 16 (*0x10*).
|
|
* Ensuite le résultat de `strlen()` est comparé à 4, en cas de non égalité, le
|
|
programme branche sur `f()` qui met fin à l'exécution.. Nous pouvons donc en
|
|
déduite que notre saisie doit être de 4 caractères exactement.
|
|
* La pile est ensuite décrémentée de 8
|
|
* *0x804adf5* est ensuite positionné sur la pile, Il semble que se soit une
|
|
adresse. Le contenu de cette espace mémoire est *0x66737a65*.
|
|
* `%ebx` est ensuite poussé sur la pile
|
|
* `bb()` est appelée, c'est cette fonction qui se charge du 'déchiffrement'
|
|
|
|
### La fonction `bb()`
|
|
|
|
Voici le code de cette fonction donné par `objdump` avec l'affichage des sauts :
|
|
|
|
```
|
|
8049470: 56 push %esi
|
|
8049471: 53 push %ebx
|
|
8049472: 8b 5c 24 10 mov 0x10(%esp),%ebx
|
|
8049476: 8b 74 24 0c mov 0xc(%esp),%esi
|
|
804947a: 0f b6 13 movzbl (%ebx),%edx
|
|
804947d: 84 d2 test %dl,%dl
|
|
804947f: /-------- 74 2d je 80494ae <bb+0x3e>
|
|
8049481: | 0f b6 06 movzbl (%esi),%eax
|
|
8049484: | 83 f0 12 xor $0x12,%eax
|
|
8049487: | 38 c2 cmp %al,%dl
|
|
8049489: /--|-------- 75 35 jne 80494c0 <bb+0x50>
|
|
804948b: | | b8 01 00 00 00 mov $0x1,%eax
|
|
8049490: | | /----- eb 14 jmp 80494a6 <bb+0x36>
|
|
8049492: | | | 8d b6 00 00 00 00 lea 0x0(%esi),%esi
|
|
8049498: | | | /-> 0f b6 14 06 movzbl (%esi,%eax,1),%edx
|
|
804949c: | | | | 83 c0 01 add $0x1,%eax
|
|
804949f: | | | | 83 f2 12 xor $0x12,%edx
|
|
80494a2: | | | | 38 ca cmp %cl,%dl
|
|
80494a4: +--|--|--|-- 75 1a jne 80494c0 <bb+0x50>
|
|
80494a6: | | \--|-> 0f b6 0c 03 movzbl (%ebx,%eax,1),%ecx
|
|
80494aa: | | | 84 c9 test %cl,%cl
|
|
80494ac: | | \-- 75 ea jne 8049498 <bb+0x28>
|
|
80494ae: | \-------> b8 01 00 00 00 mov $0x1,%eax
|
|
80494b3: | 5b pop %ebx
|
|
80494b4: | 5e pop %esi
|
|
80494b5: | c3 ret
|
|
80494b6: | 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
|
|
80494bd: | 8d 76 00 lea 0x0(%esi),%esi
|
|
80494c0: \----------> 31 c0 xor %eax,%eax
|
|
80494c2: 5b pop %ebx
|
|
```
|
|
|
|
Voici un déroullé des instructions principales de cette fonction :
|
|
|
|
* `8049472` : on mets en place le contenu de l'adresse vers notre série de
|
|
4 octets mystères (*0x66737a65*) dans `%ebx`
|
|
* `8049476` : l'adresse vers notre saisie est positionnée dans `%esi`
|
|
* `804947a` : l'octet de poids faible de `%ebx` est copié dans `%edx`
|
|
* `804947d` : et logique de `%dl` sur lui même, si le test est vrai alors le
|
|
programme branche sur la fin "normale" de la fonction. Ceci signifiait que
|
|
notre chaine mystère est vide (donc pas de mot de passe dans le programme).
|
|
* `8049481` : l'octet de poids faible de notre saisie `%esi` est positionné
|
|
dans `%eax`
|
|
* `8049484` : un `xor` est ensuite réalisé entre *0x12* et `%eax`
|
|
* `8049487` : les bits de poids faible de `%eax` et `%edx` sont comparés
|
|
* `8049489` : en cas d'inégalité, la fonction se termine.
|
|
* `804948b` : *0x1* est écrit dans `%eax`.
|
|
* `8049490` : Branchement vers l'instruction `80494a6`
|
|
* `80494a6` : l'octet de poids faible de `(%esi,%eax,1)` correspondant à la
|
|
lettre suivante de notre chaine mystère `ecx`
|
|
* `80494aa` : si l'opération booléenne de `%cl` sur lui même est différente de
|
|
zéro alors le programme branche sur `8049498`. Ce test permet de savoir si on
|
|
est en fin de chaine (`\0`) sur notre chaine mystère. Sinon le fil de code
|
|
continue jusqu'à la fin de la fonction.
|
|
* `8049498` : le programme prend le caractère suivant de notre saisir et le
|
|
place dans `%edx`
|
|
* `804949c` : `%eax` est incrémenté de 1
|
|
* `804949f` : un `xor` est ensuite réalisé entre *0x12* et `%edx`
|
|
* `80494a2` : les bits de poids faible de `%eax` et `%edx` sont comparés
|
|
* `80494a4` : s'il ne sont pas égaux alors fin du programme, sinon nous
|
|
revenons à l'instruction `80494a6`.
|
|
|
|
En clair, notre chaine mystère est bien le **mot de passe "chiffré"**, un simple
|
|
*xor* avec *0x12* sur chacun des caractères de ce dernier nous permettra de
|
|
trouver le mot de passe à saisir. On utilise pour cela la propriété suivante du
|
|
*"ou exclusif"* :
|
|
|
|
$$A \oplus B = C \implies C \oplus B = A$$
|
|
|
|
Là encore un script Python permet de faire le travail pour nous:
|
|
|
|
```python
|
|
#!/bin/env python3
|
|
hextext = "66737a65"
|
|
finaltext = ""
|
|
cleartext = ""
|
|
for i in range(0, len(hextext) - 1, 2):
|
|
c = '{}{}'.format(hextext[i], hextext[i+1])
|
|
cleartext += chr(int(c, 16) ^ 0x12)
|
|
cur_size=0
|
|
bits_processed=0
|
|
for i in range(0, len(cleartext) - 1, 4):
|
|
if (len(cleartext) - 4 * bits_processed) > 4:
|
|
cur_size = 4
|
|
else:
|
|
cur_size = len(cleartext) - 4 * bits_processed
|
|
for j in range(i+cur_size,i,-1):
|
|
finaltext += cleartext[j-1]
|
|
bits_processed+=1
|
|
print('Level 4 text: {}'.format(finaltext))
|
|
```
|
|
|
|
Ce qui donne:
|
|
|
|
```bash
|
|
./level4.py
|
|
Level 4 text: what
|
|
```
|
|
|
|
Le mot de passe de ce niveau est donc `what`
|
|
|
|
## Niveau 5
|
|
|
|
Ici encore la technique du `ctrl+c` fonctionne bien :
|
|
|
|
```
|
|
[...]
|
|
Program received signal SIGINT, Interrupt.
|
|
0xf7fc7559 in __kernel_vsyscall ()
|
|
(gdb) bt
|
|
[...]
|
|
#5 0x08049248 in r ()
|
|
#6 0x08049543 in yay ()
|
|
#7 0x080490dc in main ()
|
|
```
|
|
|
|
Donc la fonction intéressante est `yay()`, voici le code assembleur avec les
|
|
sauts affichés par `objdump` :
|
|
|
|
```
|
|
08049530 <yay>:
|
|
8049530: 53 push %ebx
|
|
8049531: 83 ec 14 sub $0x14,%esp
|
|
8049534: 68 84 a5 04 08 push $0x804a584
|
|
8049539: e8 22 fb ff ff call 8049060 <wprintf@plt>
|
|
804953e: e8 dd fc ff ff call 8049220 <r>
|
|
8049543: 89 04 24 mov %eax,(%esp)
|
|
8049546: 89 c3 mov %eax,%ebx
|
|
8049548: e8 33 fb ff ff call 8049080 <strlen@plt>
|
|
804954d: 83 c4 10 add $0x10,%esp
|
|
8049550: 83 f8 03 cmp $0x3,%eax
|
|
8049553: /----- 76 36 jbe 804958b <yay+0x5b>
|
|
8049555: | 0f b6 03 movzbl (%ebx),%eax
|
|
8049558: | 89 da mov %ebx,%edx
|
|
804955a: | 31 c9 xor %ecx,%ecx
|
|
804955c: | 84 c0 test %al,%al
|
|
804955e: +----- 74 2b je 804958b <yay+0x5b>
|
|
8049560: | /-> 83 c2 01 add $0x1,%edx
|
|
8049563: | | 31 c1 xor %eax,%ecx
|
|
8049565: | | 0f b6 02 movzbl (%edx),%eax
|
|
8049568: | | 84 c0 test %al,%al
|
|
804956a: | \-- 75 f4 jne 8049560 <yay+0x30>
|
|
804956c: | 80 f9 43 cmp $0x43,%cl
|
|
804956f: +----- 75 1a jne 804958b <yay+0x5b>
|
|
8049571: | 83 ec 0c sub $0xc,%esp
|
|
8049574: | 68 24 a6 04 08 push $0x804a624
|
|
8049579: | e8 e2 fa ff ff call 8049060 <wprintf@plt>
|
|
804957e: | 89 1c 24 mov %ebx,(%esp)
|
|
8049581: | e8 ca fa ff ff call 8049050 <free@plt>
|
|
8049586: | 83 c4 18 add $0x18,%esp
|
|
8049589: | 5b pop %ebx
|
|
804958a: | c3 ret
|
|
804958b: \----> e8 f0 fc ff ff call 8049280 <f>
|
|
```
|
|
|
|
Sans rentrer dans les détails instruction par instruction comme fait lors du
|
|
niveau précédent, cette fonction :
|
|
|
|
* Vérifie que la saisir utilisateur soit supérieure à 3 caractères
|
|
(instructions `8049548` à `8049553`)
|
|
* Initialise `%ecx` à 0 via un `xor` sur lui même (instruction `804955a`).
|
|
* Boucle sur les caractères contenus dans la chaine saisie par l'utilisateur et
|
|
réalise un `xor` de celui-ci avec `%ecx`. Le résultat de cette opération est
|
|
stockée dans `%ecx` (instructions `8049555` à `804956a`).
|
|
* Cette boucle s'arrête lorsque le caractère fin de chaine (`\0`) est trouvé
|
|
(instruction `8049568`)
|
|
* Compare `%ecx` à `0x43`, s'il y a égalité alors le programme continue, sinon
|
|
la fonction `f()` est appelée, amenant vers la mauvaise fin.
|
|
|
|
Cat algorithme de chiffrement est vraiment faible, quelques ligne en Python
|
|
permettent de générer des mots de passes valides:
|
|
|
|
```python
|
|
#!/bin/env python3
|
|
import sys
|
|
|
|
password = sys.argv[1]
|
|
|
|
if len(password) < 3:
|
|
print('Minimal password size: 3 char, get {}'.format(len(password)))
|
|
sys.exit(1)
|
|
xor=0
|
|
target=0x43
|
|
for letter in password:
|
|
xor=xor^ord(letter)
|
|
last_letter=xor^target
|
|
print("here is your password: {}{}".format(password,chr(last_letter)))
|
|
```
|
|
|
|
L'utilisation est simple, on donne un mot de passe et il ajoute le caractère
|
|
manquant pour arriver à `0x43`:
|
|
|
|
```
|
|
./level5.py Toto
|
|
here is your password: Totoc
|
|
```
|
|
|
|
Maintenant il est possible de passer le dernier niveau :
|
|
|
|
```
|
|
.hackme
|
|
[...]
|
|
Level 5 is onboard! Will you make it?
|
|
Totoc
|
|
Yay!
|
|
|
|
************************************************
|
|
* ____ _ _ *
|
|
* / ___|___ _ __ __ _ _ __ __ _| |_ ___| | *
|
|
* | | / _ \| '_ \ / _` | '__/ _` | __/ __| | *
|
|
* | |__| (_) | | | | (_| | | | (_| | |_\__ \_| *
|
|
* \____\___/|_| |_|\__, |_| \__,_|\__|___(_) *
|
|
* |___/ *
|
|
* *
|
|
* You passed all the levels!! *
|
|
************************************************
|
|
```
|