Degré de difficulté
P
our atteindre ces buts, une analyse très De cela, nous prendre des mesures suffi-
détaillée du fonctionnement des logiciels santes pour résoudre chaque type de problè-
malveillants est nécessaire ; c'est l'ingé- mes auxquels nous pouvons nous heurter.
nierie inverse qui entre ici en jeu.Les concepteurs
des programmes malveillants (virus, troyens, Hooking
rootkits) essaient de rendre cette analyse extrê- Comme on peut remarquer, il existe beaucoup
mement difficile, par exemple à l'aide des techni- d'astuces qui ont pour but de rendre difficile
ques empêchant le déboguage, des techniques l'utilisation du débogueur (aussi bien au niveau
polymorphiques, des techniques stealth ou des Ring0 que Ring3) qui est un principal outil dans
programmes pour la compression des fichiers ; l'ingénierie inverse. De cela, il est nécessaire
ces derniers non seulement réduisent la taille des
fichiers exécutables, mais aussi ajoute une cou-
che protectrice plus ou moins complexe. Cet article explique...
Dans ces situations, c'est le temps qui
• Comment utiliser hooking pour l'analyse des
compte avant tout : grâce à une analyse longue
programmes malveillants (malware).
et précise, tôt ou tard, nous atteindrons notre
• Comment exploiter Structure Exception Han-
but et pourrons connaître tous les détails du dling pour la création d'un désassembleur de
danger. Hélas, il arrive que nous n'avons pas taille.
assez de temps et dans ce cas il faut optimiser
les opérations liées aux procédures de l'ana-
Ce qu'il faut savoir...
lyse. Imaginez un ver exploitant une erreur
inconnue d'un programme qui lui permet de • Connaître Assembleur x86 y C.
se répandre via Internet. Le temps investi dans • Connaître Win32 Api et Structure Exception
l'analyse et la compréhension du fonctionne- Handling.
ment de ce ver marque la limite entre la vrai • Avoir les notions de base des techniques utili-
catastrophe des utilisateurs et le danger réduit sées par les logiciels malveillants et les virus.
et neutralisé.
Tableau 1. Les données disponibles pour la gestion des exceptions, si celle- C'est pourquoi, il faut développer
ci est active une technique le moins agressive
T Données envers le code source qui permet-
trait à la fonction accrochée de fonc-
ESP+4 Pointeur à la structure
tionner normalement tout en restant
EXCEPTION_RECORD
constamment sous contrôle. Cette
ESP+8 Pointeur à la structure ERR technique est appelée Detour (elle
ESP+C Pointeur à la structure a été présenté par Galen Hunt et
CONTEXT_RECORD Doug Brubacher des laboratoires
Microsoft)
appel de la fonction démarrera sans à détecter parce qu'il est suspect si le
doute le code précédent. En nous point d'entrée d'une fonction système Detour
servant du schéma d'Inline Hooking, contient le saut inconditionnel vers une Par rapport à Inline hooking, la
nous remplacerons ces premières autre adresse dans la mémoire. Nous technique Detour implémente deux
instructions par notre propre crochet pouvons utiliser une autre option étant nouvelles conceptions : la fonction
qui modifiera le flux normal de la la combinaison de PUSH + RET . Detour et la fonction Trampoline.
fonction dans la direction du filtre.
01 68EFBEADDE push 0DEADBEEF • Fonction Detour doit se compo-
Différentes possibilités 02 C3 retn ser d'une partie dans laquelle
pour le même objectif 03 A118000000 mov eax,[00000018] sont réalisées les premières
Il existe différentes méthodes de chan- opérations sur les données ob-
gement du flux dans la direction du Dans ce cas, nous remplaçons les tenues, ensuite vient l'appel à la
notre code. La plus simple consiste à 6 premiers octets. Si nous regar- fonction Trampoline et à la fin, se
changer les premiers octets de Close- dons ce fragment avec attention, trouve la partie du code qui sera
Handle par un saut inconditionnel. nous constatons que le code origi- lancé après la terminaison de la
nal CloseHandle est effectivement fonction Trampoline.
01 E9732FADDE jmp 0DEADBEEF changé, mais non du point de vue • Fonction Trampoline contient aussi
02 64A118000000 mov eax,fs:[00000018] des instructions ajoutées, mais aussi bien les instructions de la fonction
certaines d'entre elles sont perdues cible, remplacées complètement
Nous avons remplacé 5 premiers et d'autres ont changé. C'est une par le saut inconditionnel, que
octets par un saut vers l'adresse question à discuter. Bien que nous celles qui ont été changées par-
0xDEADBEEF. Bien sûr, cette avions atteint notre objectif et inter- tiellement. Ensuite, il y a un saut
adresse n'est pas valide car nous cepté tous les appels de la fonction, à l'instruction suivante correspon-
travaillons en mode utilisateur. la modification étaient si importante dant à la fonction cible.
À cette adresse, il y aurait le code qu'elle a entraîné les anomalies du
que nous avons introduit dans l'espa- comportement, ce qui en résultat Ainsi, nous pouvons résoudre le
ce adressable du fichier exécutable. pourrait provoquer la fin inattendue problème lié aux données perdues
Vu que c'est la méthode la plus du programme pendant le premier ou aux instructions modifiées qui
simple, elle est aussi la plus facile appel CloseHandle. se produisait dans Inline Hooking.
Il s'agit de sauver ces instructions
dans la fonction Trampoline pour
qu'elles puissent être exécutées. En-
suite, nous sautons à une instruction
suivante où la fonction cible fonction-
nera normalement. Une fois la fonc-
tion cible terminée, nous reprenons
le contrôle sur la fin de la fonction
Detour. Elle est capable de récupérer
le chemin de démarrage en restau-
rant le contrôle de la fonction source
ou la possibilité de réaliser un autre
type d'opération.
Mais comment savoir combien
d'instructions faut-il copier dans la
fonction Trampoline à partir de la
fonction cible ? Chaque fonction
Figure 2. Le schéma de base de la technique Detour cible sera différente, et de cela il
+ +
147 €
12 Nos 6 Nos
99 € 12 Nos 6 Nos
+ +
Commande
Merci de remplir ce bon de commande et de nous le retourner par fax : 0048 22 887 10 10 ou par courrier :
Software-Wydawnictwo Sp. z o. o., Piaskowa 3, 01-067 Varsovie, Pologne ; E-mail : abonnement@software.com.pl
Adresse ...........................................................................................................................................................................................................
E-mail ................................................................................................
Je règle par :
Dans la ligne 25, nous enregis- Objectif Taille 2 octets +3 octets des précé-
trons l'adresse de la mémoire où Le but de cette partie du code est dents = 5 octets. On continue
commencent nos instructions sur- l'analyse de la longueur des instruc-
veillées, ce qui nous permettra au tions surveillées jusqu'à trouver la 04 64A118000000 mov eax,fs:[00000018]
moment de l'émulation des appels valeur supérieure ou égale à celle
et des sauts conditionnels et incon- qui occuperait notre crochet, indé- Taille 6 octets +5 octets des précé-
ditionnels. pendamment du fait, si ce serait un dents = 11 octets. C'est déjà fait !
saut inconditionnel (jmp 5 octets) ou Notre désassembleur de taille
push+ ret (6 octets). Imaginons par retournera 11. Que cela signifie-t-il ?
Analyse exemple que le type de notre cro- Cela veut dire que pour un crochet
des instructions chet est push+ret et nous essayons composé de six octets, pour ne pas
surveillées d'accrocher CloseHandle. Nous perdre aucune instruction, il faut co-
Jusqu'alors, tout ce que nous avons indiquons au désassembleur que pier onze octets de la fonction cible
présenté, pourrait être contenu dans notre crochet occupe 6 octets (Hoo- dans la fonction Trampoline.
le bloc préparation de l'environne- kLength = 6). Il commencera alors
ment. Nous allons commencer à exa- à compter la longueur de la première Analyse des données
miner le code appartenant à l'analyse instruction Nous avons ici un bloc initial de l'ana-
des instructions surveillées. lyse. Jusqu'à la ligne 46, nous avons
01 8BFF mov edi,edi l'algorithme permettant d'émuler les
Listing 1. Observation du code appels et les sauts. Cet algorithme
Taille 2 octets. Vu qu'il est inférieure consiste à vérifier le décalage entre
27 Int1h: à 6, il continue comme suit EIP dans lequel l'exception s'est pro-
28 mov ecx, eax duite et la valeur précédente du re-
29 sub eax, [PrevEip]
02 55 push ebp gistre. Si ce décalage est important,
30 cmp ax, 10h
31 jb NoCall nous avons à faire avec un appel ou
32 mov ebx, dword [edx] Taille 1 octet +2 octets de l'instruc- un saut, c'est pourquoi nous passe-
33 mov edx,ebx tion précédente = 3 octets. Toujours rons à la récupération du contexte
34 sub ebx, [PrevEip] inférieur à 6. pour qu'il pointe vers une instruction
35 cmp bl, 7
surveillée suivante au lieu d'exécu-
36 jbe HabemusCall
37 mov edi,[PrevEip] 03 8BEC mov ebp,esp ter le processus jusqu'à l'adresse 4
38 inc edi
39 inc edi
40 mov dword [esi + 0B8h], edi
41 mov ecx,edi
42 jmp NoCall
43 HabemusCall:
44 mov dword [esi + 0B8h], edx
45 mov ecx,edx
46 NoCall:
47 mov [PrevEip], ecx
48 sub ecx, Code
49 cmp ecx, [HookLength]
50 jge Success
51 RetSEH:
52 or word [esi + 0C0h], 0100h
; Activation de Trap Flag
53 xor eax,eax
54 ret
55 Success:
56 mov [LenDasm], ecx
;Nous restaurons la longueur
de l'instruction
57 mov esp, [OrigEsp]
;Nous récupérons Esp
58 mov ebp, [OrigEbp]
; Nous récupérons Ebp
59 pop dword [fs:0]
;Nous nettoyons l'étendue SEH
60 add esp, 4
;Nous ajustons la pile
laquelle pointait l'appel ou le saut. ver. Imaginons aussi que ce petit empêche notre analyse et l'utilisation
Nous compterons aussi avec notre code était compacté avec le pro- du débogueur. Il ne reste pas aussi
compteur combien d'octets occupe gramme contenant plusieurs types longtemps dans la mémoire qu'il soit
notre instruction. de protections anti-déboguage et possible de le décompresser sur le
Dans les lignes 47, 48, 49, nous anti-désassemblage. Nous devons disque dur à l'aide d'un outil de dé-
vérifions s'il y a suffisamment d'ins- vite savoir comment il s'infiltre dans compactage (ProcDump...). En fait,
tructions pour mettre notre crocher. un autre fichier exécutable et quel le fichier exécutable ne reste dans la
La partie la plus principale du dé- code il introduit. Pour tout cela, il mémoire que pendant des dizaines
sassembleur sont les lignes 52, 53 et serait nécessaire d'effectuer sur- de secondes empêchant également
54. Là, nous activons Trap Flag en le-champ le désassemblage du de capturer son image. Que pou-
configurant à 1 le bit approprié dans fichier exécutable, mais comme vons-nous faire ?
le registre EFLAGS. C'est la base nous l'avions dit, il contient un pro- Pour résoudre ce problème, on
pour le nettoyage pas à pas. gramme de compactage (packer) qui pourrait accrocher ExitProcess, la
À partir de moins de 256 octets,
nous avons construit un désassem- Listing 2. ACPInject
bleur de taille tout à fait opérationnel.
Maintenant nous pouvons l'appliquer #include <stdio.h>
#include <windows.h>
dans la pratique.
typedef BOOL (WINAPI *PQUEUEAPC)(FARPROC,HANDLE,LPDWORD);
int main(int argc, char *argv[])
Application pratique {
PROCESS_INFORMATION strProces;
pour l'analyse d'un STARTUPINFOA strStartupProces;
programme malveillant PQUEUEAPC QueueUserApc;
DWORD MessageAddr,Ret1,Ret2,Longueur;
On utilise les techniques differentes : char *szExecutableName;
unsigned char Snippet[]=
• Pour nos besoins, nous allons "\x90" /* nop */
créer un programme qui intro- "\x6A\x00" /* push NULL */
"\x6A\x00" /* push NULL */
duira et démarrera du code dans
"\x6A\x00" /* push NULL */
le fichier exécutable qui lui sera "\x6A\x00" /* push NULL */
passé en paramètre. Les techni- "\xB9\x00\x00\x00\x00" /* mov ecx,MessageBox*/
ques d'insertion du code dans les "\xFF\xD1" ; /* Call ecx */
processus sont bien connues. Il Longeur = (DWORD) strlen(
"c:\\windows\\system32\\calc.exe") + 1;
en existe différents types, mais
ZeroMemory( &strStartupProces, sizeof( strStartupProces) );
toutes sont basées pratiquement strStartupProces.cb = sizeof( strStartupProces );
sur les mêmes API. ZeroMemory( &strProces, sizeof( strProces) );
• VirtualAllocEx sert à allouer de la szExecutableName = (char*) malloc( sizeof(char) * Długość);
mémoire dans le processus cible. if( szExecutableName ) strncpy(szExecutableName,
"c:\\windows\\system32\\calc.exe",Longueur);
Le code sera introduit dans cet
else exit(0);
espace mémoire. Pour cela, nous _QueueUserApc = (PQUEUEAPC)GetProcAddress( GetModuleHandle (
utiliserons WriteProcessMemory. "kernel32.dll" ), "QueueUserAPC");
• Au moment du démarrage du MessageAddr = (DWORD) GetProcAddress ( LoadLibraryA(
code, nous pouvons choisir "user32.dll") , "MessageBoxA" );
// U32!MessageBoxA
parmi CreateRemoteThread ou
*( DWORD* )( Snippet + 10 ) = MessageAddr;
SetThreadContext. Ret1 = CreateProcessA( szExecutableName, NULL, NULL, NULL,
0, CREATE_SUSPENDED,
Mais nous nous servirons d'une NULL,NULL,
autre fonction : QueueUserAPC. &strStartupProces,&strProces);
Ret2 = (DWORD) VirtualAllocEx( strProces.hProcess,NULL,sizeof(Snippet),
MEM_COMMIT,
Domaines d'application PAGE_EXECUTE_READWRITE);
Ce programme introduit dans la
calculette Windows un petit code WriteProcessMemory(strProces.hProcess,(LPVOID)Ret2, Snippet,
qui entraîne l'affichage d'une boîte sizeof(Snippet), NULL);
_QueueUserApc((FARPROC)Ret2,strProces.hThread,NULL);
de message (Message Box). La
ResumeThread(strProces.hThread);
calculette ne s'affichera pas après return 0;
le démarrage de ce code. Imaginons }
qu'au lieu d'un code innocent, nous
introduisons un code malicieux d'un