diff --git a/content/secu_logicielle/td9-hackme/files/Makefile b/content/secu_logicielle/td9-hackme/files/Makefile new file mode 100644 index 0000000..bc83db0 --- /dev/null +++ b/content/secu_logicielle/td9-hackme/files/Makefile @@ -0,0 +1,18 @@ +pframe: + curl -o pframe.tgz https://dept-info.labri.fr/~thibault/SecuLog/pframe.tgz && \ + tar -xf pframe.tgz &&\ + rm -rf pframe.tgz + +.gdbinit: + +configure: pframe .gdbinit + $(shell echo "python import pframe" > .gdbinit) + +PHONY: % +gdb: configure + PYTHONPATH=${PWD}/pframe${PYTHONPATH:+:${PYTHONPATH}} \ + gdb hackme + +PHONY: clean +clean: + @rm -rf $(BUILD_DIR) pframe .gdbinit diff --git a/content/secu_logicielle/td9-hackme/files/hackme b/content/secu_logicielle/td9-hackme/files/hackme new file mode 100755 index 0000000..2039eda Binary files /dev/null and b/content/secu_logicielle/td9-hackme/files/hackme differ diff --git a/content/secu_logicielle/td9-hackme/files/level3.py b/content/secu_logicielle/td9-hackme/files/level3.py new file mode 100755 index 0000000..f511720 --- /dev/null +++ b/content/secu_logicielle/td9-hackme/files/level3.py @@ -0,0 +1,20 @@ +#!/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))) +print(cleartext) +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)) diff --git a/content/secu_logicielle/td9-hackme/files/level4.py b/content/secu_logicielle/td9-hackme/files/level4.py new file mode 100755 index 0000000..11a27ad --- /dev/null +++ b/content/secu_logicielle/td9-hackme/files/level4.py @@ -0,0 +1,19 @@ +#!/bin/env python3 +import sys +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 3 text: {}'.format(finaltext)) diff --git a/content/secu_logicielle/td9-hackme/index.md b/content/secu_logicielle/td9-hackme/index.md new file mode 100644 index 0000000..743fc4e --- /dev/null +++ b/content/secu_logicielle/td9-hackme/index.md @@ -0,0 +1,480 @@ +--- +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 += +Il nous demande ̀a l'utilisateur une saisir, lorsque je rentre un texte, il +répond `Nope`: + +``` +./hackme +This is level 0, welcome! What do you have to say? +bonjour +Nope! +``` + +### 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 +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 sytème `exit_group` est lancé avec 1 en param̀etre (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 +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! : + +``` +./hackme +This is level 0, welcome! What do you have to say? +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 + +``` +[...] +(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 + 0x0804935e <+14>: call 0x8049220 + 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 +=> 0x08049372 <+34>: add $0x10,%esp + 0x08049375 <+37>: test %eax,%eax +``` + +nous pouvons voir que les chaines sont positionné sur la pile. Affichons les +avec `gdb`: + +``` +(gdb) p (char*)($eax) +$1 = 0x804f580 "ThisIsMyTest +(gdb) p (char*)0x804d030 +$2 = 0x804d030

"HelloDad" +``` + +Nous avons notre mot de passe `HelloDad`. Recommçons l'exécution avec un `watch` +sur notre variable: + +``` +(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()` + +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()` + +``` +... +(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 + 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 ; \0 n'est pas trouvé +``` + +## Level 2 + +C'est reparti pour un tour! Nouvelle exécution du programme en plç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 () +``` + +Voici le code désassemblé de `r2()` + +``` +(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 + 0x080493ae <+14>: call 0x8049220 + 0x080493b3 <+19>: mov %eax,%ebx + 0x080493b5 <+21>: mov %eax,(%esp) + 0x080493b8 <+24>: call 0x80492f0 + 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 +=> 0x080493ca <+42>: add $0x10,%esp + 0x080493cd <+45>: test %eax,%eax + 0x080493cf <+47>: jne 0x80493eb + 0x080493d1 <+49>: sub $0xc,%esp + 0x080493d4 <+52>: push $0x804a318 + 0x080493d9 <+57>: call 0x8049060 + 0x080493de <+62>: mov %ebx,(%esp) + 0x080493e1 <+65>: call 0x8049050 + 0x080493e6 <+70>: add $0x18,%esp + 0x080493e9 <+73>: pop %ebx + 0x080493ea <+74>: ret + 0x080493eb <+75>: call 0x8049280 +End of assembler dump. +``` + +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`. + +écrire un script python pour faire le job + +```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 + +``` +[...] +#5 0x08049248 in r () +#6 0x08049403 in wut () +#7 0x080490d2 in main () +``` + +`r()` se se charge de la saisie, mais que fait `wut()`, interessons nous +à cette fonction d'abord passant dans sa frame: + + +``` +(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 + 0x080493fe <+14>: call 0x8049220 +=> 0x08049403 <+19>: add $0x10,%esp + 0x08049406 <+22>: cmpl $0x65727545,(%eax) <- intéressant + 0x0804940c <+28>: jne 0x8049433 + 0x0804940e <+30>: cmpl $0x21614b,0x4(%eax) <- et encore interessant + 0x08049415 <+37>: mov %eax,%ebx + 0x08049417 <+39>: jne 0x8049433 + 0x08049419 <+41>: sub $0xc,%esp + 0x0804941c <+44>: push $0x804a424 + 0x08049421 <+49>: call 0x8049060 + 0x08049426 <+54>: mov %ebx,(%esp) + 0x08049429 <+57>: call 0x8049050 + 0x0804942e <+62>: add $0x18,%esp + 0x08049431 <+65>: pop %ebx + 0x08049432 <+66>: ret + 0x08049433 <+67>: call 0x8049280 +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` + +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)) +``` + +Le mot de passe est `EureKa!` + +## Level 4 + +Même cheminement que pour le niveau 3: + +``` +... +#5 0x08049248 in r () +#6 0x080494e3 in aa () +#7 0x080490d7 in main () +``` + + +Interessons nous à cette fonction: + +``` +(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 + 0x080494de <+14>: call 0x8049220 +=> 0x080494e3 <+19>: mov %eax,(%esp) + 0x080494e6 <+22>: mov %eax,%ebx + 0x080494e8 <+24>: call 0x8049080 + 0x080494ed <+29>: add $0x10,%esp + 0x080494f0 <+32>: cmp $0x4,%eax + 0x080494f3 <+35>: jne 0x8049524 + 0x080494f5 <+37>: sub $0x8,%esp + 0x080494f8 <+40>: push $0x804adf5 + 0x080494fd <+45>: push %ebx + 0x080494fe <+46>: call 0x8049470 <- wuat is bb? + 0x08049503 <+51>: add $0x10,%esp + 0x08049506 <+54>: test %eax,%eax + 0x08049508 <+56>: je 0x8049524 + 0x0804950a <+58>: sub $0xc,%esp + 0x0804950d <+61>: push $0x804a4ec + 0x08049512 <+66>: call 0x8049060 + 0x08049517 <+71>: mov %ebx,(%esp) + 0x0804951a <+74>: call 0x8049050 + 0x0804951f <+79>: add $0x18,%esp + 0x08049522 <+82>: pop %ebx + 0x08049523 <+83>: ret + 0x08049524 <+84>: call 0x8049280 +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. + +Mais avant ça `aa()` met les élements en place: + + * 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 + procgramme branche sur `f()` qui met fin à l'exécution.. Nous pouvons donc en + déduite que notre saisie doit être de 4 cacactè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 chage du 'déchiffrement' + +### La fonction `bb` + +``` +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 +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 +804948b: | | b8 01 00 00 00 mov $0x1,%eax +8049490: | | /----- eb 14 jmp 80494a6 +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 +80494a6: | | \--|-> 0f b6 0c 03 movzbl (%ebx,%eax,1),%ecx +80494aa: | | | 84 c9 test %cl,%cl +80494ac: | | \-- 75 ea jne 8049498 +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 +``` + + * `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` + * `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 signifirait que + notre chaine mystère est vide (donc pas de mot de passe). + * `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 poid faible de `(%esi,%eax,1)` correspondant à la + lettre suivante de notre chaine mystère `ecx` + * `80494aa` : si l'opération booleene de `%cl` sur lui même est différente de + zéro alors le probramme 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* nous permettra de trouver le mot de passe à saisir. + +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 +```