Analyse de sality.aa - Première partie

Fri 08 August 2008 by damien

À la suite d'une attaque virale, nous avons récupéré un portable infecté. Le but premier était de fournir suffisamment d'informations à la victime pour mettre en place des contre-mesures.

Cependant au fur et à mesure de l'analyse, nous avons trouvé que ce virus est assez caractéristique des malwares professionnels que l'on trouve de nos jours. Il s'agit d'une souche d'un virus appelé Sality.aa (Kaspersky: Virus.Win32.Sality.aa) apparue en Juillet 2008.

L'analyse étant assez longue, nous l'avons scindé en plusieurs parties. La première partie présente l'infecteur, c'est-à-dire l'exécutable responsable du lancement de la charge malveillante.

Récupération du virus

Nous avons en notre possession un pc portable infecté par le virus. Nous insérons un clé USB et immédiatement apparait sur la clé un binaire caché accompagné d'un fichier autorun.inf, tout cela ayant pour but d'exécuter automatiquement le virus lors de l'insertion de la clé.

Fonctionnement général du stub d'initialisation du virus

Avant de se répliquer et de se propager ce virus doit s'initialiser pour pouvoir exécuter sa charge virale. Nous allons nous intéresser à cette partie dans ce billet.

Avant de rentrer dans les détails techniques de l'analyse, voici les différentes fonctions effectuées par le stub d'initialisation.

  • calcul de l'adresse de la section contenant le code d'initialisation ;
  • déchiffrement de la section ;
  • exécution de la charge virale.

Rentrons maintenant dans le vif du sujet. Pour ceux qui sont allergiques à l'assembleur, un schéma résume à la fin de l'article les points importants de l'analyse.

Analyse du binaire

Après ouverture dans IDA le binaire apparait bien obfusqué. Pour nous faciliter la tâche, nous allons effectuer une analyse dynamique dans une machine virtuelle isolée du réseau.

Heureusement pour nous, le binaire n'est pas protégé du tout contre l'analyse. L'analyse dans un editeur PE nous montre que le fichier infecté comporte une section dont le nom n'est pas standard : .hdata. De plus cette section est exécutable et en lecture/ecriture. Nous suspectons donc que le malware doit déchiffrer et exécuter cette section.

Calcul de l'adresse de la section

L'entrypoint du malware est obfusqué et en fait calcule juste l'adresse du début de la section .hdata pour ensuite sauter dedans. L'analyse est perturbée par certains patterns d'obfuscation.

Des appels inutiles de fonctions :

0100739E   . 6A 00          PUSH 0
010073A0   . FF15 1C110001  CALL DWORD PTR DS:[<&KERNEL32.FindClose>]

Des calculs inutiles sur les registres :

010073C0   . 0FA4C1 24      SHLD ECX,EAX,24
010073C4   . 8BF5           MOV ESI,EBP010073C6   . 0FA5F7         SHLD EDI,ESI,CL

Des sauts au-dessus d'une instruction :

010073AE   . EB 01          JMP SHORT malware_.010073B1
010073B0   . 9C             PUSHFD
010073B1   > 8BF5           MOV ESI,EBP

Une fois enlevé l'obfuscation, le code est résumé par quelques instructions.

Un saut sur lui-meme pour récupérer son adresse:

010073B3   . E8 00000000    CALL malware_.010073B8

L'adresse est sauvegardée dans edx:

010073C9   . 5A             POP EDX

Plusieurs calculs sur edx pour finalement retrouver l'adresse virtuelle de début de la section .hdata:

010073CA   . 81C2 1D700000  ADD EDX,701D
...
010073D7   . 81C2 8D100000  ADD EDX,108D
...
010073E6   . 81C2 AB630000  ADD EDX,63AB
...
010073F5   . 81EA 0D180000  SUB EDX,180D
...
01007403   > 52             PUSH EDX

Le calcul donne :

0x10073b8 + 0x701d + 0x108d + 0x63ab - 0x180d = 0x1014000

Ce qui est exactement l'adresse du début de la section.

Il calcule ensuite une autre adresse dans la section qui s'avéra être plus tard l'adresse des données à déchiffrer.

01007404   . 81C2 B2070000  ADD EDX,7B2
...
01007415   . 81C2 4B100000  ADD EDX,104B
...
01007423   . 81EA E7060000  SUB EDX,6E7
...
01007431   . 52             PUSH EDX

Ce qui donne :

0x1014000 + 0x7b2 + 0x104b - 0x6e7 = 0x1015116

Finalement il fait un push/ret sur l'adresse du début de la section. De plus il laisse dans la pile 2 adresses 0x1014000 et 0x1015116.

Déchiffrement de la section

Nous suspections depuis le début l'utilisation de chiffrement sur une partie de la section, car elle est modifiable et exécutable. Nous traçons donc le code jusqu'à ce que nous voyions des accès en écriture dans la section.

01014461   0FAFC8           IMUL ECX,EAX
01014464   81E1 B4A7CE59    AND ECX,59CEA7B4
0101446A   8916             MOV DWORD PTR DS:[ESI],EDX
0101446C   0FB7CF           MOVZX ECX,DI
0101446F   F7C3 1C6F7661    TEST EBX,61766F1C
...
010144B7   0FA4C1 24        SHLD ECX,EAX,24
010144BB   0FBCC8           BSF ECX,EAX
010144BE   891E             MOV DWORD PTR DS:[ESI],EBX
010144C0   0FB7CF           MOVZX ECX,DI
010144C3   0FC1C8           XADD EAX,ECX
...

Ces affectations correspondent au remplissage d'une table par une plage de valeurs. Ces valeurs sont constantes mais calculées via une série d'additions et de soustractions permettant l'obfuscation. La table se trouve à l'adresse 0x01015016.

Voici cette table en mémoire dans la section :

01015000  64 71 C6 F4 67 2C 4A B9 AC 12 10 47 84 F5 BA 9B
01015010  B0 B1 76 7F 5C FD 64 01 02 03 04 05 06 07 08 09
01015020  0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19
01015030  1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29
01015040  2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39
01015050  3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49
01015060  4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59
01015070  5A 5B 5C 5D 5E 5F 60 61 62 63 00 65 66 67 68 69
01015080  6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79
01015090  7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89
010150A0  8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99
010150B0  9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9
010150C0  AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9
010150D0  BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9
010150E0  CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9
010150F0  DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9
01015100  EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 F6 F7 F8 F9
01015110  FA FB FC FD FE FF

Nous reconnaissons une table d'initialisation d'un RC4. Nous avons besoin de trouver la clé qui sert au chiffrement. Pour ce faire nous cherchons à identifier la génération de la permutation à partir de la clé. Pour mémoire la génération de la clé par RC4 se fait avec l'algorithme suivant :

pour i de 0 à 255  ; Génération de la table.
    S[i] := i
finpour
j := 0
pour i de 0 à 255
    j := (j + S[i] + clé[i mod longueur_clé]) mod 256
    échanger(S[i],S[j])
finpour

Nous cherchons les accès mémoire pour trouver où se trouve la deuxième boucle de génération de la permutation. Nous localisons rapidement la partie de code incriminée :

01014708   0FA5F7           SHLD EDI,ESI,CL
0101470B   FFC0             INC EAX
0101470D   0FCF             BSWAP EDI
0101470F   0216             ADD DL,BYTE PTR DS:[ESI]   ; j = j + cle[i]
01014711   2BF3             SUB ESI,EBX
01014713   85F1             TEST ECX,ESI
01014715   15 736A8534      ADC EAX,34856A73
0101471A   0FA4F7 6B        SHLD EDI,ESI,6B
0101471E   58               POP EAX
0101471F   02D0             ADD DL,AL         ; j = j + S[i]
01014721   53               PUSH EBX
01014722   01EA             ADD EDX,EBP
01014724   0FBBF7           BTC EDI,ESI
01014727   F7D3             NOT EBX
01014729   D1F3             SAL EBX,1
0101472B   8A22             MOV AH,BYTE PTR DS:[EDX]    ; ah = table_permutation[j]
0101472D   2BD5             SUB EDX,EBP
0101472F   87DF             XCHG EDI,EBX
01014731   33D9             XOR EBX,ECX
01014733   87DF             XCHG EDI,EBX
01014735   01D5             ADD EBP,EDX
01014737   0FB7FD           MOVZX EDI,BP
0101473A   0FB7D9           MOVZX EBX,CX
0101473D   EB 01            JMP SHORT malware_.01014740
...
01014740   8845 00          MOV BYTE PTR SS:[EBP],AL  ; table_permutation[j] = table_permutation[i]
01014743   29D5             SUB EBP,EDX
01014745   0FBBF7           BTC EDI,ESI
01014748   0FBAFF 23        BTC EDI,23
0101474C   F7C6 1B726D7C    TEST ESI,7C6D721B
01014752   03CD             ADD ECX,EBP
01014754   0FCB             BSWAP EBX
01014756   F7D3             NOT EBX
01014758   C7C3 5BB2ADBC    MOV EBX,BCADB25B
0101475E   8821             MOV BYTE PTR DS:[ECX],AH ; table_permutation[i] = ah
01014760   2BCD             SUB ECX,EBP

Nous en déduisons que la clé se trouve à l'adresse 0x01015000 et a une taille 11 octets. Voici la clé :

01015000  64 71 C6 F4 67 2C 4A B9 AC 12 10

Le pseudocode de l'algorithme de chiffrement du RC4 est le suivant :

i := 0
j := 0
tant_que générer une sortie:
    i := (i + 1) mod 256
    j := (j + S[i]) mod 256
    échanger(S[i],S[j])
    octet_chiffrement = S[(S[i] + S[j]) mod 256]
    result_chiffré = octet_chiffrement XOR octet_message
fintant_que

Nous retrouvons cette routine de chiffrement dans le code suivant :

010147B9   FEC2             INC DL         ; i= i + 1
010147BB   56               PUSH ESI
010147BC   68 7F2E3206      PUSH 6322E7F
010147C1   893C24           MOV DWORD PTR SS:[ESP],EDI
010147C4   01EA             ADD EDX,EBP
010147C6   F6C7 D0          TEST BH,0D0
010147C9   89EE             MOV ESI,EBP
010147CB   11EE             ADC ESI,EBP
010147CD   021A             ADD BL,BYTE PTR DS:[EDX]    ; j = j + table_permutation[i]
010147CF   29EA             SUB EDX,EBP
010147D1   0FCF             BSWAP EDI
010147D3   BE D447EEF9      MOV ESI,F9EE47D4
010147D8   0FC0C4           XADD AH,AL
010147DB   83E0 00          AND EAX,0
010147DE   01D5             ADD EBP,EDX
010147E0   D3DF             RCR EDI,CL
010147E2   BF FDCC5FA6      MOV EDI,A65FCCFD
010147E7   0FACF7 5A        SHRD EDI,ESI,5A
010147EB   8A45 00          MOV AL,BYTE PTR SS:[EBP] ; al = table_permutation[i]
010147EE   2BEA             SUB EBP,EDX
010147F0   4F               DEC EDI
...
010147FA   BF AAC57467      MOV EDI,6774C5AA
010147FF   0FBBCE           BTC ESI,ECX
01014802   8A6D 00          MOV CH,BYTE PTR SS:[EBP] ; ch = table_permutation[j]
01014805   2BEB             SUB EBP,EBX
...
01014816   0FBDFE           BSR EDI,ESI
01014819   8BFA             MOV EDI,EDX
0101481B   8803             MOV BYTE PTR DS:[EBX],AL   ; table_permutation[j] = al
0101481D   29EB             SUB EBX,EBP
0101481F   FFCF             DEC EDI
...
01014830   0FCF             BSWAP EDI
01014832   8BFA             MOV EDI,EDX
01014834   882A             MOV BYTE PTR DS:[EDX],CH    ; table_permutation[i] = ch
01014836   29EA             SUB EDX,EBP
01014838   1BFA             SBB EDI,EDX
...
01014844   02C5             ADD AL,CH           ; k = table_permutation[i] + table_permutation[j]
01014846   5F               POP EDI
01014847   83C7 01          ADD EDI,1
0101484A   8A4405 00        MOV AL,BYTE PTR SS:[EBP+EAX]; al = table_permutation[k]
0101484E   3007             XOR BYTE PTR DS:[EDI],AL    ; Donnees[i-1] = Donneess[i-1] xor al
01014850   80E9 01          SUB CL,1

Il est à noter que la routine de chiffrement est elle aussi obfusquée. Finalement nous déduisons que les données chiffrées se trouvent à l'adresse 0x01015116 et ont une taille de 0xfeea octets.

Finalement après avoir déchiffré cette partie de la section, le programme saute dans cette zone :

010148B5   33DA             XOR EBX,EDX
010148B7   0FACD8 37        SHRD EAX,EBX,37
010148BB   28E6             SUB DH,AH
010148BD   F6DC             NEG AH
010148BF   C3               RETN           ; saut vers malware_.01015116

Avec un script python nous vérifions que notre analyse est correcte.

import sys
import binascii
from Crypto.Cipher import ARC4

f = open(sys.argv[1], 'rb')
data = f.read()f.close()
key = binascii.unhexlify("6471C6F4672C4AB9AC1210")
rc = ARC4.new(key)
c = rc.decrypt(data)
sys.stdout.write(c)
$ python decrypt.py ciphered_block.raw | hexdump -C
00000000  e8 00 00 00 00 5d 81 ed  05 10 40 00 58 2d 63 cc  |.....]....@.X-c.|
00000010  00 00 89 85 43 12 40 00  80 bd 73 27 40 00 00 75  |....C.@...s'@..u|
00000020  19 c7 85 3a 14 40 00 22  22 22 22 c7 85 29 14 40  |...:.@.""""..).@|
00000030  00 33 33 33 33 e9 82 00  00 00 33 db 64 67 8b 1e  |.3333.....3.dg..|
00000040  30 00 85 db 78 0e 8b 5b  0c 8b 5b 1c 8b 1b 8b 5b  |0...x..[..[....[|
00000050  08 f8 eb 0a 8b 5b 34 8d  5b 7c 8b 5b 3c f8 66 81  |.....[4.[|.[<.f.|
00000060  3b 4d 5a 74 05 e9 42 01  00 00 8b f3 03 76 3c 81  |;MZt..B......v<.|
00000070  3e 50 45 00 00 74 05 e9  30 01 00 00 89 9d 11 14  |>PE..t..0.......|

Exécution de la charge virale

Le début du bloc déchiffré contient bien des instructions valides. Il récupère son adresse puis décale ebp de 0x401005.

01015116    E8 00000000     CALL malware_.0101511B
0101511B    5D              POP EBP
0101511C    81ED 05104000   SUB EBP,401005
01015122    58              POP EAX
...
01015128    8985 43124000   MOV DWORD PTR SS:[EBP+401243],EAX
0101512E    80BD 73274000   CMP BYTE PTR SS:[EBP+402773],0
01015135    75 19           JNZ SHORT malware_.01015150
01015137    C785 3A144000   MOV DWORD PTR SS:[EBP+40143A],22222222

Ensuite il retrouve les adresses des fonctions dont il a besoin. Pour cela il parcourt la liste des dlls stockées dans le PEB.

01015152    64:67:8B1E 3000 MOV EBX,DWORD PTR FS:[30] ; pointeur sur le PEB
01015158    85DB            TEST EBX,EBX
0101515A    78 0E           JS SHORT malware_.0101516A
0101515C    8B5B 0C         MOV EBX,DWORD PTR DS:[EBX+C]
0101515F    8B5B 1C         MOV EBX,DWORD PTR DS:[EBX+1C]
01015162    8B1B            MOV EBX,DWORD PTR DS:[EBX]
01015164    8B5B 08         MOV EBX,DWORD PTR DS:[EBX+8] ; pointeur sur l'adresse de la dll

Il vérifie que la dll est valide :

01015174    66:813B 4D5A    CMP WORD PTR DS:[EBX],5A4D ; MZ
01015179    74 05           JE SHORT malware_.01015180
0101517B    E9 42010000     JMP malware_.010152C2
01015180    8BF3            MOV ESI,EBX
01015182    0376 3C         ADD ESI,DWORD PTR DS:[ESI+3C]
01015185    813E 50450000   CMP DWORD PTR DS:[ESI],4550 ; PE
0101518B    74 05           JE SHORT malware_.01015192
0101518D    E9 30010000     JMP malware_.010152C2

Il cherche ensuite l'adresse de :

  • LoadLibraryA
  • GetProcAddress
  • CloseHandle
  • CreateFileMappingA
  • CreateMutexA
  • CreateThread
  • SetErrorMode
  • VirtualAlloc
  • lstrlenA
  • ReleaseMutex
  • GetLastError
  • MapViewOfFile
  • Sleep
  • GetModuleFileNameA
  • GetTempPathA
  • CopyFileA
  • CreateFileA
  • CreateFileW
  • OpenFile
  • GetFileSize
  • UnmapViewOfFile
  • SetFilePointer
  • SetEndOfFile
  • lstrcmpiA
  • lstrcatA
  • GetTickCount
  • CreateDirectoryA
  • WideCharToMultiByte
  • ExitProcess
  • _lopen

Il crée ensuite un mapping appelé l8geqpHJTkdns0 avec kernel32.CreateFileMappingA.

0101520C    57              PUSH EDI
0101520D    68 00800000     PUSH 8000
01015212    6A 00           PUSH 0
01015214    6A 04           PUSH 4
01015216    6A 00           PUSH 0
01015218    6A FF           PUSH -1
0101521A    FF95 61144000   CALL DWORD PTR SS:[EBP+401461]  ; kernel32.CreateFileMappingA

Il crée ensuite un mapping appelé purity_control_90830 avec kernel32.CreateFileMappingA et le map avec kernel32.MapViewOfFile :

01015226    57              PUSH EDI
01015227    68 00540100     PUSH 15400
0101522C    6A 00           PUSH 0
0101522E    6A 04           PUSH 4
01015230    6A 00           PUSH 0
01015232    6A FF           PUSH -1
01015234    FF95 61144000   CALL DWORD PTR SS:[EBP+401461]  ; kernel32.CreateFileMappingA
0101523E    68 00540100     PUSH 15400
01015243    6A 00           PUSH 0
01015245    6A 00           PUSH 0
01015247    6A 06           PUSH 6
01015249    50              PUSH EAX
0101524A    FF95 E6144000   CALL DWORD PTR SS:[EBP+4014E6]   ; kernel32.MapViewOfFile

Il recopie ensuite 0xe6ec octets à partir de l'adresse 0x1015116 dans le mapping (0x7c0000 dans notre cas) :

01015263    B9 ECE60000     MOV ECX,0E6EC
01015268    8DB5 00104000   LEA ESI,DWORD PTR SS:[EBP+401000]
0101526E    8BF8            MOV EDI,EAX
01015270    8B06            MOV EAX,DWORD PTR DS:[ESI]
01015272    3907            CMP DWORD PTR DS:[EDI],EAX
01015274    74 02           JE SHORT malware_.01015278
01015276    F3:A4           REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]

Il fait un appel à CreateThread.

01015293    50              PUSH EAX
01015294    6A 00           PUSH 0
01015296    6A 00           PUSH 0
01015298    FF95 83144000   CALL DWORD PTR SS:[EBP+401483]           ; kernel32.CreateThread

Il rentre ensuite dans une boucle avec un sleep de 12 ms en surveillant une variable à l'adresse ebp+402773.

010152B1    6A 0C           PUSH 0C
010152B3    FF95 F0144000   CALL DWORD PTR SS:[EBP+4014F0]           ; kernel32.Sleep

Thread de chargement (loader)

Intéressons-nous maintenant au code exécuté par le thread. Le thread commence par récupérer le chemin complet puis le nom du fichier en train de s'exécuter.

0101582C   68 FE010000      PUSH 1FE
01015831   50               PUSH EAX
01015832   6A 00            PUSH 0
01015834   FF95 07154000    CALL DWORD PTR SS:[EBP+401507]           ; kernel32.GetModuleFileNameA

Il crée ensuite un mutex appelé Op1mutx9.

01015939    50              PUSH EAX
0101593A    6A 00           PUSH 0
0101593C    6A 00           PUSH 0
0101593E    FF95 72144000   CALL DWORD PTR SS:[EBP+401472]           ; kernel32.CreateMutexA

Dans le code déchiffré est stocké un PE, le virus vérifie que celui-ci est valide.

01015977    75 0C           JNZ SHORT malware_.01015985
01015979    8B95 7C164000   MOV EDX,DWORD PTR SS:[EBP+40167C]
0101597F    81C2 EC340000   ADD EDX,34EC
01015985    66:813A 4D5A    CMP WORD PTR DS:[EDX],5A4D
0101598A    0F85 4C020000   JNZ malware_.01015BDC
01015990    8B42 3C         MOV EAX,DWORD PTR DS:[EDX+3C]
01015993    03D0            ADD EDX,EAX
01015995    66:813A 5045    CMP WORD PTR DS:[EDX],4550
0101599A    0F85 3C020000   JNZ malware_.01015BDC

L'offset du PE est 0x34ec à partir de l'adresse de la section déchiffrée (0x1015116).

01018602  4D 5A 90 00 03 00 00 00  MZ......
0101860A  04 00 00 00 FF FF 00 00  ....ÿÿ..
01018612  B8 00 00 00 00 00 00 00  ¸.......
0101861A  40 00 00 00 00 00 00 00  @.......
01018622  00 00 00 00 00 00 00 00  ........
0101862A  00 00 00 00 00 00 00 00  ........
01018632  00 00 00 00 00 00 00 00  ........
0101863A  00 00 00 00 D8 00 00 00  ....Ø...
01018642  0E 1F BA 0E 00 B4 09 CD  ..º..´.Í
0101864A  21 B8 01 4C CD 21 54 68  !¸.LÍ!Th
01018652  69 73 20 70 72 6F 67 72  is progr
0101865A  61 6D 20 63 61 6E 6E 6F  am canno
01018662  74 20 62 65 20 72 75 6E  t be run
0101866A  20 69 6E 20 44 4F 53 20   in DOS
01018672  6D 6F 64 65 2E 0D 0D 0A  mode....

Il s'avère que ce PE est un binaire packé avec UPX. Le virus va mapper proprement le binaire en mémoire comme le ferait le loader de Windows. Pour celà il commence par allouer de la mémoire où mapper le binaire :

010159DE    6A 40           PUSH 40
010159E0    68 00300000     PUSH 3000
010159E5    05 00000001     ADD EAX,malware_.01000000
010159EA    50              PUSH EAX
010159EB    6A 00           PUSH 0
010159ED    FF95 A5144000   CALL DWORD PTR SS:[EBP+4014A5]   ; kernel32.VirtualAlloc
...
01015A27    F3:A4           REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>

Il parcourt ensuite les headers de chaque section et mappe chaque section dans la zone allouée par VirtualAlloc. Puis reconstruit l'IAT du fichier upxé. Il résoud les adresses des fonctions suivantes, présentes dans l'IMAGE_IMPORT_DESCRIPTOR du fichier :

  • LoadLibraryA
  • GetProcAddress
  • VirtualProtect
  • ExitProcess
  • RegCloseKey
  • malloc
  • SHFileOperationA
  • wsprintfA

Ces fonctions sont les fonctions nécessaires au stub d'upx pour décompresser et exécuter le binaire packé. Finalement il saute sur l'entrypoint du binaire upxé et passe la main au stub d'upx qui va décompresser le binaire et l'exécuter. Dans la prochaine partie, nous étudierons le binaire upxé.

La figure suivante résume simplement tout la machinerie mise en place par le virus.

infecteur