CreateProcess
janvier 19th, 2008 at 01:29 admin
Hum, un post un peu forcé, histoire de poser quelque chose sur ce blog. J’avoue que sur ce coup je suis un peu décu de mon taff. Bref, un échec ne peut qu’être enrichissant … Je me suis lancé dans l’idée de coder ma propre fonction CreateProcess afin de réaliser une attaque précise, malheureusement j’ai rencontré 2 gros problèmes..
D’abord pour ce mettre dans l’ambiance, on va s’écouter du Burzum, ca aide à décrasser le cerveau.
La création d’un processus n’est pas si compliqué que ca. Tout d’abord il nous faut un joli binaire, on commence gentiment en l’ouvrant avec CreateFile puis on lui associe un objet section avec l’API CreateFileMapping avec l’attribut SEC_IMAGE. Cet attribut permet de dire à l’API quelle représente une image exécutable afin que l’API nous fabrique une structure SECTION_IMAGE_INFORMATION dessus, nous verrons son utilité par la suite. Dans le code suivant j’utilise directement les API natives, j’avais envie de me prendre la tête
InitializeObjectAttributes(&ObjectAttributes, &usPath, OBJ_CASE_INSENSITIVE, NULL, NULL); //ouvre le fichier binaire Status=ZwOpenFile(&hFile, FILE_EXECUTE|SYNCHRONIZE, &ObjectAttributes, &IoBlock, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); if(!NT_SUCCESS(Status)) { printf("Error with ZwOpenFile : 0x%x\n", Status); goto end; } InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); //cree l'objet section avec l'attribut SEC_IMAGE specifique au executable images. Status=ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &ObjectAttributes, 0, PAGE_EXECUTE, SEC_IMAGE, hFile); if(!NT_SUCCESS(Status)) { printf("Error with ZwCreateSection : 0x%x\n", Status); goto end; }
Maintenant qu’on a notre handle sur la section on peut crée l’objet EPROCESS himsefl avec l’API ZwCreateProcess.
NTSTATUS NtCreateProcess( __out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in_opt POBJECT_ATTRIBUTES ObjectAttributes, __in HANDLE ParentProcess, __in BOOLEAN InheritObjectTable, __in_opt HANDLE SectionHandle, __in_opt HANDLE DebugPort, __in_opt HANDLE ExceptionPort ) Routine Description: This routine creates and initializes a process object. Arguments: ProcessHandle - Returns the handle for the new process. DesiredAccess - Supplies the desired access modes to the new process. ObjectAttributes - Supplies the object attributes of the new process. ParentProcess - Supplies a handle to the process' parent process. If this parameter is not specified, then the process has no parent and is created using the system address space. Flags - Process creation flags SectionHandle - Supplies a handle to a section object to be used to create the process' address space. If this parameter is not specified, then the address space is simply a clone of the parent process' address space. DebugPort - Supplies a handle to a port object that will be used as the process' debug port. ExceptionPort - Supplies a handle to a port object that will be used as the process' exception port.
Remarquez le commentaire au niveau du SectionHandle, si jamais celui-ci est a NULL, on fabrique un fork du process courant, ce qui dans certains cas peut s’avérer utile.
Quand on appel ZwCreateProcess, le kernel se charge de mettre en place la structure EPROCESS associé avec lui en le créant avec ObCreateObject. Son espace mémoire est crée avec MmCreateProcessAddressSpace et init avec MmInitializeProcessAddressSpace.
La suite consiste à initialiser le primary du futur process. Pour cela on doit lui allouer une mémoire qui lui servira de stack. La taille de stack est retrouvé à l’aide du PE header du binaire, ce qui est cool c’est que la fonction ZwQuerySection peut nous renvoyer une structure SECTION_IMAGE_INFORMATION qui contient les valeurs suivantes :
typedef struct _SECTION_IMAGE_INFORMATION { PVOID TransferAddress; ULONG ZeroBits; SIZE_T MaximumStackSize; SIZE_T CommittedStackSize; ULONG SubSystemType; union { struct { USHORT SubSystemMinorVersion; USHORT SubSystemMajorVersion; }; ULONG SubSystemVersion; }; ULONG GpValue; USHORT ImageCharacteristics; USHORT DllCharacteristics; USHORT Machine; BOOLEAN ImageContainsCode; BOOLEAN Spare1; ULONG LoaderFlags; ULONG Reserved[ 2 ]; } SECTION_IMAGE_INFORMATION, *PSECTION_IMAGE_INFORMATION;
2 champs nous intéresse, CommittedStackSize et MaximumStackSize. Le premier correspond à la taille de stack à allouer obligatoirement, le second à la taille max. Sachant que le thread n’aura pas forcément besoin de toute sa stack, on va éviter le gaspillage en réservant la mémoire sur une taille de MaximumStackSize tout en utilisant physiquement que CommitedStackSize de mémoire.
Plus tard, pour crée le thread avec ZwCreateThread on va avoir besoin de lui fournir une structure INITIAL_TEB :
typedef struct _INITIAL_TEB { struct { PVOID OldStackBase; PVOID OldStackLimit; } OldInitialTeb; PVOID StackBase; PVOID StackLimit; PVOID StackAllocationBase; } INITIAL_TEB, *PINITIAL_TEB;
Comme nous connaissons les besoins de la main stack, nous allons remplir cette structure. Première étape, allouer en MEM_RESERVE MaximumStackSize de mémoire avec ZwAllocateVirtualMemory dans l’espace mémoire fraichement crée. On récupère l’adresse dans le champ StackAllocationBase. Ensuite, on fabrique la pile selon le schéma suivant :
StackAllocationBase | | | StackLimit | | | | | | StackBase | +-----------------+ L'espace representé par StackAllocationBase est en MEM_RESERVE. Celui entre StackLimit et StackBase est en MEM_COMMIT|MEM_RESERVE.
Tout l’espace de StackAllocationBase est en MEM_RESERVE, celui de l’adresse la plus haute de StackAllocationBase (Stackbase) jusqu’a Stackbase-ImageInfo.CommittedStackSize est en MEM_COMMIT|MEM_RESERVE.
Cela veut dire que physiquement on aura réservé dans la ram que CommittedStackSize de mémoire, alors que les VAD (Virtual Address Descriptors) du process décriront l’espace représenter par StackAllocationBase comme utilisé.
//recup la structure ImageInfo Status=ZwQuerySection(hSection, SectionImageInformation, &ImageInfo, sizeof(ImageInfo), NULL); if(!NT_SUCCESS(Status)) { printf("Error with ZwQuerySection : 0x%x\n", Status); goto end; } printf("ImageInfo.MaximumStackSize : 0x%x\n", ImageInfo.MaximumStackSize); printf("ImageInfo.CommittedStackSize : 0x%x\n", ImageInfo.CommittedStackSize); RtlZeroMemory(&InitialTeb, sizeof(InitialTeb)); //Alloue ImageInfo.MaximumStackSize et stoque l'adresse dans InitialTeb.StackAllocationBase //les pages de sont que MEM_RESERVE Status=ZwAllocateVirtualMemory(hProcess, &InitialTeb.StackAllocationBase, 0, &ImageInfo.MaximumStackSize, MEM_RESERVE, PAGE_READWRITE); if(!NT_SUCCESS(Status)) { printf("Error with ZwAllocateVirtualMemory (1) : 0x%x\n", Status); goto end; } InitialTeb.StackBase=(PVOID)((ULONG)InitialTeb.StackAllocationBase+ImageInfo.MaximumStackSize); InitialTeb.StackLimit=(PVOID)((ULONG)InitialTeb.StackBase-ImageInfo.CommittedStackSize); n=(ULONG)(ImageInfo.CommittedStackSize); p=(PVOID)InitialTeb.StackLimit; //Commit les pages entre StackBase et StackLimit qui seront utilisées pour la stack Status=ZwAllocateVirtualMemory(hProcess, (PVOID*)&p, 0, &n, MEM_COMMIT, PAGE_READWRITE); if(!NT_SUCCESS(Status)) { printf("Error with ZwAllocateVirtualMemory (2): 0x%x\n", Status); goto end; }
Après tout ca, on peut tranquillement créer notre thread avec ZwCreateThread de proto :
NTSTATUS NtCreateThread( __out PHANDLE ThreadHandle, __in ACCESS_MASK DesiredAccess, __in_opt POBJECT_ATTRIBUTES ObjectAttributes, __in HANDLE ProcessHandle, __out PCLIENT_ID ClientId, __in PCONTEXT ThreadContext, __in PINITIAL_TEB InitialTeb, __in BOOLEAN CreateSuspended ) /*++ Routine Description: This routine creates and initializes a thread object. Arguments: ThreadHandle - Returns the handle for the new thread. DesiredAccess - Supplies the desired access modes to the new thread. ObjectAttributes - Supplies the object attributes of the new thread. ProcessHandle - Supplies a handle to the process that the thread is being created within. ClientId - Returns the CLIENT_ID of the new thread. ThreadContext - Supplies a pointer to a context frame that represents the initial user-mode context for a user-mode thread. The absence of this parameter indicates that a system thread is being created. InitialTeb - Supplies the contents of certain fields for the new threads TEB. This parameter is only examined if both a trap and exception frame were specified. CreateSuspended - Supplies a value that controls whether or not a user-mode thread is created in a suspended state. --*/
Hop le code très simple qui réalise cela :
RtlZeroMemory(&Context, sizeof(CONTEXT)); //Definit le context de depart du thread Context.SegGs=0; Context.SegFs=0x3B; //segment fs r3 Context.SegEs=0x23; //segment de data r3 Context.SegDs=0x23; Context.SegSs=0x23; Context.SegCs=0x1B; //segment de code r3 Context.EFlags=0x200; // force interrupts on, clear all else. Context.Esp=(ULONG)InitialTeb.StackBase-4; //TransferAddress est renvoye dans la structure ImageInfo, c'est l'entrypoint de notre programme Context.Eip=(ULONG)ImageInfo.TransferAddress; printf("ImageInfo.TransferAddress (EntryPoint) : 0x%x\n", ImageInfo.TransferAddress); Status=ZwCreateThread(&hThread, //ThreadHandle THREAD_ALL_ACCESS, //DesiredAccess NULL, //ObjectAttributes hProcess, //ProcessHandle &ClientId, //ClientId, &Context, //ThreadContext &InitialTeb, //InitialTeb TRUE); //CreateSuspended if(!NT_SUCCESS(Status)) { printf("Error with ZwCreateThread : 0x%x\n", Status); goto end; }
Remarquez que le thread est crée en suspended. On doit faire encore une petite opération avec de la lancer.
Entre temps nous somme censés initialiser le champ ProcessParameters du PEB du futur process à l’aide de la structure RTL_USER_PROCESS_PARAMETERS et de l’API RtlCreateProcessParameters :
typedef struct _RTL_USER_PROCESS_PARAMETERS { ULONG MaximumLength; ULONG Length; ULONG Flags; ULONG DebugFlags; HANDLE ConsoleHandle; ULONG ConsoleFlags; HANDLE StandardInput; HANDLE StandardOutput; HANDLE StandardError; CURDIR CurrentDirectory; // ProcessParameters UNICODE_STRING DllPath; // ProcessParameters UNICODE_STRING ImagePathName; // ProcessParameters UNICODE_STRING CommandLine; // ProcessParameters PVOID Environment; // NtAllocateVirtualMemory ULONG StartingX; ULONG StartingY; ULONG CountX; ULONG CountY; ULONG CountCharsX; ULONG CountCharsY; ULONG FillAttribute; ULONG WindowFlags; ULONG ShowWindowFlags; UNICODE_STRING WindowTitle; // ProcessParameters UNICODE_STRING DesktopInfo; // ProcessParameters UNICODE_STRING ShellInfo; // ProcessParameters UNICODE_STRING RuntimeData; // ProcessParameters RTL_DRIVE_LETTER_CURDIR CurrentDirectores[ RTL_MAX_DRIVE_LETTERS ]; } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; extern "C" NTSYSAPI NTSTATUS NTAPI RtlCreateProcessParameters( PRTL_USER_PROCESS_PARAMETERS *ProcessParameters, PUNICODE_STRING ImagePathName, PUNICODE_STRING DllPath, PUNICODE_STRING CurrentDirectory, PUNICODE_STRING CommandLine, PVOID Environment, PUNICODE_STRING WindowTitle, PUNICODE_STRING DesktopInfo, PUNICODE_STRING ShellInfo, PUNICODE_STRING RuntimeData );
On retrouve un peu la même structure que celle de PROCESS_INFORMATION qu’on fournit en entrée à CreateProcess. Pour init le PEB on utilise le code suivant :
VOID CreateProcessParameters(HANDLE hProcess, PPEB Peb, PUNICODE_STRING ImageFile) { ULONG n; PVOID p; NTSTATUS Status; PRTL_USER_PROCESS_PARAMETERS pp; Status=RtlCreateProcessParameters(&pp, ImageFile, 0, 0, 0, 0, 0, 0, 0, 0); if(!NT_SUCCESS(Status)) { printf("Error with RtlCreateProcessParameters : 0x%x\n", Status); return; } pp->Environment=CopyEnvironment(hProcess); n=pp->MaximumLength; p=0; //alloue l'espace pour la structure RTL_USER_PROCESS_PARAMETERS Status=ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_READWRITE); if(!NT_SUCCESS(Status)) { printf("Error with ZwAllocateVirtualMemory : 0x%x\n", Status); return; } //copie la structure dans l'espace memoire du process Status=ZwWriteVirtualMemory(hProcess, p, pp, pp->MaximumLength, 0); if(!NT_SUCCESS(Status)) { printf("Error with ZwWriteVirtualMemory : 0x%x\n", Status); return; } //met a jour le champ du PEB Status=ZwWriteVirtualMemory(hProcess, (PCHAR)Peb + 0x10, &p, sizeof(p), 0); if(!NT_SUCCESS(Status)) { printf("Error with ZwWriteVirtualMemory : 0x%x\n", Status); return; } Status=RtlDestroyProcessParameters(pp); }
Enfin il reste la partie la plus mystérieuse, informé le subsystem à travers le LPC \Windows\ApiPort du process csrss.exe. Je vous avouerais que c’est la partie la moins documentée, qui, de ce fait, pose le plus de problème. D’après ce que j’ai pu récupérer, on aurait un code dans ce style là :
typedef struct _CSR_CAPTURE_HEADER { ULONG Length; PCSR_CAPTURE_HEADER RelatedCaptureBuffer; ULONG CountMessagePointers; PCHAR FreeSpace; ULONG_PTR MessagePointerOffsets[1]; // Offsets within CSR_API_MSG of pointers } CSR_CAPTURE_HEADER, *PCSR_CAPTURE_HEADER; typedef ULONG CSR_API_NUMBER; typedef struct _PBASE_CREATEPROCESS_MSG { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; DWORD CreationFlags; CLIENT_ID DebuggerClientId; DWORD VdmBinaryType; }BASE_CREATEPROCESS_MSG ,*PBASE_CREATEPROCESS_MSG; typedef struct _BASE_API_MSG { PORT_MESSAGE h; PCSR_CAPTURE_HEADER CaptureBuffer; CSR_API_NUMBER ApiNumber; ULONG ReturnValue; ULONG Reserved; union { BASE_CREATEPROCESS_MSG CreateProcess; } u; } BASE_API_MSG, *PBASE_API_MSG; VOID InformCsrss(HANDLE hProcess, HANDLE hThread, ULONG pid, ULONG tid) { NTSTATUS Status; BASE_API_MSG m; PBASE_CREATEPROCESS_MSG a=(PBASE_CREATEPROCESS_MSG)&m.u.CreateProcess; RtlZeroMemory(&m, sizeof(m)); a->hProcess=hProcess; a->hThread=hThread; a->dwProcessId=pid; a->dwThreadId=tid; Status=CsrClientCallServer((PVOID)&m, 0, 0x10000, sizeof(*a)); if(!NT_SUCCESS(Status)) { printf("Error with CsrClientCallServer : 0x%x\n", Status); return; } }
Hop quand ca c’est fait, il n’y a plus qu’a lancé le thread avec ResumeThread et c’est partit. Normalement …
Le code que je fournis ne marche pas à chaque fois, j’ai un peu de mal à déterminer d’ou provient le bug et je serais content si quelqu’un pouvais me donner une piste voir même la solution.
En fait, au départ, si j’ai commencé ce code, c’était dans l’optique de crée un process à partir de rien sur le disque. Je voulais uniquement crée la section depuis la mémoire d’un process « lanceur » qui aurait servit de base pour ZwCreateProcess, apparemment dès qu’on utilise l’attribut SEC_IMAGE avec ZwCreateSection, il faut obligatoirement un handle sur un fichier exécutable. De ce fait, je n’ai pas réussit à implémenter ma petite idée, tant pis
Vous trouverez le code+binaire ici :
http://ivanlef0u.fr/repo/CreateProcess.rar
Sinon, pour jouer, quelques liens sur le petit monstre du moment, je veux bien sur parler du rooktit infectant le MBR Pour ceux qui s’ennuient vous pouvez même récupérer le binaire chez OffensiveComputing.
http://www2.gmer.net/mbr/
http://www.symantec.com/enterprise/security_response/weblog/2008/01/from_bootroot_to_trojanmebroot.html
http://www.microsoft.com/security/portal/Entry.aspx?name=VirTool:WinNT/Sinowal.A
Refs :
Windows Intenal, Chapter 6, Flow of CreateProcess
http://book.itzero.com/read/microsoft/0507/Microsoft.Press.Microsoft.Windows.Internals.Fourth.Edition.Dec.2004.internal.Fixed.eBook-DDU_html/0735619174/ch06lev1sec2.html
Kernel-mode backdoors for Windows NT
http://www.phrack.org/issues.html?issue=62&id=6&mode=txt
What Goes On Inside Windows 2000: Solving the Mysteries of the Loader
http://msdn.microsoft.com/msdnmag/issues/02/03/Loader/
Windows 2000 Native Api Reference
Example 6.2: Creating a Win32 Process
A catalog of NTDLL kernel mode to user mode callbacks, part 6: LdrInitializeThunk
http://www.nynaeve.net/?p=205
Entry Filed under: RE
15 Comments
1. mxatone | janvier 19th, 2008 at 09:30
Je pense que ton idee est fesable, apres il est vrai que comme tu ne te base pas sur une image executable pure (une section tagé comme executable), il faut que tu implementes des mecanismes toi même. Tel que le rensignement de SECTION_IMAGE_INFORMATION et biensur la relocation.
Je n’ai jamais testé alors cet à prendre au conditionnel. Il faudrait regarder dans le noyau les checks fait, mais je pense pas que ZwCreateProcess check d’une facon ou l’autre que la section a ete taggé SEC_IMAGE.
Tu n’as pas reussi à faire ce que tu voulais mais je trouve cet article très instructifs pour comprendre les étapes détaillés de la création d’un processus. So merci
2. Parano | janvier 19th, 2008 at 23:49
Sa sens l’injection de code
3. SynApsus | janvier 20th, 2008 at 12:48
Tout d’abord hello à toi Ivan !
Ca faisait bien longtemps que j’étais hors circuit, mais bon j’ai mes rechutes.
Je pense que tu devrais comme dit mxatone, abandonner le SEC_IMAGE qui est un peu verrouillé et coder ton moteur de chargement+relocs+IAT. ( a moins que l’IAT ne soit remplie par une autre des APIs que tu appelles )
C’est pas très dur à faire, c’est un peu méthodique c’est tout
Au pire j’ai un code à moi pr ça, mais bon l’ASM te donne des boutons non ?
Et puis ce que tu veux charger n’étant pas stricto sensu un exécutable, l’IAT et les relocs ben euh
Allez, @+ et bonne chance.
Si tu le pousses loin ton truc pourrait devenir intéressant pour lancer des binaires de n’importe quel format : tu pourrais meme definir ton IvanPE format.
4. le concombre masqué | janvier 21st, 2008 at 09:39
tu pourrais meme definir ton IvanPE format.
Je passe sur la description d’IMAGE_OPTIONAL_HEADER. On n’est pas dans la merde.
5. admin | janvier 21st, 2008 at 11:58
Yo,
Merci pour vos réponses mxatone et SynApsus. J’ai regardé d’un peu plus près le comportement de ZwCreateProcess dans le cas ou on lui passe une section crée sans attribut SEC_IMAGE. Pas de chance ZwCreateProcess nous renvoie STATUS_SECTION_NOT_IMAGE (0xC0000049), ce status provient de la routine MmInitializeProcessAddressSpace. Sachant que ZwCreateSection ne supporte le flag SEC_IMAGE uniquement si il existe un handle du fichier valide. Je l’ai donc bien dmc …
6. SynApsus | janvier 21st, 2008 at 19:59
Et il se fait comment le check de MmInitializeProcessAddressSpace ? Tas ptetre moyen de fooler ça avec ton _IMAGE_IVAN_HEADERS nan ?
7. admin | janvier 22nd, 2008 at 18:06
SynApsus, les flags de la section sont stockés dans le kernel, dans l’objet SECTION_OBJECT.
Tu vois le joli flag Image dans MMSECTION_FLAGS :p
8. SynApsus | janvier 23rd, 2008 at 10:12
J’imagines que tu veux pas de driver, ni chercher un bug kernel pour aller reecrire ça. Et mapper une vraie image PE en memoire, quitte à re-coller sur cet espace ben tout le code que tu veux y mettre depuis un autre fichier par exemple…
9. akhenathon | janvier 30th, 2008 at 17:02
Encore une fois un très bon article
Je te félicite pour tout le travail que tu effectue
10. martin | février 12th, 2008 at 07:41
De ce que je peux voir le proc demarre bien, mais ldrpinitialize() fait une exception qui fait une exception, qui fait une exception…
Un pb de pile ?
11. admin | février 13th, 2008 at 10:59
Vista, XP ?
12. martin | février 13th, 2008 at 15:59
Xp sp2, sorry. J’ai un peu + d’infos…
peb/teb sont invalides d’apres windbg.
le flag SYNCHRONIZE et FILE_READ_SHARE c’est pas trop apprecie par le SEC_IMAGE donc la section est pas mappe sur xp. Ensuite
J’ai du supprimer le RtlCopymem ca plantait tout le temps car la section est RO de toute facon sur xp sp2 ?!
Le getContext du thread ne fonctionne pas jai que des 0 dans les registres,c’est pas terrible.
Sinon en mettant un brkptr avec windbg sur le retour de la livraison APC du threadcreate dans le process nouvellement cree on voit que c’est le iretd en sortant du chgt de context pour demarrer le thread qui est foireux. la pile cree pointe n’est pas bonne.
A suivre…
13. Ben | février 17th, 2008 at 00:51
Very interesting article, thanks.
14. barton | août 17th, 2008 at 15:08
true xek!11
15. broucaries | avril 6th, 2011 at 11:39
Des nouvelles ? Cela m’interesse pour implementer un vrai exec sous windows ou meme un fork pour gnulib
Trackback this post