PAGE_EXECUTE_WRITECOPY

mars 25th, 2007 at 08:07 admin

Il pleut, une journée triste si bien décrite par Baudelaire : « Quand la pluie étalant ses immenses traînées ; D’une vaste prison imite les barreaux ». Cette prison dont on essaye de s’échapper dès qu’on se retrouve submergé dans les transports par la viande désincarnée des autres errant sur cette planète. C’est durant ce moment ou mon esprit se déconnecte de la réalité, oubliant les 150 Kg de viande bovine m’écrasant le pied, qu’une idée apparaît, une simple idée qui pourtant m’empêche de remarquer le 90C aux yeux verts à ma gauche. La chose est née, j’augmente le volume de mon mp3, le son d’un bon groupe de métal aidant à la concentration. L’idée semble si intéressante, si puissante que j’ai du mal à la contrôler, mon esprit s’emballe dans des raisonnements incongrus, les substances me servant de nourriture n’aidant pas vraiment à la pensée logique je décide de me calmer et de remettre ça à plus tard. De retour chez moi je commence à me documenter sur le sujet, lire, comprendre, apprendre, foutu leitmotiv. Après avoir longtemps réfléchit, je décide de partager ma pensée, attention seules les personnes ayant des lunettes anti-cyberDDOS pourront lire cet article.


Tout d’abord veuillez excuser l’auteur pour l’intro dramaco-littéraire, celui-ci sera privé de ses chocapics pendant 1 semaine.

Windows n’étant pas un OS trop débile il fait parfois des efforts pour économiser de la mémoire. Ainsi le système au lieu d’avoir 57 kernel32.dll chargées en mémoire préfère en allouer une seule dans la mémoire physique (votre RAM) et de la partager parmi tout les process. Ces DLL partagées portent le nom de KnownDLLs, on peut les trouver dans le registre sous la clé HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\KnownDLLs, ce sont en fait les DLL les plus utilisées par les processus en général, par exemple : kernel32.dll, user32.dll, advapi32.dll, shell32.dll … etc

Le mécanisme utilisé par l’OS pour crée se partage est le file mapping, procédé qui permet d’associer l’espace mémoire virtuel d’un process à un fichier sur le disque. Cela permet de manipuler plus efficacement les données car celles-ci sont directement accessible dans la mémoire virtuelle du processus, plus besoin de WriteFile et de ReadFile W00t. En fait ces objets sont appelés des sections, les API CreateFileMapping et MapViewOfFile, de la librairie kernel32, qui permettent de crée des fichiers mappés utilisent les API natives NtCreateSection et NtMapViewOfSection du noyau.

Ainsi lors du démarrage, l’OS va mappé ses fichiers en mémoire et dès qu’un process sera lancé, il ira vérifier si les dll dont il a besoin sont déjà chargées, si elles le sont alors il les mappe dans sa mémoire.

En regardant le chapitre 5 du Windows Internals on apprend que c’est le processus smss.exe qui se charge de créer ses sections au démarrage. Hop hop regardons cela avec IDA :

smss.exe SmpInitializeKnownDllsInternal
loc_48585C45:
push    [ebp+hDll] ; DllHandle obtenu avec NtOpenFile
lea     eax, [ebp+Length]
push    1000000h ; Attributes, SEC_IMAGE 0x01000000
push    10h ; Protect, PAGE_EXECUTE 0x10
push    0 ; SectionSize
push    eax ; ObjectAttributes
push    0F001Fh ; DesiredAccess, SECTION_ALL_ACCESS: 0xf001f
lea     eax, [ebp+var_14]
push    eax ;SectionHandle
call    ds:__imp__NtCreateSection@28 ; NtCreateSection(x,x,x,x,x,x,x)

Ces objets sont ensuite placés dans la sous-directory \\KnownDlls de l’ObpRootDirectoryObject. Pour cela dans la structure OBJECT_ATTRIBUTES :

typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;a
PUNICODE_STRING ObjectName;a
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

Le champ RootDirectory est initialisé avec un handle renvoyé par NtCreateDirectoryObject qui à crée la directory \\KnownDlls au début de la fonction SmpInitializeKnownDllsInternal:

mov     ebx, [ebp+hDirObject]
[...]
mov     ecx, _SmpLiberalSecurityDescriptor
and     [ebp+SecurityQualityOfService], 0
test    ecx, ecx
mov     [ebp+Length], 18h ;sizeof(OBJECT_ATTRIBUTES)
mov     [ebp+RootDirectory], ebx ;hDirObject
mov     [ebp+Attributes], 50h ; Attributes OBJ_CASE_INSENSITIVE (0x00000040L) + OBJ_PERMANENT (0x00000010L)
mov     [ebp+ObjectName], esi ; Nom de la dll dans le registre
mov     [ebp+SecurityDescriptor], ecx ; SmpLiberalSecurityDescriptor

Ce qui au final nous donne l’arborescence suivante :

lkd> !object e14af158
Object: e14af158  Type: (843ed340) Directory
ObjectHeader: e14af140
HandleCount: 32  PointerCount: 69
Directory Object: e1000c08  Name: KnownDlls

Hash Address  Type          Name
---- -------  ----          ----
00  e1491df8 Section       gdi32.dll
e13fe4d8 Section       imagehlp.dll
e14e4780 Section       url.dll
01  e14f34e8 Section       appHelp.dll
02  e14f3908 Section       Normaliz.dll
e14f2af8 Section       MPR.dll
03  e14b8ee8 Section       ole32.dll
e14ece90 Section       urlmon.dll
04  e14bbfd8 Section       lz32.dll
e14e1bd8 Section       olesvr32.dll
06  e14e0ad8 Section       shell32.dll
e14efc28 Section       wldap32.dll
09  e14ec648 Section       version.dll
e14e21e8 Section       user32.dll
10  e14bede0 Section       olecli32.dll
14  e14f8f20 Section       MSASN1.dll
16  e14bd160 SymbolicLink  KnownDllPath
e14f0b68 Section       COMCTL32.dll
17  e14f43b8 Section       CRYPT32.dll
18  e14b34e0 Section       oleaut32.dll
e14af758 Section       advapi32.dll
19  e14f1320 Section       iertutil.dll
e14f0bf8 Section       SHLWAPI.dll
e14f3b40 Section       wow32.dll
e14c0fd8 Section       olecnv32.dll
20  e14f5738 Section       CRYPTUI.dll
21  e14f3480 Section       USERENV.dll
23  e14a3b38 Section       comdlg32.dll
26  e14eb780 Section       wininet.dll
27  e14f7830 Section       WINTRUST.dll
e14cdad8 Section       olethk32.dll
28  e14f1c10 Section       msvcrt.dll
31  e14e2d30 Section       rpcrt4.dll
e14f13b0 Section       SHDOCVW.dll
32  e14b1780 Section       kernel32.dll
36  e14f6580 Section       NETAPI32.dll

Toutes ces DLL sont donc dans votre RAM et attendent gentiment qu’on vienne les chercher. Maintenant que nos sections sont crées, on va pouvoir les utiliser. On s’intéresse donc au fonctionnement du Loader, qui, lors de la création d’un process fait appel à fonction LdrpInitializeProcess de ntdll [1]. Cette fonction utilise LdrpMapDll pour mappé les DLL en mémoire. LdrpMapDll vérifie avec LdrpCheckForKnownDll si les DLL demandées sont déjà mappées par le système. Si oui alors il y a appel à NtOpenSection :

NTSYSAPI
NTSTATUS
NTAPI
NtOpenSection(
OUT PHANDLE             SectionHandle,
IN ACCESS_MASK          DesiredAccess,
IN POBJECT_ATTRIBUTES   ObjectAttributes);

ntdll LdrpCheckForKnownDll
mov     eax, _LdrpKnownDllObjectDirectory ; Handle sur l'ObjectDirectory KnwownDlls
and     [ebp+SecurityDescriptor], 0
and     [ebp+SecurityQualityOfService], 0
mov     [ebp+RootDirectory], eax
lea     eax, [ebp+usDLL] ;UNICODE_STRING sur le nom de la DLL
mov     [ebp+ObjectName], eax
lea     eax, [ebp+Length]
push    eax
push    0Eh
lea     eax, [ebp+hSection]
push    eax
mov     [ebp+Length], 18h
mov     [ebp+Attributes], 40h ;OBJ_CASE_INSENSITIVE 0x00000040
call    _NtOpenSection@12 ; NtOpenSection(x,x,x)

A noter que la variable globale LdrpKnownDllObjectDirectory est initialisée au début du LdrpInitializeProcess :

NTSYSAPI
NTSTATUS
NTAPI
NtOpenDirectoryObject(
OUT PHANDLE             DirectoryObjectHandle,
IN ACCESS_MASK          DesiredAccess,
IN POBJECT_ATTRIBUTES   ObjectAttributes);

push    offset aKnowndlls ; "KnownDlls"
lea     eax, [ebp+var_D4]
push    eax
call    _RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
push    18h
pop     edi
lea     eax, [ebp+var_D4]
mov     [ebp+var_F0], eax
lea     eax, [ebp+var_F8]
push    eax
push    3
push    offset _LdrpKnownDllObjectDirectory
mov     [ebp+var_F8], edi
mov     [ebp+var_F4], esi
mov     [ebp+var_EC], 40h
mov     [ebp+var_E8], esi
mov     [ebp+var_E4], esi
call    _ZwOpenDirectoryObject@12 ; ZwOpenDirectoryObject(x,x,x)

On a le handle sur notre section, il ne reste plus qu’a la mappé dans l’espace mémoire du processus avec NtMapViewOfSection

NTSYSAPI
NTSTATUS
NTAPI
NtMapViewOfSection(
IN HANDLE               SectionHandle,
IN HANDLE               ProcessHandle,
IN OUT PVOID            *BaseAddress OPTIONAL,
IN ULONG                ZeroBits OPTIONAL,
IN ULONG                CommitSize,
IN OUT PLARGE_INTEGER   SectionOffset OPTIONAL,
IN OUT PULONG           ViewSize,
IN SECTION_INHERIT      InheritDisposition,
IN ULONG                AllocationType OPTIONAL,
IN ULONG                Protect );

ntdll LdrpMapDll
loc_7C92C3AE:
mov     [ebp+var_1C], ebx
mov     [ebp+var_28], ebx
mov     eax, [ebp+var_5C]
mov     esi, [eax+14h]
mov     ecx, [ebp+var_38]
mov     [eax+14h], ecx
push    4
push    ebx
push    1
lea     eax, [ebp+var_28]
push    eax
push    ebx
push    ebx
push    ebx
lea     eax, [ebp+var_1C]
push    eax             ; pMapping
push    0FFFFFFFFh      ; -1
push    [ebp+hSection]
call    _ZwMapViewOfSection@40 ; ZwMapViewOfSection(x,x,x,x,x,x,x,x,x,x)

Tout cela le Memory Manager le gère avec les VAD (Virtual Address Descriptor), structures noyau organisé sous la forme d’un arbre binaire de recherche, elles servent à décrire l’organisation de l’espace mémoire d’un process. Un VAD est représenté par la structure suivante :

typedef struct _MMVAD {
union {
LONG_PTR Balance : 2;
struct _MMVAD *Parent;
} u1;
struct _MMVAD *LeftChild;
struct _MMVAD *RightChild;
ULONG_PTR StartingVpn;
ULONG_PTR EndingVpn;

union {
ULONG_PTR Longanes;
MMVAD_FLAGS VadFlags;
} u;
PCONTROL_AREA ControlArea;
PMMPTE FirstPrototypePte;
PMMPTE LastContiguousPte;
union {
ULONG LongFlags2;
MMVAD_FLAGS2 VadFlags2;
} u2;
} MMVAD, *PMMVAD;

La racine de l’arbre (VadRoot) se trouve à l’offset 0x11c de la structure EPROCESS.

On peut les voir avec Olly en cliquant sur M (Memory Map) ou bien sous windbg avec la commande !vadump

0:001> !vadump
[...]
BaseAddress:       77da0000 (ImageBase de advapi32.dll)
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000002  PAGE_READONLY
Type:              01000000  MEM_IMAGE
[...]

Maintenant regardons la protection de la zone mémoire :

0:001> !vprot 77da0000
BaseAddress:       77da0000
AllocationBase:    77da0000
AllocationProtect: 00000080  PAGE_EXECUTE_WRITECOPY
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000002  PAGE_READONLY
Type:              01000000  MEM_IMAGE

2 valeurs m’intéressent :
Protect
Access protection of the pages in the region. This member is one of the values listed for the AllocationProtect member.

AllocationProtect
Memory protection when the region was initially allocated.

En se référant à la SDK on peut lire :
PAGE_EXECUTE_WRITECOPY : 0×80
Enables execute, read, and write access to the committed region of image file code pages. The pages are shared read-on-write and copy-on-write.

Cela veut dire que la mémoire est accessible en lecture, écriture, exécution MAIS si on tente d’écrire dedans, le système nous alloue notre propre copie de la DLL dans la RAM. C’est pour cela que lorsqu’on utilise un inline hook en userland il faut le faire pour chaque process car le système remarquant qu’il y a eu modification alloue une zone mémoire pour la DLL modifiée. [2]

Alors maintenant PENSONS… Si on désactivait cette protection, il n’y aurait qu’une seule version de la DLL en mémoire et donc une modification dans n’importe quel process affecterait le système entièrement !
Imaginez la puissance des rootkits userland qui n’aurait plus besoin de s’injecter dans chaque process pour placer leurs hooks d’API !

Revenons un peu en arrière, lors de la création des différentes sections par le process smss.exe, le paramètre Attributes de la fonction NtCreateSection valait : SEC_IMAGE 0×01000000.
Hum cela doit avoir une influence sur la création des VAD lors de l’appel à NtMapViewOfSection (admirez l’instinct du reverser :p)

C’est partit pour une descente aux enfers.

L’API native NtMapViewOfSection fait appel à l’API du Memory Manager MmMapViewOfSection :

ntoskrnl NtMapViewOfSection
push    [ebp+Protect]
push    [ebp+AllocationType]
push    [ebp+InheritDisposition]
lea     eax, [ebp+var_20]
push    eax
lea     eax, [ebp+var_4C]
push    eax
push    [ebp+CommitSize]
push    [ebp+ZeroBits]
lea     eax, [ebp+var_1C]
push    eax
mov     edi, [ebp+Object]
push    edi
push    ebx
call    _MmMapViewOfSection@40 ; MmMapViewOfSection(x,x,x,x,x,x,x,x,x,x)

Ensuite on voyage dans le noyau pour arriver à la fonction MiMapViewOfImageSection. Sachant que les flags des VAD sont définis de la façon suivante :

typedef struct _MMVAD_FLAGS {
ULONG_PTR CommitCharge : COMMIT_SIZE; // limits system to 4k pages or bigger! #define COMMIT_SIZE 19 (32 bits system)
ULONG_PTR NoChange : 1;
ULONG_PTR VadType : 3;
ULONG_PTR MemCommit: 1;
ULONG_PTR Protection : 5;
ULONG_PTR Spare : 2;
ULONG_PTR PrivateMemory : 1;    // used to tell VAD from VAD_SHORT
} MMVAD_FLAGS;

Et en me basant sur les sources de ReactOS de la fonction MiMapViewOfImageSection suivante:

#define MM_ZERO_ACCESS         0  // this value is not used.
#define MM_READONLY            1
#define MM_EXECUTE             2
#define MM_EXECUTE_READ        3
#define MM_READWRITE           4  // bit 2 is set if this is writable.
#define MM_WRITECOPY           5
#define MM_EXECUTE_READWRITE   6
#define MM_EXECUTE_WRITECOPY   7

typedef enum _MI_VAD_TYPE {
VadNone,
VadDevicePhysicalMemory,
VadImageMap,
VadAwe,
VadWriteWatch,
VadLargePages,
VadRotatePhysical,
VadLargePageSection
} MI_VAD_TYPE, *PMI_VAD_TYPE;

//
// Allocate and initialize a VAD for the specified address range.
//

Vad = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD), MMVADKEY);

if (Vad == NULL) {
if (TryLargePages == TRUE) {
MiReleasePhysicalCharges (
BYTES_TO_PAGES ((PCHAR)LargeEndingAddress + 1 - (PCHAR)LargeStartingAddress),
Process);

LOCK_WS_UNSAFE (Thread, Process);
MiFreeLargePages (LargeStartingAddress, LargeEndingAddress, FALSE);
UNLOCK_WS_UNSAFE (Thread, Process);
}
MiDereferenceControlArea (ControlArea);
return STATUS_INSUFFICIENT_RESOURCES;
}

RtlZeroMemory (Vad, sizeof(MMVAD));
Vad->StartingVpn = MI_VA_TO_VPN (LargeStartingAddress);
Vad->EndingVpn = MI_VA_TO_VPN (LargeEndingAddress);
Vad->u2.VadFlags2.Inherit = (InheritDisposition == ViewShare);
Vad->u.VadFlags.VadType = VadImageMap;

//
// Set the protection in the VAD as EXECUTE_WRITE_COPY.
//

Vad->u.VadFlags.Protection = MM_EXECUTE_WRITECOPY; // !!!!!!!!!!!!!!
Vad->ControlArea = ControlArea;

On peut voir le passage ou sont initialisés les différents paramètres du VAD. Regardons ce que ça donne sous IDA :

mov     eax, [esi+14h] ;esi pointe sur le Vad, Vad->u.VadFlags.Protection
lkd> dt nt!_MMVAD
+0x000 StartingVpn      : Uint4B
+0x004 EndingVpn        : Uint4B
+0x008 Parent           : Ptr32 _MMVAD
+0x00c LeftChild        : Ptr32 _MMVAD
+0x010 RightChild       : Ptr32 _MMVAD
+0x014 u                : __unnamed
+0x018 ControlArea      : Ptr32 _CONTROL_AREA
+0x01c FirstPrototypePte : Ptr32 _MMPTE
+0x020 LastContiguousPte : Ptr32 _MMPTE
+0x024 u2               : __unnamed

Le    +0x014 u : __unnamed
Correspond à l'union :
union {
ULONG_PTR LongFlags;
MMVAD_FLAGS VadFlags;
} u;

and     eax, 0E7FFFFFFh ; 11100111111111111111111111111111b
or      eax, 7100000h ;        111000100000000000000000000b ; en même tps on met le VadType à 2, 21 ème bit à 1 (VadImageMap)
mov     [esi+14h], = MM_EXECUTE_WRITECOPY

Qui correspond au code :

Vad->u.VadFlags.Protection = MM_EXECUTE_WRITECOPY;
Vad->ControlArea = ControlArea;

Pour désactiver cette protection il suffirait donc de patcher ce code de la façon suivante :

mov     eax, [esi+14h] ;esi pointe sur le Vad, Vad->u.VadFlags.Protection
and     eax, 0E7FFFFFFh ; 11100111111111111111111111111111b
or      eax, 6100000h ;        110000100000000000000000000b
mov     [esi+14h], = MM_EXECUTE_READWRITE

Cela nécessite de patcher le kernel, de préférence en hard avant le lancement du système mais tout est possible, un programme modifiant l’image du noyau sur le disque pourrait réaliser cette opération avant le lancement de l’OS.

Je n’ai pas vraiment le courage de tester cette méthode, pas pour l’instant. Un rootkit userland pourrait maîtriser entièrement le système, même les fonctions des DLL des process système sans avoir besoin de s’injecter partout. Bref un vrai petit monstre.

Evidemment patcher le noyau reste une opération lourde, réalisable uniquement avant le lancement de l’OS, ce qui nécessite de pouvoir modifier la séquence de BOOT afin d’effectuer notre malveillante modification. L’idée je pense semble intéressante et pourrait faire du chemin…

Reférences :
What Goes On Inside Windows 2000: Solving the Mysteries of the Loader [1]
LdrpMapDll
http://msdn.microsoft.com/msdnmag/issues/02/03/Loader/

Memory Protection [2]
http://msdn2.microsoft.com/en-us/library/aa366785.aspx

Remarque :
Le passage des constantes MM_XXX à PAGE_XXX se fait avec la macro MI_CONVERT_FROM_PTE_PROTECTION définit par :

extern ULONG MmProtectToValue[32];
#define MI_CONVERT_FROM_PTE_PROTECTION(PROTECTION_MASK)(MmProtectToValue[PROTECTION_MASK])

Elle va chercher dans un tableau de ULONG la valeur correspondant aux flags du VAD. Par exemple si on a le champ protection du VAD qui vaut 7 (MM_EXECUTE_WRITECOPY) alors on regarde dans le tableau MmProtectToValue au 7 ème indice pour trouver 0×80 (PAGE_EXECUTE_WRITECOPY) :

lkd> dd nt!MmProtectToValue
805569f0  00000001 00000002 00000010 00000020
80556a00  00000004 00000008 00000040 00000080 <- w00t

Entry Filed under: Non classé

6 Comments

  • 1. zours  |  mars 27th, 2007 at 18:54

    Tu sais que tu redonnes leurs lettres de noblesse aux blogs, toi ?
    Bon OK j’y comprends presque rien, mais c’est beau à voir quand même.


  • 2. Christophe  |  mars 28th, 2007 at 15:00

    Je crois que tu dois être le premier à avoir poussé l’idée jusqu’au bout sous NT ^_^ Ca vaudrait la peine de faire une chtite rump session au SSTIC sur le sujet.


  • 3. Heurs  |  mars 31st, 2007 at 11:37

    Si j’ai un disque dur qui me servira à rien j’essairais de te terminer le patch ;-)

    Sinon trés sympa l’article, et trés interessent également. Il est vrai que ce n’est pas le genre de sujet qu’on aborde souvent, donc ca fait toujours plaisir.


  • 4. admin  |  mars 31st, 2007 at 11:58

    Je ne l’ai pas dit, mais il suffit tout simplement de patcher le process smss.exe pour modifier l’attribut de la section. Si j’ai reverse mon kernel c’etait pour voir à quoi correspondait SEC_IMAGE, patcher le kernel c’était juste pour vous faire peur :p


  • 5. Intox  |  mars 31st, 2007 at 13:54

    Je vois que tu t’arretes pas, j’en arrive a la conclusion que tu tournes avec des piles duracell.
    Et en plus ca a l’air vachement interessant, parce que ca casserait également les couilles aux messieurs tout fier de le kernel a moitié protegé par des signatures de driver sur vista. Si tu pousses l’idée, je suis toujours la pour leaker :D


  • 6. LesPointsFrTousDesTAPS  |  avril 1st, 2007 at 15:44

    Bon, est-ce que vous pensez vraiment tout ce que vous écrivez comme comments ou bien c’est seulement pour encourager Ivan? Je comprends que vous voulez être gentil, mais c’est comme ma mère qui me disait que j’étais le plus beau, essayer d’être un peu crédible au moins!! Vous voulez pousser l’idée?? Attendez, c’est moi ou vous n’avez pas pensé au truc deux secondes avant d’envoyer Ivan au SSTIC ou de faire un patch pour vista? Voici des faits:

    1. Il y a plein de programmes legit (FW, anti-virus, etc.) qui changent des fonctions dans les dll pour leur apps.

    2. Si vous faites le truc d’Ivan, tous ces changements vont être vu par tout les autres process.

    3. Chaos ensured.

    Je vote pour que les comments soient constructif, qu’il y ait un vrai échange d’idées. Pour tout ceux qui sont en amour avec Ivan vous pouvez lui écrire un mail comme ça personne va être embeter avec vos conneries.

    Signé -PasuneTapsDePointFr


Trackback this post


Calendar

mars 2024
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
25262728293031

Most Recent Posts