Archive for novembre 12th, 2007

Lost Page

Il y des jours comme ca, on croit avoir une bonne idée, on commence à coder un peu, sans trop réfléchir, pour se rendre compte qu’en fait, c’est super chaud ce qu’on veut faire … C’est marrant de voir comment on peut chier dans la colle parfois. J’étais en train de ranger ma collection de RSS (170 au total) quand je me suis dit qu’on devait pouvoir tricker avec la mémoire user-land non explorée.

Je me suis donc coder un petit prog qui alloue les dernières portions de mémoire avant l’adresse 0×80000000.

#include <windows.h>
#include <stdio.h>

#pragma comment (lib, "ntdll.lib")

extern "C" ULONG __stdcall NtAllocateVirtualMemory(
	IN HANDLE ProcessHandle,
	IN OUT PVOID *BaseAddress,
	IN ULONG ZeroBits,
	IN OUT PULONG AllocationSize,
	IN ULONG AllocationType,
	IN ULONG Protect
);

ULONG main(int argc, char * argv[])
{
	ULONG Status, Size=0x1000, A;
	PVOID Addr;

 	for(A=0x70, Addr=0; A<0x80; A++, Addr=0)
 	{

 		Addr=(PVOID)((A<<24)+0xFF0000);

 		printf("Addr: 0x%x\n", Addr);
 		Status=NtAllocateVirtualMemory((HANDLE)-1, &Addr, 0, &Size,
 		MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); 

		if(Status)
 			printf("Error with NtAllocateVirtualMemory : 0x%x\n", Status);
 		else
 			printf("RULZ\n");

 		VirtualFree(Addr, Size, MEM_RELEASE);	

 	}
 	return 0;
}

A l’exécution j’obtiens :

[...]
Addr: 0x7dff0000
RULZ
Addr: 0x7eff0000
RULZ
Addr: 0x7fff0000
Error with NtAllocateVirtualMemory : 0xc00000f0

WTF avec l’addr 0x7fff00, la fonction NtAllocateVirtualMemory renvoie STATUS_INVALID_PARAMETER_2, qui veut dire que l’adresse d’allocation n’est pas bonne … Un petit doute s’empare de moi, ai-je mal retenu la limite entre le user-land et le kernel ? OK je sors le kd et je dump quelques constantes :

kd> ? poi(nt!MmSystemRangeStart)
Evaluate expression: -2147483648 = 80000000

Cool, le kernel-land commence bien en 0×80000000. Je regarde l’adresse user-land la plus élevée possible qui normalement devrait être 0x7fffffff :

kd> ? poi(nt!MmHighestUserAddress)
Evaluate expression: 2147418111 = 7ffeffff

Bim en plein dans mon cul, la fonction NtAllocateVirtualMemory vérifie que l’adresse d’allocation + la taille demandé ne dépasse pas MmHighestUserAddress et renvoie une erreur sinon. Un rapide calcul nous montre que MmSystemRangeStart-MmHighestUserAddress=0×1001, soit 4Ko (une page !) allant de 0x7fff000 à 0x7fffffff qui ne seront jamais utilisé par le process.

J’étais donc partit sur l’idée d’exploiter cette sorte de no man’s land pour y cacher des dates, du codes, voir même des chocapicz. Comme la seule fonction user-land capable d’allouer de la mémoire est NtAllocateVirtualMemory il me fallait concevoir à driver qui allait servir à ajouter cette zone mémoire dans le context de mon process.

Avec mon driver il est évident que je n’allais pas utiliser ZwAllocateMemory, j’ai donc décidé de jouer avec les MDL. Processus classique, j’alloue un buffer de la taille d’une page (PAGE_SIZE) dans la paged pool du kernel avec ExAllocatePoolWithTag puis je crée un MDL avec IoAllocateMdl qui représente la page de ce buffer. Ensuite MmProbeAndLockPages pour que ma page soit résidente en mémoire (jamais swappée) et enfin MmMapLockedPagesSpecifyCache pour mapper ma page à l’adresse 0x7fff0000 et là c’est le drame ! MmMapLockedPagesSpecifyCache contrôle aussi que l’allocation se fait bien à une adresse inférieure à 7ffeffff, ouinnz !

A partir de ce moment, je commence à désespérer, je sens que j’ai encore eu une idée en mousse. Arrive un moment ou je me dis que la solution la plus évidente serait d’ajouter moi même ma page dans l’espace mémoire du process. Mais pour cela, il faut s’attaquer à l’enfer qu’est la pagination … Pour ceux qui connaissent déjà, ils peuvent sauter ce post et m’aider un coder mon tool ;)

Bion, un process dispose d’un espace user-land de 2Go virtuellement disponible. Cela signifie qu’il existe un mécanisme faisant croire au process qu’il dispose de 2Go de RAM alors qu’en réalité vous en avez juste assez pour faire tourner démineur sous Vista, c’est à dire 1 Go :p. Concrètement, le process manipule des addresse virtuelles qui peuvent aller de 0×0000000 à 0x7ffeffff, le système va effectuer une translation d’adresse pour retrouver l’adresse physique correspondante dans la RAM. Comme il ya au maximum 4GO de RAM allouable sur un système x86 32 bits il existe une extension kernel appelée PAE qui permet de profiter pleinement de toute votre mémoire, je ne m’attarderais pas dessus.

Chaque process va donc allouer son codes et ses datas dans les zones mémoire de son choix, seules les pages nécessaires seront utilisées physiquement. Cependant comme un programme à son propre espace mémoire de 2Go, il faut bien évidement que l’adresses 0×400000 ne renvoie pas sur la même adresse physique pour le processs A et pour le process B. C’est là qu’on rentre dans le principe de la pagination.

En gros chaque process possède une table qui lui est propre, servant à translater les adresses virtuelles en adresses physiques. Cette table se présente sous la forme d’un tableau de tableaux. Le premier tableau contient des Page Directorie Entries (PDE), le second des Page Table Entries (PTE). Chaque process possède 1024 (0×400) PDE et chaque PDE possède 1024 PTEs. Un PTE représente un page mémoire c’est à dire 4Ko. Si on calcul, 1024*1024*4096=2^32, on a bien toute notre mémoire de représentée. Les PDE et PTE on la forme suivante :

PDE_PTE

Pour plus d’infos je vous laisse lire les man intels. Ce qu’il faut retenir ce sont les champs « Page-Table Base Address » et « Page Base Address ». En fait, un PDE va référencer un PTE à travers son l’indice de sa page dans la RAM. Par exemple si j’ai un PDE avec un Page-Table Base Address de 0×879, alors ma table de PTEs commence à 0×879*PAGE_SIZE en RAM. Une adresse virtuelle sert à indiquer quels PDE et PTE sont utilisés pour retrouver son adresse physique. Celle-ci est découpée en 3 parties comme vous le voyez sur le schéma suivant :

Virtual_Address

Si je prends l’adresse virtuelle 0x7e390000 alors l’indice du PDE parmi les 1024 existant est 0x1f8 :

kd> ? 7e390000 >>0x16
Evaluate expression: 504 = 000001f8

L’indice de son PTE parmit les 1024 est 0×390 :

kd> ? (7e390000 & 3FF000)>>0xC
Evaluate expression: 912 = 00000390

Les 12 derniers bytes servent à indiquer à quel offset nos données se trouvent dans la page. Au final on a donc :

Addr_tranlation

J’ai aussi dit que chaque process avait sa propre table de PDEs. Cela est réalisé grâce au registre cr3 qui contient l’adresse physique du tableau de PDE, on retrouve cette valeur dans l’KPROCESS dans le champ DirectoryTableBase (offset 0×18) :

804e3592 cc              int     3
kd> !process -1
PROCESS 84256da0  SessionId: 0  Cid: 02d4    Peb: 7ffd5000  ParentCid: 03ac
    DirBase: 01d62000  ObjectTable: e110bef8  HandleCount:  23.
    Image: instdrv.exe
    VadRoot ffa5c058 Vads 31 Clone 0 Private 48. Modified 1334. Locked 0.
    DeviceMap e1716068
    Token                             e1110040
    ElapsedTime                       06:17:31.720
    UserTime                          00:00:00.040
    KernelTime                        00:00:00.440
    QuotaPoolUsage[PagedPool]         15360
    QuotaPoolUsage[NonPagedPool]      1240
    Working Set Sizes (now,min,max)  (322, 50, 345) (1288KB, 200KB, 1380KB)
    PeakWorkingSetSize                322
    VirtualSize                       12 Mb
    PeakVirtualSize                   12 Mb
    PageFaultCount                    319
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      91

        THREAD ffa07030  Cid 02d4.02bc  Teb: 7ffdf000 Win32Thread: e1117d90 WAIT: (WrUserRequest) UserMode Non-Alertable
            80dd8138  SynchronizationEvent

kd> dt nt!_KPROCESS 84256da0
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 ProfileListHead  : _LIST_ENTRY [ 0x84256db0 - 0x84256db0 ]
   +0x018 DirectoryTableBase : [2] 0x1d62000
   [..]

A noter que le tableau des PDEs se retrouve toujours à l’adresse virtuelle 0xC0300000

Maintenant on va prendre un exemple concret ! Dans le cas suivant, j’ai fait en sorte que KD se retrouve dans le context d’un process nommé instdrv.exe.

kd> .process /i 84256da0
You need to continue execution (press 'g' ) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.

kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
804e3592 cc              int     3

kd> !peb
PEB at 7ffd5000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            No
    ImageBaseAddress:         00400000
    Ldr                       00191e90
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 00191f28 . 00192258
    Ldr.InLoadOrderModuleList:           00191ec0 . 001922e8
    Ldr.InMemoryOrderModuleList:         00191ec8 . 001922f0
            Base TimeStamp                     Module
          400000 3b6a692f Aug 03 11:04:47 2001 C:\mdl\instdrv.exe
        7c910000 41109627 Aug 04 09:54:15 2004 C:\WINDOWS\system32\ntdll.dll
        7c800000 46239be7 Apr 16 17:53:11 2007 C:\WINDOWS\system32\kernel32.dll
        77da0000 411095e8 Aug 04 09:53:12 2004 C:\WINDOWS\system32\ADVAPI32.dll
        77e50000 46923412 Jul 09 15:11:46 2007 C:\WINDOWS\system32\RPCRT4.dll
        77fc0000 4110961d Aug 04 09:54:05 2004 C:\WINDOWS\system32\Secur32.dll
        7e390000 45f02dce Mar 08 16:37:50 2007 C:\WINDOWS\system32\USER32.dll
        77ef0000 4677dae9 Jun 19 15:32:25 2007 C:\WINDOWS\system32\GDI32.dll
    SubSystemData:     00000000
    ProcessHeap:       00080000
    ProcessParameters: 00020000
    WindowTitle:  'C:\mdl\instdrv.exe'
    ImageFile:    'C:\mdl\instdrv.exe'
    CommandLine:  '"C:\mdl\instdrv.exe" '
    DllPath:      'C:\mdl;C:\WINDOWS\system32;C:\WINDOWS\system;C:\WINDOWS;.;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem'
    Environment:  00010000
        ALLUSERSPROFILE=C:\Documents and Settings\All Users
        APPDATA=C:\Documents and Settings\fu\Application Data
        CLIENTNAME=Console
        CommonProgramFiles=C:\Program Files\Fichiers communs
        COMPUTERNAME=FU
        ComSpec=C:\WINDOWS\system32\cmd.exe
        FP_NO_HOST_CHECK=NO
        HOMEDRIVE=C:
        HOMEPATH=\Documents and Settings\fu
        LOGONSERVER=\\FU
        NUMBER_OF_PROCESSORS=1
        OS=Windows_NT
        Path=C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem
        PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
        PROCESSOR_ARCHITECTURE=x86
        PROCESSOR_IDENTIFIER=x86 Family 15 Model 72 Stepping 2, AuthenticAMD
        PROCESSOR_LEVEL=15
        PROCESSOR_REVISION=4802
        ProgramFiles=C:\Program Files
        SESSIONNAME=Console
        SystemDrive=C:
        SystemRoot=C:\WINDOWS
        TEMP=C:\DOCUME~1\fu\LOCALS~1\Temp
        TMP=C:\DOCUME~1\fu\LOCALS~1\Temp
        USERDOMAIN=FU
        USERNAME=fu
        USERPROFILE=C:\Documents and Settings\fu
        windir=C:\WINDOWS
        _NT_SYMBOL_PATH=SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols

Ok on peut voir que le tableau de PDEs de se process se trouve à l’adresse physique 0x01d62000 en regardant le registre cr3.

kd> r cr3
cr3=01d62000

La table des PDEs se trouve donc en à l’adresse physique 0x01d62000 :

kd> !dd 01d62000l 400
# 1d62000 0ade4067 0699a067 00000000 00000000
# 1d62010 00000000 00000000 00000000 00000000
# 1d62020 00000000 00000000 00000000 00000000
# 1d62030 00000000 00000000 00000000 00000000
# 1d62040 00000000 00000000 00000000 00000000
# 1d62050 00000000 00000000 00000000 00000000
# 1d62060 00000000 00000000 00000000 00000000
# 1d62070 00000000 00000000 00000000 00000000
[...]

On la retrouve bien à l’adresse virtuelle 0xC0300000 :

kd> dd 0xC0300000 l 400
c0300000  0ade4067 0699a067 00000000 00000000
c0300010  00000000 00000000 00000000 00000000
c0300020  00000000 00000000 00000000 00000000
c0300030  00000000 00000000 00000000 00000000
c0300040  00000000 00000000 00000000 00000000
c0300050  00000000 00000000 00000000 00000000
c0300060  00000000 00000000 00000000 00000000
c0300070  00000000 00000000 00000000 00000000
[...]

Maintenant je prends la DLL user32.dll qui commence à l’adresse virtuelle 0x7e390000 :

kd> db 7e390000
7e390000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
7e390010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
7e390020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
7e390030  00 00 00 00 00 00 00 00-00 00 00 00 d8 00 00 00  ................
7e390040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
7e390050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
7e390060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS
7e390070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$......

Ok PE Header classique. On va retrouver l’adresse physique correspondant à 0x7e390000. C’est partit, d’abord on calcul l’indice du PDE :

PDE
kd> ? 7e390000 >> 16
Evaluate expression: 504 = 000001f8

Ok il est à l’indice 0x1F8 dans le tableau. Pour afficher son contenu on a 2 choix, soit on utilise l’addresse virtuelle C0300000 soit l’adresse physique 0x01d6200 (la commande !dd affiche le contenu à l’adresse phys passée en argument) :

kd> dd C0300000+1f8*4 l 1
c03007e0  07ed7067

kd> !dd 0x01d62000+1f8*4 l 1
# 1d627e0 07ed7067

Ok on a le PDE. La suite, trouvé le PTE, on prend l’adresse 0x7e390000 et on extrait l’indice du PTE :

<
PTE
kd> ? (7e390000 & 3FF000)>>C
Evaluate expression: 912 = 00000390

Le PTE se trouve à l’indice 0×390. Grâce au PDE on connait l’adresse physique de la table des PTEs (7ed7*1000), il suffit de faire l’opération suivante pour connaître le contenu du PTE :

kd> !dd (7ed7*1000)+390*4 l 1
# 7ed7e40 078e8025

On y est presque, pour ceux qui on survécu la délivrance est proche ! Le champ Page Base Address du PTE nous indique l’indice de la page physique qui contient nos données. Il suffit donc de faire (PTE.Page_Base_Address)*PAGE_SIZE+Offset pour retrouver nos datas (ici l’offset est nul) :

kd> !db 78e8*1000
# 78e8000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
# 78e8010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
# 78e8020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# 78e8030 00 00 00 00 00 00 00 00-00 00 00 00 d8 00 00 00 ................
# 78e8040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
# 78e8050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
# 78e8060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
# 78e8070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......

Wootz on retrouve notre en-tête PE de user32.dll.

Vous ne inquietez pas, il existe des commandes fournit par le kernel debuggger pour retrouver l’indice de la page physique correspond à notre adresse virtuelle. Par exemple, il y a la commande !pte :

kd> !pte 7e390000 (user32.dll)
               VA 7e390000
PDE at   C03007E0        PTE at C01F8E40
contains 07ED7067      contains 078E8025
pfn 7ed7 ---DA--UWEV    pfn 78e8 ----A--UREV

Et la commande !vtop :

kd> !vtop 0 7e390000
Pdi 1f8 Pti 390
7e390000 078e8000 pfn(078e8)

Plus d’aide je vous laisse lire l’aide de Windbg sur le sujet.

Revenons à notre problème. Je veux utiliser la page disponible à l’adresse virtuelle 0x7fff0000, comment faire ? Simple, ajouté un PTE au bon endroit :] exemple avec calc.exe

kd> !process 0 0
[...]
PROCESS ffba2030  SessionId: 0  Cid: 0104    Peb: 7ffd5000  ParentCid: 03c0
    DirBase: 00246000  ObjectTable: e15b1f30  HandleCount:  25.
    Image: calc.exe

kd> .process /i ffba2030
You need to continue execution (press 'g' ) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.

kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
804e3592 cc              int     3

kd> db 7fff0000
7fff0000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0060  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0070  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

On voit bien que la page 0x7fff0000 est invalide. Si on regarde son PTE on peut voir :

kd> !pte 0x7fff0000
               VA 7fff0000
PDE at   C03007FC        PTE at C01FFFC0
contains 0079C067      contains 00000000
pfn 79c ---DA--UWEV

Le PTE n’est pas présent, il n’y donc pas de pages physiques référençant cette zone mémoire. Ce que veux dire qu’un simple mov eax, dword ptr [0x7FFF000] nous générera une exception. Maintenant je regarde le PTE qui référence l’ImageBase de calc.exe

kd> !pte 1000000 (calc.exe ImageBase)
               VA 01000000
PDE at   C0300010        PTE at C0004000
contains 0024E067      contains 0C60C025
pfn 24e ---DA--UWEV    pfn c60c ----A--UREV

Le PTE est présent, normal, je copie son contenu à l’adresse virtuelle 0xC01FFFC0 qui contient la valeur du PTE pour l’adresse virtuelle 0x7fff0000.

kd> ed C01FFFC0 0C60C025

kd> db 7fff0000
7fff0000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
7fff0010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
7fff0020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
7fff0030  00 00 00 00 00 00 00 00-00 00 00 00 f0 00 00 00  ................
7fff0040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
7fff0050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
7fff0060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS
7fff0070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$.......

Ho le joli mapping :]. J’ai mappé la page qui contient le PE header de calc.exe à la l’adresse virtuelle 0x7fff0000, c’est pas beau ca ! Juste pour être sur, j’attache olly sur le calc.exe et je le lance l’instruction MOV EAX,DWORD PTR DS:[7FFF0000], hoo pas d’exception, eax contient bien 0x00905A4D, youpilol !

Bien évident faire cette opération from scratch sous KD ce n’est pas vraiment conseillé, néanmoins ca montre que c’est possible. Je vais essayer d’implémenter cela dans mon driver et je releaserais le code plus tard.

Grâce à cette technique on a pu voir qu’il est possible de profiter de le page mémoire se trouvant à la plus haute adresse user-land. Après libre à nous d’y mettre ce qu’on veut. En le faisant de cette manière on profite du fait que les VADs (Virtual Address Descriptors) décrivant l’utilisation de l’espace mémoire de notre process ne sont pas mis à jour ce qui rend cette zone mémoire invisible aux API comme NtQueryVirtualMemory. Pour savoir que la page est présente il faut y accéder.

Pendant que j’y suis je voudrais rappeler l’existence de la technique du Shadow Walker qui consiste en gros à faire croire qu’une page est swappée sur le disque quand on tente d’y accéder. Après si c’est une lecture/écriture on dirige la requête vers une page « clean ». Je vous laisse lire l’article de phrack à ce sujet puis l’article/code d’un pote, b0l0k qui je trouve n’a pas eu le succès escompté.

J’essaierais d’y revenir plus tard dessus. En attendant j’espère que vous avez apprécié l’idée de jouer avec la page perdue !

ref :
Address Tanslation
http://book.itzero.com/read/microsoft/0507/microsoft.press.microsoft.windows.internals.fourth.edition.dec.2004.internal.fixed.ebook-ddu_html/0735619174/ch07lev1sec5.html

Intel Manuals, Windows Internals, google et autres …

14 comments novembre 12th, 2007


Calendar

novembre 2007
L Ma Me J V S D
« oct   déc »
 1234
567891011
12131415161718
19202122232425
2627282930  

Posts by Month

Posts by Category