From 60aecd0591309fa86373c7fe4f1c0dc88e8dbedf Mon Sep 17 00:00:00 2001 From: Yorick Barbanneau Date: Sat, 22 Apr 2023 00:56:30 +0200 Subject: [PATCH] Add level5 And reword some parts --- .../td9-hackme/files/level5.py | 13 + content/secu_logicielle/td9-hackme/index.md | 283 +++++++++++++----- 2 files changed, 222 insertions(+), 74 deletions(-) create mode 100755 content/secu_logicielle/td9-hackme/files/level5.py diff --git a/content/secu_logicielle/td9-hackme/files/level5.py b/content/secu_logicielle/td9-hackme/files/level5.py new file mode 100755 index 0000000..246969f --- /dev/null +++ b/content/secu_logicielle/td9-hackme/files/level5.py @@ -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))) diff --git a/content/secu_logicielle/td9-hackme/index.md b/content/secu_logicielle/td9-hackme/index.md index 743fc4e..1c2a61e 100644 --- a/content/secu_logicielle/td9-hackme/index.md +++ b/content/secu_logicielle/td9-hackme/index.md @@ -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 +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 ++++ 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 Day of +the Tentacle: *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 -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 -+++ 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

"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

"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 ; \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,23 +278,18 @@ 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 - 0x080493f1 <+1>: sub $0x14,%esp - 0x080493f4 <+4>: push $0x804a37c - 0x080493f9 <+9>: call 0x8049060 + 0x080493f0 <+0>: push %ebx + 0x080493f1 <+1>: sub $0x14,%esp + 0x080493f4 <+4>: push $0x804a37c + 0x080493f9 <+9>: call 0x8049060 0x080493fe <+14>: call 0x8049220 => 0x08049403 <+19>: add $0x10,%esp 0x08049406 <+22>: cmpl $0x65727545,(%eax) <- intéressant @@ -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 <- wuat is bb? + 0x080494fe <+46>: call 0x8049470 <- cet appel est intéressant non? 0x08049503 <+51>: add $0x10,%esp 0x08049506 <+54>: test %eax,%eax 0x08049508 <+56>: je 0x8049524 @@ -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 : + 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 + 804953e: e8 dd fc ff ff call 8049220 + 8049543: 89 04 24 mov %eax,(%esp) + 8049546: 89 c3 mov %eax,%ebx + 8049548: e8 33 fb ff ff call 8049080 + 804954d: 83 c4 10 add $0x10,%esp + 8049550: 83 f8 03 cmp $0x3,%eax + 8049553: /----- 76 36 jbe 804958b + 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 + 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 + 804956c: | 80 f9 43 cmp $0x43,%cl + 804956f: +----- 75 1a jne 804958b + 8049571: | 83 ec 0c sub $0xc,%esp + 8049574: | 68 24 a6 04 08 push $0x804a624 + 8049579: | e8 e2 fa ff ff call 8049060 + 804957e: | 89 1c 24 mov %ebx,(%esp) + 8049581: | e8 ca fa ff ff call 8049050 + 8049586: | 83 c4 18 add $0x18,%esp + 8049589: | 5b pop %ebx + 804958a: | c3 ret + 804958b: \----> e8 f0 fc ff ff call 8049280 +``` + +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 +```