Manual Unpacking

Packer : ASPACK 2.12

Objectif :

Cible :

Outils nécessaires :

Fichiers joints :

Index

  1. Introduction
  2. Collecte des infos sur la cible
  3. Dump de la section .rdata
  4. Recherche de l'original entry-point (OEP)
  5. Dump Full du programme
  6. Insertion de la section .rdata dans le dump
  7. Recherche de la table des imports
  8. Mise à jour du PE
  9. Infos pratiques relevés sur Aspack 2.12
  10. Script pour WinDbg pour dumper auto la section .rdata

1. Introduction

Voila un petit packer qui fait souvent peur aux newbies, alors que c'est l'un des plus simple à unpacker. Il existe des tools génériques pour unpacker aspack 2.12 mais la technique décrite ici pour l'unpacker manuellement est intéressante surtout en ce qui concerne la reconstruction des imports et mérite, je pense, qu'on s'y attarde un peu.

2. Collecte des infos sur la cible

On ouvre la cible avec Lord-PE et on récupère les infos suivantes :

Entry-point : 00006001
Import-Table : RVA = 00006FAC, size = 00000064


Sections Table :

names		Voffset		Vsize		Roffset		Rsize		Flags
.text		00001000	00001000	00000400	00000200	C0000040
.rdata		00002000	00001000	00000600	00000200	C0000040
.data		00003000	00001000	00000800	00000200	C0000040
.rsrc		00004000	00002000	00000A00	00000200	C0000040
.aspack		00006000	00002000	00000C00	00002000	C0000040
.adata		00008000	00001000	00002C00	00000000	C0000040

On voit qu'apparemment aspack garde la structure d'un exe avec les sections d'origines et ajoute 2 sections à la fin ( .aspack et .adata ). Si on regarde l'entry-point on voit qu'il pointe dans la section .aspack qui contiendra sûrement le code du loader pour la décompression. L'offset de la dernière section .adata est pointé sur la fin du fichier et on voit d'après sa Rsize qu'elle n'existe pas sur le disque en fait, par contre en mémoire elle occupera une taille de 1000h bytes. J'en déduis que ça pourrait être une sorte de zone tampon utile au loader pour décompresser des données.

Les sections ont aussi un flag qui ne nous permettra pas désassembler sous wdasm, il faudra mettre la valeur E0000020.

3. Dump de la section .rdata

Nous savons que la section .rdata est la section des imports, celle-ci est pour l'instant compressé par aspack. Le loader à un moment ou un autre ira décompresser cette section. Notre approche ici sera de poser un break-point en accès lecture ou écriture sur la mémoire à l'adresse virtuel de cette section, ainsi nous saurons quand le loader s'occupera de cette section et nous pourrons tracer un peu et attendre sa décompression pour la dumper.

Car à ce moment là, la section .rdata sera claire, si nous ne dumpons pas à ce moment, les tableaux des OriginalFirstThunk et FirstThunk seront modifiés en mémoire par aspack et aussi par le PE loader afin de pointer vers les adresses réelles des fonctions importées (voir tutorial sur la reconstruction de la table des imports si cela n'est pas clair).

On prend notre debugger, moi Windbg et on pose notre piège :

Si vous utiliser SoftIce, le principe reste le même seule le nom des commandes change un peu.

Voila la moitié du boulot à été fait. Habituez vous avec les imports pour arriver à repérer visuellement les imports.
Vous pouvez aussi utiliser le script pour windbg que je vous ai concocté pour dumpé cette section .rdata.

4. Recherche de l'original entry-point (OEP)

On en profite à ce point puisque nous sommes plus très loin de la fin du loader d'aspack pour tracer la fin du code et trouver l'OEP. (On repart de la ou on était pour extraire .rdata c-à-d après les 2 break en accès mémoire sur 402000)

004063a6 59               pop     ecx
image00400000+63a7:
004063a7 0bc9 or ecx,ecx
image00400000+63a9:
004063a9 8985a8030000 mov [ebp+0x3a8],eax ss:0023:004063bb=00000000
image00400000+63af:
004063af 61 popad
image00400000+63b0:
004063b0 7508 jnz image00400000+0x63ba (004063ba) [br=1]
image00400000+63ba:
004063ba 6800104000 push 0x401000 <= Très intéressant
image00400000+63bf:
004063bf c3 ret
image00400000+1000:
00401000 6a00 push 0x0
image00400000+1002:
00401002 e893020000 call image00400000+0x129a (0040129a)
Breakpoint 1 hit
kernel32!GetModuleHandleA:
77e5ad86 837c240400 cmp dword ptr [esp+0x4],0x0 ss:0023:0012ffc0=00000000
0:000>
kernel32!GetModuleHandleA+5:
77e5ad8b 0f8437010000 je kernel32!GetModuleFileNameA+0x11f (77e5aec8) [br=1]
kernel32!GetModuleFileNameA+11f:
77e5aec8 64a118000000 mov eax,fs:[00000018] fs:0038:00000018=7ffde000
kernel32!GetModuleFileNameA+125:
77e5aece 8b4030 mov eax,[eax+0x30] ds:0023:7ffde030=7ffdf000
kernel32!GetModuleFileNameA+128:
77e5aed1 8b4008 mov eax,[eax+0x8]{image00400000 (00400000)} ds:0023:7ffdf008=00400000
kernel32!GetModuleFileNameA+12b:
77e5aed4 e9cdfeffff jmp kernel32!GetModuleHandleA+0x20 (77e5ada6)
kernel32!GetModuleHandleA+20:
77e5ada6 c20400 ret 0x4
image00400000+1007:
00401007 a314304000 mov [image00400000+0x3014 (00403014)],eax ds:0023:00403014=00000000
image00400000+100c:
0040100c 6a00 push 0x0
image00400000+100e:
0040100e 682b104000 push 0x40102b
image00400000+1013:
00401013 6a00 push 0x0
image00400000+1015:
00401015 6814314000 push 0x403114
image00400000+101a:
0040101a ff3514304000 push dword ptr [image00400000+0x3014 (00403014)]{image00400000 (00400000)} ds:0023:00403014=00400000
image00400000+1020:
00401020 e845020000 call image00400000+0x126a (0040126a)

On voit à l'adresse 004063ba que l'on saute en 401000 juste après un popad. C'est la technique classique d'aspack. De plus on change carrément de section avec ce jump on se trouvait dans la section .aspack pour aller dans .text. Pas de doutes c'est l'OEP

offset OEP = 1000h

Qui nous servira pour rectifier l'entry-point de notre dump que nous pouvons maintenant effectuer

5. Dump Full du programme

Avec un dumper genre Lord-PE on va dumper la cible, assurez-vous que ces options soit cochés avant de dumper :

Cela à pour effet de faire correspondre la Rsize et le Roffset des sections avec leur Voffset et Vsize, en gros d'avoir sur le disque la même image qu'occupe le programme en mémoire et de faire correspondre les adresses et tailles des sections dans le PE header. Voilà rien de très compliqué dans cette partie.

6. Insertion de la section .rdata dans le dump

Maintenant nous allons faire un peu de découpage et de collage avec notre dump et notre section .rdata dumpé. Pour ça on prend un éditeur hexa comme Hex Workshop. Le but est de placer la section .rdata dumpé au même endroit que celle contenue dans le dump mais qui est faussée.

Notre dump contient à présent sa section .rdata d'origine.

7. Recherche de la table des imports

Maintenant que nous avons la bonne section rdata dans le dump et que les offset correspondent, on va pouvoir rechercher le début de la table des imports pour pouvoir ajuster son offset dans le PE sinon le programme ne marchera pas. Cette recherche ce fait dans cette optique :

Une table des imports est composée de plusieurs éléments IMAGE_IMPORT_DESCRIPTOR pour chaque DLL importée. Une IMAGE_IMPORT_DESCRIPTOR est une structure de 5 éléments occupant 1 DWORD chacun donc 5 dword au total. On sait que le 4ème élément d'une structure IMAGE_IMPORT_DESCRIPTOR est un pointeur vers un nom de DLL.

Il suffit de rechercher un nom de DLL et de regarder son offset et de lancer une recherche dans le fichier hexa qui fait référence à cette offset. On tombera alors sur le 4eme élément de IMAGE_IMPORT_DESCRIPTOR. on aura plus qu'a parcourir les éléments jusqu'a 5 dword de 0 qui caractérise une structure IMAGE_IMPORT_DESCRIPTOR vide pour marquer la fin de l'import-table. Voyons ça en pratique.

00001FF0 00000000 00000000 00000000 00000000 16210000 08210000 00000000 .................!...!......
0000200C C0200000 B2200000 EC200000 A6200000 D2200000 94200000 DE200000 . ... ... ... ... ... ... ..
00002028 00000000 74200000 00000000 00000000 FC200000 0C200000 68200000 ....t ........... ... ..h ..
00002044 00000000 00000000 2A210000 00200000 00000000 00000000 00000000 ........*!... ..............
00002060 00000000 00000000 16210000 08210000 00000000 C0200000 B2200000 .........!...!....... ... ..
0000207C EC200000 A6200000 D2200000 94200000 DE200000 00000000 92004469 . ... ... ... ... ........Di
00002098 616C6F67 426F7850 6172616D 4100B800 456E6444 69616C6F 67000001 alogBoxParamA...EndDialog...
000020B4 47657444 6C674974 656D0000 02014765 74446C67 4974656D 54657874 GetDlgItem....GetDlgItemText
000020D0 41009B01 4C6F6164 49636F6E 4100BB01 4D657373 61676542 6F784100 A...LoadIconA...MessageBoxA.
000020EC 10025365 6E644D65 73736167 65410000 55534552 33322E64 6C6C0000 ..SendMessageA..USER32.dll..
00002108 75004578 69745072 6F636573 73001101 4765744D 6F64756C 6548616E u.ExitProcess...GetModuleHan
00002124 646C6541 00004B45 524E454C 33322E64 6C6C0000 00000000 00000000 dleA..KERNEL32.dll..........
00001FF0 00000000 00000000 00000000 00000000 16210000 08210000 00000000 .................!...!......
0000200C C0200000 B2200000 EC200000 A6200000 D2200000 94200000 DE200000 . ... ... ... ... ... ... ..
00002028 00000000 74200000 00000000 00000000 FC200000 0C200000 68200000 ....t ........... ... ..h ..
00002044 00000000 00000000 2A210000 00200000 00000000 00000000 00000000 ........*!... ..............
00002060 00000000 00000000 16210000 08210000 00000000 C0200000 B2200000 .........!...!....... ... ..
0000207C EC200000 A6200000 D2200000 94200000 DE200000 00000000 92004469 . ... ... ... ... ........Di
00002098 616C6F67 426F7850 6172616D 4100B800 456E6444 69616C6F 67000001 alogBoxParamA...EndDialog...
000020B4 47657444 6C674974 656D0000 02014765 74446C67 4974656D 54657874 GetDlgItem....GetDlgItemText
000020D0 41009B01 4C6F6164 49636F6E 4100BB01 4D657373 61676542 6F784100 A...LoadIconA...MessageBoxA.
000020EC 10025365 6E644D65 73736167 65410000 55534552 33322E64 6C6C0000 ..SendMessageA..USER32.dll..
00002108 75004578 69745072 6F636573 73001101 4765744D 6F64756C 6548616E u.ExitProcess...GetModuleHan
00002124 646C6541 00004B45 524E454C 33322E64 6C6C0000 00000000 00000000 dleA..KERNEL32.dll..........

Voilà on a trouvé notre table des imports qui débute donc à l'offset : 202C et qui s'étend sur 5*3 dwords (60 bytes => 3Ch)

8. Mise à jour du PE

Notre dump est prêt à fonctionner, mais il faut encore mettre à jour le PE avec les informations suivantes :

Cette fois c'est bon notre dump est fonctionnel et désassemblable avec les imports.

9. Infos pratiques relevés sur Aspack 2.12

004063a6 59               pop     ecx				
004063a7 0bc9 or ecx,ecx
004063a9 8985a8030000 mov [ebp+0x3a8],eax
004063af 61 popad ; restaure les registres 004063b0 7508 jnz image004063BA
004063ba 6800104000 push 0x401000 ; push sur la pile l'OEP
004063bf c3 ret ; recup l'OEP sur la pile et y saute

10. Script pour Windbg pour dumper auto la section .rdata

Comme pour dumper la section .rdata d'un programme packé par Aspack, la technique reste toujours la même. On peut automatiser ça dans un script pour WinDbg qu'il suffira de charger et qui fera le boulot tout seul. Voila le script que j'ai créé :

**********************************************
*
* Script pour windbg
* écrit par Netix
*
* Dump auto de la section .rdata
* pour un programme packé par aspack 2.12
* la section sera dumpé sur c:\rdata.dmp
*
**********************************************
bp 406001 ;* à remplacer par l'entry point de votre programme
G ;* lance l'exe (jusqu'au break point)
ba r1 402000 ;* mettre l'adresse de votre section .rdata
G ;* lance l'exe
G ;* re lance après le 2eme break
p4 ;* step 4 instructions
.writemem c:\rdata.dmp 402000 L1000 ;* dump la section sur le disque

Il suffira juste de changer l'entry-point et l'adresse de votre section .rdata selon votre programme et vous aurez votre section dumpée automatiquement.

Pour lancer le script il suffit de taper la commande suivante $< suivi du nom de fichier du script, exemple :

$<c:\Mes documents\aspack\test\script.txt