2010
05.10

001] Introduction

Me revoilà aujourd’hui pour vous parler d’un framework développé en python qui permet de faire pas mal de petites choses sympa. Typiquement, les différents modules qui compose volatility servent à extraire de l’information d’une « empreinte mémoire« . L’exemple classique est quand vous demander la création d’un snapshot sous VMWare : celui-ci va créer un fichier ayant l’extension .vmem. Pour rentrer un peu plus dans les détails, ce fichier VMEM n’est ni plus ni moins une copie du contenu de la RAM. Cette manière d’opérer permet de disposer d’un dump de la RAM « propre » : il faut comprendre ici que le dispositif de prise d’empreinte n’affecte en rien cette image. Je vais donc aujourd’hui me focaliser sur le fonctionnement de ce genre d’outils, à savoir comment parser un tel fichier, quelles informations peut-on tirer de ce genre d’analyse et comment donner du sens à une tel analyse.

010] Le « Background »

Après cette petite introduction, on va pouvoir commencer à rentrer dans le vif du sujet de façon progressive. Tout commence il y a quelques semaines, lorsque je consulte les différentes organisations qui ont été acceptées cette année au Google Summer Of Code. En cherchant des projets qui avaient rapport avec de la programmation système ou/et orienté sécurité je tombe sur le site d’HoneyNet. C’est là que je découvre le challenge #3, je prends alors connaissance des règles : grosso-modo vous êtes plongé dans une enquête forensique ou vous devez analyser un dump mémoire « post-mortem » d’une machine windows (comprendre ici que l’utilisateur c’est fait avoir par un pdf malicieux).
Le challenge semble sympa, d’autant plus qu’un ami à moi sh4ka me le recommande étant donné qu’il semble « faisable » (il n’est pas nécessaire d’être w4rl0rdz spécialiste des analyses forensiques windows pour répondre à quelques questions). C’est donc accompagné de Taron, que nous tentons de résoudre le challenge. Celui-ci va donc me servir de fil rouge pour vous parlez un petit peu du fonctionnement d’outil dédié à ce genre d’analyse.

011] Volatility

Comme je le disais au début de ce post, le sérieux avantage que nous allons avoir c’est que ce framework est codé en python et que les créateurs de l’application fournissent le code. Avant de rentrer dans l’aspect technique de la chose, je vais expliquer succinctement quelques features cool du framework. Tout d’abord le programme possède un « usage » assez détaillé, lorsque vous recherchez une feature et son utilisation vous ne passez pas trois plombes à la mettre en œuvre, il est ensuite capable d’opérer à des analyses sur des fichiers dumps de système Windows XP SP2/3, et de manière transparente gère plusieurs types de fichiers : crashdump, vmem, hibernation file etc. Il est aussi capable de réaliser la translation VA <-> PA lorsque l’image est issue d’architecture Intel 32Bit avec l’extension PAE d’activé ou non. Bien évidemment, on voit de suite que ce genre d’outil reste assez compliqué à développer étant donné les différentes architectures et les différentes versions de systèmes d’exploitation.

La première question du challenge est la suivante :

1. List the processes that were running on the victim’s machine.

Pour y répondre nous allons bien évidemment utiliser ce framework, voici l’output produit :


D:\Outils\Volatility-1.1.2\Volatility-1.1.2>python volatility pslist -f "Bob.vmem"
Name                 Pid    PPid   Thds   Hnds   Time
System               4      0      58     573    Thu Jan 01 00:00:00 1970
smss.exe             548    4      3      21     Fri Feb 26 03:34:02 2010
csrss.exe            612    548    12     423    Fri Feb 26 03:34:04 2010
winlogon.exe         644    548    21     521    Fri Feb 26 03:34:04 2010
services.exe         688    644    16     293    Fri Feb 26 03:34:05 2010
lsass.exe            700    644    22     416    Fri Feb 26 03:34:06 2010
vmacthlp.exe         852    688    1      35     Fri Feb 26 03:34:06 2010
svchost.exe          880    688    28     340    Fri Feb 26 03:34:07 2010
svchost.exe          948    688    10     276    Fri Feb 26 03:34:07 2010
svchost.exe          1040   688    83     1515   Fri Feb 26 03:34:07 2010
svchost.exe          1100   688    6      96     Fri Feb 26 03:34:07 2010
svchost.exe          1244   688    19     239    Fri Feb 26 03:34:08 2010
spoolsv.exe          1460   688    11     129    Fri Feb 26 03:34:10 2010
vmtoolsd.exe         1628   688    5      220    Fri Feb 26 03:34:25 2010
VMUpgradeHelper      1836   688    4      108    Fri Feb 26 03:34:34 2010
alg.exe              2024   688    7      130    Fri Feb 26 03:34:35 2010
explorer.exe         1756   1660   14     345    Fri Feb 26 03:34:38 2010
VMwareTray.exe       1108   1756   1      59     Fri Feb 26 03:34:39 2010
VMwareUser.exe       1116   1756   4      179    Fri Feb 26 03:34:39 2010
wscntfy.exe          1132   1040   1      38     Fri Feb 26 03:34:40 2010
msiexec.exe          244    688    5      181    Fri Feb 26 03:46:06 2010
msiexec.exe          452    244    0      -1     Fri Feb 26 03:46:07 2010
wuauclt.exe          440    1040   8      188    Sat Feb 27 19:48:49 2010
wuauclt.exe          232    1040   4      136    Sat Feb 27 19:49:11 2010
firefox.exe          888    1756   9      172    Sat Feb 27 20:11:53 2010
AcroRd32.exe         1752   888    8      184    Sat Feb 27 20:12:23 2010
svchost.exe          1384   688    9      101    Sat Feb 27 20:12:36 2010

Le script parcourt l’ensemble du fichier très rapidement, on ne voit quasiment aucun temps d’attente. C’était la première fois que j’utilisais un outil de ce genre et j’avoue avoir été très impressionné par le résultat : rapide et de qualité. Je commence bien à comprendre que cet outil sera l’un des principaux fleurets pour aller le plus loin possible dans l’intrigue du challenge. Maintenant réfléchissons, nous avons un dump mémoire de la mémoire physique d’un système windows x86, il serait donc peut-être temps de mettre en œuvre les différentes connaissances que nous avons sur la pagination/segmentation/win kernel internals pour réussir à comprendre comment un script python est capable de reconstituer l’arborescence des processus. Nous savons que le kernel de windows tiens à jour une double-linked-list (le dernier élément pointe sur le premier, une espèce de circle-double-linked-list) de structure de type EPROCESS ; la structure EPROCESS qui représente un processus lancé sur la machine (n’oublions pas que ces structures sont chainées à l’offset 0x88 sur winxp donc n’oubliez par de réaligner le pointeur pour obtenir le début de la structure). On pouvait connaître l’adresse virtuelle de cette liste grâce à la variable PsActiveProcessHead exporté par le noyau de windows. Juste pour rappel voici la structure EPROCESS pour un windows XP :


kd> dt nt!_EPROCESS poi(nt!PsActiveProcessHead)-0x88
+0x000 Pcb              : _KPROCESS
+0x06c ProcessLock      : _EX_PUSH_LOCK
+0x070 CreateTime       : _LARGE_INTEGER 0x0
+0x078 ExitTime         : _LARGE_INTEGER 0x0
+0x080 RundownProtect   : _EX_RUNDOWN_REF
+0x084 UniqueProcessId  : 0x00000004
+0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x81589628 - 0x80560bd8 ] <-
+0x090 QuotaUsage       : [3] 0
+0x09c QuotaPeak        : [3] 0
+0x0a8 CommitCharge     : 7
+0x0ac PeakVirtualSize  : 0x29e000
+0x0b0 VirtualSize      : 0x1dc000
+0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x0bc DebugPort        : (null)
+0x0c0 ExceptionPort    : (null)
+0x0c4 ObjectTable      : 0xe1001cb0 _HANDLE_TABLE
+0x0c8 Token            : _EX_FAST_REF
+0x0cc WorkingSetLock   : _FAST_MUTEX
+0x0ec WorkingSetPage   : 0
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock   : 0
+0x114 ForkInProgress   : (null)
+0x118 HardwareTrigger  : 0
+0x11c VadRoot          : 0x817f1078
+0x120 VadHint          : 0x817f1078
+0x124 CloneRoot        : (null)
+0x128 NumberOfPrivatePages : 3
+0x12c NumberOfLockedPages : 0
+0x130 Win32Process     : (null)
+0x134 Job              : (null)
+0x138 SectionObject    : (null)
+0x13c SectionBaseAddress : (null)
+0x140 QuotaBlock       : 0x80560c80 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch  : (null)
+0x148 Win32WindowStation : (null)
+0x14c InheritedFromUniqueProcessId : (null)
+0x150 LdtInformation   : (null)
+0x154 VadFreeHint      : (null)
+0x158 VdmObjects       : (null)
+0x15c DeviceMap        : 0xe10000d0
+0x160 PhysicalVadList  : _LIST_ENTRY [ 0x817cc920 - 0x817cc920 ]
+0x168 PageDirectoryPte : _HARDWARE_PTE
+0x168 Filler           : 0
+0x170 Session          : (null)
+0x174 ImageFileName    : [16]  "System"
+0x184 JobLinks         : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x18c LockedPagesList  : (null)
+0x190 ThreadListHead   : _LIST_ENTRY [ 0x817cc774 - 0x8177075c ]
+0x198 SecurityPort     : 0xe175f3a0
+0x19c PaeTop           : (null)
+0x1a0 ActiveThreads    : 0x2b
+0x1a4 GrantedAccess    : 0x1f0fff
+0x1a8 DefaultHardErrorProcessing : 1
+0x1ac LastThreadExitStatus : 0
+0x1b0 Peb              : (null)
+0x1b4 PrefetchTrace    : _EX_FAST_REF
+0x1b8 ReadOperationCount : _LARGE_INTEGER 0x4f
+0x1c0 WriteOperationCount : _LARGE_INTEGER 0x6c
+0x1c8 OtherOperationCount : _LARGE_INTEGER 0x3a6
+0x1d0 ReadTransferCount : _LARGE_INTEGER 0x9104
+0x1d8 WriteTransferCount : _LARGE_INTEGER 0x5d200
+0x1e0 OtherTransferCount : _LARGE_INTEGER 0x22c94
+0x1e8 CommitChargeLimit : 0
+0x1ec CommitChargePeak : 0x1ca
+0x1f0 AweInfo          : (null)
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm               : _MMSUPPORT
+0x238 LastFaultCount   : 0
+0x23c ModifiedPageCount : 0x4bc
+0x240 NumberOfVads     : 4
+0x244 JobStatus        : 0
+0x248 Flags            : 0x40000
+0x248 CreateReported   : 0y0
+0x248 NoDebugInherit   : 0y0
+0x248 ProcessExiting   : 0y0
+0x248 ProcessDelete    : 0y0
+0x248 Wow64SplitPages  : 0y0
+0x248 VmDeleted        : 0y0
+0x248 OutswapEnabled   : 0y0
+0x248 Outswapped       : 0y0
+0x248 ForkFailed       : 0y0
+0x248 HasPhysicalVad   : 0y0
+0x248 AddressSpaceInitialized : 0y00
+0x248 SetTimerResolution : 0y0
+0x248 BreakOnTermination : 0y0
+0x248 SessionCreationUnderway : 0y0
+0x248 WriteWatch       : 0y0
+0x248 ProcessInSession : 0y0
+0x248 OverrideAddressSpace : 0y0
+0x248 HasAddressSpace  : 0y1
+0x248 LaunchPrefetched : 0y0
+0x248 InjectInpageErrors : 0y0
+0x248 VmTopDown        : 0y0
+0x248 Unused3          : 0y0
+0x248 Unused4          : 0y0
+0x248 VdmAllowed       : 0y0
+0x248 Unused           : 0y00000 (0)
+0x248 Unused1          : 0y0
+0x248 Unused2          : 0y0
+0x24c ExitStatus       : 259
+0x250 NextPageColor    : 0x120e
+0x252 SubSystemMinorVersion : 0 ''
+0x253 SubSystemMajorVersion : 0 ''
+0x252 SubSystemVersion : 0
+0x254 PriorityClass    : 0x2 ''
+0x255 WorkingSetAcquiredUnsafe : 0 ''
+0x258 Cookie           : 0

On sait aussi que sous windows le kerneland est « commum » à chaque processus ; en effet tous les processus pointeront sur la même adresse physique pour une adresse virtuelle égale ou supérieur à  0x80000000 (nt!MmSystemRangeStart).

De plus on retrouve l’adresse physique du répertoire de page dans la structure EPROCESS, plus précisément dans Pcb.DirectoryTableBase. Cela veut tout simplement dire que si on arrive à retrouver une structure EPROCESS dans le fichier VMEM on sera capable de retrouver le répertoire de page, et donc de réaliser dans un premier temps une translation d’adresse virtuelle en une adresse physique. Notons que cette translation sera valable uniquement pour une adresse noyau. C’est en fait cette technique qu’utilise Volatility voici la fonction :


def find_dtb(addr_space, types):
print "Guessing de la base du repertoire de page"

try:
flat_address_space = FileAddressSpace(addr_space.name,fast=True)
except:
op.error("Unable to open image file %s" % (filename))

offset=0;

end = os.path.getsize(addr_space.name)

while offset <= end:
value=flat_address_space.fread(8)
if value == None:
break
(type,size) = unpack('=HH',value[:4]) // On prend les 4premiers octet
if(size == 0x1b and type == 0x03):
if process_imagename(addr_space,types,offset).find('Idle') != -1:
return process_dtb(addr_space, types, offset)
offset+=8

return None

Pour résumer le code, le tool va scanner le fichier par paquet de 8octets à la recherche d’une signature qui caractérise les structures EPROCESS. Cette signature de 4octets ne va bien sur pas suffir à éliminer tous les « faux-positif » mais pour cela on pourra par exemple utiliser le nom du processus afin de valider la structure, ou rechercher des champs qui sont censé pointer dans le kerneland et donc de vérifier si ils sont plus grand ou égale à nt!MmSystemRangeStart.



La signature basé sur les 4premiers octets

kd> dt nt!_EPROCESS 0x80559580 -r
+0x000 Pcb              : _KPROCESS
+0x000 Header           : _DISPATCHER_HEADER
+0x000 Type             : 0x3 ''
+0x001 Absolute         : 0 ''
+0x002 Size             : 0x1b ''
+0x003 Inserted         : 0 ''


Cette technique nous la devons à Andreas Schuster. Cependant, on comprend assez rapidement que cette technique peut poser problème étant donné que les offsets dans la structure EPROCESS changent selon les différentes versions de windows, donc encore une fois avoir un outil générique se révèle très compliqué à créer. Cependant, c’est cette technique que j’ai décidé de mettre en œuvre pour retrouver la nt!PsActiveProcessHead.

Encore faut-il avoir une technique de s10ux pour retrouver l’adresse de cette double-linked-list de façon générique. C’est grâce à la structure KPCR, en effet cette structure CPU specific est extrêmement importante pour le noyau ; on va retrouver des informations cruciale dedans tel que l’adresse de l’IDT/GDT. N’oublions pas que cette structure se trouve toujours à l’adresse virtuelle 0xffdff000. Voici cette structure :



lkd> dt nt!_KPCR 0x83168c00
+0x000 NtTib            : _NT_TIB
+0x000 Used_ExceptionList : 0xae26fd08 _EXCEPTION_REGISTRATION_RECORD
+0x004 Used_StackBase   : (null)
+0x008 Spare2           : (null)
+0x00c TssCopy          : 0x801df000
+0x010 ContextSwitches  : 0x52883d3
+0x014 SetMemberCopy    : 1
+0x018 Used_Self        : (null)
+0x01c SelfPcr          : 0x83168c00 _KPCR
+0x020 Prcb             : 0x83168d20 _KPRCB
+0x024 Irql             : 0 ''
+0x028 IRR              : 0
+0x02c IrrActive        : 0
+0x030 IDR              : 0xffffffff
+0x034 KdVersionBlock   : 0x83167bc0
+0x038 IDT              : 0x80b95400 _KIDTENTRY
+0x03c GDT              : 0x80b95000 _KGDTENTRY
+0x040 TSS              : 0x801df000 _KTSS
+0x044 MajorVersion     : 1
+0x046 MinorVersion     : 1
+0x048 SetMember        : 1
+0x04c StallScaleFactor : 0x892
+0x050 SpareUnused      : 0 ''
+0x051 Number           : 0 ''
+0x052 Spare0           : 0 ''
+0x053 SecondLevelCacheAssociativity : 0 ''
+0x054 VdmAlert         : 0
+0x058 KernelReserved   : [14] 0
+0x090 SecondLevelCacheSize : 0
+0x094 HalReserved      : [16] 0
+0x0d4 InterruptMode    : 0
+0x0d8 Spare1           : 0 ''
+0x0dc KernelReserved2  : [17] 0
+0x120 PrcbData         : _KPRCB


Pour se convaincre que le KPCR est toujours placé à cette adresse virtuelle, il suffit de translater l’adresse 0xffdff000 avec deux répertoires de pages différents et on devrait constater dans les deux cas que cette même adresse virtuelle pointe vers la même adresse physique :



kd> !process 0 0 calc.exe
PROCESS 813b8b50  SessionId: 0  Cid: 06c8    Peb: 7ffdb000  ParentCid: 05dc
DirBase: 0fae8000  ObjectTable: e1ce8b30  HandleCount:  37.
Image: calc.exe

kd> !process 0 0 explorer.exe
PROCESS 81471910  SessionId: 0  Cid: 05dc    Peb: 7ffd6000  ParentCid: 05c4
DirBase: 08f9f000  ObjectTable: e177f478  HandleCount: 331.
Image: explorer.exe

kd> !vtop 0fae8 0xffdff000
X86VtoP: Virt ffdff000, pagedir fae8000
X86VtoP: PDE fae8ffc - 0003a163
X86VtoP: PTE 3a7fc - 00040163
X86VtoP: Mapped phys 40000
Virtual address ffdff000 translates to physical address 40000.
kd> !vtop 08f9f 0xffdff000
X86VtoP: Virt ffdff000, pagedir 8f9f000
X86VtoP: PDE 8f9fffc - 0003a163
X86VtoP: PTE 3a7fc - 00040163
X86VtoP: Mapped phys 40000
Virtual address ffdff000 translates to physical address 40000.


Maintenant que nous savons récupérer le KPCR de façon générique, nous allons devoir user d’une technique supplémentaire pour retrouver la nt!PsActiveProcessHead à partir du KPCR. C’est grâce à un article paru sur rootkit.com que nous allons réussir cette manœuvre, le monsieur nous explique que le KPCR.KdVersionBlock pointe sur une structure du type (pour processeur 32bits) :


kd> dps poi(0xffdff000+0x34)
8054c738  0a28000f
8054c73c  00020006
8054c740  030c014c
8054c744  0000002d
8054c748  804d7000 nt!_imp__VidInitialize <PERF> (nt+0x0)
8054c74c  ffffffff
8054c750  8055ab20 nt!PsLoadedModuleList
8054c754  ffffffff
8054c758  806921f4 nt!KdpDebuggerDataListHead
8054c75c  ffffffff
8054c760  806921f4 nt!KdpDebuggerDataListHead
8054c764  806921f4 nt!KdpDebuggerDataListHead
8054c768  00000000
8054c76c  00000000
8054c770  4742444b
8054c774  00000290
8054c778  804d7000 nt!_imp__VidInitialize <PERF> (nt+0x0)
8054c77c  00000000
8054c780  804e3b25 nt!RtlpBreakWithStatusInstruction
8054c784  00000000
8054c788  00000000
8054c78c  00000000
8054c790  0008012c
8054c794  00000018
8054c798  804e3195 nt!KiCallUserMode
8054c79c  00000000
8054c7a0  7c91ead0
8054c7a4  00000000
8054c7a8  8055ab20 nt!PsLoadedModuleList
8054c7ac  00000000
8054c7b0  80560bd8 nt!PsActiveProcessHead

KdVersionBlock points to:
0x0-0xC = Unknown
0x10 = KernelBase
0x18 = PsLoadedModuleList
0x20 = Pointer to Pointer to Debug Data
0x28 = LIST_ENTRY to Debug Data, Forward
0x38 = Debugger Tag.

On peut s’empêcher de constater que l’on retrouve notre symbole nt!PsActiveProcessHead à l’adresse 0x8054C7B0 soit *(KPCR.KdVersionBlock)+0x78. Cette astuce se révèle fiable et nous servira pour l’écriture du PoC. Comme je l’ai dis plus haut, un dump mémoire se révèle être un monde de variable, et pour réussir à se retrouver dans ce monde il nous faut des constantes, des accroches et notre point de départ est le fait que le KPCR est toujours mappé à l’adresse virtuelle 0xffdff000. Notre technique commence à prendre forme, cependant il reste un détail à régler : cette image mémoire a été fait sur un système avec l’extension PAE d’activé ; la translation d’adresse virtuelle en adresse physique se révèle donc un peu plus corsé je vais donc tenter de vous l’expliquez avec un petit exemple. Avant de commencer, PAE est une extension qui permet au système de pouvoir gérer plus de mémoire physique du fait que dans le système de pagination nous avons une nouvelle table : classiquement nous avions sans PAE une page directory, une page table (PDE/PTE rappelez vous) et nous pouvions pointer l’octet en mémoire physique ; maintenant nous avons une page directory pointer table, une page directory, une page table (donc PDPE/PDE/PTE) pour réussir à translater une adresse. Allez en avant, on se place dans le contexte d’un processus X, on récupère les modules qui sont chargés dans son address-space et on tente de dumper à la base d’un de ces modules :


kd> db 0x75910000
75910000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
75910010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
75910020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
75910030  00 00 00 00 00 00 00 00-00 00 00 00 e0 00 00 00  ................
75910040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
75910050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
75910060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS
75910070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$.......

Maintenant notre objectif est de pouvoir réaliser la même chose avec l’adresse physique ! On sait que le répertoire de page est stocké dans le control-register numéro 3, allons voir sa valeur :


kd> r cr3
cr3=1eecc280

D’après les manuels intels le cr3 n’est pas seulement composé de la base du répertoire de page, c’est pour cela que nous lui appliquons un masque ; voici la structure du CR3 :


kd> ? (0x1eecc280 & 0xFFFFFFE0)
Evaluate expression: 518832768 = 1eecc280

On extrait l’indice qui nous servira à récuperer le PDPTE :


kd> ? (0x75910000 >> 0x1E)
Evaluate expression: 1 = 00000001

On va récuperer le PDPTE :


kd> !dq 0x1eecc280+1*8 l 1
#1eecc288 00000000`0d6b1801

On extrait l’adresse physique de base du Page Directory :


kd> ? (0x000000000d6b1801 & 0x00006FFFFFFFFF000)
Evaluate expression: 225120256 = 0d6b1000

On extrait l’indice ou se trouvera le PDE :


kd> ? (0x75910000 >> 0x15) & 0x1FF
Evaluate expression: 428 = 000001ac

On récupere le PDE :


kd> !dq 0x0d6b1000+(0x1ac*8) l 1
# d6b1d60 00000000`0cb25867

On extrait la base du Page Table :


kd> ? (0x000000000cb25867 & 0x00006FFFFFFFFF000)
Evaluate expression: 213012480 = 0cb25000

On extrait l’indice ou sera le PTE :


kd> ? (0x75910000 >> 0xC) & 0x1FF
Evaluate expression: 272 = 00000110

On recupere le PTE :


kd> !dq 0x0cb25000+(0x110*8) l 1
# cb25880 80000000`156c7025

On extrait la base de la page :


kd> ? (0x80000000156c7025 & 0x00006FFFFFFFFF000)
Evaluate expression: 359428096 = 156c7000

On extrait l’offset dans la page :


kd> ? (0x75910000 & 0x00000FFF)
Evaluate expression: 0 = 00000000

Et on dump :


kd> !db 0x156c7000
#156c7000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
#156c7010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
#156c7020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#156c7030 00 00 00 00 00 00 00 00-00 00 00 00 e0 00 00 00 ................
#156c7040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
#156c7050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
#156c7060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
#156c7070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......

Pour aller plus vite on aurait pu utilise !vtop :


kd> !vtop 0 0x75910000
X86VtoP: Virt 75910000, pagedir 1eecc280
X86VtoP: PAE PDPE 1eecc288 - 000000000d6b1801
X86VtoP: PAE PDE d6b1d60 - 000000000cb25867
X86VtoP: PAE PTE cb25880 - 80000000156c7025
X86VtoP: PAE Mapped phys 156c7000
Virtual address 75910000 translates to physical address 156c7000.

Bon vous voyez que c’est assez rébarbatif de réaliser ce genre de translation (sans oublier qu’on peut faire face à des larges-page qui est un cas à traiter différemment), c’est pourtant ce qui va falloir implémenter si l’on veut pouvoir récupérer de l’information utile à partir de notre dump mémoire. C’est le cœur du PoC cette translation, sans cela on ne peut rien faire ; par contre une fois que celle-ci est implémenter elle sera la base de toutes les actions : reconstruction de l’espace virtuel d’un processus, liste des processus, liste des modules etc.

100]How to deal with VMEM file

Après avoir vu les différents aspects théoriques du framework, on est maintenant capable de comprendre comment fonctionne globalement les outils d’analyses tels que Volatility, Sandman (bien que l’analyse ne se réalise pas sur le même type de fichier on va retrouver ces notions de translations etc), PtFinderPE etc. Pour ma part, j’ai choisis d’implémenter un classique tasklist. Mon poc fonctionne en différentes étapes, premièrement il doit retrouver l’eprocess du processus « Idle » (noté que cela aurait pu être n’importe quel processus, seulement c’est le 1er processus dans la nt!PsActiveProcessHead donc à priori la 1ère que l’on va rencontrer dans le dump ; soit du temps de parcours en moins), une fois retrouver nous disposons d’un répertoire de page capable de réaliser quelques translations utiles pour notre PoC. Ensuite, il suffit d’aller parcourir ce répertoire de page afin de trouver l’adresse physique du KPCR (n’oublions pas que cette structure est mappé à l’adresse virtuelle 0xffdff000 dans tous les processus), ensuite on récupère le champ KdVersionBlock afin de pouvoir atteindre le symbole nt!PsActiveProcessHead de façon générique. Maintenant, il nous suffit de parcourir la liste chainé, en n’oubliant pas de translater les adresses virtuelles en physique afin de pouvoir se retrouver dans le fichier. Au final j’obtiens un résultat assez correct, pour réaliser l’ensemble de ces opérations le programme met 3 secondes environs ce qui est tout à fait raisonnable pour se balader dans un fichier de 500mo. Voici un output :



D:\TODO\forensic>poc.exe
** VMEM Forensic : Find the PsActiveProcessHead (IA-32 WinXpSP2-PAE-4kbPages) by 0vercl0k **

[INFOS] Page Directory is at 0x319000.
[INFOS] PAE PDPE 0x319018 - 0x31d001.
[INFOS] PAE PDE  0x31dff0 - 0x32d16300000000.
[INFOS] PAE PTE  0x32dff8 - 0x4016300000000.
[INFOS] Virtual address 0xffdff000 translates to physical address 0x40000.

Physical address of KPCR structure : 0x40000.
Virtual address of KdVersionBlock : 0x80544cb8.

[INFOS] Page Directory is at 0x319000.
[INFOS] PAE PDPE 0x319010 - 0x31c001.
[INFOS] PAE PDE  0x31c010 - 0x4001e300000000.
[INFOS] Virtual address 0x80544d30 translates to physical address 0x544d30 (larg
e page, 2MBytes page).

Hmm well, you can find nt!PsActiveProcessHead at 0x544d30.
Virtual address of nt!PsActiveProcessHead is 0x80559258.

*|* DROP IT LIKE ITS HOT, DROP IT LIKE ITS HOT *|*

- System(4).
- smss.exe(548).
- csrss.exe(612).
- winlogon.exe(644).
- services.exe(688).
- lsass.exe(700).
- vmacthlp.exe(852).
- svchost.exe(880).
- svchost.exe(948).
- svchost.exe(1040).
- svchost.exe(1100).
- svchost.exe(1244).
- spoolsv.exe(1460).
- vmtoolsd.exe(1628).
- VMUpgradeHelper(1836).
- alg.exe(2024).
- explorer.exe(1756).
- VMwareTray.exe(1108).
- VMwareUser.exe(1116).
- wscntfy.exe(1132).
- msiexec.exe(244).
- msiexec.exe(452).
- wuauclt.exe(440).
- wuauclt.exe(232).
- firefox.exe(888).
- AcroRd32.exe(1752).
- svchost.exe(1384).
Elapsed time : 3027 ms.


Bien sur le code n’est en aucun cas générique que ce soit pour un système non-pae car il faudrait revoir la fonction permettant de réaliser les translations, ni pour un système autre que Windows XP étant donné que j’utilise des offsets pour les structures EPROCESS (à moins que ceux ci ne change pas selon les différentes versions, ..c’est bon de rêver :)). Donc c’est bien là qu’on constate que coder un outil générique c’est énormément de temps passé à analyser les combinaisons des différentes configurations possibles. Voilà en ce qui concerne le PoC, si quelqu’un le modifie ou l’améliore ça serait cool de m’avertir pour que je puisse regarder les améliorations, optimisations etc :).

101] The end

Je vous aurais donc présenter un peu la base des analyses forensics dans des images mémoires, cependant un aspect n’a pas été évoqué : quand il y a forensic, il y’a bien sur des procédés anti-forensic. En ce qui concerne Volatility, on a bien constaté qu’il se fier à une signature pour retrouver des potentiels EPROCESS et ensuite éliminer les faux positifs avec une comparaison avec le nom du processus par exemple.

Et bien on peut très bien imaginer un procéder anti-Volatility qui irait modifier les structures EPROCESS en mémoire (DKOM) afin que le framework ne retrouve plus cette signature : du coup il serait incapable de translater aucune adresse virtuelle, et donc n’accéderait à aucune donnée utile présent dans le fichier VMEM. Je n’ai pas tenté de coder un driver qui réaliserait cela, donc si quelqu’un à le courage de tenter cette petit technique ça serait sympathique qu’il nous lache un feedback en commentaire : mais à priori je ne vois pas comment volatility pourrait s’en sortir ;D. On arrive à la fin du post, donc je vais vous laisser avec les liens qui m’ont aidés, le poc et son binaire.

http://www.sstic.org/2007/presentation/Autopsie_d_une_intrusion_tout_en_memoire_sous_Windows/ <- et bien ici on retrouve une superbe conférence qui retrace de façon plus généraliste ce que j’ai pu présenter sur les fichiers VMEM, mais Mr Ruff aborde aussi tous les fichiers alternatifs au VMEM ainsi que les façons d’obtenir des images saines de la mémoire pour une analyse forensic, degustez chaque page :) .

http://blogs.msdn.com/ntdebugging/archive/2010/04/14/understanding-pte-part2-flags-and-large-pages.aspx <- cette article relate plutôt de ce qui est en rapport avec la pagination et PAE au niveau des droits d’accès aux pages etc.

http://esec.fr.sogeti.com/FR/documents/conferences/sstic2008/article-sstic08_damien.pdf <- encore un autre article du sstic, c’est une vrai bible : on y retrouve tous les points techniques en rapport avec le sujet.

http://0vercl0k.tuxfamily.org/bl0g/Binaires/Volatility/Volatility.zip <- Sources + binaires !

http://0vercl0k.tuxfamily.org/bl0g/Sources/Volatility/ <- Sources consultables en ligne :)

A la prochaine, see ya !

PS : Gros gr33tz à ivanlef0u et à elz pour les corrections/relectures :].

7 commentaires pour le moment

Ajoutez votre commentaire
  1. […] This post was mentioned on Twitter by MatToufoutu, Ludovic Tokata. Ludovic Tokata said: Excellent article de @0vercl0ck sur l'analyse forensics de dump mémoire Win http://0vercl0k.tuxfamily.org/bl0g/?p=153 […]

  2. Et sinon t’as essaye de faire les autres questions du challenge … ?

  3. Yo,
    Wi bien sûr j’en ai fait quelques une, avant de constater que volatility assurait grave :]]. On a du s’arrêter à la moitié avec Taron par manque de temps, du coup moi je me suis intéressé au fonctionnement interne :].
    En tout cas, j’attends un petit compte-rendu sur le challenge de sh4ka par exemple qui souhaitait rédigé un petit quelque chose à ce propos.
    Cya :].

  4. Salut,
    Effectivement je t’ai laché, mon PC m’ayant lui-même lâché 😀
    Et puis manque de temps, pour te dire j’ai pas encore codé une ligne pour le GSoC..
    Nice l’article, a+ sur IRC

  5. Yo,
    Outch dur : D. Et merci dude :>, à la prochaine sur IRC o/
    Cya.

  6. T’es hot ; je dormirais moins bête ce soir. Merci.

  7. […] 0vercl0k’s w0rld. This entry was posted in Code and tagged artifacts, Digital, Extraction, from, Memory, volatile, Volatility. Bookmark the permalink. ← Convertir une image virtualbox vers vmware […]

Get Adobe Flash player