Add level5
And reword some parts
This commit is contained in:
parent
4907f833e2
commit
60aecd0591
2 changed files with 222 additions and 74 deletions
13
content/secu_logicielle/td9-hackme/files/level5.py
Executable file
13
content/secu_logicielle/td9-hackme/files/level5.py
Executable file
|
@ -0,0 +1,13 @@
|
|||
#!/bin/env python3
|
||||
import sys
|
||||
|
||||
password = sys.argv[1]
|
||||
if len(password) < 3:
|
||||
print('Minimal password size: 3, 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)))
|
|
@ -9,9 +9,9 @@ author:
|
|||
|
||||
## Level 0
|
||||
|
||||
### Première exécution du programme
|
||||
=
|
||||
Il nous demande ̀a l'utilisateur une saisir, lorsque je rentre un texte, il
|
||||
### Première exécution du programme
|
||||
|
||||
Le programme demande à l'utilisateur une saisir, lorsque je rentre un texte, il
|
||||
répond `Nope`:
|
||||
|
||||
```
|
||||
|
@ -21,27 +21,70 @@ bonjour
|
|||
Nope!
|
||||
```
|
||||
|
||||
Il doit donc s'attendre à un mot de passe bien précis.
|
||||
|
||||
|
||||
### Avec strace
|
||||
|
||||
Lors de l'exécution du programme ̀a l'aide de `strace`, nous pouvons d'abord voir
|
||||
-- après les projections mémoire avec `mmap` et d'autres éléments -- ds appels
|
||||
systèmes `write` pour afficher le message invitant ̀a la saisir, Cet affichage
|
||||
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.
|
||||
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 sytème `exit_group` est lancé avec 1 en param̀etre (terminer tous
|
||||
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
|
||||
evécuté notre programme confirme que le processus a quitté avec 1 comme code de
|
||||
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 interessantes. On y voit des
|
||||
traces des fonctions `wprintf` (qui pourrait être utile pour la suite),
|
||||
`strlen`. `getline`. On voit aussi `IAmSuperSecure`, comme dirait Bernard dans
|
||||
*Day of the Tentacle* This is all too easy! :
|
||||
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 state rien n'est
|
||||
sûr. Mais juste comme ça essayons tout de même un `lstrace` sur nore 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
|
||||
|
@ -50,26 +93,11 @@ IAmSuperSecure
|
|||
Ok, that was easy!
|
||||
```
|
||||
|
||||
Le ltrace confirme :
|
||||
|
||||
```
|
||||
ltrace ./hackme
|
||||
__libc_start_main(0x80490a0, 1, 0xffec1274, 0x8049590 <unfinished ...>
|
||||
wprintf(0x804a064, 0x40000, 7, 0x80495d3This is level 0, welcome! What do you have to say?
|
||||
) = 51
|
||||
getline(0xffec1188, 0xffec118c, 0xf7f215c0, 0xf7db2096IAmsuperSecure
|
||||
) = 15
|
||||
strcmp("IAmsuperSecure", "IAmSuperSecure") = 1
|
||||
wprintf(0x804a048, 0xf7fd4950, 0, 0x80492c2Nope!
|
||||
) = 6
|
||||
exit(1 <no return ...>
|
||||
+++ exited (status 1) +++
|
||||
```
|
||||
|
||||
## Level 1
|
||||
|
||||
Une fois pframe installé, le point d'arrêt positionné, et remonté dans la
|
||||
fonction appelante
|
||||
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:
|
||||
|
||||
```
|
||||
[...]
|
||||
|
@ -96,8 +124,9 @@ Dump of assembler code for function r1:
|
|||
0x08049375 <+37>: test %eax,%eax
|
||||
```
|
||||
|
||||
nous pouvons voir que les chaines sont positionné sur la pile. Affichons les
|
||||
avec `gdb`:
|
||||
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)
|
||||
|
@ -106,8 +135,10 @@ $1 = 0x804f580 "ThisIsMyTest
|
|||
$2 = 0x804d030 <p> "HelloDad"
|
||||
```
|
||||
|
||||
Nous avons notre mot de passe `HelloDad`. Recommçons l'exécution avec un `watch`
|
||||
sur notre variable:
|
||||
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 obsurcie.
|
||||
Essayons donc de comprendre comment. Recommençons l'exécution avec un `watch`
|
||||
la chaine contenue sur le programme:
|
||||
|
||||
```
|
||||
(gdb) wa * (char*)0x804d030
|
||||
|
@ -124,18 +155,7 @@ New value = 72 'H'
|
|||
#1 0x080490c3 in main ()
|
||||
```
|
||||
|
||||
La fonction permettant le déchiffrage est `z()`
|
||||
|
||||
En observant le contenu de cette variable, on comprend alors que le
|
||||
déchiffrement du mot de passe du niveau 2 se fait en retirant `0x9` à chaque
|
||||
caractère de noure chaine
|
||||
|
||||
```
|
||||
(gdb) p (char*)0x804d030
|
||||
$3 = 0x804d030 <p> "QnuuxMjm"
|
||||
```
|
||||
|
||||
Ce fonctionnement est confirme par le code assembleur de `z()`
|
||||
La fonction permettant le déchiffrage est `z()`, passons à son désassemblage:
|
||||
|
||||
```
|
||||
...
|
||||
|
@ -162,9 +182,13 @@ Dump of assembler code for function z:
|
|||
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. On retrouv
|
||||
|
||||
## Level 2
|
||||
|
||||
C'est reparti pour un tour! Nouvelle exécution du programme en plçant un point
|
||||
C'est reparti pour un tour! Nouvelle exécution du programme en plaçant un point
|
||||
d'arrêt sur `srtcmp@plt` et on arrive jusqu'au niveau 2:
|
||||
|
||||
```
|
||||
|
@ -178,7 +202,8 @@ Breakpoint 1, 0x08049030 in strcmp@plt ()
|
|||
#2 0x080490cd in main ()
|
||||
```
|
||||
|
||||
Voici le code désassemblé de `r2()`
|
||||
Cette fois-ci c'est la fonction `r2()` qui s'occupe du "déchiffrement, voici son
|
||||
code désassemblé :
|
||||
|
||||
```
|
||||
(gdb) frame 1
|
||||
|
@ -218,15 +243,16 @@ Profitons-en pour afficher le contenu de nos deux chaines:
|
|||
```
|
||||
(gdb) p (char*)0x804adec
|
||||
$10 = 0x804adec "R`jw}]nj"
|
||||
I a Tea
|
||||
(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 -> on ajoute `0x9`.
|
||||
C'est le chiffrement inverse de celui vu au niveau 1, ici on ajoute `0x9` à
|
||||
chaque caractère.
|
||||
|
||||
écrire un script python pour faire le job
|
||||
Pour déchiffrer le mote de passe écrit dans le programme, j'ai écris un script
|
||||
Python :
|
||||
|
||||
```python
|
||||
cyphertext = "R`jw}]nj"
|
||||
|
@ -236,10 +262,14 @@ for l in range(len(cyphertext)):
|
|||
print(cleartext)
|
||||
```
|
||||
|
||||
ce qui nous donne 'IWantTea'
|
||||
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 ()
|
||||
|
@ -248,17 +278,12 @@ ce qui nous donne 'IWantTea'
|
|||
```
|
||||
|
||||
`r()` se se charge de la saisie, mais que fait `wut()`, interessons nous
|
||||
à cette fonction d'abord passant dans sa frame:
|
||||
à cette fonction d'abord passant dans sa frame puis en la désassemblant:
|
||||
|
||||
|
||||
```
|
||||
(gdb) frame 6
|
||||
#6 0x08049403 in wut ()
|
||||
```
|
||||
|
||||
Puis en désassemblant son code:
|
||||
|
||||
```
|
||||
(gdb) disass
|
||||
Dump of assembler code for function wut:
|
||||
0x080493f0 <+0>: push %ebx
|
||||
|
@ -284,15 +309,14 @@ Dump of assembler code for function wut:
|
|||
End of assembler dump.
|
||||
```
|
||||
|
||||
Après la saisie effectuée par l'utilisateur pour ce niveau 3, voici deux
|
||||
comparaisons intéressantes. La première s'effectue sur les 4 octets à l'adresse
|
||||
contenur dans `%eax`. La suivante sur le contenu à l'adresse de `%eax + 0x4`
|
||||
Voici deux comparaisons intéressantes. La première s'effectue sur les 4 octets à
|
||||
l'adresse contenur 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"
|
||||
|
@ -314,11 +338,16 @@ for i in range(0, len(cleartext) - 1, 4):
|
|||
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 et chaque élément de 4 octets
|
||||
doit être inversé.
|
||||
|
||||
Le mot de passe est `EureKa!`
|
||||
|
||||
## Level 4
|
||||
|
||||
Même cheminement que pour le niveau 3:
|
||||
comme pour le niveau 3, il faut utiliser la technique du `ctrl+c` pour utiliser
|
||||
`bt`:
|
||||
|
||||
```
|
||||
...
|
||||
|
@ -328,7 +357,8 @@ Même cheminement que pour le niveau 3:
|
|||
```
|
||||
|
||||
|
||||
Interessons nous à cette fonction:
|
||||
C'est la fonction `aa()` qui appelle `r()` (fonction de saicie), Interessons
|
||||
nous à elle en la désassamblant :
|
||||
|
||||
```
|
||||
(gdb) disass
|
||||
|
@ -347,7 +377,7 @@ Dump of assembler code for function aa:
|
|||
0x080494f5 <+37>: sub $0x8,%esp
|
||||
0x080494f8 <+40>: push $0x804adf5
|
||||
0x080494fd <+45>: push %ebx
|
||||
0x080494fe <+46>: call 0x8049470 <bb> <- wuat is bb?
|
||||
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>
|
||||
|
@ -363,8 +393,8 @@ Dump of assembler code for function aa:
|
|||
End of assembler dump.
|
||||
```
|
||||
|
||||
Cette fonction appelle une autre foncion : `wut()`. C'est elle qui semble se
|
||||
charcher de la vérification de la saisie.
|
||||
Cette fonction appelle une autre foncion : `bb()`. C'est elle qui semble se
|
||||
charger de la vérification de la saisie.
|
||||
|
||||
Mais avant ça `aa()` met les élements en place:
|
||||
|
||||
|
@ -383,8 +413,9 @@ Mais avant ça `aa()` met les élements en place:
|
|||
* `%ebx` est ensuite poussé sur la pile
|
||||
* `bb()` est appelée, c'est cette fonction qui se chage du 'déchiffrement'
|
||||
|
||||
### La fonction `bb`
|
||||
### La fonction `bb()`
|
||||
|
||||
voic le code de cette fonction donné par `objdump` avec l'affichage des sauts :
|
||||
```
|
||||
8049470: 56 push %esi
|
||||
8049471: 53 push %ebx
|
||||
|
@ -418,6 +449,8 @@ Mais avant ça `aa()` met les élements en place:
|
|||
80494c2: 5b pop %ebx
|
||||
```
|
||||
|
||||
Voici un déroullé des instructions principales de cette fonction :
|
||||
|
||||
* `8049472` : on mets en place le contenu de l'adresse contenant notre série de
|
||||
4 octets mystères *0x66737a65* dans `%ebx`
|
||||
* `8049476` : l'adresse vers notre saisie est positionnée dans `%esi`
|
||||
|
@ -447,7 +480,11 @@ Mais avant ça `aa()` met les élements en place:
|
|||
revenons à l'instruction `80494a6`.
|
||||
|
||||
En clair, notre chaine mystère est bien le **mot de passe "chiffré"**, un simple
|
||||
*xor* avec *0x12* nous permettra de trouver le mot de passe à saisir.
|
||||
*xor* avec *0x12* sur cachun des caractères de ce dernier nous permettra de
|
||||
trouver le mot de passe à saisir. On utilise pour celà 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:
|
||||
|
||||
|
@ -478,3 +515,101 @@ Ce qui donne:
|
|||
./level4.py
|
||||
Level 4 text: what
|
||||
```
|
||||
|
||||
Le mot de pase 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'arrete 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, ammenant 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
|
||||
```
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue