Vous êtes ici

The Unparsable

Portrait de ivan
Je fouinais dans ma réserve d'échantillons de virus liés au groupe APT1 quand j'en ai découvert un qui sortait du lot. Il s'agit de e480c8839e819eaa9b19d53acfa95052, et sa particularité est qu'il a mis en échec l'intégralité des analyseurs que je lui ai jetés au visage. Pourtant, il s'agit bien d'un exécutable Windows valide, et l'OS de Microsoft ne semble pas avoir de difficultés à le lancer.
Remontons les manches et jetons un coup d’œil sous le capot.

Si on en croit les plaintes des outils testés, il y a un problème au niveau des imports de ce fichier et effectivement, quand je regarde ce qui se trouve dans la structure IMAGE_IMPORT_DESCRIPTOR lors de la lecture des données de la section relative aux imports, je constate que les valeurs sont complètement dans les choux. Que se passe-t-il ? Où sont ces fichus imports, pour commencer ? J'ouvre le binaire dans un éditeur hexadécimal pour tirer les choses au clair, et tombe nez à nez avec cette horreur :

00000000   4D 5A 4B 45  52 4E 45 4C  33 32 2E 44  4C 4C 00 00  50 45 00 00  MZKERNEL32.DLL..PE..
00000014   4C 01 03 00  BE B0 11 40  00 AD 50 FF  76 34 EB 7C  48 01 0F 01  L......@..P.v4.|H...
00000028   0B 01 4C 6F  61 64 4C 69  62 72 61 72  79 41 00 00  18 10 00 00  ..LoadLibraryA......
0000003C   10 00 00 00  00 90 00 00  00 00 40 00  00 10 00 00  00 02 00 00  ..........@.........
00000050   04 00 00 00  00 00 39 00  04 00 00 00  00 00 00 00  00 C0 01 00  ......9.............
00000064   00 02 00 00  00 00 00 00  02 00 00 00  00 00 10 00  00 10 00 00  ....................
00000078   00 00 10 00  00 10 00 00  00 00 00 00  0A 00 00 00  00 00 00 00  ....................
0000008C   00 00 00 00  EE B1 01 00  14 00 00 00  00 00 00 00  00 00 00 00  ....................
000000A0   FF 76 38 AD  50 8B 3E BE  F0 B0 41 00  6A 27 59 F3  A5 FF 76 04  .v8.P.>...A.j'Y...v.
000000B4   83 C8 FF 8B  DF AB EB 1C  00 00 00 00  47 65 74 50  72 6F 63 41  ............GetProcA
000000C8   64 64 72 65  73 73 00 00  00 00 00 00  00 00 00 00  40 AB 40 B1  ddress..........@.@.
000000DC   04 F3 AB C1  E0 0A B5 1C  F3 AB 8B 7E  0C 57 51 E9  AB 2C 01 00  ...........~.WQ..,..
000000F0   56 10 E2 E3  B1 04 D3 E0  03 E8 8D 53  18 33 C0 55  40 51 D3 E0  V..........S.3.U@Q..
00000104   8B EA 91 FF  56 4C 99 59  D1 E8 13 D2  E2 FA 5D 03  EA 45 59 89  ....VL.Y......]..EY.
00000118   6B 08 56 8B  F7 2B F5 F3  A4 AC 5E B1  80 AA 3B 7E  34 0F 82 AC  k.V..+....^...;~4...

Quiconque est habitué au format verra immédiatement que le header PE a été lourdement modifié, et la présence de chaînes comme KERNEL32.DLL ou LoadLibraryA laisse penser que les imports que nous recherchons sont justement ici (là où lls n'ont rien à faire). Une seule explication : cet exécutable a été modifié (voire entièrement conçu) à la main, dans le but de faire planter les outils d'analyse. Car il faut savoir que malgré une jolie norme d'environ 70 pages, le loader de Windows fait preuve d'un laxisme outrancier et tolère des déformations du format PE qui rendent très complexe le décorticage des binaires... Car chez les créateurs de virus, l'implémentation de référence, c'est évidemment celle de l'OS et pas ce qui est marqué sur le papier. Malheureusement pour ceux qui écrivent les outils d'analyse, il faut donc s'aligner à l'aveuglette sur cette manière de faire, pour laquelle il n'existe naturellement aucune documentation officielle. Même les plus grands (IDA, OllyDbg) s'y cassent parfois les dents.
Mais revenons à nos moutons. Si on regarde la table des sections de ce binaire, on peut remarquer quelque chose d'intéressant pour celle qui contient les imports :

SECTION TABLE:
--------------
 
Name			PS�ի��
VirtualSize		e000
VirtualAddress		1000
SizeOfRawData		1f0
PointerToRawData	10
PointerToRelocations	40f000
PointerToLineNumbers	413d53
NumberOfRelocations	1c5
NumberOfLineNumbers	0
NumberOfRelocations	1c5
Characteristics		IMAGE_SCN_CNT_CODE
			IMAGE_SCN_CNT_INITIALIZED_DATA
			IMAGE_SCN_MEM_EXECUTE
			IMAGE_SCN_MEM_READ
			IMAGE_SCN_MEM_WRITE

La valeur qui surprend, c'est PointerToRawData (0x10). En théorie, on doit trouver ici un multiple de IMAGE_OPTIONAL_HEADER.FileAlignment (traditionnellement, 0x200). Si ce n'est pas le cas, il faut arrondir en-dessous pour s'aligner sur le dernier multiple. Une subtilité qui rappelée par ReversingLabs dans leur conférence sur le sujet à la Black Hat 2011.
En l'espèce, la section recherchée ne commence donc pas à l'offset 0x10 dans le fichier, mais à l'offset 0x0 : les outils qui omettraient ce détail liraient alors les données recherchées au mauvais endroit.

Mais les ennuis ne s'arrêtent pas là. Voici à quoi ressemble la structure que l'on vient de réussir à lire.

struct image_import_descriptor_t
{
	uint32 OriginalFirstThunk; // = 0x00000000
	uint32 TimeDateStamp; // = 0x00000000
	uint32 ForwarderChain; // = 0x00000000
	uint32 Name; // = 0x00000002
	uint32 FirstThunk; // = 0x914511e8
};

Là encore, c'est très piégeux.
OriginalFirstThunk est laissé à zéro ; dans ce cas-là, on peut utiliser FirstThunk à la place. Le champ Name est censé contenir une Adresse Relative Virtuelle (RVA) - celle-ci est ensuite traduite pour retrouver la position de la donnée pointée dans le fichier sur le disque. Que se passe-t-il lorsque la traduction échoue ? Windows utilise l'adresse telle quelle, au cas où. Évidemment, c'est le cas ici. Pour finir, le dernier DWORD (FirstThunk) se trouve à mi-chemin entre deux sections. Voyez plutôt :

Si vous vous remémorez la table donnée plus haut, la section commençait à l'offset 0x10 et avait une taille de 0x1f0... Je ne sais pas exactement comment le loader Windows gère le ré-alignement de la section, mais il me semblerait logique que sa limite haute soit maintenue à 0x200. Or ici, les deux derniers octets lus (45 91) sont en-dehors des limites. Que se passe-t-il ? Il ne s'agit que de spéculation, mais je pense que la représentation mémoire qu'utilise le le système d'exploitation fait qu'il n'y a aucun débordement possible. Les octets "en trop" sont laissés à zéro, ce qui nous donnerait FirstThunk = 0x000011e8... Pile poil la valeur qui nous fait tomber au bon endroit.
...Après quoi on tombe encore sur des adresses qui se traduisent mal et qu'il faut ré-interpréter, etc. etc.

Voila pour la lecture des imports ! En vrac, il y a aussi des petits pièges ailleurs, comme le IMAGE_DATA_DIRECTORY relatif aux informations de Debug qui a une taille nulle, ou d'autres qui n'existent tout simplement pas.
Et vu l'échec de IDA pour obtenir le code désassemblé, je pense qu'il y a encore des astuces intéressantes qui se cachent dans ce PE.
Si vous poussez l'analyse plus loin, faîtes-moi signe !

Source de l'image d'en-tête : http://wondermark.com/new-shirt-unparsable-symbols/