cryptexec: Next-generation runtime binary encryption

              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;
}