Unix Keygenning
Fermons la fenêtre, préparons-nous, et partons vers la banquise.
Là-bas, pas d'outils graphique, pas de belles images. Uniquement de la console. Encore et toujours.
Bon, maintenant que les fainéants et les pleutres ont disparus, nous allons pouvoir commencer.
Pour suivre ce tutoriel, il vous sera nécessaire de posséder quelques acquis en programmation ASM. Même si je vais m'efforcer, au comble du masochisme, de vous décrire point par point chaque instruction, ces acquis vous permettront de combler mes lacunes pédagogiques.
Le keygenning ayant déjà été définis dans l'article sur le keygenning windows, je préfère vous y renvoyer, plutôt que d'en rédiger une moi-même. D'autant plus que la définition me convient très bien (et que je suis fainéant).
1°) Les outils
Dans ce tutoriel nous utiliserons des outils basiques, qui font sûrement déjà partis de votre panoplie, puisqu'ils sont de base installés sur la plupart des distributions GNU/Linux.
Objdump, un desassembleur faisant partie de la sympathique famille des GNU bin utils (qui comprend également as, ld, strings, strip, readelf, pour ne citer qu'eux). Une suite bien utile dans sa totalité, pour l'analyse de fichier binaire.
Gdb, le Gnu DeBugger,un debugger plutôt complet.
Nasm (ou as) & ld, assembleur & linker, pour la création du keygen. Ou le langage de programmation de votre choix.
2°) À l'attaque
Le keygenMe est disponible ici : http://bayfiles.com/file/1kkJ/7Brju2/keygenMe
Avant de partir à toute allure dans des travers techniques et incompréhensibles, pourquoi ne pas lancer le joujou hors debugger, histoire de voir de quoi il retourne ?
Bon, étant donné la masse d'entre vous qui trouvent mon idée géniale, voyons ce que ça donne ;
$ ./KeygenMe
--------------------
KeygenMe
By kallimero
--------------------
Pseudo : kallimero
Serial : trolololo
Registration failed.
Évidemment, ça n'a pas marché. Le contraire aurait été vraiment étonnant. Je ne suis donc pas cocu. CQFD.
Examinons le fichier via la commande file :
$ file KeygenMe
KeygenMe: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
On a déjà pas mal d'infos utiles ;
Le fichier est linké statiquement. Le linker étant le lien entre le fichier objet et le fichier exécutable. Pour faire court, le fichier objet est un fichier intermédiaire, utilisé lors de la compilation. Il contient du code machine non-exécutable, qui nécessite l'édition des liens (d'où le nom de linker). Quand le linkage est statique, le fichier exécutable n'est pas lié à une bibliothèque externe, il est lié tout seul. Quand il est dynamique, il est lié à une bibliothèque, et ce lien est mis en œuvre au lancement du fichier.
Il est strippé. C'est à dire que toute les entêtes non nécessaires pour le bon déroulement de programme ont été supprimées.
Allez, je ne vous fait pas baver plus longtemps. D'autant que votre salive vous servira pour crier de douleur un peu plus tard.
A) Désassemblons
On est parti pour se pencher sur le code assembleur. On va commencer par regarder le code gentillement désassemblé par objdump, afin de comprendre le fonctionnement intrinsèque et d'isoler la routine de génération du keygen. Le programme étant linké statiquement, cela ne devrai pas trop nous poser de soucis.
Petite parenthèse avant de découvrir le magnifique code, sur les syscalls.
De manière shématique, un syscall est un appel au kernel, pour qu'ill effectue une certaine tâche, en fonction de paramètres (arguments). Un syscall à toujours la même structure. Sous système UNIX :
Numéro du syscall dans eax
1er argument dans ebx
2nd argument dans ecx
3eme argument dans edx
possible 4, 5, 6... argument(s)
Appel au kernel (kernel interrupt) int $0x80
Le numéro de syscall correspond à la fonction que l'on souhaite effectuer.
Vous retrouverez la correspondance entre fonction et numéro ici : http://bluemaster.iu.hio.no/edu/dark/lin-asm/syscalls.html
Ou dans le fichier /usr/include/asm/unistd.h
/!\ Attention objdump utilise par défaut la syntaxe AT&T. Si vous préférez la syntaxe intel (utilisée notamment par nasm et masm), vous pouvez ajouter l'option -M intel.
On utilisera l'option -d pour indiquer le fichier à désassembler, utilisez l'option -H pour connaître la foultitude d'options intéressantes d'objdump.
Je vous montre directement l'output commenté par mes soins.
1 2$ objdump -d KeygenMe 3 4keygenMe: file format elf32-i386 5 6 7Disassembly of section .text: 8 908048080 <.text>: 10 11; Premier syscall, pour afficher « KeygenMe By kallimero » 12 8048080: b8 04 00 00 00 mov $0x4,%eax 13 8048085: bb 01 00 00 00 mov $0x1,%ebx 14 804808a: b9 70 91 04 08 mov $0x8049170,%ecx 15 804808f: ba 56 00 00 00 mov $0x56,%edx 16 8048094: cd 80 int $0x80 17 18; Second syscal pour afficher « pseudo : » 19 8048096: b8 04 00 00 00 mov $0x4,%eax 20 804809b: bb 01 00 00 00 mov $0x1,%ebx 21 80480a0: b9 c7 91 04 08 mov $0x80491c7,%ecx 22 80480a5: ba 09 00 00 00 mov $0x9,%edx 23 80480aa: cd 80 int $0x80 24 25; 3eme syscall pour enregistrer le pseudo dans une variable 26 80480ac: b8 03 00 00 00 mov $0x3,%eax 27 80480b1: bb 00 00 00 00 mov $0x0,%ebx 28 80480b6: b9 14 92 04 08 mov $0x8049214,%ecx 29 80480bb: ba 0f 00 00 00 mov $0xf,%edx 30 80480c0: cd 80 int $0x80 31 32; 4eme syscall pour afficher « serial : » 33 80480c2: b8 04 00 00 00 mov $0x4,%eax 34 80480c7: bb 01 00 00 00 mov $0x1,%ebx 35 80480cc: b9 d1 91 04 08 mov $0x80491d1,%ecx 36 80480d1: ba 09 00 00 00 mov $0x9,%edx 37 80480d6: cd 80 int $0x80 38 39; 5eme syscall pour enregistrer le serial dans une variable 40 80480d8: b8 03 00 00 00 mov $0x3,%eax 41 80480dd: bb 00 00 00 00 mov $0x0,%ebx 42 80480e2: b9 34 92 04 08 mov $0x8049234,%ecx 43 80480e7: ba 0f 00 00 00 mov $0xf,%edx 44 80480ec: cd 80 int $0x80 45 46; Routine obscure que nous allons élucider 47 80480ee: b8 14 92 04 08 mov $0x8049214,%eax 48 80480f3: ba 00 00 00 00 mov $0x0,%edx 49 80480f8: 42 inc %edx 50 80480f9: 80 3c 10 00 cmpb $0x0,(%eax,%edx,1) 51 80480fd: 75 f9 jne 0x80480f8 52 80480ff: bb 00 00 00 00 mov $0x0,%ebx 53 8048104: 81 ea 01 00 00 00 sub $0x1,%edx 54 804810a: 01 93 14 92 04 08 add %edx,0x8049214(%ebx) 55 8048110: 43 inc %ebx 56 8048111: 39 d3 cmp %edx,%ebx 57 8048113: 75 f5 jne 0x804810a 58 59 60; Routine de comparaison 61 8048115: 81 c2 01 00 00 00 add $0x1,%edx 62 804811b: 89 d1 mov %edx,%ecx 63 804811d: be 14 92 04 08 mov $0x8049214,%esi 64 8048122: bf 34 92 04 08 mov $0x8049234,%edi 65 8048127: f3 a6 repz cmpsb %es:(%edi),%ds:(%esi) 66 67 8048129: 74 22 je 0x804814d 68 ; Si la comparaison est bonne, au saute à 0x804814d 69 70; Syscall pour afficher le badBoy 71 804812b: b8 04 00 00 00 mov $0x4,%eax 72 8048130: bb 01 00 00 00 mov $0x1,%ebx 73 8048135: b9 fc 91 04 08 mov $0x80491fc,%ecx 74 804813a: ba 16 00 00 00 mov $0x16,%edx 75 804813f: cd 80 int $0x80 76 77; Syscall exit 78 8048141: b8 01 00 00 00 mov $0x1,%eax 79 8048146: bb 00 00 00 00 mov $0x0,%ebx 80 804814b: cd 80 int $0x80 81 82; Syscall pour afficher le GoodBoy 83 804814d: b8 04 00 00 00 mov $0x4,%eax 84 8048152: bb 01 00 00 00 mov $0x1,%ebx 85 8048157: b9 db 91 04 08 mov $0x80491db,%ecx 86 804815c: ba 21 00 00 00 mov $0x21,%edx 87 8048161: cd 80 int $0x80 88 89; Syscall exit 90 8048163: b8 01 00 00 00 mov $0x1,%eax 91 8048168: bb 00 00 00 00 mov $0x0,%ebx 92 804816d: cd 80 int $0x80
Je ne vous avait pas menti ; le code est principalement composé de syscall. Ceux utilisés majoritairement sont :
sys_write. 4 dans eax, et 1 dans ebx pour stdout, pour écrire du texte,
sys_read, 4 dans eax, et 0 dans ebx pour stdin, pour lire du texte,
sys_exit, 1 dans eax, et un nombre dans ebx qui représente le code d'erreur. 0 signifiant qu'il n'y as pas eu d'erreur.
Stdout est le flux de sortie, soit votre écran, et stdin le flux d'entrée, soit votre clavier.
Mêlé entre les vils et rugueux appels au système, nous pouvons distinguer un sombre morceau de code, qui s'apparenterait à une routine de génération de mot de passe.
C'est à ce moment que des connaissances en assembleur sont de mises, car le keygenning, c'est avant tout de longues nuits à jongler entre documentation et langage d'assemblage, entre café et aspirine, entre jouissances profondes et crises de nerfs.
Voyons ensemble de plus près la désormais fameuse portion de bouillie asm :
1 2 80480ee: b8 14 92 04 08 mov $0x8049214,%eax 3 ; Met le pseudo entrer dans eax (voir le 3eme syscall) 4 80480f3: ba 00 00 00 00 mov $0x0,%edx 5 ; Met 0 dans edx, sûrement pour un compteur 6 80480f8: 42 inc %edx 7 ; incrémente ebx (edx = edx +1) 8 80480f9: 80 3c 10 00 cmpb $0x0,(%eax,%edx,1) 9 Compare le byte numéro edx du pseudo avec 0 (byte de fin d'une chaîne) 10 80480fd: 75 f9 jne 0x80480f8 11 ; Si ce n'est pas un 0, on recommence
;Finalité de cette première partie : eax contient le pseudo, edx contient le nombre de byte du pseudo
1 2 80480ff: bb 00 00 00 00 mov $0x0,%ebx 3 ; On met 0 dans ebx, pour un compteur 4 8048104: 81 ea 01 00 00 00 sub $0x1,%edx 5 ; on décrément edx qui contient la taille du pseudo 6 804810a: 01 93 14 92 04 08 add %edx,0x8049214(%ebx) 7 ; On ajoute edx au byte numéro ebx du pseudo 8 8048110: 43 inc %ebx 9 ; on incrémente ebx 10 8048111: 39 d3 cmp %edx,%ebx 11;On compare edx et ebx (donc quand ebx vaut la taille du pseudo) 12 8048113: 75 f5 jne 0x804810a ; si ebx ne vaut la taille du pseudo, que tout les bytes n'ont été parcourus, on retourne plus haut
Finalité de cette seconde partie : On décale dans la table ascii chaque caractère du pseudo de sa taille totale.
Pourquoi je parle de la table ascii ? Eh bien parce que tout caractère est représenté par un nombre (l'ordinateur ne comprenant pas les lettres). La correspondance entre les lettre et les nombres est réalisée par la table ascii. Donc l'opération à l'adresse 804810a ajoute a la lettre traduite en ascii, le nombre de lettres total du pseudo, ce qui changera la lettre.
Nous reverrons ça plus tard.
B) Debuggons
Ce terme, assez barbare pour faire rougir mon correcteur orthographique, mérite un peu d'attention pour les plus débutant d'entre nous.
Le concept est assez simple ; un debugger permet, à chaque instant, de voir ce qui ce passe à l'intérieur d'un programme (état des registres, de la mémoire du programme). Littéralement, debugger signifie « qui enlève les bugs ». Bien sûr, nous n'aurons pas cette prétention là. Nous utiliserons le Gnu DeBugger, pour d'autres fin.
Démarrons donc notre fidèle ami Gdb, toujours là pour nous aider dans notre quête de sérial :
$ gdb keygenMe
Pas besoin de re-désassembler le code ici. Si c'est votre désir le plus ardent vous pouvez bien évidemment le faire, en utilisant la commande « disass ». Ici le programme ayant été linké statiquement, vous ne retrouverez qu'une section, .text, que vous ne pourrez désassembler qu'en lui indiquant l'adresse de début et celle de fin. De cette manière :
(gdb) disass 0x08048080 0x0804816d
Ce qui vous renverra à peu de chose près ce que nous avons vu avec objdump.
/!\ Attention, encore une fois, gdb utilise la syntaxe AT&T, si vous souhaitez qu'il utilise la syntaxe intel, vous pouvez le lui indiquer avec la commande « set disassembly-flavor intel ».
Nous allons maintenant poser un point d'arrêt – breakpoint en anglais – Sur la comparaison du serial entré avec celui attendu.
(gdb) b *0x08048127
Breakpoint 1 at 0x8048127
Pour ceux qui ne le savent pas encore, un breakpoint permet d'arrêter le programme en cours d'éxecution, afin d'examiner, à l'instant définis, l' état des registres, des flags, de la stack, etc...
Maintenant lançons le programme avec la commande run (que l'on peut abréger r)
(gdb) r
Starting program: /home/kallimero/Bureau/codes/asm/keygenMe
--------------------
KeygenMe
By kallimero
--------------------
Pseudo : kallimero
Serial : trolololo
Breakpoint 1, 0x08048127 in ?? ()
On rentre encore une fois, tel un rituel, des informations bidons.
Mais cette fois, paf !
Pas de chocapic mais un breakpoint. Notre programme est en stand by.
Vérifions l'état des registres, avec la commande « info registers » (que l'on peux abréger i r)
(gdb) i r
eax 0x8049214 134517268
ecx 0xa 10
edx 0xa 10
ebx 0x9 9
esp 0xffffd5d0 0xffffd5d0
ebp 0x0 0x0
esi 0x8049214 134517268
edi 0x8049234 134517300
eip 0x8048127 0x8048127
eflags 0x206 [ PF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x0 0
Comme nous l'avons vu plus haut, c'est eax qui nous intéresse, car c'est lui qui au début contient le pseudo, qui est ensuite modifié pour obtenir le sérial voulu par le programme
Tâchons de l'afficher comme une chaîne de caractère, grâce au système de conversion de gdb.
(gdb) x/s $eax
0x8049214: "tjuurvn{x\n"
(gdb)
x/s : convertir hexadécimal en chaîne de caractère (string)
$eax : le registre eax. N'oubliez pas le $
Le serial correspondant au pseudo kallimero serai donc tjuurvn{x. Le \n n'étant pas pris en compte au moment de la comparaison.
Très bien. Testons donc cela (vous pouvez quitter gdb via la commande quit ou q) :
$ ./keygenMe
--------------------
KeygenMe
By kallimero
--------------------
Pseudo : kallimero
Serial : tjuurvn{x
Yeah, you win. Serial accepted.
Ah, la douce odeur de la victoire approchante et chantante.
Comme vous avez pus le voir notre ruse et notre intelligence nous a permis de dénicher un sérial valide.
Mais évidemment, ce n'est pas finis ; sinon toutes les étapes du début ne nous auraient servis à rien, et vous savez bien que je ne me serai pas permis de vous faire perdre votre temps.
Il va falloir réaliser un programme capable de générer, pour n'importe quel pseudo, un serial valide.
Afin d'accroître notre compréhension de l'algorithme, tâchons de déterminer « mathématiquement », le serial correspondant au magnifique pseudo kallimero.
kallimero a une taille de 9 caractères.
k = 107 | 107 + 9 = 116 = t
a = 97 | 97 + 9 = 106 = j
l = 108 | 108 + 9 = 117 = u
l = 108 | 108 + 9 = 117 = u
i = 105 | 105 + 9 = 114 = r
m = 109 | 109 + 9 = 118 = v
e = 101 | 101 + 9 = 110 = n
r = 114 | 114 + 9 = 123 = {
o = 111 | 111 + 9 = 120 = x
Ce qui nous donne bien tjuurvn{x
Bien sûr c'est une étape un peu artificielle que vous pourrez sauter dans le future, mais elle est ici afin de vous faire comprendre en profondeur le keygenMe présent.
C) Le keygen
Écrire le keygen est normalement plutôt aisé quand la routine de génération du sérial est bien assimilée. Ce qui est bien évidemment notre cas.
Je vais personnellement en écrire un en assembleur, ce qui nous permet de carrément récupérer des bouts du code désassemblé. mais libre à vous d'utiliser tout autre langage.
1 2;Compilation : 3;nasm -f elf keygen.asm 4; ld -s -o keygen keygen.o 5 6section .data 7 hello: db "Type your pseudo : " 8 ser: db "Your serial : " 9 10section .bss 11 pseudo: resb 32 12 13section .text 14 global _start 15 16_start: 17 ;syscall « type your pseudo » 18 mov eax, 4 19 mov ebx, 1 20 mov ecx, hello 21 mov edx, 19 22 int 80h 23 24 ;syscall qui met ce qui est entré dans la variable pseudo 25 mov eax, 3 26 mov ebx, 0 27 mov ecx, pseudo 28 mov edx, 10 29 int 80h 30 31 32 mov eax, pseudo ; On met le pseudo dans eax 33 mov edx, 0 ; edx sert de compteur 34 35 ; On cherche la taille 36 boucle: 37 inc edx ; incrementation d'edx 38 cmp byte [eax + edx], 0 ; Compare avec le null byte 39 jne boucle ; Si c'est pas le nullbyte, on retourne en haut 40 41 42 push edx ; On sauvegarde edx en le mettant sur le pile 43 sub edx, 1 ; on enlève 1 à edx 44 boucle2: 45 add dword [pseudo+ebx], edx ; On ajoute edx au byte n°ebx du pseudo 46 inc ebx ; on incrémente le compteur 47 cmp ebx, edx ; on compare ebx a la taille totale 48 jne boucle2 ; SI on a pas encore tout parcouru, on retourne en haut 49 50 51 ;syscall « your serial » 52 mov eax, 4 53 mov ebx, 1 54 mov ecx, ser 55 mov edx, 14 56 int 80h 57 58 ;syscall qui affiche le serial 59 mov eax, 4 60 mov ebx, 1 61 mov ecx, pseudo 62 pop edx 63 int 80h 64 65 ; serial exit 66 mov eax, 1 67 mov ebx, 0 68 int 80h
Groucho