------------------
------------------
-- Les Virii --
------------------
------------------


  Par DDway

(Ndrl : virii est le pluriel de virus informatique-)

  La Crapule m'a demandé un jour dans un mail explicite de faire un ch'tit article. Pas de pot, c'est sur les virii informatiques

Avant toute chose : vous m'apprendrez l'asm !!
Bon d'accord :) on peut faire des virii en basic, en fichiers bat, en c, en c++ en pascal bref tous les langages existants et inimaginables (ndrl : le Perl, je sais pas). Si vous voulez, on peut même faire des virii BAT résidants en mémoire sisi !

  Pour commencer, voici une liste de toutes les instruction asm du 386 et je ne continuerai que si vous la connaissez par coeur (M..., elle est vachement longue !!)

AAA ajustement ascii de AL après une addition
AAD ajustement ascii de AX avant une division
AAM ajustement ascii de AL après multiplication
AAS ajustement ascii de AL après soustraction
ADC addition avec retenu CF
ADD addition sans retenu
AND allez, chercher ...
ARPL ajuste le niveau de privilège en mode protégé
BOUND teste un nombre par rapport à des limites
BSF cherche 1 bit de droite à gauche
BSR cherche 1 bit de gauche à droite
BSWAP conversion d'un registre au format INTEL
BT charge 1 bit en CF
BTC charge 1 bit en CF et complément
BTR charge 1 bit en CF puis le met à 0 dans la source
BTS la même chose mais il le met à 1 dans la source
CALL appel de sous programme
CBW convertit l'octet en AL en un mot en AX
CDQ convertit un mot en EAX en un quadruple mot en EDX:EAX
CLC met l'indicateur à 0 dans CF
CLD met l'indicateur à 0 dans DF
CLI met l'indicateur à 0 dans IF
CLTS met à 0 l'indicateur de changement de contexte en mode protégé
CMC complémente l'indicateur CF
CMP comparaison logique
CMPSB, CMPSW comparaison de chaînes de caractères
CMPSD comparaisons pour les chaines de caractères du 386
CMPXCHG compare l'accumulateur avec AL, AX ou EAX
CWD convertit le contenu de AX en un double mot DX:AX
CWDE convertit le contenu de AX en un double mot dans EAX
DAA ajustement décimal de AL après une addition
DAS ajustement décimal de AL après une soustraction
DEC décrémentation
DIV division non signée
ENTER construit un cadre de pile pour une procédure de haut niveau
ESC accès au coprocesseur (pour les bêtes de l'optimisation)
HLT arrêt du processeur en attente d'un évênement externe
IBTS insère une chaine de bits en mode protégé
IDIV division signée
IMUL multiplication signée
IN lit un octet sur le port spécifié
INC incrémentation
INSB, INSW lit une chaine sur un port
INT interruption logicielle
INTO active l'interruption 4 si l'indicateur OF est armé
IRET retour d'interruption
IRETD retour d'interruption depuis un segment 32 bit
JA saut si supérieur
JAE saut si supérieur ou égal
JB saut si inférieur
JBE saut si inférieur ou égal
JC saut si CF est à 1
JNC saut si CF est à 0
JCXZ saut si CX est à 0
JECXZ saut si ECX est à 0
JE saut si égal
JG saut si arithmetiquement supérieur ou égal
JMP saut à l'adresse indiquée
JNA saut si non supérieur
JNAE saut si non supérieur ou égal
JNB saut si non inférieur
JNBE saut si non inférieur ou égal
JNE saut si non égal
JNG saut si arithmetiquement non supérieur
JNGE non ! je l'ecris pas, devinez :)
JNL saut si non inférieur arithmetiquement
JNLE devinez
JNO saut si l'indicateur OF est à 0
JNP saut si parité impaire ou précisement PF à 0
JNS saut si positif
JNZ saut si différent
JO saut si OF est à 1
JP saut si parité paire
JPE idem
JPO idem que JNP
JS saut si négatif
JZ saut si égalité
LAHF charge en AH la partie basse du registre des indicateurs
LDS charge une adresse physique en DS:registre
LEA charge une adresse effective
LEAVE libère la pile
LES charge une adresse physique en ES:registre
LFS charge une adresse physique en FS:registre
LGDT charge le registre de la table des descripteurs globaux en mode protégé)
LGS charge une adresse physique en GS:registre
LIDT charge le registre de la table des descripteurs d'interruption en mode protégé
LLDT charge le registre de la table des descripteurs locaux
LMSW charge (encore) le mot d'état de la machine en mode protégé
LOCK verrouile le bus
LODSB, LODSW charge en AL ou AX le contenu de DS:SI
LODSD charge en EAX le contenu de DS:SI
LOOP saut tant que CX <> 0
LOOPE saut tant que CX<> 0 et ZF=1
LOOPNZ saut tant que CX<>0 et ZF=0
LOOPZ comme LOOPE
LSL charge une limite de segment en mode protégé
LSS charge une adresse physique en SS:registre
LTR charge le registre de tâche en mode protégé
MOV transfert de donnée
MOVS transfert une chaine de caractère de DS:SI en ES:DI
MOVSD transfert une chaine de caractère double mot par double mot
MOVSX transfert avec le signe
MOVZX transfert avec une extension de 0
MUL multiplication non signée
NEG négation
NOP ne fait rien (et oui !) (NB par Le_Magicien  : en réalité, exécute l'instruction XCHG AX, AX (c'est à dire échange les contenus respectifs des registres AX et AX). En gros, une instruction qui fait quelque chose, mais qui ne change rien. Très utilisée en cassage de protections de sharewares, mais pas très utile dans la programmation de virii)
NOT opération logique NON
OR opération logique
OUT transmet un octet ou un mot à un périphérique
OUTSB, OUTSW transmet une chaine de caractères à un port
OUTSD transmet un double mot à un port
POP dépile un mot
POPA dépile les registres
POPAD dépile tout les registres 32 bits
POPF dépile un mot et le transfère vers le registre des indicateurs
POPFD pareil que POPF avec un double mot sur 32 bits
PUSH place sur le haut de la pile une valeur
PUSHA empile tous les registres
PUSHAD empile tout les registres 32 bits
PUSHF empile le registre des indicateurs
PUSHFD empile le registre des indicateurs 32 bits
RCL rotation à gauche à travers CF
RCR rotation à droite à travers CF
REPZ, REPNZ préfixe de repétition
REPE, REPNE traîtement des chaîne de caractères en association avec CX

et les indicateurs

RETN, RETF retour d'un sous-programme
ROL rotation à gauche
ROR rotation à droite
SAHF copie de AH dans la partie basse du registre des indicateurs
SAL décalage à gauche avec introduction de 0
SAR décalage à droite avec signe
SBB soustraction non signée avec prise en compte de CF
SCASB, SCASW compare une chaîne avec le contenu de AL ou AX
SCASD compare une chaine avec EAX
SETA initialisation à 1 si CF et ZF sont à 0, sinon initialisation à 0
SETAE idem seulement si CF est à 0
SETB idem si CF est à 1
SETBE pareil si CF ou ZF est à 1
SETE idem si ZF est à 1 (sinon toujours initialisation à 0)
SETG pareil si ZF=0 et SF=OF
SETGE pareil si SF=OF

(Ndrl : tout le monde a du lacher là)

SETL idem si SF<>OF
SETLE pareil si ZF=1 et SF<>OF
SETNA pareil que SETBE
SETNAE comme SETB
SETNB comme SETAE
SETNBE comme SETA
SETNE initialisation à 1 si ZF=0 sinon initialisation à 0
SETNG comme SETLE
SETNGE comme SETL
SETNL comme SETGE
SETNLE comme SETG
SETNO initialisation si OF=0 sinon init à 0
SETNP idem si PF=0
SETNS si SF=0
SETNZ si ZF=0
SETO si OF=1
SETP si PF=1
SETPE comme SETP
SETPO si PF=0
SETS si SF=1
SETZ si ZF=1
SGDT sauvegarde le registre de la table des descripteurs globaux en mode protégé
SHL aller voir SAL
SHLD décalage double à gauche
SHR décalage droite avec introduction à 0
SHRD décalage double à droite
SIDT sauvegarde le registre de la table d'interruption en mode protégé
SLDT pareil pour la table des descripteur locaux
SMSW sauvegarde le mot d'état de la machine en mode protégé
STC met à 1 l'indicateur CF
STD pareil pour DF
STI idem pour IF
STIOSB, STIOSW transfert du contenu de AL en ES:DI
STOSD transfert de EAX en ES:DI
STR sauvegarde le registre de tâche (mode protégé)
SUB soustraction non signée
TEST teste si un bit est à 1
VERR teste l'autorisation de lecture d'un segment sous le mode protégé
VERW teste l'autorisation d'écriture dans un segment en mode protégé
WAIT attend que la ligne busy ne soit pas active
XADD addition signée
XBTS prend une chaine de bit (mode protégé)
XCHG échange le contenu de 2 registres (comme swap en basic)
XLAT charge en AL l'octet de la table DS:BX+AL
XOR opération logique très connue (ou exclusif)

RRRRRaaaaaaahhhhhhhh !!! C'était très long :)

   En réalité, pour faire des virii, vous n'aurez pas besoin de connaître toute cette liste mais si vous voulez cracker des logiciels et que le debogueur vous sort ce qui suit, ça peut servir de comprendre ce que ça signifie
:

     STC
     XCHG EAX, EBX
     IRET
     MOV EDX, 00000001h

  Qui peut me dire ce que produit ce code ?



1-Les Bases

  Pour commencer, qu'est-ce qu'un virus informatique ? (la bonne question :)

  Un virus informatique est un petit programme souvent d'une 100aine d'octets écrit le plus souvent en assembleur qui cherche un programme hôte pour se multiplier, certains détruisent le programme hôte (virus recouvrement) et les autres le parasitent (non-recover virus, virus parasite, virus compagnon).
  Ainsi qu'un virus biologique a besoin d'une cellule hôte pour se reproduire, un virus informatique a BESOIN du prog hôte (les virii à recouvrement sont plus rapidement detectés que les autres). Le virus informatique doit donc trouver tous les moyens possibles pour trouver des programmes hôtes pouvant l'accepter et également se protéger de ses pires ennemis : les Anti-Virus.

  Pour les bases, on va parler des programmes hôtes ("il faut connaître son ennemi"). Les 2 formats que l'on risque "d'attaquer" le plus sont bien sur les fichier *.exe et *.com de MS-DOS (on pourrait s'attaquer aux exécutables de windows, aux DLL, aux VXD... mais bon, pour un début voyons le plus simple).
  D'abord, les fichier *.com ou COre iMage, les plus simples et les plus vieux. Un fichier com est une image binaire de ce qui doit être chargé en mémoire puis executé par le processeur On les stocke sur le DD sous une forme qui représente exactement le contenu de la mémoire (une image) lorsqu'ils sont chargés. Ainsi, un programme com est chargé et lancé rapidement Le problème, c'est qu'un programme com est limité 64Ko (un segment de mémoire), ce qui comprend la PSP (256 -> 100h octets) et la pile (2 octet -> un mot). Ainsi, on ne peut pas avoir de segment de pile prédéfini pour les programme com. Ces limitation entrainent quelques problèmes :
    - Un prog com ne peut pas contenir de références directes à des adresses de segments ou de groupes
    - Pas d'appels longs directs
    - Pas de références de segments par leurs noms
    - Donc toutes les procédure d'un programme com sont déclarées NEAR

  Pour Tasm : un programme com est de la forme TINY, il commence à l'offset 100h, pour celà, utilisez les directive STARTUPCODE ou org 100h. Pour un meilleur fonctionnement, il est indispensable de séparer le code, les données non initialisées et les données du programme avec CODESEG, UDATASEG et DATASEG.
Pendant le chargement d'un programme com, les registres suivants sont définis :
    - CS, DS, ES et SS contiennent l'adresse du PSP et du programme
    - IP est initialisé à 100h (256)
    - SP est à 0FFFEh (dernier mot dans le segment du programme)

  Le DOS réserve la totalité de la mémoire disponible pour un programme com, ainsi pour le DOS, il ne reste jamais assez de mémoire pour qu'il puisse appeler un autre programme. Lorsque l'on est sous DOS, si on tape un nom de programme sans l'extension, celui-ci recherche d'abord le nom avec l'extension com, puis exe et enfin bat (très intéressant pour les virii compagnons)

  Les fichiers exe sont un tantinet plus complexes, car ils permettent la segmentation d'un programme sous DOS. Celui-ci utilise donc plusieurs segments, il n'est donc plus limité pour le code, la pile et les données Le fichier exe comporte des segments séparés pour la pile, le code et les données.
Lors d'un chargement de programme exe, DOS charge les registres de la manière suivante :
    - DS et ES contiennent l'adresse des paragraphes du PSP. Le PSP contient les arguments transmis au programme    par la ligne de commande et un pointeur vers la chaine d'environnement du programme.
    - CS:IP contient l'adresse de départ spécifiée par END dans un des modules du programme ou bien    la directive STARTUPCODE
    - SS:SP contient l'adresse du dernier mot spécifié du segment de pile

  Les programmes exe sont chargés à n'importe quelle adresse qui est un multiple entier de 16 Comme un programme exe comporte plusieurs segments, on est obligé d'utiliser l'instruction FAR si l'on désire par exemple appeler un sous programme dans un autre segment. Malheureusement (c'était trop beau), une instruction FAR doit avoir non seulement l'indication de l'offset mais aussi du segment MAIS puisque que cette adresse peut varier à chaque exécution du programme, on est emmerdé. Ce problème a été réglé en mettant au début de chaque prog une struture de données qui contient les adresses et toutes les références de segment (un header, "en-tête" en français).

  Un programme exe est chargé à l'aide de la fonction EXEC du DOS, celle-ci connait les différentes adresses où sont chargés les différents segments du programme exe. Elle peut ainsi inscrire les valeurs appropriées dans les cellules de mémoire enregistrées dans l'en-tête du fichier exe. On remarque aussi que la taille d'un fichier exe sera toujours supérieure à la taille d'un fichier com pour une fonction équivalente, le fichier exe sera également plus long à charger qu'un fichier com.

  Après que les références de segments à l'intérieur du prog exe ont été (sisi, c'est bien de l'indicatif après : 'Après que') adaptées aux adresses effectives, la fonction EXEC fixe les registres de segment DS et ES sur le début du PSP. Le programme exe peut donc facilement accéder aux informations contenues dans la PSP. L'adresse de la pile et le contenu du pointeur de pile sont stockés dans l'en-tête du fichier exe, pareil pour l'adresse du segment de code où se trouve la routine de lancement du programme et le compteur de programme.

  Pour terminer un prog exe, il suffit d'appeler la fonction 4Ch du DOS (interruption 21h) pour garder une compatibilité avec les versions ultérieures du DOS

  Maintenant, petit cadeau, le Header d'un fichier exe

Offset	Taille	     Contenu	    	   Description	



  00h  |  2   | 5a4dh='MZ'   		| signature d'un fichier exe

  02h  |  2   | long fichier mod 512	| Taille de la dernière page

  04h  |  2   | long fichier div 512    | Compteur de page

  06h  |  2   | 			| Nombre d'entrées dans la table de relogement

  08h  |  2   | multiple de 16		| Taille de l'en-tête en paragraphes

  0Ah  |  2   | 			| Nombre minimal de paragraphes nécessaires

  0Ch  |  2   | 			| Nombre maximal de paragraphes nécessaires

  0Eh  |  2   | SS initial		| valeur initiale du segment de pile

  10h  |  2   | SP initial		|

  12h  |  2   | 			| Checksum du header (souvent inutilisé)

  14h  |  2   | IP initial		| Valeur initiale du pointeur d'instruction

  16h  |  2   | CS initial		| Valeur initiale du segment de code

  18h  |  2   | Offset de la table	| offset du début de la table de relogement 

  1Ah  |  2   | Overlay			| au début, cette valeur est fixé à 0

  1Ch  |  2   | 			| Variable mémoire tampon


  Toi qui lis, je suis sûr que tu es en train dire : "M... il nous fait chier, quand est-ce qu'il va nous parler des virii informatiques". J'y viens : déjà avec cela, on peut avoir une petite idée sur le mode de fonctionnement et les problèmes que l'on risque de rencontrer lors du fonctionnement du virus.

2-Les virii infecteurs de fichiers exe

  On va pas commencer violent : je vais vous expliquer le mode de fonctionnement d'un virus se greffant à la fin du fichier exe du DOS (les fichier com étant de moins en moins utilisés).

  Bon, le problème par rapport à un virus à recouvrement com c'est que avec les fichier exécutables il va falloir modifier l'en-tête et la table du pointeur de relogement, il faut également qu'il ajoute son code au module de chargement. Le virus se placera à la fin du code du programme exe et prendra le contrôle au démarrage de celui-ci. On utilisera une routine qui copie le code du programme de la mémoire vers le fichier sur le HD pour ensuite ajuster le fichier.

  L'inconvénient c'est que le virus possédant ses propres segments de code, de données et de pile risque de planter si un prog vient contrecarrer ses suppositions quant à la façon dont ces segments seront gérés par le programme hôte.
 Exemple :
 si on utilise une pile initialisée par le prog hôte, elle peut se terminer en plein sur le virus greffé sur nôtre prog hôte, donc si on empile ou on dépile, le virus risque de s'autodétruire.

  Le seul moyen pour mettre en place les segments du virus est de modifier les valeurs initiales du segment CS et SS dans l'en-tête du fichier exe. Mais les anciens segments initiaux doivent être stockés quelque part dans le virus pour pouvoir être restaurés et ainsi rendre le contrôle au programme hôte lorsque notre virus a fini de s'exécuter. Solution : mettre 2 pointeurs vers ces références de relogement dans le segment de code du virus. Malheureusement (et oui, tout n'est pas simple dans le dur monde de l'informatique), on est obligé d'augmenter la table des pointeurs de relogement pour y ajouter des pointeurs. Vu que l'en-tête d'un fichier exe doit être un multiple de 16, les pointeurs de relogement sont alloués par groupes de 4 pointeurs de 4 octet chacun. Avec 2 références de segment, il faudra augmenter l'en-tête ultérieurement. Il existe une autre possibilité, il suffit seulement que le virus n'infecte pas les fichiers dont il doit étendre l'ent-tête. L'inconvénient et qu'une bonne moitié des fichiers exe echapperont à "l'attaque" mais vu que le module de chargement peut faire plusieurs centaines de Ko, le déplacer risque un long temps de chargement et on risque de le remarquer. Comme je le disais plus haut, la tâche principale d'un virus est de survivre donc de ne pas se faire repérer, alors je vais utiliser la 1ère solution (essayez de trouver la 2ème : très bon exercice)

  Un peu de code en asm:

VIRII segment

VIRUS:

  mov ax, cs		; on transfère le segment de code dans ax

  mov ds, ax		; donc ds=cs

  ...			

  ...			; votre virus ici

  ...

  cli			; indicateur d'interruption est mis à 0 (désactivation des interruptions externes)

  mov ss, cs:[HOTEP]	; replace les valeur initiales de la pile du prog hôte

  mov sp, cs:[HOTEP+2]	; remet la valeur initiale de SP du prog hôte

  sti			; l'indicateur d'interruption est remis à 1

  jmp dword ptr cs:[HOTEC] ;saut vers le début du prog hôte

HOTEP	dw ?,?		; valeur initiale du segment de la pile

HOTEC	dw ?,?		; valeur initiale du segment de code

  Voici à peu près en détail les étapes de la routine de copie pour infecter un nouveau programme

    - Lire l'en-tête du programme hote
    - Agrandir la taille du module de chargement pour qu'elle ait comme valeur un multiple pair de 16 octet, ainsi CS:0000 deviendra le 1er    octet du virus
    - écrire le code du virus sur le fichier hôte
    - écrire à l'adresse HOTEP (voir ch'ti listing ) du code sur le HD la valeur initiale de SS:SP qui est dans l'en-tête du fichier exe
    - écrire à l'adresse HOTEC du code sur le HD la valeur initial de SS:SP qui est aussi dans l'en-tête
    - stocker le SS initial (segment VIRII), le SP initial (offset final + taille de la pile), le CS initial (segment VIRII) et l'IP initial    (offset VIRUS) dans l'en-tête du fichier hôte
    - ajouter 2 aux entrées de la table de relogement toujours dans le header
    - ajouter deux pointeurs de relogement à la fin de la table des pointeurs de relogement du fichier exe sur le disque. Le 1er pointe vers la    partie de segment correspondant à HOTEP, le 2ème vers HOTEC
    - recalculer la taille du fichier exe pour réinitialiser correctement compteur de page et taille de la dernière page de l'en-tête
    - écrire le nouvel en-tête sur le disque

  Les fichiers à infecter doivent respecter quelques "critères d'infection" :
 - le fichier doit commencer par "MZ" (les initiales du créateur du principe des fichier exécutable : un peu mégalo :)) c'est la signature d'un fichier exe
 - le n° d'overlay doit être à 0 sinon : plantage
 - l'hôte doit avoir assez de place dans sa table de relogement, on peut la calculer comme ceci :
si(16*taille de l'header-4*nombre d'entrée dans la table de relogement-offset de la table de relogement)>=8 alors il y a suffisament de place dans la table des pointeurs
 - le programme ne doit pas être un exécutable windows. on peut le déterminer si la table de relogement se trouve à un offset de plus de 40h ou égal à 40h alors ce programme n'est pas un exécutable DOS
 - Le fichier ne doit pas avoir été infecté, on peut régler ça en plaçant dans le champ initial IP une valeur de 0060h pour un programme infecté

  Encore un petit renseignement, on doit rendre le contrôle au programme hôte de façon correcte, on doit donc réinitialiser les registres comme si il ne ne s'était rien passé. Également, lors du chargement d'un exécutable, le registre AX prend une valeur particulière choisie par le DOS pour indiquer la validité du lecteur dans le FCB se trouvant dans le PSP. AL prend la valeur 0 si l'indicateur se situant dans le 1er FCB à l'adresse 005Ch est bonne et AH prend également 0 si l'adresse 006Ch de la FCB est valide. On doit donc sauvegarder le registre AX et le restaurer avant de rendre le contrôle au programme hôte On doit également déplacer la DTA (les attributs du fichier) pour une meilleure efficacité du virus

  Bon, c'est tout ce que je dirai pour l'instant : maintenant c'est à vous de vous débrouiller, j'ai donné les principales caractéristiques d'un virus infecteur de exe, réalisez vos virus et envoyez les moi (NON COMPILÉS) à ddway@hotmail.com, le meilleur code gagnera d'avoir le prochain N° du zine dans sa boite aux lettres

  La prochaine fois, je vous monterai du code commenté sur un bon virus infecteur de exe avec peut-être une routine de cryptage (un ch'tit virus polymorphe)
Allez bon code et surtout : NE DETRUISEZ RIEN !! (un bon virus n'est pas fait pour détruire) ..

(Ndrl : j'espère qu'il est pas trop long comme article)
DDway <ddway@hotmail.com>