Volume 0x0b, Issue 0x3f, Phile #0x0d of 0x14 |=------=[ cryptexec: Next-generation runtime binary encryption ]=-------=| |=------=[ using on-demand function extraction ]=-------=| |=-----------------------------------------------------------------------=| |=----------------=[ Zeljko Vrba ]=-----------------=| |=-----------------------------------------------------------------------=| |=--------------=[Traduction de TboWan pour arsouyes.org ]=--------------=| RÉSUMÉ Excusez mon anglais maladroit, ce n'est pas ma langue maternelle. [NDT : ce n'est pas la mienne non plus ;) ] Qu'est-ce que le chiffrement de binaires et pourquoi tout chiffrer ? Pour la réponse à cette question, le lecteur peut aller lire le phrack n°58 [1] et l'article qui s'y trouve sous le nom "Runtime binary encryption". Cet article décrit une méthode pour contrôler un programme cible qui se base sur une certaine assistance du noyau du système d'exploitation ou du processeur. La méthode est implémentée pour les systèmes x86-32 GNU AS (AT&T Syntaxe). Une fois la méthode bien conçue, il est relativement facile d'y ajouter un déchiffrement du code à la volée. 1 Introduction 2 Traçage assisté par OS- et matériel- 3 Traçage en mode utilisateur 3.1 API fournie 3.2 Description de haut niveau. 3.3 Exemple d'utilisation effective 3.4 Bug d'XDE 3.5 Limitations 3.6 Considérations de portage 4 Perspectives 5 Traveaux relatifs. 5.1 ELFsh 5.2 Shiva 5.3 Burneye 5.4 Conclusion 6 References 7 Crédits A Appendice : code source A.1 crypt_exec.S A.2 cryptfile.c A.3 test2.c note : les remarques en pied de page sont marquées d'un # suivi d'un nombre. Elles sont listées en fin de chaque section. --[ 1.0 - Introduction Tout d'abord, introduisons la terminologie utilisée dans cet article pour que le lecteur ne soit pas perdu. o Les qualificatifs "cible", "fils" et "tracé" sont équivalents pour parler d'un programme sous le contrôle d'un autre programme. o Les qualificatifs "contrôlant" et "traçant" sont équivalents pour parler d'un programme qui contrôle une cible (un débuggeur, strace, ...). --[ 2.0 - Traçage assisté par OS- et matériel- Les débuggeurs actuels (à la fois ceux sous Windows et sous UNIX) utilisent des fonctionnalités marétielles x86 pour débugger. Les deux plus utilisées sont le drapeau de trace (TF - Trace Flag) et l'instruction INT3, qui a l'avantage d'être codée sur un seul octet 0xcc. TF se trouve au bits 8 du registre EFLAGS et quand il est à 1, le processeur génère l'exeption 1 (exception de débuggage) après chaque instruction qui est exécutée. Quand INT3 est exécutée, le processeur génère l'exception 3 (point d'arret - breapoint). La manière traditionnelle de tracer un programme sous UNIX est d'utiliser l'appel système ptrace(2). Le programme fait d'habitude la chose suivante (ici en pseudocode) : fork() child: ptrace(PT_TRACE_ME) execve("le programe à tracer") parent: contrôler le programme tracé avec d'autres appels à ptrace() Un autre façon de faire est d'appeller ptrace(PT_ATTACH) sur un processus qui fonctionne déjà. Les autres opérations que l'interface ptrace() propose sont la lecture/écriture des instructions/données mémoire de la cible, la lecture/écriture des registres ou la continuation de l'exécution (complètement ou jusqu'au prochain appel système - cette capacité est utilisée par le programme très connus strace(1)). Chaque fois que le programe tracé reçoit un signal, les fonction ptrace() du programme contrôlant terminent. Quand le TF est activé, le programme tracé reçoit un SIGTRAP après chaque instruction. TF n'est d'habitude jamais activé par le programme tracé#1 mais plutôt à partir de ptrace(PT_STEP). Contrairement à TF, le programme contrôlant place des codes d'opération 0xCC aux endroits stratégiques#2 dans le code. Le premier octet de l'instruction est remplacé par 0xCC et le programme contrôlant enregistre à la fois l'adresse et l'opération originale. quand l'exécution arrive à cet adresse, SIGTRAP est délivré et le programme contrôlant récupère le contrôle de l'exécution. Cette fois, il remplace (encore une fois en utilisant ptrace()) le 0xCC par l'opération originale et exécute d'un pas l'instruction originale. D'habitude, l'opération originale est encore une fois remplacée par 0xCC. bien que très puissant, ptrace() à quelques désavantages : 1. Le programme tracé ne peut l'être que par un seul programme contrôlant. 2. Le programme contrôlant et celui tracé vivent dans des espaces d'adressages différents, qui rend le changement de la mémoire tracée maladroites. 3. ptrace() est un appel système : il est lent s'il est utilisé pour tracé précisément de gros morceaux de code. Je n'irai pas plus profondément dans les mécanismes de ptrace(), il y a des tutoriaux [2] et la page de man est assez explicite. __ #1 Bien que rien ne l'en empêche - c'est dans la portion modifiable par l'utilisateur de EFLAGS. #2 D'habitude, c'est la personne faisant le déboguage qui choisi ses endroits stratégiques. --[ 3.0 - Traçage en mode utilisateur Il est possible de tracer uniquement en mode utilisateur : les instructions sont exécutées nativement, sauf les instructions de contrôle (CALL, JMP, Jcc, RET, LOOP, JCXZ). Les bases de cette idée sont bien expliquées dans [3] sur les primitives de MIX conçu par Knuth dans les années 60. Fonctionnalités de la méthode que je vais décrire : o Elle permet que seulement des portions du code de l'exécutable soient chiffrées. o Différentes portions du code peuvent être chiffrées différement. o Elle permet que du code chiffré puisse appeler librement du code non chiffré. Dans ce cas, le code non chiffré est aussi exécuté instruction par instruction. Quand appelé en dehors de la zone chiffrée, il est reste exécuté sans traçage. o Il n'y a jamais plus de 24 octets de code chiffré stocké en clair en mémoire. o indépendant de l'OS et du langage. Le reste de cette section explique l'API fournie, fourni une description de haut niveau de l'implémentation, montre un exemple d'utilisation et en discute. Voici les détails de ma propre implémentation. ----[ 3.1 - API fournie Il n'y a pas d'en-tête "officielle" fournie. Grace à la façon pratique et désordonnée de passer les paramètres en C et la déclaration de fonction implicites, vous pouvez vous en sortir quand même. L'API de déchiffrement consiste en un typedef et d'une fonction. typedef (*decrypt_fn_ptr)(void *key, unsigned char *dst, const unsigned char *src); C'est le prototype générique à laquelle votre procédure de déchiffrement doit correspondre. Elle est appellée à partir de la fonction de déchiffrement principale avec les paramètres suivants : o key : un pointeur vers la clef pour déchiffrer. Notez que dans la plupart des cas, ce n'est pas la clef directement mais un pointeur vers un certain "contexte de déchiffrement". o dst : pointeur vers le buffer de destination o src : pointeur vers le buffer source. Notez qu'il n'y a pas d'argument pour la taille : la taille de bloc est fixée à 8 octets. La procédure ne devrait pas lire plus de 8 octets à partir du src et ne dois JAMAIS sortir plus de 8 octets dans dst. Une autre contrainte inhabituelle est que la procédure de déchiffrement NE PEUT PAS modifier ses paramètres dans la pile. Si vous avez besoin de le faire, copier les paramètres de la pile dans des variables locales. C'est une conséquence de la façon que la procédure est appellée à partir du moteur de déchiffrement - voir le code pour les détails. Il n'y aucune contrainte sur la sorte de chiffrement qui peut être utilisé. TOUTE fonction bijective qui fait correspondre 8 octets vers 8 octets est utilisable. Chiffrez le code avec la fonction, et utilisez son inverse pour le déchiffrement. Si vous utilisez la fonction identité, alors, le déchiffrement devient juste un simple pas unique sans support matériel -- voir section 4 pour des travaux relatifs. Le point d'entrée vers le moteur de déchiffrement est la fonction suivante : int crypt_exec(decrypt_fn_ptr dfn, const void *key, const void *lo_addr, const void *hi_addr, const void *F, ...); La fonction de déchiffrement a la capacité de passer entre l'exécution de code chiffré ou en clair. Le code chiffré peut appeller du code en clair et le code en clair peut retourner vers le code chiffré. Mais pour que ça soit possible, il doit connaitre les adresses des frontières des codes chiffrés. Notez que c'est fonction n'est pas ré-entrante ! Il n'est pas autorisé pour TOUTE sorte de code (à la fois en clair et chiffré) fonctionnant dans la fonction crypt_exec d'appeler crypt_exec. Les choses s'arreterons SALEMENT parce que l'état internet de l'invocation précédente est alloué statiquement et sera écrasé. Les arguments sont les suivants : o dfn : pointeur vers la fonction de déchiffrement. Cette fonction est appellée avec l'argument de clef fournis par crypt_exec et l'adresse des buffers sources et destination. o key : Ce n'est normalement PAS les octets directs de la clef, mais un context de déchiffrement initialisé. Voir l'exemple de code pour le programme test2 : D'abord, la clef fournir par l'utilisateur est chargée dans un contexte de déchiffrement et l'adresse du _contexte_ est fonrnie à la fonction crypt_exec. o lo_addr, hi_addr : c'est les premières et dernières adresses qui sont chiffrées avec la même clef. C'est pour faciliter l'appel à du code non chiffré à partir du code chiffré. o F : Pointeur vers le code qui devrait être exécuté avec le moteur de déchiffrement. Il peut s'agir d'une fonction C ordinaire. Puisque les procédures de traçage sont écrites avec en tête un chiffrement par blocs de 8 octets, la fonction F doit ête au moins alignée sur 8 octets et sa longueur doit ête multiple de 8. C'est plus facile à faire (même avec du C standar) qu'il n'y parait. Voir l'exemple plus bas. o ... les arguments de la fonction appellée. crypt_exec s'arrange pour que la fonction F soit appellée avec les arguments fournis dans la liste varargs. Quand crpt_exec retourne, sa valeur de retour est celle que F a retourné. En bref, l'appel suivant : x = crypt_exec(dfn, key, lo_addr, hi_addr, F, ...); a exactement la même sémantique que celui-ci : x = F(...); où aurait été en clair. Pour l'instant, le code est fait sur mesure pour le désassembleur XDE. D'autres désassembleurs peuvent êtres utilisés mais le code qui accède au résultats doit être changé à plusieurs endroits (toutes les référentes à la variable disbuf). La procédure crypt_exec fournir une pile privée de 4Ko. Si vous utilisez votre propre procédure de déchiffrement ou désassembleur, faites attention à ne pas utiliser trops de place dans la pile. Si vous voulez allonger la pile locale, regardez à la variabel local_stk dans le code. __ #3 Dans la suite de cet article, je vais appeller ceci interchangeablement traçage ou procédure de déchiffrement. En fait, c'est une procédure de traçage avec du déchiffrement. ----[ 3.2 - Description de haut niveau. La procédure de traçage maintient deux contextes : le contexte du traçage et son propre contexte. Le contexte consiste en 8 registres d'ordre général et de drapeaux de 32-bits. Les autres registres ne sont pas modifiés par la procédure. Les deux contextes sont gérés sur la pile privée (qui est aussi appellée pour les fonctions C). L'idée est de récupérer, une à la fois, les instruction du programme tracé et de l'exécuter nativement. L'ensemble des instructions Intel ont un encodage assez irrégulier, c'est pourquoi on utilise le moteur du désassembleur XDE [5] pour retrouver à la fois le code de l'instruction et la taille totale de l'instruction. Pendant les expérimentation sous FreeBSD (qui utilise des instructions MOV préfixées par LOCK dans son chargeur dynamique), j'ai découvert un bug dans XDE qui est maintenant décrit et corrigé. Nous maintenons notre propre EIP dans traced_eip, arrondis inférieurement à la prochaines borne de 8-octets et ensuite, nous déchiffront#4 24 octets#5 dans notre propre buffer. Alors, le désassemblage a lieu et le contrôle est laissé aux procédures de l'émulateur via la table de contrôle des codes d'opération. Toutes les instructions, sauf celle de transfert de contrôle, sont exécutées nativement (dans le contexte tracé qui est restauré au moment approprié). Après l'exécution d'une seule instruction, le controle est retourné à nos fonctions de traçage. Pour éviter de perdre le contrôle, les instructions de transfert de contrôle#6 sont émulées. Le gros problème à été (jusqu'à ce que je le résolve) d'émuler les jmp indirects et les call's (qui peuvent apparaître avec toutes sortes de EA [NDT : Effective Adress - adresse effective ;)] complexes que i386 supporte). Le problème est résolu en remplacant les instructions call/jmp avec un MOV pour enregistrer le code de l'instruction, et de mofifier les bits 3-5 (reg field) de l'octet modR/M pour placer le registre cible (ce champ contien la partie du code de l'instruction dans le cas de CALL/JMP). Ensuite, nous laissons le processeur cacluler l'EA pour nous. Bien sûr, on a besoin d'un moyen pour arreter l'exécution chiffrée et de permettre au code chiffré d'appeller du code en clair : 1. Au commencement, le moteur de traçage dépile l'adresse de retour et ses arguments privés et re-empile l'adresse de retour dans la pile traçée. À cemoment : o La fenêtre de la pile est bonne pour exécuter une fonction C normale (F). o Le sommet du pointeur de pile (esp) est enregistré dans end_esp. 2. Quand les procédure du moteur de traçage rencontrent une instruction RET, elles vérifient d'abord traced_esp. Si c'est égal à end_esp, c'est un endroit où la fonction F aurait pu terminer. Nous restaurons donc le contexte tracé et n'émulons pas le RET, mais ls laissons s'exécuter nativement. De cette façon, les procédures de traçage perdent le contrôle et l'exécution normale reprend. Pour permettre au code chiffré d'appeller du code en clair, il y a les paramètres lo_addr et hi_addr. Ces paramètres déterminent les bornes inférieure et supérieure du code chiffré en mémoire. Si traced_eip tombe en debors de l'interval [lo_addr, hi_addr), le pointeur de procédure de déchiffrement est échangé avec celui de "déchiffrement" sans-opération qui recopie simplement 8 octets de la source vers la destination. Quand traced_eip retombe dans cet interval, le pointeur est de nouveau échangé. __ #4 La procédure de déchiffrement est appellée indirectement pour des raisons décrites plus loin. #5 Ce nombre vient en considératant le pire des cas : si une instruction commence qui est à une borne de 7 (modulo 8), avec un maximum de longueur d'instruction de 15 octets, ça donne un total de 22 octets = 3 blocs. Le buffer fait 32 octets pour pouvoir y mettre un JMP indirect additionnel après l'instruction tracée. Le JMP saute indirectement vers un endroit dans la procédure de traçage où l'exécution devrait continuer. #6 Les instructions INT ne sont pas considérées comme un transfert de contrôle. Après que (si) l'OS retourne de la trappe invoquée, l'exécution du programme continue séquentiellement, l'instruction juste après l'INT. Il n'y a donc aucune mesure supplémentaire qui devrait être prise. ----[ 3.3 - Exemple d'utilisation effective Une fois avec un moteur d'exécution chiffrée, comment le tester ? Pour celà, j'ai écrit un petit utilitaire appellé cryptfile qui chiffre une portion d'un fichier exécutable ($ est le prompt UNIX) : $ gcc -c cast5.c $ gcc cryptfile.c cast5.o -o cryptfile $ ./cryptfile USAGE: ./cryptfile <-e_-d> FILE KEY STARTOFF ENDOFF KEY MUST be 32 hex digits (128 bits). Les paramètres sont les suivants : o -e,-d : en mettre un est OBLIGATOIRE, ils signifient chiffrement [NDT : Encryption] et déchiffrement [NDT : Decryption]. o FILE : le fichier exécutable à chiffrer. o KEY : la clef de chiffrement. Elle doit être donnée par 32 chiffres hexadécimaux. o STARTOFF, ENDOFF : Le point de départ et de fin dans le fichier de la partie à chiffrer. Elles doiven têtre multiple de la taille des blocs (8 octets). Sinon, le fichier sera correctement chiffré, mais son exécution ne fonctionnera pas correctement. Tous le paquetage est testé sur un simple programme, test2.c. Ce programme démontre que les fonctions chiffrées peuvent appeler à la fois des fonctions chiffrées ou en clair ainsi que retourner des valeurs. Il démontre aussi que le moteur fonctionne, même en appellant des fonctions de librairies partagées. Maintenant, nous construisont le moteur d'exécution chiffrée : $ gcc -c crypt_exec.S $ cd xde101 $ gcc -c xde.c $ cd .. $ ld -r cast5.o crypt_exec.o xde101/xde.o -o crypt_monitor.o J'utilise un XDE patché. La dernière commande est là pour combiner quelques fichiers objets relogeables dans un seul fichier relogeable pour faciliter l'édition de lien avec d'autres programmes. Ensuite, nous procédons à la construction du programme de test. Nous devons d'abords nous assurer que les fonction que nous voulons chiffrer sont alignées sur 8 octets. Je spécifie ici 16, juste au cas où. Et donc : $ gcc -falign-functions=16 -g test2.c crypt_monitor.o -o test2 Nous voulons chiffrer les fonctions f1 et f2. Comment faisons-nous la correspondance entre l'offset dans le fichier et le nom des fonctions ? Heureusement, ça peut se résoudre pour ELF simplement avec l'outil readelf (c'est pour cela que j'ai choisi une façon si maladroite - je n'ai pas eu envie de me taper l'écriture d'un autre "parser" ELF). $ readelf -s test2 Symbol table '.dynsym' contains 23 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 08048484 57 FUNC GLOBAL DEFAULT UND printf 2: 08050aa4 0 OBJECT GLOBAL DEFAULT ABS _DYNAMIC 3: 08048494 0 FUNC GLOBAL DEFAULT UND memcpy 4: 08050b98 4 OBJECT GLOBAL DEFAULT 20 __stderrp 5: 08048468 0 FUNC GLOBAL DEFAULT 8 _init 6: 08051c74 4 OBJECT GLOBAL DEFAULT 20 environ 7: 080484a4 52 FUNC GLOBAL DEFAULT UND fprintf 8: 00000000 0 NOTYPE WEAK DEFAULT UND __deregister_frame.. 9: 0804fc00 4 OBJECT GLOBAL DEFAULT 13 __progname 10: 080484b4 172 FUNC GLOBAL DEFAULT UND sscanf 11: 08050b98 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 12: 080484c4 0 FUNC GLOBAL DEFAULT UND memset 13: 0804ca64 0 FUNC GLOBAL DEFAULT 11 _fini 14: 080484d4 337 FUNC GLOBAL DEFAULT UND atexit 15: 080484e4 121 FUNC GLOBAL DEFAULT UND scanf 16: 08050b98 0 NOTYPE GLOBAL DEFAULT ABS _edata 17: 08050b68 0 OBJECT GLOBAL DEFAULT ABS _GLOBAL_OFFSET_TABLE_ 18: 08051c78 0 NOTYPE GLOBAL DEFAULT ABS _end 19: 080484f4 101 FUNC GLOBAL DEFAULT UND exit 20: 08048504 0 FUNC GLOBAL DEFAULT UND strlen 21: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 22: 00000000 0 NOTYPE WEAK DEFAULT UND __register_frame_info Symbol table '.symtab' contains 145 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 080480f4 0 SECTION LOCAL DEFAULT 1 2: 08048110 0 SECTION LOCAL DEFAULT 2 3: 08048128 0 SECTION LOCAL DEFAULT 3 4: 080481d0 0 SECTION LOCAL DEFAULT 4 5: 08048340 0 SECTION LOCAL DEFAULT 5 6: 08048418 0 SECTION LOCAL DEFAULT 6 7: 08048420 0 SECTION LOCAL DEFAULT 7 8: 08048468 0 SECTION LOCAL DEFAULT 8 9: 08048474 0 SECTION LOCAL DEFAULT 9 10: 08048520 0 SECTION LOCAL DEFAULT 10 11: 0804ca64 0 SECTION LOCAL DEFAULT 11 12: 0804ca80 0 SECTION LOCAL DEFAULT 12 13: 0804fc00 0 SECTION LOCAL DEFAULT 13 14: 08050aa0 0 SECTION LOCAL DEFAULT 14 15: 08050aa4 0 SECTION LOCAL DEFAULT 15 16: 08050b54 0 SECTION LOCAL DEFAULT 16 17: 08050b5c 0 SECTION LOCAL DEFAULT 17 18: 08050b64 0 SECTION LOCAL DEFAULT 18 19: 08050b68 0 SECTION LOCAL DEFAULT 19 20: 08050b98 0 SECTION LOCAL DEFAULT 20 21: 00000000 0 SECTION LOCAL DEFAULT 21 22: 00000000 0 SECTION LOCAL DEFAULT 22 23: 00000000 0 SECTION LOCAL DEFAULT 23 24: 00000000 0 SECTION LOCAL DEFAULT 24 25: 00000000 0 SECTION LOCAL DEFAULT 25 26: 00000000 0 SECTION LOCAL DEFAULT 26 27: 00000000 0 SECTION LOCAL DEFAULT 27 28: 00000000 0 SECTION LOCAL DEFAULT 28 29: 00000000 0 SECTION LOCAL DEFAULT 29 30: 00000000 0 SECTION LOCAL DEFAULT 30 31: 00000000 0 SECTION LOCAL DEFAULT 31 32: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 33: 08050b54 0 OBJECT LOCAL DEFAULT 16 __CTOR_LIST__ 34: 08050b5c 0 OBJECT LOCAL DEFAULT 17 __DTOR_LIST__ 35: 08050aa0 0 OBJECT LOCAL DEFAULT 14 __EH_FRAME_BEGIN__ 36: 08050b64 0 OBJECT LOCAL DEFAULT 18 __JCR_LIST__ 37: 0804fc08 0 OBJECT LOCAL DEFAULT 13 p.0 38: 08050b9c 1 OBJECT LOCAL DEFAULT 20 completed.1 39: 080485b0 0 FUNC LOCAL DEFAULT 10 __do_global_dtors_aux 40: 08050ba0 24 OBJECT LOCAL DEFAULT 20 object.2 41: 08048610 0 FUNC LOCAL DEFAULT 10 frame_dummy 42: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 43: 08050b58 0 OBJECT LOCAL DEFAULT 16 __CTOR_END__ 44: 08050b60 0 OBJECT LOCAL DEFAULT 17 __DTOR_END__ 45: 08050aa0 0 OBJECT LOCAL DEFAULT 14 __FRAME_END__ 46: 08050b64 0 OBJECT LOCAL DEFAULT 18 __JCR_END__ 47: 0804ca30 0 FUNC LOCAL DEFAULT 10 __do_global_ctors_aux 48: 00000000 0 FILE LOCAL DEFAULT ABS test2.c 49: 08048660 75 FUNC LOCAL DEFAULT 10 f1 50: 080486b0 58 FUNC LOCAL DEFAULT 10 f2 51: 08050bb8 16 OBJECT LOCAL DEFAULT 20 key.0 52: 080486f0 197 FUNC LOCAL DEFAULT 10 decode_hex_key 53: 00000000 0 FILE LOCAL DEFAULT ABS cast5.c 54: 0804cba0 1024 OBJECT LOCAL DEFAULT 12 s1 55: 0804cfa0 1024 OBJECT LOCAL DEFAULT 12 s2 56: 0804d3a0 1024 OBJECT LOCAL DEFAULT 12 s3 57: 0804d7a0 1024 OBJECT LOCAL DEFAULT 12 s4 58: 0804dba0 1024 OBJECT LOCAL DEFAULT 12 s5 59: 0804dfa0 1024 OBJECT LOCALDEFAULT 12 s6 60: 0804e3a0 1024 OBJECT LOCAL DEFAULT 12 s7 61: 0804e7a0 1024 OBJECT LOCAL DEFAULT 12 sb8 62: 0804a3c0 3734 FUNC LOCAL DEFAULT 10 key_schedule 63: 0804b408 0 NOTYPE LOCAL DEFAULT 10 identity_decrypt 64: 08051bf0 0 NOTYPE LOCAL DEFAULT 20 r_decrypt 65: 08051be8 0 NOTYPE LOCAL DEFAULT 20 key 66: 08050bd4 0 NOTYPE LOCAL DEFAULT 20 lo_addr 67: 08050bd8 0 NOTYPE LOCAL DEFAULT 20 hi_addr 68: 08050bcc 0 NOTYPE LOCAL DEFAULT 20 traced_eip 69: 08050be0 0 NOTYPE LOCAL DEFAULT 20 end_esp 70: 08050bd0 0 NOTYPE LOCAL DEFAULT 20 traced_ctr 71: 0804b449 0 NOTYPE LOCAL DEFAULT 10 decryptloop 72: 08050bc8 0 NOTYPE LOCAL DEFAULT 20 traced_esp 73: 08051be4 0 NOTYPE LOCAL DEFAULT 20 stk_end 74: 0804b456 0 NOTYPE LOCAL DEFAULT 10 decryptloop_nocontext 75: 0804b476 0 NOTYPE LOCAL DEFAULT 10 .store_decrypt_ptr 76: 08051bec 0 NOTYPE LOCAL DEFAULT 20 decrypt 77: 0804fc35 0 NOTYPE LOCAL DEFAULT 13 insn 78: 08051bf4 0 NOTYPE LOCAL DEFAULT 20 disbuf 79: 08051be4 0 NOTYPE LOCAL DEFAULT 20 ilen 80: 080501f0 0 NOTYPE LOCAL DEFAULT 13 continue 81: 0804fdf0 0 NOTYPE LOCAL DEFAULT 13 control_table 82: 0804fc20 0 NOTYPE LOCAL DEFAULT 13 _unhandled 83: 0804fc21 0 NOTYPE LOCAL DEFAULT 13 _nonjump 84: 0804fc33 0 NOTYPE LOCAL DEFAULT 13 .execute 85: 0804fc55 0 NOTYPE LOCAL DEFAULT 13 _jcc_rel8 86: 0804fc5e 0 NOTYPE LOCAL DEFAULT 13 _jcc_rel32 87: 0804fc65 0 NOTYPE LOCAL DEFAULT 13 ._jcc_rel32_insn 88: 0804fc71 0 NOTYPE LOCAL DEFAULT 13 ._jcc_rel32_true 89: 0804fc6b 0 NOTYPE LOCAL DEFAULT 13 ._jcc_rel32_false 90: 0804fc72 0 NOTYPE LOCAL DEFAULT 13 rel_offset_fixup 91: 0804fc7d 0 NOTYPE LOCAL DEFAULT 13 _retn 92: 0804fca6 0 NOTYPE LOCAL DEFAULT 13 ._endtrace 93: 0804fcbe 0 NOTYPE LOCAL DEFAULT 13 _loopne 94: 0804fce0 0 NOTYPE LOCAL DEFAULT 13 ._loop_insn 95: 0804fcd7 0 NOTYPE LOCAL DEFAULT 13 ._doloop 96: 0804fcc7 0 NOTYPE LOCAL DEFAULT 13 _loope 97: 0804fcd0 0 NOTYPE LOCAL DEFAULT 13 _loop 98: 0804fcec 0 NOTYPE LOCAL DEFAULT 13 ._loop_insn_true 99: 0804fce2 0 NOTYPE LOCAL DEFAULT 13 ._loop_insn_false 100: 0804fcf6 0 NOTYPE LOCAL DEFAULT 13 _jcxz 101: 0804fd0a 0 NOTYPE LOCAL DEFAULT 13 _callrel 102: 0804fd0f 0 NOTYPE LOCAL DEFAULT 13 _call 103: 0804fd38 0 NOTYPE LOCAL DEFAULT 13 _jmp_rel8 104: 0804fd41 0 NOTYPE LOCAL DEFAULT 13 _jmp_rel32 105: 0804fd49 0 NOTYPE LOCAL DEFAULT 13 _grp5 106: 0804fda4 0 NOTYPE LOCAL DEFAULT 13 ._grp5_continue 107: 08050bdc 0 NOTYPE LOCAL DEFAULT 20 our_esp 108: 0804fdc9 0 NOTYPE LOCAL DEFAULT 13 ._grp5_call 109: 0804fdd0 0 NOTYPE LOCAL DEFAULT 13 _0xf 110: 08050be4 0 NOTYPE LOCAL DEFAULT 20 local_stk 111: 00000000 0 FILE LOCAL DEFAULT ABS xde.c 112: 0804b419 0 NOTYPE GLOBAL DEFAULT 10 crypt_exec 113: 08048484 57 FUNC GLOBAL DEFAULT UND printf 114: 08050aa4 0 OBJECT GLOBAL DEFAULT ABS _DYNAMIC 115: 08048494 0 FUNC GLOBAL DEFAULT UND memcpy 116: 0804b684 4662 FUNC GLOBAL DEFAULT 10 xde_disasm 117: 08050b98 4 OBJECT GLOBAL DEFAULT 20 __stderrp 118: 0804fc04 0 OBJECT GLOBAL HIDDEN 13 __dso_handle 119: 0804b504 384 FUNC GLOBAL DEFAULT 10 reg2xset 120: 08048468 0 FUNC GLOBAL DEFAULT 8 _init 121: 0804c8bc 364 FUNC GLOBAL DEFAULT 10 xde_asm 122: 08051c74 4 OBJECT GLOBAL DEFAULT 20 environ 123: 080484a4 52 FUNC GLOBAL DEFAULT UND fprintf 124: 00000000 0 NOTYPE WEAK DEFAULT UND __deregister_frame.. 125: 0804fc00 4 OBJECT GLOBAL DEFAULT 13 __progname 126: 08048520 141 FUNC GLOBAL DEFAULT 10 _start 127: 0804b258 431 FUNC GLOBAL DEFAULT 10 cast5_setkey 128: 080484b4 172 FUNC GLOBAL DEFAULT UND sscanf 129: 08050b98 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 130: 080484c4 0 FUNC GLOBAL DEFAULT UND memset 131: 080487c0 318 FUNC GLOBAL DEFAULT 10 main 132: 0804ca64 0 FUNC GLOBAL DEFAULT 11 _fini 133: 080484d4 337 FUNC GLOBAL DEFAULT UND atexit 134: 080484e4 121 FUNC GLOBAL DEFAULT UND scanf 135: 08050200 2208 OBJECT GLOBAL DEFAULT 13 xde_table 136: 08050b98 0 NOTYPE GLOBAL DEFAULT ABS _edata 137: 08050b68 0 OBJECT GLOBAL DEFAULT ABS _GLOBAL_OFFSET_TABLE_ 138: 08051c78 0 NOTYPE GLOBAL DEFAULT ABS _end 139: 08049660 3421 FUNC GLOBAL DEFAULT 10 cast5_decrypt 140: 080484f4 101 FUNC GLOBAL DEFAULT UND exit 141: 08048900 3421 FUNC GLOBAL DEFAULT 10 cast5_encrypt 142: 08048504 0 FUNC GLOBAL DEFAULT UND strlen 143: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 144: 00000000 0 NOTYPE WEAK DEFAULT UND __register_frame_info Nous voyons que la fonction f1 est à l'adresse 0x8048660 et de taille 75 = 0x4B. La fonction f2 est à l'adresse 0x80486B0 et de taille 58 = 3A. Un simple calcul montre qu'elles sont en fait consécutives en mémoire et donc, nous n'avons pas besoin de les chiffrer séparément mais d'un simple bloc depuis 0x8048660 jusqu'à 0x80486F0. $ readelf -l test2 Elf file type is EXEC (Executable file) Entry point 0x8048520 There are 6 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4 INTERP 0x0000f4 0x080480f4 0x080480f4 0x00019 0x00019 R 0x1 [Requesting program interpreter: /usr/libexec/ld-elf.so.1] LOAD 0x000000 0x08048000 0x08048000 0x06bed 0x06bed R E 0x1000 LOAD 0x006c00 0x0804fc00 0x0804fc00 0x00f98 0x02078 RW 0x1000 DYNAMIC 0x007aa4 0x08050aa4 0x08050aa4 0x000b0 0x000b0 RW 0x4 NOTE 0x000110 0x08048110 0x08048110 0x00018 0x00018 R 0x4 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .hash .dynsym .dynstr .rel.dyn .rel.plt .init .plt .text .fini .rodata 03 .data .eh_frame .dynamic .ctors .dtors .jcr .got .bss 04 .dynamic 05 .note.ABI-tag À partir de ceci, nous voyons que ces deux adresses (0x8048660 et 0x80486F0) tombent dans le premier segment LOAD qui est localisé à VirtAddr 0x804800 et à l'offset 0 dans le fichier. Pour correspondre l'adresse virtuelle avec l'offset dans le fichier, nous soustrayons donc simplement 0x8048000 de chaque adresse, ce qui nous donne 0x660 = 1632 et 0x6F0 = 1776. Si vous avez ELFsh[2], alors, vous pouvez rendre les choses encore plus faciles. L'exemple suivant montre comment ELFsh peut être utilisé pour obtenir les mêmes informations : $ elfsh Welcome to The ELF shell 0.51b3 .::. .::. This software is under the General Public License .::. Please visit http://www.gnu.org to know about Free Software [ELFsh-0.51b3]$ load test2 [*] New object test2 loaded on Mon Jun 13 20:45:33 2005 [ELFsh-0.51b3]$ sym f1 [SYMBOL TABLE] [Object test2] [059] 0x8048680 FUNCTION f1 size:0000000075 foffset:001632 scope:Local sctndx:10 => .text + 304 [ELFsh-0.51b3]$ sym f2 [SYMBOL TABLE] [Object test2] [060] 0x80486d0 FUNCTION f2 size:0000000058 foffset:001776 scope:Local sctndx:10 => .text + 384 [ELFsh-0.51b3]$ exit [*] Unloading object 1 (test2) * Good bye ! .::. The ELF shell 0.51b3 Le champ foffset fournis l'offset du symbole dans l'exécutable, alors que size donne sa taille. Ici, tous les nombres sont décimaux. Maintenant, nous sommes prèts à chiffrer une partie de l'exécutable avec un mot de passe très "imaginatif" et ensuite, de le tester : $ echo -n "password" | openssl md5 5f4dcc3b5aa765d61d8327deb882cf99 $ ./cryptfile -e test2 5f4dcc3b5aa765d61d8327deb882cf99 1632 1776 $ chmod +x test2.crypt $ ./test2.crypt Au prompt, entrez la même chaine hexadécimale et entrez alors les nombres 12 et 34 pour a et b. Le résultat doit être 1662 et esp avant et après doit être le même. Une fois que vous êtes sûr que le programme fonctionne correctement, vous pouvez le strip(1)er [NDT : épurer, pour virer les symboles]. ----[ 3.4 - Bug d'XDE Pendant le développement, j'ai trouvé un bug dans le désassembleur XDE : il ne gère pas correctement le préfixe LOCK (0xF0). À cause de ce bug, XDE dit que 0F0 est une instruction sur un octet. Voici le patch nécessaire pour corriger le désassembleur [NDT : en français, c'est cadeau] : --- xde.c Sun Apr 11 02:52:30 2004 +++ xde_new.c Mon Aug 23 08:49:00 2004 @@ -101,6 +101,8 @@ if (c == 0xF0) { if (diza->p_lock != 0) flag |= C_BAD; /* deux fois */ + diza->p_lock = c; + continue; } break; J'ai aussi eu besoin de retirer __cdecl des fonctions, une "fonctionnalité" des compilateurs C Win32 qui n'est pas nécessaire sur les plateformes UNIX. ----[ 3.5 - Limitations o Le moteur d'XDE ne peut (probablement) pas gérer les nouvelles instructions (SSE, MMX, etc.). Il ne peut certainement pas gérer 3dNow! parce qu'elles commencent par 0x0F 0x0F, une séquence d'octets pour laquelle XDE dis que c'est un encodage d'instruction invalide. o Le traceur partage la même mémoire que le tracé. Si le tracé est si mal foutu qu'il écrit vers des zones (aléatoire) de mémoire qu'il ne possède pas, il peut tomber par hasard et écraser une portion des procédures de traçage. o Chaque forme de traçage a son impact sur la vitesse. Je ne'ai pas encore beaucoup mesuré pour l'instant comment ça ralentis l'exécution du programme (et surtout comparativement à ptrace()). o Ne gère pas encore toutes les instructions 386 (surtout pour les calls/jumps et RET imm16). Dans ce cas, le tracé s'arrete avec HLT ce qui devrait causer GPF sous n'importe quel OS qui lance les processus autre part qu'en ring 0 o La taille de bloc de 8 octet est codée carrément à plusieur endroits du code. Le source (à la fois C et ASM) devrait être paramétré avec une certaine sorte de BLOCKSIZE #define. o La procédure de traçage n'est pas ré-entrante ! Ce qui veut dire que n'importe quel code exécuté sous crypt_exec ne peut pas rappeler crypt_exec parce qu'il va écraser son propre contexte ! o Le code lui-même n'est pas optimal : - identity_decrypt pourrait utiliser des mov sur 4 octets - plus de registres pourraient être utiliser pour minimaliser les références en mémoire. ----[ 3.6 - Considérations de portage C'est aussi dur que ça se voit - il n'y a pas un seul morceau de code indépendant de la machine dans la procédure principale qui pourrait être utilisée sur une autre architecture de processeur. Je pense que le portage ne devrait pas être si difficile, surtout de la réécriture de méchanismes du programme courant. Certains points à regarder sont les suivants : o Être sur de gérer tout les contrôles de flux d'instructions. o Les instructions move peuvent affecter les flags du processeur. o Êcrire une procédure de désassemblage. La plupart des architectures RISC ont un ensemble régulier d'instructions et devrait être plus facile à désassembler que du code x86. o Voici du code auto-modifiable : vider la file d'instruction anticipées pourrait être nécessaire. o Gérer les sauts et chargements retardés si l'architecture les fournsi. Ça pourraît être du bidouillage. o Vous pourriez avoir besoin de passer outre les protections de pages avant d'appeller de déchiffreur (segment de donnée non exécutable). À cause de la non disponiblité d'harware non-x86, je n'ai pas pu implémenter de déchiffreur pour un autre type de processeur. --[ 4 - Perspectives o Un meilleur algo de chiffrement. Le mode ECB est mauvais, surtout avec des petits blocs de 8 octets. Des alternatives possibles pourraient être les suivantes : 1. Arrondir traced_eip inférieurement à un multiple de 8 octets 2. Chiffrer le résultat avec la clef. 3. Xorer le résultat avec les octets de l'instruction. De cette façon, le chiffrement dépend de l'endroit en mémoire. Le déchiffrement fonctionne de la même façon. Cependant, il serait compliquerait le programme cryptfile.c. o Chiffrer les données. Concevoir une façon transparente (pour le programmeur C) d'accéder aux données chiffrées. Au moins deux approches viennent à l'esprit : 1) jouer avec les pages mémoires et gérer les erreurs de lecture/écritures ou 2) utiliser XDE pour décoder tous les accès mémoires et faire un déchiffrement ou chiffrement, en fonction tu type d'accès (lecture ou écriture). La première approche semble être trop lente (plein de changement de contexte par lecture de donnée) pour être pratiquable. o Nouveau jeux d'instruction et architecture. Étendre XDE pour gérer les nouvelles instructions x86. Porter les procédures vers d'autres architectures qu'i386 (les premièrs à l'esprit sont AMD64, puis ARM, SPARC...). o Effectuer le déchiffrement sur des cartes à puce. C'est lent, mais il n'y a pas de danger de compromission de la clef. o Moteur de déchiffrement polymorphique. ----[ 5 - Traveaux relatifs. Cette section fourni un bref aperçu des traveaux existants, soit parce qu'ils sont similaires dans leur technique de codage (ELFsh et le traçage sans ptrace) ou pour leur aspect de protection de code. 5.1 ELFsh --------- L'article de l''équipe d'ELFsh sur elfsh et e2dbg [7], aussi dans cette édition de phrack. Un point commun entre nos travaux est l'approche du traçage de programme sans utiliser ptrace(2). Leur dernier travail est un débuffer ELF embarqué et scriptable, e2dbg. Ils sont aussi en train de passer outre les protections PaX, une possibilité que je n'ai pas prise en compte. 5.2 Shiva --------- Le chiffreur de binaire Shiva [8], fourni uniquement sous forme binaire. Il essaye vraiment très fort d'éviter le reverse engineering en incluant des fonctionnalités comme la détection du drapeau de trap, les défenses ptrace(), les blocs demandé-chargés (pour que l'image complètement déchiffrée ne puisse pas être copiée via /proc), l'utilisation d'int3 pour émuler certaines instruction, et par un niveau de chiffrement. Le 2ème niveau, protégé par mot de passe, est optionnel et chiffré en utilisant de l'AES 128-bits. Le niveau 3 utilise TEA, l'algorithme minuscule de chiffrement [NDT : tiny encryption algorithm]. D'après l'analyse dans [9], "pour des programmes suffisement grands, pas plus d'un tiers du programme ne sera déchiffré à n'importe quel moment"[NDT]. C'est BEAUCOUP PLUS de code déchiffré que dans mon cas : 24 octets, indépendant de tout facteur extérieur. Shiva est aussi très lié au format ELF, alors que ma méthode n'est liée à aucun système d'exploitation ou de format d'exécutable (bien que le code IS actuel est limité aux architectures 32-bits x86). [NDT : "for sufficiently large programs, no more than 1/3 of the program will be decrypted at any given time"] 5.3 Burneye ----------- Il y a en fait deux outils publiés par team-teso : burneye et burneye (objobf) [10]. Burneye est un outil de chiffrement de binaire très puissant. Similaire à Shiva, il a trois niveaux : 1) Camouflage [NDT : obfuscation]. 2) Chiffrement basé sur mot de passe utilisant RC4 et SHA1 (pour générer la clef d'après une passe-phrase). 3) Le niveau de prise d'emprunte. Le niveau de prise d'empruntes est le plus intéréssant : les données à propos du système cible sont collectées (par exemple, la quantité de mémoire, etc...) et condensées dans une "emprunte digitale". L'exécutable est chiffré en prenant en compte l'emprunte pour que le binaire résultant ne puisse-être utilisé que sur l'hôte correspondant à l'emprunte. Il y a deux options de prise d'emprunte : o Une tolérance à l'emprunte peut-être spécifiée pour que des petites déviations soient permises. De cette façon, par exemple, la mémoire peut-être augmentée sur le système cible et l'exécutable fonctionne toujours. Si le nombre de différences est trop grand, il ne fonctionne plus. o Sceller : le programme produit avec cette option va fonctionner sur n'importe quel système. Cependant, la première fois qu'il se lance, il crée une emprunte de l'hôte et se "scelle" à cet hôte. Le binaire original est supprimé par sécurité après coup. Le binaire chiffré peut aussi se supprimer lui-même quand une certaine variable d'environnement est placée pendant l'exécution du programme. objobf est juste un camoufleur d'objet relogeables. Il n'y a pas de niveau de chiffrement. L'entrée est un objet relogeable ordinaire et la sortie est un code transformé, camouflé et fonctionnellement équivalent [NDT : "homologue fonctionnel" si on était en biologie :p]. Les transformations de codes incluent : insertion d'instructions inutiles, randomisation de l'ordre de blocs de base et division de blocs basiques à des endroits aléatoires. 5.4 Conclusion -------------- Voici les principalles fonctionnalités distinctives de la technique de chiffrement de code présentée ici : o Une très petite quantité de code en clair en mémoire à chaque instant - seulement 24 octets. Les autres outils laissent beaucoup plus de code en clair en mémoire. o Aucune manipulation de chargeur ou de format d'exécutable ne sont nécessaires. Il y a un simple outil qui chiffre le code déjà en place. Il est indépendant du format d'exécutable puisque ses arguments sont l'offset de la fonction dans l'exécutable (qui correspond à l'adresse de la fonction à l'exécution). o Le code est lié à l'architecture x86 32bits, cependant, il devrait être portable sans changement pour les autres systèmes d'exploitations tournant sous x86-32. Des arrangements spéciaux pour protéger les pages sont nécessaires si PaX ou NX sont activés. L'inconvénient est que la version actuelle du moteur est très vulnérable au reverse-engineering. il petu être reconnu très facilement en scannant à la recherche de séquences fixes d'instructions (les procédures de chiffrement). Une fois que le déchiffreur est localisé, il est facile de surveiller quelques adresses mémoires fixées pour obtenir à la fois EIP et l'instruction originale à cet EIP. La clef est facilement obtenue, mais c'est le cas de toutes les approches où la clef reste en mémoire. Cependant, le déchiffreur dans sa version actuelle a un avantage : puisque c'est du code ordinaire qui ne fait pas de trucs spéciaux, il peut être facilement combiné avec un outil plus résistant au reverse-engineering, comme Shiva ou Burneye. ----[ 6 - References 1. Phrack magazine : http://www.phrack.org 2. tutoriaux ptrace : http://linuxgazette.net/issue81/sandeep.html http://linuxgazette.net/issue83/sandeep.html http://linuxgazette.net/issue85/sandeep.html 3. D. E. Knuth: The Art of Computer Programming, vol.1: Fundamental Algorithms. 4. Fenris. http://lcamtuf.coredump.cx/fenris/whatis.shtml 5. XDE. http://z0mbie.host.sk 6. Code source du programme décrit. Le source que j'ai écrit ici est sous licence MIT [NDT : en fait, il s'agit de la licence X11]. Les autres fichiers ont des licences différentes. L'archive contient aussi une version patchée de XDE. http://www.core-dump.com.hr/software/cryptexec.tar.gz 7. ELFsh, the ELF shell. Un programme puissant de manipulation de fichier ELF. http://elfsh.devhell.org 8. Shiva binary encryptor. http://www.securereality.com.au 9. Reverse Engineering Shiva. http://blackhat.com/presentations/bh-federal-03/bh-federal-03-eagle/ bh-fed-03-eagle.pdf 10. Burneye et Burneye2 (objobf). http://packetstormsecurity.org/groups/teso/indexsize.html ----[ 7 - Crédits Mes remerciements vont à mayhem qui a relu cet article. Ses suggestions ont été très utiles, rendant ce texte beaucoup plus mature que l'original. --[ A - Appendice : code source Ici, je ne fourni que mon propre code source. Le source complet peut être obtenu à partir de [6]. Il inclu : o Tous les sources listés ici, o le désassembleur XDE patché et o le source de l'algorithme cryptographique CAST5. ----[ A.1 - The tracer source: crypt_exec.S [NDT : dans la suite, "insn" signifie "instruction"] /* Copyright (c) 2004 Zeljko Vrba Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* [ NDT : voici une tracution trouvée là : http://wiki.pps.jussieu.fr/twiki/pub/Main/MitLicence/LicenceMIT.pdf ] Copyright (c) 2004 Zeljko Vrba Par la présente, la permission est accordée, libre de charge, à n'importe quelle personne obtenant une copie de ce logiciel et les fichiers de documentation associés (le Logiciel), de distribuer le Logiciel sans restriction, incluant sans limitation des droits d'utilisation, de copie, de modification, de fusion, de publication, de distribution, de sous licence, et de vente de copies de ce logiciel, et permet aux personnes à qui le logiciel est fourni d'être soumises aux conditions suivantes : La notification de copyright ci-dessus et cette notification de permission seront incluses dans toutes les copies ou parties substantielles du logiciel. LE LOGICIEL EST FOURNI "TEL QUEL", SANS GARANTIE D?AUCUNE SORTE, EXPLICITE OU IMPLICITE, INCLUANT SANS LIMITES LES GARANTIES DU COMMERCE, SOUS UNE FORME CONVENABLE POUR UN BUT PARTICULIER ET SANS INFRACTION. EN AUCUN CAS LES AUTEURS OU LES SUPPORTS DE COPYRIGHT NE SERONT RESPONSABLES D'EVENTUELLES RECLAMATIONS, DOMMAGES OU AUTRES RESPONSABILITÉS SI, DANS LE CADRE DU CONTRAT, DES ACTES DÉLICTUEUX OU AUTRES SURVIENNENT, AVEC OU SANS RAPPORT AVEC LE LOGICIEL OU SON UTILISATION OU D'AUTRES AGISSEMENTS EN RAPPORT AVEC LE LOGICIEL. */ .text /************************************************************************ * void *crypt_exec( * decrypt_fn_ptr dfn, const void *key, * const void *lo_addr, const void *hi_addr, * const void *addr, ...) * typedef (*decrypt_fn_ptr)( * void *key, unsigned char *dst, const unsigned char *src); * * - dfn est un pointeur vers descripteur de fonction * - key est un pointeur vers les données de la clefs du chiffrement * - addr est l'adresse où l'exécution devrait commencer. À cause de la * manière que le code est déchiffré et exécuté, ça DOIT être aligné sur * 8 octets (BLOCKSIZE) !! * - Le reste sont les arguments de la fonction appellée. * * crypt_exec s'arrete quand le pointeur de pile devient égal à sa * valeur en entrée, et exécuter "ret" fera quitter la fonction appellée. * Ce travail assume un code C compilé normalement. * * Retourne la valeur que la fonction aurait normalement retourné. * * Ce code appelle : * int xde_disasm(unsigned char *ip, struct xde_instr *outbuf); * XDE disassembler engine is compiled and used with PACKED structure! * * On assume que l'algorithme de chiffrement utilise des blocs de 64bits. * Une très bonne protection serait d'exécuter le déchiffrement sur * des cartes à puce. * * Un peut de terminologie : * "Tracé" réfère au programme original en train d'être exécuté * instruction par instruction. La technique utilisée ressemble aux * procédures de traçage de Knuth (et en fait, on fait du vrai traçage * quand on retire le déchiffrement). * * "notre" réfère à nos donnée de pile, etc. * * TODO et limitations : * - certaines instructions ne sont pas émulées * (FAR CALL/JMP/RET, RET NEAR imm16) * - les codes instruction de LOOP* et JCXZ n'ont pas été testés * - _jcc_rel32 a été testé uniquement indirectement par _jcc_rel8 ***********************************************************************/ /* Offsets dans la structure xde_instr. */ #define OPCODE 23 #define OPCODE2 24 #define MODRM 25 /* Installe notre pile et sauve le contexte tracé. Le contexte est sauvé à la fin de notre pile. */ #define SAVE_TRACED_CONTEXT \ movl %esp, traced_esp ;\ movl $stk_end, %esp ;\ pusha ;\ pushf /* Restaure le contexte tracé à partir du sommet courant de la pile. Après cela, restaure le pointeur de pile. */ #define RESTORE_TRACED_CONTEXT \ popf ;\ popa ;\ movl traced_esp, %esp /* La procédure de chiffrement "identité". Elle recopie juste 8 octets (BLOCKSIZE) de la source vers la destination. Avec une convention d'appel du C normal. N'est pas globale. */ identity_decrypt: movl 8(%esp), %edi /* adresse de destination */ movl 12(%esp), %esi /* adresse address */ movl $8, %ecx /* 8 octets */ cld rep movsb ret crypt_exec: .globl crypt_exec .extern disasm /* Récupère tous les arguments. Nous sommes appelés à partir du C et nous ne sommes pas sensé sauvegarder les registres. Voici la pile à l'entrée : [ ret_addr dfn key lo_addr hi_addr addr ...args ] */ popl %eax /* adresse de retour */ popl r_decrypt /* pointeur de fonction de déchiffrement réelle */ popl key /* clef de chiffrement */ popl lo_addr /* eip min */ popl hi_addr /* eip max */ popl traced_eip /* eip vers le début du traçage */ pushl %eax /* remet l'adresse de retour sur la pile */ /* Maintenant, le schéma de la pile est comme si la fonction (commencant à traced_eip) avait été appellée normalement d'après les conventions du C (l'arguments vararg suit l'adresse de retour). */ movl %esp, end_esp /* C'est utilisé pour arreter le traçage */ movl $0, traced_ctr /* remet à 0 le conteur d'insns */ decryptloop: /* Cette boucle trace une simple instruction. Le CONTEXTE au débu de chaque itération : traced_eip : pointe vers la prochaine instruction du programme tracé D'abord, nous échangeons les piles pour avoir la notre et enregistront les registres du programme tracé incluant eflags. Les instructions sont chiffrées en mode ECB par bloc de 8 octets. Ainsi donc, nous devons toujours commencer le déchiffrement à la borne inférieur de 8 octets. Le total de trois blocs (24 octets) sont déchiffrés pour une instruction. C'est du à des contraintes d'alignement et de longueur max d'instruction : si l'instruction commence à l'adresse qui est congruente à 7 modulo 8 + 16 octets de taille max (avec une petite marge) fourni une envergure d'instruction de trois blocs. Oui, je sais que ECB sucks, mais c'est en fait juste une preuve de concept. Concevez quelque chose de mieux vous-même si vous en avez besoin. */ SAVE_TRACED_CONTEXT decryptloop_nocontext: /* Ce point d'entrée de boucle ne sauge pas le contexte tracé. Il est utilisé par l'émulation des instructions de transfert de contrôle où nous faisons tout le travail nous-même et n'avons pas besoin de contexte tracé. Le CONTEXTE à ce point d'entrée est le même que pour decryptloop. D'abord, décider si nous déchiffrons ou traçons juste le code en clair. */ movl traced_eip, %eax movl $identity_decrypt, %ebx /* assume aucun déchiffrement */ cmpl lo_addr, %eax jb .store_decrypt_ptr /* traced_eip < lo_addr */ cmpl hi_addr, %eax ja .store_decrypt_ptr /* traced_eip > hi_addr */ movl r_decrypt, %ebx /* dans l'interval, on déchiffre */ .store_decrypt_ptr: movl %ebx, decrypt /* Déchiffre trois blocs commencant à eax, ré-utilisant les arguments sur la pile pour un total de trois appels. ATTENTION ! Pour que ça fonctionne correctement, la fonction de déchiffrement NE PEUT PAS modifier ses arguments ! */ andl $-8, %eax /* arrondis en bas le traced_eip sur 8 octets */ pushl %eax /* buffer src */ pushl $insn /* buffer dst */ pushl key /* pointeur vers la clef */ call *decrypt /* 1er block */ addl $8, 4(%esp) /* avance dst */ addl $8, 8(%esp) /* avance src */ call *decrypt /* 2ème block */ addl $8, 4(%esp) /* avance dst */ addl $8, 8(%esp) /* avance src */ call *decrypt /* 3ème block */ addl $12, %esp /* libère les arguments de la pile */ /* Obtien le point réel de début d'instruction dans le buffer déchiffré. traced_eip est pris modulo la taille de bloc (8) et ajouté au début de l'adresse du buffer déchiffré. Alors on appelle XDE (appel conventionnel du C) pour récupérer les informations nécessaires sur l'instructions. */ movl traced_eip, %eax andl $7, %eax /* traced_eip mod 8 */ addl $insn, %eax /* offset dans le buffer déchiffré */ pushl $disbuf /* address vers laquel désassembler */ pushl %eax /* offset d'insn pour désassembler */ call xde_disasm /* désassemble et retourne len */ movl %eax, ilen /* enregistre la taille de l'instruction */ popl %eax /* déchiffre le début d'insn */ popl %ebx /* libère les arguments restants de la pile */ /* Calcule l'offset dans la table de contrôle des procédure de gestion de sinstructions. Les instructions autres que de transfert de contrôle sont juste exécutée dans le contexte tracé, les autres sont émulées. Avant d'exécuter les instructions, l'eip tracé est avancé d'une instruction et le nombre d'instructions exécutées est incrémenté. Nous ajoutons aussi un "jump *continue" indirecte après l'exécution, pour continuer l'exécution au bon endroit de notre code. Les codes instruction du JMP indirect sont 0xFF 0x25. */ movl ilen, %ebx addl %ebx, traced_eip /* avance l'eip tracé */ incl traced_ctr /* incrémente le conteur */ movw $0x25FF, (%eax, %ebx) /* JMP indirecte; little-endian! */ movl $continue, 2(%eax, %ebx) /* enregistre l'adresse */ movzbl OPCODE+disbuf, %esi /* charge l'octet d'instruction */ jmp *control_table(,%esi,4) /* exécuté par le gestionnaire approprié */ .data /* Les procédures d'émulation commencent ici. Elles sont dans le segment de donnée parce que le segment de code n'est pas en écriture et nous sommes en train de modifier notre propre code. Nous n'avons pas encore envie de trafiquer avec mprotect(). Un jour (le support de tables de pages non-exécutables sur x86-64) ça sera fait de toute façon... Le CONTEXTE à l'entrée de chaque procédure d'émulation : eax : début de l'adresse d'insn déchiffrée (COURANTE) à exécuter ilen : longueur de l'instruction en octets stack top -> [tracé: eflags edi esi ebp esp ebx edx ecx eax] traced_esp : esp du programme original traced_eip : eip de la prochaine insn à exécuter (PAS l'insn COURANTE !) */ _unhandled: /* Les codes d'instructions non gérées générées par le compilateur. Une fois que les procédures d'émulations sont écrites, elles seront gérées :) Exécuter une instruction privilégiée, comme HLT, est la façon la plus facile de terminer le programme. %eax garde l'adresse de l'instruction que nous sommes en train de tracé, ça peut donc être récupéré par le débugger. */ hlt _nonjump: /* Émulation commune pour toutes les instructions autre que de transfert de contrôle. Le buffer d'instruction (insn) est déjà rempli par des blocks déchiffrés. Les instructions déchiffrées peuvent commencer au milieur du buffer insn, donc, l'instruction jmp est ajustée pour sauter à l'insn en évitant l'inutile au début d'insn. Quand l'instruction est exécutée, notre exécution continue à l'endroit pointé par "continue". Normalement, c'est decryptloop, mais occasionnellement, ça peut être temporairement changé (par exemple dans _grp5). */ subl $insn, %eax /* insn commence dans le buffer insn */ movb %al, .execute+1 /* actualise l'instruction jmp */ RESTORE_TRACED_CONTEXT .execute: jmp insn /* relatif, il n'y a que l'offset de mis à jour */ insn: .fill 32, 1, 0x90 _jcc_rel8: /* Saut conditionnel relatif sur 8-bits. Il est géré par un saut relatif de 32-bits, une fois que l'offset est ajusté. Le code d'instruction doit aussi être ajusté : les courts sauts sont 0x70-0x7F, les longs sont 0x0F 0x80-0x8F. (les conditions correspondent directement). Convertir les courts en longs demande d'ajouter 0x10 au deuxième code d'instruction. */ movsbl 1(%eax), %ebx /* charge l'offset signé et étendu */ movb (%eax), %cl /* charge l'instruction */ addb $0x10, %cl /* ajuste le code à la forme longue */ /* On laisse le calcul à _jcc_rel32 en tant que saut sur 32-bits */ _jcc_rel32: /* Émule un saut relatif conditionnel sur 32-bits. On pop les drapeaux tracés, laissons l'instruction Jcc s'exécuter nativement, et ensuite, ajustons l'eip tracé nous-même, en fonction de si Jcc a eu lieu ou pas. CONTEXTE : ebx : offset de saut, signé et étendu sur 32 bits cl : deuxième code d'instruction réel de l'instruction (d'abord, on a 0x0F comme échapement). */ movb %cl, ._jcc_rel32_insn+1 /* sauve le code en instruction */ popf /* restaure les drapeaux tracés */ ._jcc_rel32_insn: /* Codage explicite du saut conditionnel relatif sur 32-bits. Il est exécuter avec les drapeaux tracés. L'offset du saut (32-bits) est aussi fourni. */ .byte 0x0F, 0x80 .long ._jcc_rel32_true - ._jcc_rel32_false ._jcc_rel32_false: /* La condition du Jcc était fausse. On sauvegarde les drapeaux tracés et on continue avec l'instruction suivante. */ pushf jmp decryptloop_nocontext ._jcc_rel32_true: /* La condition du Jcc était vraie. Les drapeaux tracés sont sauvegardés, et ensuite, l'exécution tombe dans la procédure commune d'ajustement d'offset de eip. */ pushf rel_offset_fixup: /* Point d'entrée commun pour régler eip pour les instructions de contrôle de flux relatifs. CONTEXTE: traced_eip : déjà avancé à l'instruction qui devrait être la suivante. C'est fait dans decrypt_loop avant de transferer le contrôle à un qualconque gestionnaire d'instruction. ebx : offset signé et étendu sur 32-bits à ajouter à eip */ addl %ebx, traced_eip jmp decryptloop_nocontext _retn: /* retour proche (sans imm16). C'est l'endroit où la fin de trace est vérifiée. Si, à ce point, esp égal end_esp, ceci veut dire que crypt_exec retournerais à son appellant. */ movl traced_esp, %ebp /* compare esp avec l'ancien */ cmpl %ebp, end_esp /* quand l'appellant de crypt_exec retourne */ je ._endtrace /* l'adresse était au sommet de la pile */ /* Pas égaux, émule le ret. */ movl %esp, %ebp /* sauve notre pile courante */ movl traced_esp, %esp /* récupère la pile tracée */ popl traced_eip /* pop l'adresse de retour */ movl %esp, traced_esp /* ré-écrit la pile tracée */ movl %ebp, %esp /* restaure notre pile courante */ jmp decryptloop_nocontext ._endtrace: /* Ici, le contexte tracé est complètement restauré et RET est exécuté nativement. Notre procédure de traçage n'a plus le contrôle après le RET. D'après les conventions d'appel du C, l'appellant de crypt_exec reçoit la valeur de retour de la fonction tracée. Un détail auquel faire attention : la pile ressemble maintenant à : stack top -> [ ret_addr ...args ] Mais nous l'avons appelée comme ceci : stack top -> [ ret_addr dfn key lo_addr hi_addr addr ...args ] et c'est ce que le compilateur attent avant de dépiler la liste des arguments. Nous devons donc régler la pile. Le pointeur de pile peut être simplement ajusté par -20 au lieu de reconstruire l'état précédent parce que les fonctions C sont libres de modifiers leurs arguments. CONTEXTE : CONTEXT: ebp: esp tracé actuel */ movl (%ebp), %ebx /* adresse de retour */ subl $20, %ebp /* 5 faux arguments */ movl %ebx, (%ebp) /* mettre l'adresse de retour au sommet de la pile */ movl %ebp, traced_esp /* enregistre la pile ajustée */ RESTORE_TRACED_CONTEXT ret /* retourne sans garder le contrôle */ /* Les instructions LOOPNE, LOOP et LOOP sont exécutées à partir du gestionnaire commun (_doloop). Il n'y a que le code d'instruction qui est écrit par des gestionnaires séparés. 28 est l'offset du registre ecx tracé qui est sauvegardé sur notre pile. */ _loopne: movb $0xE0, ._loop_insn /* code de loopne */ jmp ._doloop _loope: movb $0xE1, ._loop_insn /* code de loope */ jmp ._doloop _loop: movb $0xE2, ._loop_insn /* code de loop */ ._doloop: /* * Récupère le contexte tracé qui est utile pour l'exécution de * LOOP* : l'offset signé, l'ecx tracé et les drapeaux tracés. */ movsbl 1(%eax), %ebx movl 28(%esp), %ecx popf ._loop_insn: /* Codage explicite de l'instruction de boucle et de l'offset. */ .byte 0xE0 /* code de LOOP* : E0, E1, E2 */ .byte ._loop_insn_true - ._loop_insn_false ._loop_insn_false: /* La condition de LOOP* est fausse. Ne sauvegarder que le contexte modifié (drapeaux et ecx) et continuer le traçage. */ pushf movl %ecx, 28(%esp) jmp decryptloop_nocontext ._loop_insn_true: /* La condition de LOOP* est vraie. Sauvegarder que le contexte modifé et sauter à rel_offset_fixup pour régler l'eip tracé. */ pushf movl %ecx, 28(%esp) jmp rel_offset_fixup _jcxz: /* JCXZ. C'est plus facile à simuler qu'a exécuter nativement. */ movsbl 1(%eax), %ebx /* prend l'offset signé */ cmpl $0, 28(%esp) /* test si ecx tracé vaut 0 */ jz rel_offset_fixup /* si oui, regle l'EIP tracé */ jmp decryptloop_nocontext _callrel: /* CALL relatif. */ movb $1, %cl /* 1 pour indiquer un appel relatif */ movl 1(%eax), %ebx /* prend l'offset */ _call: /* Émulation du CALL. CONTEXTE : c1 : indicateur relatif/absolu. ebx : adresse absolue (cl==0) ou offset relatif (cl!=0). */ movl %esp, %ebp /* sauve notre pile */ movl traced_esp, %esp /* empile l'eip tracé */ pushl traced_eip /* la pile tracée */ movl %esp, traced_esp /* re-écri la pile tracée */ movl %ebp, %esp /* restaure notre pile */ testb %cl, %cl /* si pas zéro, alors c'est un */ jnz rel_offset_fixup /* call relatif */ movl %ebx, traced_eip /* sauver l'eip distant */ jmp decryptloop_nocontext /* continue l'exécution */ _jmp_rel8: /* JMP relatif sur 8-bits. */ movsbl 1(%eax), %ebx /* prend l'offset signé */ jmp rel_offset_fixup _jmp_rel32: /* JMP relatif sur 32-bits. */ movl 1(%eax), %ebx /* prend l'offset */ jmp rel_offset_fixup _grp5: /* C'est le cas pour le code 0xFF qui échape le GRP5 : le code instruction réel est caché dans les bits 5, 4 et 3 de l'octet modR/M. */ movb MODRM+disbuf, %bl /* prend l'octet modRM */ shr $3, %bl /* déplace les bits 3-5 vers 0-2 */ andb $7, %bl /* et test seulement les bits 0-2 */ cmpb $2, %bl /* < 2, pas de transfert de contrôle*/ jb _nonjump cmpb $5, %bl /* > 5, pas de transfert */ ja _nonjump cmpb $3, %bl /* CALL lointain */ je _unhandled cmpb $5, %bl /* JMP lointain */ je _unhandled movb %bl, %dl /* pour des références futures */ /* modR/M égal 2 ou 4 (CALL ou JMP proche). Dans ce cas, le champ reg de modR/M (bits 3-5) est une partie du code instruction. Remplace l'octet d'instruction 0xFF par 0x8B (MOV r/m32 vers lecode reg32). Remplace le champt reg avec 3 (l'index de registre ebx). */ movb $0x8B, (%eax) /* remplace par le code de MOV_to_reg32 */ movb 1(%eax), %bl /* prend l'octet modR/M */ andb $0xC7, %bl /* cache les bits 3-5 */ orb $0x18, %bl /* les mettres à 011=3 : index de registre ebx */ movb %bl, 1(%eax) /* met la cible du MOV à ebx */ /* On met à jour temporairement la valeur de continue pour continuer l'exécution dans ce code au lieu de sauter vers decryptloop. Nous exécutons MOV dans le contexte TRACÉ parce qu'il doit utiliser les registres tracés pour calculer l'adresse. Avant de sauvegarder NOTRE esp pour que le contexte TRACÉ ne soit pas perdu (MOV met à jours ebx, le call tracé ne bidouillera aucun registre). D'abord, nous avons NOTRE contexte, mais après, nous devons restaurer le contexte TRACÉ. Pour pouvoir le faire, nous devons ajuster esp pour pointer vers le contexte tracé avant de restaurer. */ movl $._grp5_continue, continue movl %esp, %ebp /* sauve le pointeur de contexte tracé dans ebp */ pusha /* stocke notre contexte; eflags inutiles */ movl %esp, our_esp /* notre pointeur de contexte */ movl %ebp, %esp /* ajuster le pointeur de contexte tracé */ jmp _nonjump ._grp5_continue: /* C'est ici que l'exécution continue après que MOV ai calculé l'adresse effective pour nous. CONTEXTE à l'entrée : ebx : adresse cible ou l'exécution tracée devrait continuer dl : partie du code (bits 3-5) de modR/M, déplacé aux bits 0-2 */ movl $decryptloop, continue /* restaure la valeur de continue */ movl our_esp, %esp /* restaure notre esp */ movl %ebx, 16(%esp) /* pour qu'ebx soit restaurer */ popa /* Notre contexte et le nouveau ebx */ cmpb $2, %dl /* CALL proche indirecte */ je ._grp5_call movl %ebx, traced_eip /* JMP proche indirect */ jmp decryptloop_nocontext ._grp5_call: xorb %cl, %cl /* note : l'adresse dans ebx est absolue */ jmp _call _0xf: /* 0x0F opcode esacpe for two-byte opcodes. Only 0F 0x80-0x8F range are Jcc rel32 instructions. Others are normal instructions. */ movb OPCODE2+disbuf, %cl /* extended opcode */ cmpb $0x80, %cl jb _nonjump /* < 0x80, not Jcc */ cmpb $0x8F, %cl ja _nonjump /* > 0x8F, not Jcc */ movl 2(%eax), %ebx /* load 32-bit offset */ jmp _jcc_rel32 control_table: /* C'est la table de saut pour renvoyer à l'exécution des instructions. Quand le code réel de l'instruction est trouvé, le traceur saute indirectement vers la procédure basée sur cette table. */ .rept 0x0F /* 0x00 - 0x0E */ .long _nonjump /* code normal */ .endr .long _0xf /* 0x0F deux octets d'échapement */ .rept 0x60 /* 0x10 - 0x6F */ .long _nonjump /* code normal */ .endr .rept 0x10 /* 0x70 - 0x7F */ .long _jcc_rel8 /* relatif sur 8 bits */ .endr .rept 0x10 /* 0x80 - 0x8F */ .long _nonjump /* saut long géré depuis .endr /* le code d'échapement _0xf */ .rept 0x0A /* 0x90 - 0x99 */ .long _nonjump .endr .long _unhandled /* 0x9A: saut lointaint vers un pointeur complet */ .rept 0x05 /* 0x9B - 0x9F */ .long _nonjump .endr .rept 0x20 /* 0xA0 - 0xBF */ .long _nonjump .endr .long _nonjump, _nonjump /* 0xC0, 0xC1 */ .long _unhandled /* 0xC2: retn imm16 */ .long _retn /* 0xC3: retn */ .rept 0x06 /* 0xC4 - 0xC9 */ .long _nonjump .endr .long _unhandled, _unhandled /* 0xCA, 0xCB : ret lointain */ .rept 0x04 .long _nonjump .endr .rept 0x10 /* 0xD0 - 0xDF */ .long _nonjump .endr .long _loopne, _loope /* 0xE0, 0xE1 */ .long _loop, _jcxz /* 0xE2, 0xE3 */ .rept 0x04 /* 0xE4 - 0xE7 */ .long _nonjump .endr .long _callrel /* 0xE8 */ .long _jmp_rel32 /* 0xE9 */ .long _unhandled /* jump lointain vers un pointeur complet */ .long _jmp_rel8 /* 0xEB */ .rept 0x04 /* 0xEC - 0xEF */ .long _nonjump .endr .rept 0x0F /* 0xF0 - 0xFE */ .long _nonjump .endr .long _grp5 /* 0xFF: groupe 5 instructions */ .data continue: .long decryptloop /* Où continuer après 1 insn */ .bss .align 4 traced_esp: .long 0 /* esp tracé */ traced_eip: .long 0 /* eip tracé */ traced_ctr: .long 0 /* incrémenté de 1 à chaque insn */ lo_addr: .long 0 /* eip chiffré min */ hi_addr: .long 0 /* eip chiffré max */ our_esp: .long 0 /* notre esp... */ end_esp: .long 0 /* esp quand nous devrons arreter */ local_stk: .fill 1024, 4, 0 /* espace de pile locale (pour appeller C) */ stk_end = . /* nous avons besoin de ceci... */ ilen: .long 0 /* longueur d'instruction */ key: .long 0 /* pointeur vers la clef */ decrypt: .long 0 /* fonction de déchiffrement UTILISÉE */ r_decrypt: .long 0 /* fonction de déchiffrement RÉELLE */ disbuf: .fill 128, 1, 0 /* buffer de désassemblage de xde */ ----[ A.2 - Source de l'outil de chiffrement de fichier : cryptfile.c /* Copyright (c) 2004 Zeljko Vrba Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* [ NDT : voici une tracution trouvée là : http://wiki.pps.jussieu.fr/twiki/pub/Main/MitLicence/LicenceMIT.pdf ] Copyright (c) 2004 Zeljko Vrba Par la présente, la permission est accordée, libre de charge, à n'importe quelle personne obtenant une copie de ce logiciel et les fichiers de documentation associés (le Logiciel), de distribuer le Logiciel sans restriction, incluant sans limitation des droits d'utilisation, de copie, de modification, de fusion, de publication, de distribution, de sous licence, et de vente de copies de ce logiciel, et permet aux personnes à qui le logiciel est fourni d'être soumises aux conditions suivantes : La notification de copyright ci-dessus et cette notification de permission seront incluses dans toutes les copies ou parties substantielles du logiciel. LE LOGICIEL EST FOURNI "TEL QUEL", SANS GARANTIE D?AUCUNE SORTE, EXPLICITE OU IMPLICITE, INCLUANT SANS LIMITES LES GARANTIES DU COMMERCE, SOUS UNE FORME CONVENABLE POUR UN BUT PARTICULIER ET SANS INFRACTION. EN AUCUN CAS LES AUTEURS OU LES SUPPORTS DE COPYRIGHT NE SERONT RESPONSABLES D'EVENTUELLES RECLAMATIONS, DOMMAGES OU AUTRES RESPONSABILITÉS SI, DANS LE CADRE DU CONTRAT, DES ACTES DÉLICTUEUX OU AUTRES SURVIENNENT, AVEC OU SANS RAPPORT AVEC LE LOGICIEL OU SON UTILISATION OU D'AUTRES AGISSEMENTS EN RAPPORT AVEC LE LOGICIEL. */ /* * Ce programme chiffre une portion d'un fichier, écrivant un nouveau * fichier avec le suffixe .crypt. Les permissions (exécuter et autres) NE * SONT PAS préservées ! * La taille de blocs de 8 octets est codée en dur. */ #include #include #include #include #include "cast5.h" #define BLOCKSIZE 8 #define KEYSIZE 16 typedef void (*cryptblock_f)(void*, u8*, const u8*); static unsigned char *decode_hex_key(char *hex) { static unsigned char key[KEYSIZE]; int i; if(strlen(hex) != KEYSIZE << 1) { fprintf(stderr, "KEY must have EXACTLY %d hex digits.\n", KEYSIZE << 1); exit(1); } for(i = 0; i < KEYSIZE; i++, hex += 2) { unsigned int x; char old = hex[2]; hex[2] = 0; if(sscanf(hex, "%02x", &x) != 1) { fprintf(stderr, "non-hex digit in KEY.\n"); exit(1); } hex[2] = old; key[i] = x; } return key; } static void *docrypt( FILE *in, FILE *out, long startoff, long endoff, cryptblock_f crypt, void *ctx) { char buf[BLOCKSIZE], enc[BLOCKSIZE]; long curroff = 0; size_t nread = 0; while((nread = fread(buf, 1, BLOCKSIZE, in)) > 0) { long diff = startoff - curroff; if((diff < BLOCKSIZE) && (diff > 0)) { /* ceci gère le mauvais alignement suivant (chaque . est 1 octet) ...[..|......].... ^ ^ ^ curoff+BLOCKSIZE | startoff curroff */ if(fwrite(buf, 1, diff, out) < diff) { perror("fwrite"); exit(1); } memmove(buf, buf + diff, BLOCKSIZE - diff); fread(buf + BLOCKSIZE - diff, 1, diff, in); curroff = startoff; } if((curroff >= startoff) && (curroff < endoff)) { crypt(ctx, enc, buf); } else { memcpy(enc, buf, BLOCKSIZE); } if(fwrite(enc, 1, nread, out) < nread) { perror("fwrite"); exit(1); } curroff += nread; } } int main(int argc, char **argv) { FILE *in, *out; long startoff, endoff; char outfname[256]; unsigned char *key; struct cast5_ctx ctx; cryptblock_f mode; if(argc != 6) { fprintf(stderr, "USAGE: %s <-e|-d> FILE KEY STARTOFF ENDOFF\n", argv[0]); fprintf(stderr, "KEY MUST be 32 hex digits (128 bits).\n"); return 1; } if(!strcmp(argv[1], "-e")) { mode = cast5_encrypt; } else if(!strcmp(argv[1], "-d")) { mode = cast5_decrypt; } else { fprintf(stderr, "invalid mode (must be either -e od -d)\n"); return 1; } startoff = atol(argv[4]); endoff = atol(argv[5]); key = decode_hex_key(argv[3]); if(cast5_setkey(&ctx, key, KEYSIZE) < 0) { fprintf(stderr, "error setting key (maybe invalid length)\n"); return 1; } if((endoff - startoff) & (BLOCKSIZE-1)) { fprintf(stderr, "STARTOFF and ENDOFF must span an exact multiple" " of %d bytes\n", BLOCKSIZE); return 1; } if((endoff - startoff) < BLOCKSIZE) { fprintf(stderr, "STARTOFF and ENDOFF must span at least" " %d bytes\n", BLOCKSIZE); return 1; } sprintf(outfname, "%s.crypt", argv[2]); if(!(in = fopen(argv[2], "r"))) { fprintf(stderr, "fopen(%s): %s\n", argv[2], strerror(errno)); return 1; } if(!(out = fopen(outfname, "w"))) { fprintf(stderr, "fopen(%s): %s\n", outfname, strerror(errno)); return 1; } docrypt(in, out, startoff, endoff, mode, &ctx); fclose(in); fclose(out); return 0; } ----[ A.3 - Le programme de test : test2.c /* Copyright (c) 2004 Zeljko Vrba Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* [ NDT : voici une tracution trouvée là : http://wiki.pps.jussieu.fr/twiki/pub/Main/MitLicence/LicenceMIT.pdf ] Copyright (c) 2004 Zeljko Vrba Par la présente, la permission est accordée, libre de charge, à n'importe quelle personne obtenant une copie de ce logiciel et les fichiers de documentation associés (le Logiciel), de distribuer le Logiciel sans restriction, incluant sans limitation des droits d'utilisation, de copie, de modification, de fusion, de publication, de distribution, de sous licence, et de vente de copies de ce logiciel, et permet aux personnes à qui le logiciel est fourni d'être soumises aux conditions suivantes : La notification de copyright ci-dessus et cette notification de permission seront incluses dans toutes les copies ou parties substantielles du logiciel. LE LOGICIEL EST FOURNI "TEL QUEL", SANS GARANTIE D?AUCUNE SORTE, EXPLICITE OU IMPLICITE, INCLUANT SANS LIMITES LES GARANTIES DU COMMERCE, SOUS UNE FORME CONVENABLE POUR UN BUT PARTICULIER ET SANS INFRACTION. EN AUCUN CAS LES AUTEURS OU LES SUPPORTS DE COPYRIGHT NE SERONT RESPONSABLES D'EVENTUELLES RECLAMATIONS, DOMMAGES OU AUTRES RESPONSABILITÉS SI, DANS LE CADRE DU CONTRAT, DES ACTES DÉLICTUEUX OU AUTRES SURVIENNENT, AVEC OU SANS RAPPORT AVEC LE LOGICIEL OU SON UTILISATION OU D'AUTRES AGISSEMENTS EN RAPPORT AVEC LE LOGICIEL. */ #include #include #include #include #include "cast5.h" #define BLOCKSIZE 8 #define KEYSIZE 16 /* * f1 et f2 sont chiffrée avec la clef sur 128bits suivante : * 5f4dcc3b5aa765d61d8327deb882cf99 (MD5 de la chaine "password") */ static int f1(int a) { int i, s = 0; for(i = 0; i < a; i++) { s += i*i; } printf("called plaintext code: f1 = %d\n", a); return s; } static int f2(int a, int b) { int i; a = f1(a); for(i = 0; i < b; i++) { a += b; } return a; } static unsigned char *decode_hex_key(char *hex) { static unsigned char key[KEYSIZE]; int i; if(strlen(hex) != KEYSIZE << 1) { fprintf(stderr, "KEY must have EXACTLY %d hex digits.\n", KEYSIZE << 1); exit(1); } for(i = 0; i < KEYSIZE; i++, hex += 2) { unsigned int x; char old = hex[2]; hex[2] = 0; if(sscanf(hex, "%02x", &x) != 1) { fprintf(stderr, "non-hex digit in KEY.\n"); exit(1); } hex[2] = old; key[i] = x; } return key; } int main(int argc, char **argv) { int a, b, result; char op[16], hex[256]; void *esp; struct cast5_ctx ctx; printf("enter decryption key: "); scanf("%255s", hex); if(cast5_setkey(&ctx, decode_hex_key(hex), KEYSIZE) < 0) { fprintf(stderr, "error setting key.\n"); return 1; } printf("a b = "); scanf("%d %d", &a, &b); asm("movl %%esp, %0" : "=m" (esp)); printf("esp=%p\n", esp); result = crypt_exec(cast5_decrypt, &ctx, f1, decode_hex_key, f2, a, b); asm("movl %%esp, %0" : "=m" (esp)); printf("esp=%p\n", esp); printf("result = %d\n", result); return 0; }