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
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