Volume 0x0b, Issue 0x3f, Phile #0x0d of 0x14
|=------=[ cryptexec: Next-generation runtime binary encryption ]=-------=|
|=------=[ using on-demand function extraction ]=-------=|
|=-----------------------------------------------------------------------=|
|=----------------=[ Zeljko Vrba <zvrba@globalnet.hr> ]=-----------------=|
|=-----------------------------------------------------------------------=|
|=--------------=[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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}