Posts filed under 'C:'

Domains

Les domaines http://ivanlef0u.fr et http://www.ivanlef0u.fr (pour le blog) sont fonctionnels. Mettez à jour vos liens.

Si vous trouvez ce blog trop calme, suivez moi sur Twitter.

6 comments février 9th, 2011

WMI and Windows patches

Avant de lancer un exploit patché sur une machine Windows on préfère savoir à l’avance si la box est à jours ou non. Cela évite certaines mauvaises surprises et perte de temps à tester dans tous les sens un exploit codé par un chinois.

Comme vous le savez Microsoft édite ses patchs tous les seconds mardis de chaque mois. Le lendemain du patch tuesday est souvent consacré à differ (à l’aide de turbodiff ou de patchdiff2) les mises à jours. Après avoir localisé le bug et pondu un POC qui trigger la vuln on peut enfin coder quelque chose de plus sérieux :] En général un local EOP (Escalation Of Privilege) est le bienvenu.

Par la suite le jour arrive ou il s’agit de balancer notre exploit sur la machine cible. A ce moment on ressent la goute de sueur froide qui coule le long de notre dos parce que la loi du chaos peut tout foutre en l’air. L’un des paramètres qui décidera ou non de la réussite de notre attaque est bien sur le niveau de mise à jour de la machine.

Sous Windows XP connaitre les udpates présentes est plutôt simple. Il existe des outils dédiés comme WinUpdatesList de NirSoft En fonction des ‘KB’ installés on sait ou non si la machine est vulnérable.

Pour information WinUpdatesList va lire la clé de registre HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Hotfix.

Problème, sous Vista et 7 cette clé n’existe plus. On peut toujours passer par le menu ‘Installed Updates‘ mais c’est fastidieux si on doit vérifier beaucoup de patches.

Par chance Windows implémente WMI (Windows Management Instrumentation). Il s’agit en gros d’une interface standard d’échange d’information entre différentes applications. On peut la scripter avec du VBS et du PowerShell. Chose que seul news0ft sait faire ..

En interne Windows maintient un ensemble de classes contenant diverses informations sur le systèmes et ses applications regroupé sur une arborescence CIM (Common Information Model). Il est possible de voir l’ensemble de ces classes avec l’outil WMI Explorer.

Par exemple, si vous enregistrez le code suivant dans un .vbs et que l’exécutez il vous donnera les noms de tous les patches présents sur votre machine.

strComputer = "."
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colQuickFixes = objWMIService.ExecQuery("Select * from Win32_QuickFixEngineering")
For Each objQuickFix in colQuickFixes
    Wscript.Echo "Patch : " & objQuickFix.ServicePackInEffect
Next

La même chose est possible via l’outil en ligne de commande wmic. La commande ‘wmic QFE list full /format:htable’ vous sort un dump HTML des différents patches.

Remarquez que dans les 2 cas on a fait une requête sur la table Win32_QuickFixEngineering. La requête se présente sous la forme d’un langage de ‘query’ appelé WQL. En fait on récupère l’ensemble des instances de cette classe via un SELECT puis on énumère la property ServicePackInEffect de chaque objet.

Bien sur comme on préfère coder en C il existe une interface COM pour WMI. Pour l’utiliser il suffit d’instancier une instance de CLSID_WbemLocator afin se connecter et d’obtenir un objet IWbemServices. A partir de là on peut appeler la méthode ExecQuery avec du WQL pour récupérer ce qui nous intéresse.

Le code suivant est inspiré de l’exemple de la MSDN ‘Example: Creating a WMI Application‘ et permet de savoir si un patch est présent ou pas.

#define ANSI
#define _WIN32_DCOM
#define  _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <comdef.h>
#include <Wbemidl.h>
#include <stdio.h>
 
#pragma comment(lib, "wbemuuid.lib")
#pragma comment(lib, "comsuppw.lib")
 
BOOL IsKBInstalled(PCHAR Kb)
{
	BOOL Status=TRUE;
	HRESULT hres;
	IWbemLocator *pLoc=0;
        IWbemServices *pSvc=0;
        IEnumWbemClassObject* pEnumerator=NULL;
	IWbemClassObject *pclsObj;
	ULONG uReturn=0;
	VARIANT vtProp;
	CHAR Buffer[256];
	DWORD i=0;
 
    //Initialize COM
    hres=CoInitializeEx(0, COINIT_MULTITHREADED); 
    if(FAILED(hres))
    {
    	printf("[-] Error with CoInitializeEx : 0x%x\n", hres);
	goto end;
    }
 
    //Initialize 
    hres=CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE,NULL);
    if(FAILED(hres))
    {
    	printf("[-] Error with CoInitializeSecurity : 0x%x\n", hres);
    	CoUninitialize();
        goto end;
    }
 
    //Obtain the initial locator to Windows Management
    //on a particular host computer.
    hres=CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&pLoc);
    if(FAILED(hres))
    {
    	printf("[-] Error with CoCreateInstance : 0x%x\n", hres);
		CoUninitialize();
		goto end;
    }
 
    // Connect to the root\cimv2 namespace with the
    // current user and obtain pointer pSvc
    // to make IWbemServices calls.
    hres=pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc);                              
    if(FAILED(hres))
    {
    	printf("[-] Error with ConnectServer : 0x%x\n", hres);
        pLoc->Release();     
        CoUninitialize();
        goto end;
    }
 
    // Set the IWbemServices proxy so that impersonation
    // of the user (client) occurs.
    hres=CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
    if(FAILED(hres))
    {
    	//printf("[-] Error with CoSetProxyBlanket : 0x%x\n", hres);
        pSvc->Release();
        pLoc->Release();     
        CoUninitialize();
        goto end;
    }
 
    // Use the IWbemServices pointer to make requests of WMI. 
    // Make requests here:
    _snprintf(Buffer, sizeof(Buffer)-1, "select * from Win32_QuickFixEngineering where ServicePackInEffect=\"KB%s\"", Kb);
    hres=pSvc->ExecQuery(bstr_t("WQL"), bstr_t(Buffer), WBEM_FLAG_FORWARD_ONLY|WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
    if(FAILED(hres))
    {
        pSvc->Release();
        pLoc->Release();     
        CoUninitialize();
        goto end;
    }
    else
    { 
    	i=0;
        while(pEnumerator)
        {
            hres=pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
            if(uReturn==0)
                break;
            // Get the value of the Name property
            hres=pclsObj->Get(L"ServicePackInEffect", 0, &vtProp, 0, 0);
            //wprintf(L"ServicePackInEffect: %s\n", vtProp.bstrVal);
            VariantClear(&vtProp);
            i++;
        }
    }
 
	if(i==0)
		Status=FALSE;
end:
    // Cleanup
    pSvc->Release();
    pLoc->Release();     
    CoUninitialize();
    return Status;
}
 
 
int __cdecl main(int argc, char *argv[])
{
	if(argc!=2)
	{
		printf("[-] Usage is : %s <KB patch number>\n", argv[0]);
		return 0;
	}
 
	printf("KB%s is %s\n", argv[1], IsKBInstalled(argv[1]) ? "present" : "not present");
}

Exemple avec le patch du mois d’octobre 2010 MS10-073 (KB981957).

C:\wmi.exe 981957
KB981957 is present

Maintenant vous pouvez ripper ce code et le mettre dans votre exploit qui pourra par lui même vérifier si le système est à jour sur 2000, XP, Vista et 7 :]

Code et binaire:
http://ivanlef0u.fr/repo/wmi.rar

4 comments novembre 13th, 2010

Windows deamon exploitme

Rien de nouveau dans ce post. Juste un exemple de petit deamon sous Windows qui permet d’émuler un accept()+fork()+execve() comme on en voit souvent lors de challenges. D’abord pour montrer que c’est faisable et ensuite pour avoir du fun avec les potes :]

Le binaire que je fournis ne possède aucune mitigation. A vous de voir donc si vous voulez utiliser du GS, de l’ASLR, du DEP, voir du SAFESEH le tout avec du SEHOP. Vous pouvez bien sûr toujours changer la vuln :]

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
 
#pragma comment (lib, "ws2_32.lib")
 
#define PORT 1337
 
//
//
//
int Fork(SOCKET Csock)
{
	CHAR ProcessName[MAX_PATH];
	STARTUPINFO StartupInfo;
	PROCESS_INFORMATION ProcessInformation;
 
	GetModuleFileName(GetModuleHandle(NULL), ProcessName, sizeof(ProcessName)-sizeof(CHAR));
	printf("[*] Forking \"%s\" process \n", ProcessName);
 
	RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
	RtlZeroMemory(&ProcessInformation, sizeof(ProcessInformation));
 
	StartupInfo.cb=sizeof(StartupInfo);
	StartupInfo.dwFlags=STARTF_USESTDHANDLES;
	StartupInfo.hStdInput=(HANDLE)Csock;
	StartupInfo.hStdOutput=(HANDLE)Csock;
	StartupInfo.hStdError=(HANDLE)Csock;
 
	if(CreateProcess(ProcessName, NULL, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &StartupInfo, &ProcessInformation)==0)
	{
		printf("[-] Error in fork() with CreateProcess: %u\n", GetLastError());
		return 0;	
	}
	CloseHandle(ProcessInformation.hProcess);
	CloseHandle(ProcessInformation.hThread);
 
	printf("[+] New process %u created !\n", ProcessInformation.dwProcessId);
 
	return 1;	
}
 
//
//
//
int Parent()
{
	int AddrLen;
	BOOL OptVal;
	SOCKET Ssock, Csock;
	SOCKADDR_IN Ssaddr, Csaddr;
 
	printf("[*] Windows vulnerable deamon by Ivanlef0u - H4CKZ ME - BE M4D!\n");
 
	//
	// Use WSASocket() instead of socket() because we don't want an asynchronous socket.
	//
	Ssock=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
	if(Ssock==INVALID_SOCKET)
	{
		printf("[-] Error in Parent() with WSASocket(): %d\n", WSAGetLastError());	
		goto end;
	}
 
	OptVal=TRUE;
	if(setsockopt(Ssock, SOL_SOCKET, SO_REUSEADDR, (char*)&OptVal, sizeof(OptVal))==SOCKET_ERROR)
	{
		printf("[-] Error in Parent() with setsockopt(): %d\n", WSAGetLastError());	
		goto end;
	}
 
	Ssaddr.sin_family=AF_INET;
	Ssaddr.sin_port=htons(PORT);
	Ssaddr.sin_addr.s_addr=INADDR_ANY;
	if(bind(Ssock, (struct sockaddr *)&Ssaddr, sizeof(Ssaddr))==SOCKET_ERROR)
	{
		printf("[-] Error in Parent() with bind(): %d\n", WSAGetLastError());
		goto end;
	}
 
	if(listen(Ssock, 10)==SOCKET_ERROR)
	{
		printf("[-] Error in Parent() with listen(): %d\n", WSAGetLastError());
		goto end;
	}
 
	while(1)
	{
		AddrLen=sizeof(Csaddr);
		Csock=accept(Ssock, (struct sockaddr *)&Csaddr, &AddrLen);
		if(Csock==INVALID_SOCKET)
			continue;
		printf("[*] Connection from : %s:%hu\n", inet_ntoa(Csaddr.sin_addr), ntohs(Csaddr.sin_port));
 
		if(Fork(Csock)==0)
			printf("[-] Error in Parent() with Fork() :(\n");
 
		closesocket(Csock);
	}
 
end:
	closesocket(Ssock);
	WSACleanup();
	return 1;	
}
 
 
//
//
//
int Child()
{
	CHAR Buff[256];
	printf("How it taste motherfucker ?!\n");
	gets(Buff);  // BOFME !
	printf("%s\n", Buff);
	return 1;	
}
 
 
//
//
//
int __cdecl main(int argc, char * argv[])
{
	int Err, AddrLen;
	SOCKADDR_IN Csaddr;
	SOCKET Csock;
	WSADATA WSAData;
 
	Err=WSAStartup(WINSOCK_VERSION, &WSAData);
	if(Err!=0)
	{
		printf("[-] Error in main() with WSAStartup(): %d\n", Err);
		return 0;
	}
 
	//
	// Check if STD_INPUT_HANDLE is a socket. 
	// If yes then we are the child. 
	// If no we are the parend so we start the deamon.
	//
	Csock=(SOCKET)GetStdHandle(STD_INPUT_HANDLE);
	AddrLen=sizeof(Csaddr);
	if(getsockname(Csock, (struct sockaddr *)&Csaddr, &AddrLen)==0)
	{
		//
		// We don't need standard IO buffering
		//
		setvbuf(stdin, NULL, _IONBF, 0);
		setvbuf(stdout, NULL, _IONBF, 0);
		setvbuf(stderr, NULL, _IONBF, 0);	
 
		Child();
 
		closesocket(Csock);
		WSACleanup();
		TerminateProcess(GetCurrentProcess(), 0);		
	}
	else
		Parent();
 
	return 0;
}

Source+binaire:
http://ivanlef0u.fr/repo/exploitme.rar
hf!

2 comments septembre 5th, 2010

CVE-2010-2568 Lnk shorcut

Microsoft Security Advisory (2286198)

The Stuxnet Sting

Microsoft Windows automatically executes code specified in shortcut files

Vulnerability in Windows « LNK » files?

  1. Décompressez les fichiers dans ‘C:\’. Lancez un DbgView ou coller un KD à votre VM.
  2. Renommez ‘suckme.lnk_’ en ‘suckme.lnk’ et laissez la magie de shell32.dll faire le reste.
  3. Regardez vos logs.

http://ivanlef0u.fr/repo/suckme.rar

Testé sous XP SP3.

kd> g
Breakpoint 1 hit
eax=00000001 ebx=00f5ee7c ecx=0000c666 edx=00200003 esi=00000001 edi=7c80a6e4
eip=7ca78712 esp=00f5e9c4 ebp=00f5ec18 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
SHELL32!_LoadCPLModule+0x10d:
001b:7ca78712 ff15a0159d7c    call    dword ptr [SHELL32!_imp__LoadLibraryW (7c9d15a0)] ds:0023:7c9d15a0={kernel32!LoadLibraryW (7c80aeeb)}
kd> dd esp
00f5e9c4  00f5ee7c 000a27bc 00f5ee78 00000000
00f5e9d4  00000020 00000008 00f5ee7c 00000000
00f5e9e4  00000000 0000007b 00000000 00000000
00f5e9f4  00200073 002000e0 0000064c 0000028c
00f5ea04  1530000a 00000000 003a0043 0064005c
00f5ea14  006c006c 0064002e 006c006c 006d002e
00f5ea24  006e0061 00660069 00730065 00000074
00f5ea34  00090608 7c92005d 00000000 00000007
kd> db 00f5ee7c
00f5ee7c  43 00 3a 00 5c 00 64 00-6c 00 6c 00 2e 00 64 00  C.:.\.d.l.l...d.
00f5ee8c  6c 00 6c 00 00 00 92 7c-c8 f2 f5 00 00 17 72 02  l.l....|......r.
00f5ee9c  4b d2 00 00 d8 f2 f5 00-8b d2 a1 7c 00 00 00 00  K..........|....
00f5eeac  ac 80 9d 7c 30 d8 0d 00-34 d8 0d 00 b8 d7 0d 00  ...|0...4.......
00f5eebc  9a d2 a1 7c 30 d8 0d 00-c8 f2 f5 00 50 40 15 00  ...|0.......P@..
00f5eecc  50 40 15 00 00 00 00 00-b8 00 92 7c 40 b7 0c 00  P@.........|@...
00f5eedc  a8 ef f5 00 41 00 92 7c-18 07 09 00 5d 00 92 7c  ....A..|....]..|
00f5eeec  c8 f2 f5 00 00 ef f5 00-00 00 00 00 b8 00 92 7c  ...............|
kd> kv
ChildEBP RetAddr  Args to Child              
00f5ec18 7ca81a74 00f5ee7c 000a27bc 00f5f2c4 SHELL32!_LoadCPLModule+0x10d (FPO: [1,145,4])
00f5ee50 7ca82543 00f5ee74 000a27bc 000a27c0 SHELL32!CPL_LoadAndFindApplet+0x4a (FPO: [4,136,4])
00f5f294 7cb56065 000a25b4 000a27bc 000a27c0 SHELL32!CPL_FindCPLInfo+0x46 (FPO: [4,264,4])
00f5f2b8 7ca13714 00000082 00000000 00000104 SHELL32!CCtrlExtIconBase::_GetIconLocationW+0x7b (FPO: [5,0,0])
00f5f2d4 7ca1d306 000a25ac 00000082 00f5f570 SHELL32!CExtractIconBase::GetIconLocation+0x1f (FPO: [6,0,0])
00f5f410 7ca133b6 000dd7e0 00000082 00f5f570 SHELL32!CShellLink::GetIconLocation+0x69 (FPO: [6,68,4])
00f5f77c 7ca03c88 000dd7e0 00000000 0015aa00 SHELL32!_GetILIndexGivenPXIcon+0x9c (FPO: [5,208,4])
00f5f7a4 7ca06693 00131c60 000dd7e0 0015aa00 SHELL32!SHGetIconFromPIDL+0x90 (FPO: [5,0,4])
00f5fe20 7ca12db0 00131c64 0015aa00 00000000 SHELL32!CFSFolder::GetIconOf+0x24e (FPO: [4,405,4])
00f5fe40 7ca15e3c 00131c60 00131c64 0015aa00 SHELL32!SHGetIconFromPIDL+0x20 (FPO: [5,0,0])
00f5fe68 7ca03275 000f8090 0014d5b0 0014a910 SHELL32!CGetIconTask::RunInitRT+0x47 (FPO: [1,2,4])
00f5fe84 75f11b9a 000f8090 75f11b18 75f10000 SHELL32!CRunnableTask::Run+0x54 (FPO: [1,1,4])
00f5fee0 77f49598 00155658 000cb748 77f4957b BROWSEUI!CShellTaskScheduler_ThreadProc+0x111 (FPO: [1,17,0])
00f5fef8 7c937ac2 000cb748 7c98e440 0014cfe0 SHLWAPI!ExecuteWorkItem+0x1d (FPO: [1,0,4])
00f5ff40 7c937b03 77f4957b 000cb748 00000000 ntdll!RtlpWorkerCallout+0x70 (FPO: [Non-Fpo])
00f5ff60 7c937bc5 00000000 000cb748 0014cfe0 ntdll!RtlpExecuteWorkerRequest+0x1a (FPO: [3,0,0])
00f5ff74 7c937b9c 7c937ae9 00000000 000cb748 ntdll!RtlpApcCallout+0x11 (FPO: [4,0,0])
00f5ffb4 7c80b729 00000000 00edfce4 00edfce8 ntdll!RtlpWorkerThread+0x87 (FPO: [1,7,0])
00f5ffec 00000000 7c920250 00000000 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])

42 comments juillet 18th, 2010

Win7 and CreateRemoteThread

Depuis Vista et 7 le fonctionnement de l’API CreateRemoteThread à quelque peu changé. En fait il n’est plus possible d’aller injecter des threads dans des processus qui ne nous appartiennent pas ou pour être plus précis ceux qui sont dans d’autres sessions. Cela pose problème lorsqu’on veut jouer avec l’OS pour par exemple injecter des DLLs dans les processus d’autres utilisateurs. Après avoir regardé de plus près ce qui ne marche pas, je vous propose une petite solution simple mais efficace pour contourner cette restriction.

On se place dans le contexte d’un user loggé sous 7 appartenant au groupe ‘Administrateurs’ avec niveau d’UAC par défaut.

Avant de pouvoir faire un CreateRemoteThread sur le processus d’un autre utilisateur il faut pouvoir ouvrir un handle sur le process visé avec OpenProcess vu qu’on appartient au groupe Administrateurs on peut activer le SeDebugPrivilege et lancer notre process avec le token Administrateur qu’on a obtenu lorsqu’on s’est loggé à l’aide de la commande runas. Par défaut avec l’UAC même lorsqu’on se logge en admin on ne travaille pas directement avec le vrai token administrateur mais avec un token utilisateur. Lorsqu’une action administrative est nécessaire, l’UAC (si activé) prompt un avertissement pour lancer l’application avec le token administrateur.

La doc de CreateRemoteThread spécifie que :

‘Terminal Services isolates each terminal session by design. Therefore, CreateRemoteThread fails if the target process is in a different session than the calling process.’

Au moins ce comportement est documenté :]

Lorsqu’on essaye d’appeler CreateRemoteThread avec un handle sur le process d’un autre utilisateur la fonction nous renvoie NULL et GetLastError donne 8 (ERROR_NOT_ENOUGH_MEMORY). Déjà on se dit WTF?!. Traçons la fonction pour voir d’ou provient l’erreur. Avant de plonger plus loin on n’oublie pas que depuis 7 les fonctions de kernel32.dll ont été découpées dans plusieurs DLLs virtuelles. Même si la table des imports de kernel32.dll indique que CreateRemoteThread se situe dans API-MS-Win-Core-ProcessThreads-L1-1-0.dll on arrive au dans final kernelbase.dll et c’est pareil pour le reste des fonctions de kernel32.dll.

Au final on tombe dans kernelbase!CreateRemoteThreadEx. En traçant cette fonction l’erreur ne provient pas du syscall NtCreateThreadEx mais de CsrClientCallServer qui renvoie 0xC0000001 (STATUS_UNSUCCESSFUL). Plus loin CreateRemoteThreadEx transforme cette erreur en 0xC00000017 (STATUS_NO_MEMORY) et BaseSetLastNTError met à jour le GetLastError à 8. La bonne nouvelle c’est que ce n’est pas le syscall en lui même qui échoue mais un appel au subsystem csrss qui fait tout foirer. En fait le thread a été créé avec le CREATE_SUSPENDED, il attend donc un appel à ResumeThread ou (ZwResumeThread) pour être lancé. Il y effectivement cet appel dans CreateRemoteThreadEx mais il ne s’exécute qu’en cas de réussite de CsrClientCallServer.

Petit description du process csrss.exe sous 7 (from Windows Internals 5th) :

Session space contains information global to each session.A session consists of the processes and other system objects (such as the window station, desktops, and windows) that represent a single user’s logon session. Each session has a session-specific paged pool area used by the kernel-mode portion of the Windows subsystem (Win32k.sys) to allocate session-private GUI data structures. In addition, each session has its own copy of the Windows subsystem process (Csrss.exe) and logon process (Winlogon.exe). The session manager process (Smss.exe) is responsible for creating new sessions, which includes loading a session-private copy of Win32k.sys, creating the sessionprivate object manager namespace, and creating the session-specific instances of the Csrss and Winlogon processes.

En fait chaque utilisateur a donc son process csrss.exe. Voici le prototype de CsrClientCallServer et le code d’appel dans CreateRemoteThreadEx :

NTSTATUS
NTAPI
CsrClientCallServer(
    struct _CSR_API_MESSAGE *Request,
    struct _CSR_CAPTURE_BUFFER *CaptureBuffer OPTIONAL,
    ULONG ApiNumber,
    ULONG RequestLength
);


kernelbase.dll
7597BD24    6A 0C           PUSH 0C
7597BD26    68 01000100     PUSH 10001
7597BD2B    53              PUSH EBX
7597BD2C    8D85 F0FDFFFF   LEA EAX, DWORD PTR SS:[EBP-210]
7597BD32    50              PUSH EAX
7597BD33    FF15 00129775   CALL NEAR DWORD PTR DS:[<&ntdll.CsrClientCallServer>]      ; ntdll.CsrClientCallServer
7597BD39    8B85 10FEFFFF   MOV EAX, DWORD PTR SS:[EBP-1F0]
7597BD3F    8985 E8FDFFFF   MOV DWORD PTR SS:[EBP-218], EAX
7597BD45    399D E8FDFFFF   CMP DWORD PTR SS:[EBP-218], EBX
7597BD4B    0F8C 13D80100   JL KERNELBA.75999564

On s’intéresse au paramètre ApiNumber qui est défini par la macro :

#define CSR_MAKE_API_NUMBER( DllIndex, ApiIndex ) \
    (CSR_API_NUMBER)(((DllIndex) << 16) | (ApiIndex))

Ici on a ApiIndex qui vaut 1 et DllIndex qui vaut 1. On sort les tables faites par j00ru et on voit que CsrClientCallServer va appeler
basesrv!BaseSrvCreateThread. Après debug on s'aperçoit que c'est csrsrv!CsrLockProcessByClientId qui pose problème. Hop on sort IDA et on analyse CsrLockProcessByClientId

unsigned int __stdcall CsrLockProcessByClientId(int a1, int ret)
{
  int v2; // ebx@1
  int v3; // edi@1
  int v4; // esi@1
  int v6; // edx@6
  unsigned int v7; // [sp+18h] [bp+Ch]@1

  v3 = ret;
  *(_DWORD *)ret = 0;
  v2 = CsrRootProcess + 8;
  v7 = 0xC0000001u;
  v4 = CsrRootProcess + 8;
  RtlEnterCriticalSection(&CsrProcessStructureLock);
  while ( *(_DWORD *)(v4 - 8 ) != a1 )
  {
    v4 = *(_DWORD *)v4;
    if ( v4 == v2 )
    {
      RtlLeaveCriticalSection(&CsrProcessStructureLock);
      return v7;
    }
  }
  v7 = 0;
  CsrLockedReferenceProcess(v4 - 8);
  *(_DWORD *)v3 = v6;
  return v7;
}

Directement en voyant CsrRootProcess je me suis rappelé le CsrWalker. Pour faire simple sous XP le process csrss maintient une liste de tous les processes et threads lancés sur le système. Ainsi si on extrait cette liste on peut s'en servir comme d'un anti-rootkit (ou pour mieux cacher un rootkit :]).

Sous 7 cela change. Ce n'est plus la liste de tous les process qu'on a dans csrss mais bien ceux qui sont dans la même session (comprendre même user) que ce processus. Ainsi CsrLockProcessByClientId est appelé par BaseSrvCreateThread pour justement mettre à jour la liste des threads qui appartiennent au process cible. Or vu que le process existe dans une autre session csrsrv!CsrLockProcessByClientId échoue et renvoie 0xC0000001 (STATUS_UNSUCCESSFUL) relayé par basesrv!BaseSrvCreateThread. Voila l'origine de la valeur de retour de CsrClientCallServer.

Pendant que j'y suis il est possible de dumper les CLIENT_ID des processes de la CsrRootProcess à l'aide du kernel debugger:

1: kd> p
eax=024df7a0 ebx=00000000 ecx=000003b0 edx=00000008 esi=024df7f0 edi=003f7ea0
eip=75884458 esp=024df770 ebp=024df7a4 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
basesrv!BaseSrvCreateThread+0x3f:
001b:75884458 ff1504108875    call    dword ptr [basesrv!_imp__CsrLockProcessByClientId (75881004)] ds:0023:75881004={CSRSRV!CsrLockProcessByClientId (75895ad5)}

1: kd> !list -x "dt nt!_CLIENT_ID @$extret-8 " poi(CsrRootProcess)+8 
   +0x000 UniqueProcess    : 0x000001a8 
   +0x004 UniqueThread     : 0x000001ac 

   +0x000 UniqueProcess    : 0x000001e4 
   +0x004 UniqueThread     : 0x000001e8 

   +0x000 UniqueProcess    : 0x000000f0 
   +0x004 UniqueThread     : 0x0000006c 

   +0x000 UniqueProcess    : 0x00000550 
   +0x004 UniqueThread     : 0x0000078c 

   +0x000 UniqueProcess    : 0x00000480 
   +0x004 UniqueThread     : 0x0000043c 

   +0x000 UniqueProcess    : 0x00000258 
   +0x004 UniqueThread     : 0x000004dc 

   +0x000 UniqueProcess    : 0x0000019c 
   +0x004 UniqueThread     : 0x00000388 

   +0x000 UniqueProcess    : 0x00000b20 
   +0x004 UniqueThread     : 0x00000b24 

   +0x000 UniqueProcess    : 0x00000b28 
   +0x004 UniqueThread     : 0x00000b2c 

   +0x000 UniqueProcess    : 0x00000d1c 
   +0x004 UniqueThread     : 0x00000d20 

   +0x000 UniqueProcess    : 0x000006d0 
   +0x004 UniqueThread     : 0x00000288 

   +0x000 UniqueProcess    : 0x00000120 
   +0x004 UniqueThread     : 0x00000428 

   +0x000 UniqueProcess    : 0x00000f5c 
   +0x004 UniqueThread     : 0x000001d8 

En fait rien ne nous oblige à notifier le subsystem lorsqu'on crée un nouveau thread. Par exemple un des rôles du process csrss est de killer tous les processes avant un shutdown, c'est pour cela qu'il maintient la liste CsrRootProcess de chaque user sous 7. Donc ne pas le prévenir qu'un nouveau thread a été crée ne pose pas vraiment de problème pour la stabilité du système. Justement on ne va pas le faire :]

Plusieurs solutions existent pour pouvoir créer un thread dans le process d'une autre session. La première et la plus bourrin serait de directement passer par l'API native ZwCreateThreadEx. Pourquoi pas mais cela demande de manipuler une API et des structures non documentées.

Une autre possibilité serait de passer par ntdll!RtlCreateUserThread comme montré ici. Pareil dans ce cas on manipule toujours des structures et API non documentées.

De mon coté j'ai choisi de faire plus simple. Comme CsrClientCallServer est importé par kernelbase.dll. Il suffit de hooker son IAT pour remplacer temporairement CsrClientCallServer par notre fonction qui renvoie toujours une valeur de succès. Au moins on peut se baser sur les headers du SDK pour le faire. J'ai envie de dire que c'est une solution comme une autre et qu'elle ne propose rien de vraiment innovant mais au moins on travaille avec des APIs documentées et donc fiables. La seule chose bancale c'est qu'il ne suffit pas de modifier la valeur de retour de CsrClientCallServer mais aussi le champ ReturnValue de la structure CSR_API_MESSAGE passée en argument. Pour ça on modifie directement en fonction du disass, c'est à dire qu'on met à jour la valeur pointée par ebp-0x1F0.

Le POC permet donc d'injecter une DLL dans n'importe quel processus sous 7. Tant qu'on l'exécute depuis un shell administrateur. La DLL qui est injectée fait appel à OutputDebugString donc le comportement a changé aussi sous 7. Donc mettez à jour la valeur de HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter.

Les codes et binaires ici :
http://ivanlef0u.fr/repo/CreateRemoteThread7.rar

Enjoy!

18 comments mai 26th, 2010

Twitter

J’ai craqué …. j’ai un Twitter …
http://twitter.com/Ivanlef0u

5 comments avril 27th, 2010

KdSystemDebugControl

Je commence enfin à jouer avec Windows 7. Depuis le temps que je m’amusais avec mon vieux XP il fallait bien que cela change. Justement arrivé sur Win7 j’ai voulu manipuler NtSystemDebugControl l’API qui permet de faire tout ce qu’on veut depuis l’user-land sans se fatiguer avec un driver ou \Device\PhysicalMemory. Sauf qu’avec Win7 une bonne partie des features cools ne sont plus disponibles. Embêtant lorsqu’on veut bidouiller son OS, surtout si on est sur la version 64bits ou un driver signé est obligatoire. Justement en bootant en mode DEBUG, Windbg des Debugging Tools for Windows est capable de faire des choses sympa en Local Kernel Debugging. Justement sachant qu’il fonctionne en user-land, par où passe-t-il pour récupérer des informations sur la mémoire noyau ou sur les MSRs ? Avant il passait par NtSystemDebugControl mais sous Win7 ?

En fait rien de nouveau, Alex Ionescu avait déjà parlé de ce mécanisme à Recon 2k6 dans son talk ‘Subverting Windows 2003 SP1 Kernel Integrity Protection‘. Une version bridée de l’API NtSystemDebugControl est toujours accessible depuis l’user-land simplement avec le SeDebugPrivilege mais les fonctionnalités intéressantes sont accessibles uniquement en kernel-land à travers l’API exportée par le noyau : KdSystemDebugControl. Voici son prototype:

NTSTATUS
NTAPI
KdSystemDebugControl(
    SYSDBG_COMMAND Command,
    PVOID InputBuffer,
    ULONG InputBufferLength,
    PVOID OutputBuffer,
    ULONG OutputBufferLength,
    PULONG ReturnLength,
    KPROCESSOR_MODE PreviousMode
);

Maintenant il faut comprendre comment Windbg fait pour parler à cette API depuis l’user-land. En fait c’est assez simple, il passe par un driver ! Il s’agit de %systemroot%\system32\kldgbdrv.sys qui agit en tant que wrapper pour KdSystemDebugControl. On retrouve le chargement de ce driver à l’aide du Service Control Manager dans la fonction LocalLiveKernelTargetInfo::InitDriver(void) de dbgeng.dll

Quelques infos sur ce driver :

lkd> !devobj \device\kldbgdrv
Device object (87bca750) is for:
 kldbgdrv \Driver\kldbgdrv DriverObject 898b59e0
Current Irp 00000000 RefCount 1 Type 00000022 Flags 00000040
Dacl 8b250ca0 DevExt 87bca808 DevObjExt 87bca810 
ExtensionFlags (0x00000800)  
                             Unknown flags 0x00000800
Device queue is not busy.

lkd> !drvobj \Driver\kldbgdrv 3
Driver object (898b59e0) is for:
 \Driver\kldbgdrv
Driver Extension List: (id , addr)

Device Object list:
87bca750  

DriverEntry:   94191e00	
DriverStartIo: 00000000	
DriverUnload:  94191a10	
AddDevice:     00000000	

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

Grâce à la forme des IRP_MJ_* on devine que Windbg parle au driver avec des IOCTLs (classique). Justement voici l’ensemble des fonctions de la classe LocalLiveKernelTargetInfo de dbgeng.dll

LocalLiveKernelTargetInfo::CheckLowMemory(void)                                               
LocalLiveKernelTargetInfo::DebugControl(_SYSDBG_COMMAND,void *,ulong,void *,ulong,ulong *)                                                                                 
LocalLiveKernelTargetInfo::GetDescription(ushort *,ulong,ulong *)
LocalLiveKernelTargetInfo::GetTargetContext(ThreadInfo *,unsigned __int64,void *)
LocalLiveKernelTargetInfo::GetTargetKdVersion(_DBGKD_GET_VERSION64 *)
LocalLiveKernelTargetInfo::InitDriver(void)
LocalLiveKernelTargetInfo::Initialize(void)
LocalLiveKernelTargetInfo::LocalLiveKernelTargetInfo(void)
LocalLiveKernelTargetInfo::ReadBusData(ulong,ulong,ulong,ulong,void *,ulong,ulong *)
LocalLiveKernelTargetInfo::ReadControl(ulong,unsigned __int64,void *,ulong,ulong *)
LocalLiveKernelTargetInfo::ReadIo(ulong,ulong,ulong,unsigned __int64,void *,ulong,ulong *)
LocalLiveKernelTargetInfo::ReadMsr(ulong,unsigned __int64 *)
LocalLiveKernelTargetInfo::ReadPhysical(unsigned __int64,void *,ulong,ulong,ulong *)
LocalLiveKernelTargetInfo::ReadVirtual(ProcessInfo *,unsigned __int64,void *,ulong,ulong *)
LocalLiveKernelTargetInfo::WaitForEvent(ulong,ulong,ulong,ulong *)
LocalLiveKernelTargetInfo::WriteBusData(ulong,ulong,ulong,ulong,void *,ulong,ulong *)
LocalLiveKernelTargetInfo::WriteControl(ulong,unsigned __int64,void *,ulong,ulong *)
LocalLiveKernelTargetInfo::WriteIo(ulong,ulong,ulong,unsigned __int64,void *,ulong,ulong *)
LocalLiveKernelTargetInfo::WriteMsr(ulong,unsigned __int64)
LocalLiveKernelTargetInfo::WritePhysical(unsigned __int64,void *,ulong,ulong,ulong *)
LocalLiveKernelTargetInfo::WriteVirtual(ProcessInfo *,unsigned __int64,void *,ulong,ulong *)
LocalLiveKernelTargetInfo::`scalar deleting destructor'(uint)
LocalLiveKernelTargetInfo::~LocalLiveKernelTargetInfo(void)    

Cool ! Si Windbg à accès à tout ça on devrait nous aussi pouvoir en profiter. Reste à déterminer la forme des IOCTLs envoyés au driver. Pour cela on va reverser à la fois dbgeng.dll et kldbgdrv.sys.
Après avoir passé un peu de temps dessus on obtient les informations suivantes :

  • KdSystemDebugControl ne fonctionne que si KdDebuggerEnabled est à 1. Cette variable est mise à 1 par KdInitSystem() uniquement si on booté en DEBUG.
  • Le code IOCTL est 0x22C007 on est donc en METHOD_NEITHER. Je vous rassure tout est probé correctement, aussi bien dans le driver que par KdSystemDebugControl :p
  • Le driver vérifie que notre process a le SeDebugPrivilege à l’aide de SeSinglePrivilegeCheck
  • Le format du parametre lpInBuffer de DeviceIoControl est toujours le même, sous 32 bits on a :
    typedef struct _KLDBG
    {
    	SYSDBG_COMMAND DbgCommandClass;
    	PVOID DbgCommand;
    	DWORD DbgCommandLen;
    }KLDBG, * PKLDBG;

    nInBufferSize vaut donc 0xC. Quand aux paramètres lpOutBuffer et nOutBufferSize ils prennent les mêmes valeurs que les champs DbgCommand et DbgCommandLen;

  • On peut obtenir les définitions des SYSDBG_COMMAND dans le fichier kdtypes.h du NDK.

Il serait possible d’activer le debugger kernel en appelant KdEnableDebugger mais il faudrait charger un driver non signé pour cela. En 32 bits ca se fait bien tant qu’on ne veut pas le charger au boot mais en 64 bits c’est plus compliqué.

Maintenant il est temps de coder un POC. On va donc s’interfacer avec le driver et lui envoyer des IOCTLs. Mais d’abord on va vérifier plusieurs choses :

  • On active le SeDebugPrivilege.
  • On vérifie qu’on a booté en DEBUG avec la clé HKLM\System\CurrentControlSet\Control\SystemStartOptions.
  • On charge le driver à l’aide du Service Control Manager. Par contre c’est Windbg qui l’installe la première fois qu’on effectue un Live Kernel Debugging le POC donc le POC n’effectue qu’un simple StartService.
  • Ensuite on peut ouvrir le device \\.\kldbgdrv et lui envoyer des IOCTLs
  • On va demander 2 choses au driver, la première nous renvoyer la valeur du MSR IA32_SYSENTER_EIP (0×176) utilisé par SYSENTER et qui pointe sur nt!KiFastCallEntry puis de nous dumper 256 bytes à partir de cette mémoire noyau.

J’ai testé tout cela avec succès sur un Win7 32 bits à jour. Bien sur il faut lancer le binaire en tant qu’administrateur à cause de l’UAC. Je vous conseille de regarder le kdtypes.h du NDK afin de jouer avec les autres features de cette API. A noter qu’il ne faut pas modifier grand chose pour faire tourner la version 64 bits :p De plus en DEBUG PatchGuard n’est pas activé.

Vous trouvez le code et le binaire du POC ici :
http://ivanlef0u.fr/repo/kldbg.rar

Enjoy !

Autres refs :
Enabling the local kernel debugger on Vista RTM

Attacking the Windows Kernel

Bypassing PatchGuard on Windows x64

Subverting PatchGuard Version 2

PatchGuard Reloaded: A Brief Analysis of PatchGuard Version 3

Bypassing PatchGuard 3

5 comments mars 30th, 2010

Padocon CTF | Karma300

Depuis quelques temps je participe à différent CTFs online avec la team Nibbles. Je m’intéresse particulièrement à l’exploitation et le reverse des binaires sous Linux.  Dernièrement nous avons participé au Padocon CTF et nous sommes bien amusé. J’ai écrit sur le blog de Nibbles une writeup sur une épreuve qui demandait d’exploiter une format string dans une situation assez difficile. Mais je préfère vous laissez découvrir l’article sur le blog :]

Padocon CTF | Karma300

2 comments février 15th, 2010

Repository is moving

Un petit post juste pour vous dire que j’ai bougé le repository afin de ne plus avoir de problèmes par la suite à cause de fichiers pas très clean ;)

Voici la nouvelle adresse :
http://ivanlef0u.fr/repo/

Enjoy !

4 comments janvier 25th, 2010

LdrpHashTable

Après avoir passé un peu de temps sur du linux ARM et PPC je retourne sous Windows parce que c’est la vraie vie. Rien de très innovant cette fois, juste un trick permettant de retrouver les DLLs chargées par un processus. Bien sur je veux parler de DLLs qui tenteraient de se cacher par rapport à certaines APIs et outils. On va voir que le loader de Windows maintient des structures en internes pour se simplifier la vie et que celles ci peuvent nous servir aussi.


Une liste des DLL qu’un processus à en mémoire est maintenu par le loader au niveau du PEB. On parle aussi bien des DLLs chargées statiquement que dynamiquement. Cette structure de type PEB_LDR_DATA
se situe au niveau du champ Ldr (offset 0xC) du PEB :

//
// Loader Data stored in the PEB
//
typedef struct _PEB_LDR_DATA
{
    ULONG Length;
    BOOLEAN Initialized;
    PVOID SsHandle;
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    PVOID EntryInProgress;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

Comme vous pouvez le voir il y a 3 listes qui contiennent les mêmes informations mais pas dans le même ordre :

  • InLoadOrderModuleList : Liste des modules dans leur ordre de chargement.
  • InMemoryOrderModuleList : Liste des modules dans leur ordre croissant d’ImageBase.
  • InInitializationOrderModuleList : Liste des modules dans leur ordre d’initialisation. Cette liste est différente de la InLoadOrderModuleList parcequ’une DLL est initialisé uniquement lorsque ses imports sont complets. Dans certains cas il est nécessaire de charger et d’initialiser d’autres DLLs avant la nôtre.

Ces listes sont en fait maintenues dans des structures de type LDR_DATA_TABLE_ENTRY :

//
// Loader Data Table Entry
//
typedef struct _LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    PVOID DllBase;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    union
    {
        LIST_ENTRY HashLinks;
        PVOID SectionPointer;
    };
    ULONG CheckSum;
    union
    {
        ULONG TimeDateStamp;
        PVOID LoadedImports;
    };
    PVOID EntryPointActivationContext;
    PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

On retrouve des informations comme le nom de la DLL, son ImageBase et sa taille (SizeOfImage). Il existe aussi un champ assez peu connu dans cette structure : HashLinks. En fait lorsque le loader doit charger une DLL dans un processus il n’utilise pas les listes qu’on a vues plus haut pour des raisons de performances, en effet parcourir une liste chainée se fait en temps linéaire et peut être long s’il y a beaucoup d’éléments. Pour aller plus vite le loader maintient une hashtable afin de diminuer le temps de recherche. Le champ HashLinks fait justement partie de cette hashtable.

En fait on parle de la variable globale LdrpHashTable, c’est un tableau de 32 LIST_ENTRY. Les DLLs y sont reparties en fonction de leur nom, la fonction de hashage prend la première lettre du nom de la DLL en uppercase et le soustrait à L’A’, le tout est bien sur passé dans un modulo 32. Au final tout cela se résume par ces définitions :

#define LDRP_HASH_TABLE_SIZE 32
#define LDRP_HASH_MASK       (LDRP_HASH_TABLE_SIZE-1)
#define LDRP_COMPUTE_HASH_INDEX(wch) ( (RtlUpcaseUnicodeChar((wch)) - (WCHAR)'A') & LDRP_HASH_MASK )
LIST_ENTRY LdrpHashTable[LDRP_HASH_TABLE_SIZE];

LdrpHashTable est accédée par :

  • LdrpInitializeProcess : Initialise la table au début.
  • LdrpInsertMemoryTableEntry : Ajoute une entrée à la table.
  • LdrpCheckForLoadedDll : Vérifie si la DLL est déjà chargée.

Maintenant imaginons une DLL malveillante qui s’enlèverait des listes InLoadOrderModuleList, InMemoryOrderModuleList et InInitializationOrderModuleList. La question se pose si il est possible de la retrouver sans scanner la mémoire en cherchant des patterns du format PE dans un try{}except(){} tout moche.

Justement comme la LdrpHashTable est souvent oublié par les attaquants on peut l’utiliser pour énumérer les DLL d’un processus. J’ai codé un petit tool qui parcourt la mémoire d’un processus à l’aide de ReadProcessMemory() pour dumper cette hashtable.

C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe
Process DLLs dumper using LdrpHashTable
By Ivanlef0u
BE M4D !

Usage is : GetModuleListByHashTable.exe  (0 mean this process)

C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe  0
Process DLLs dumper using LdrpHashTable
By Ivanlef0u
BE M4D !

LdrpHashTable is at 7C98E260
Dumping Dlls
ListHead[0] : 0x7C98E260
ListHead[1] : 0x7C98E268
ListHead[2] : 0x7C98E270
ListHead[3] : 0x7C98E278
ListHead[4] : 0x7C98E280
ListHead[5] : 0x7C98E288
ListHead[6] : 0x7C98E290
        ListEntry : 0x00241EFC
        FullDllName : C:\ProgHack\c\LdrpHashTable\GetModuleListByHashTable.exe

ListHead[7] : 0x7C98E298
ListHead[8] : 0x7C98E2A0
ListHead[9] : 0x7C98E2A8
ListHead[10] : 0x7C98E2B0
        ListEntry : 0x00241FFC
        FullDllName : C:\WINDOWS\system32\kernel32.dll

ListHead[11] : 0x7C98E2B8
ListHead[12] : 0x7C98E2C0
        ListEntry : 0x0024209C
        FullDllName : C:\WINDOWS\system32\MSVCRT.dll

ListHead[13] : 0x7C98E2C8
        ListEntry : 0x00241F54
        FullDllName : C:\WINDOWS\system32\ntdll.dll

ListHead[14] : 0x7C98E2D0
ListHead[15] : 0x7C98E2D8
ListHead[16] : 0x7C98E2E0
ListHead[17] : 0x7C98E2E8
ListHead[18] : 0x7C98E2F0
ListHead[19] : 0x7C98E2F8
ListHead[20] : 0x7C98E300
ListHead[21] : 0x7C98E308
ListHead[22] : 0x7C98E310
ListHead[23] : 0x7C98E318
ListHead[24] : 0x7C98E320
ListHead[25] : 0x7C98E328
ListHead[26] : 0x7C98E330
ListHead[27] : 0x7C98E338
ListHead[28] : 0x7C98E340
ListHead[29] : 0x7C98E348
ListHead[30] : 0x7C98E350
ListHead[31] : 0x7C98E358

C:\ProgHack\c\LdrpHashTable>

Maintenant raison de plus de développer à coté un code qui enlève une DLL de ces 4 listes :]

C:\ProgHack\c\LdrpHashTable>HideDll.exe
Hide ntdll from InMemoryOrderModuleList, InLoadOrderModuleList, InInitialization
OrderModuleList and LdrpHashTable
By Ivanlef0u
BE M4D !
# Ici le process est en attente

On effectue un dump de ce process avec GetModuleListByHashTable :

C:\ProgHack\c\LdrpHashTable>tasklist | find "HideDll.exe"
HideDll.exe                 3436                         0       708 Ko

C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe 3436
Process DLLs dumper using LdrpHashTable
By Ivanlef0u
BE M4D !

LdrpHashTable is at 7C98E260
Dumping Dlls
ListHead[0] : 0x7C98E260
ListHead[1] : 0x7C98E268
ListHead[2] : 0x7C98E270
ListHead[3] : 0x7C98E278
ListHead[4] : 0x7C98E280
ListHead[5] : 0x7C98E288
ListHead[6] : 0x7C98E290
ListHead[7] : 0x7C98E298
        ListEntry : 0x00241EFC
        FullDllName : C:\ProgHack\c\LdrpHashTable\HideDll.exe

ListHead[8] : 0x7C98E2A0
ListHead[9] : 0x7C98E2A8
ListHead[10] : 0x7C98E2B0
        ListEntry : 0x00241FFC
        FullDllName : C:\WINDOWS\system32\kernel32.dll

ListHead[11] : 0x7C98E2B8
ListHead[12] : 0x7C98E2C0
        ListEntry : 0x0024209C
        FullDllName : C:\WINDOWS\system32\MSVCRT.dll

ListHead[13] : 0x7C98E2C8
ListHead[14] : 0x7C98E2D0
ListHead[15] : 0x7C98E2D8
ListHead[16] : 0x7C98E2E0
ListHead[17] : 0x7C98E2E8
ListHead[18] : 0x7C98E2F0
ListHead[19] : 0x7C98E2F8
ListHead[20] : 0x7C98E300
ListHead[21] : 0x7C98E308
ListHead[22] : 0x7C98E310
ListHead[23] : 0x7C98E318
ListHead[24] : 0x7C98E320
ListHead[25] : 0x7C98E328
ListHead[26] : 0x7C98E330
ListHead[27] : 0x7C98E338
ListHead[28] : 0x7C98E340
ListHead[29] : 0x7C98E348
ListHead[30] : 0x7C98E350
ListHead[31] : 0x7C98E358

C:\ProgHack\c\LdrpHashTable>

Hop, on voit bien que ntdll.dll n’apparait plus. Pour information des outils comme Process Explorer et LordPE ne ‘voient’ plus la DLL lorsqu’elle est unlink des 3 premières doubles listes chainées. De ce fait beaucoup de malwares le font pour être tranquille. Avec la LdrpHashTable on est capable de voir ces vilaines DLLs.

Sauf que les choses ne sont pas si simples. Il existe des outils comme VMMap qui sont capable de détecter nos DLLs autrement. Comment ? En fait lorsqu’une DLL est chargé en mémoire, elle est mappée, le système maintient donc en interne un objet de type Section le fichier qui est mappé. A l’aide de VirtualQueryEx() on peut connaître le type de mémoire et avec GetMappedFileName() le nom du fichier mappé. Ces APIs utilisent toutes deux le syscall ZwQueryVirtualMemory. Comme par hasard VMMap aussi :]
Je me suis donc codé un petit tool qui fonctionne pareil.

# On reprend le process vu plus haut qui cache ses DLLs
C:\ProgHack\c\LdrpHashTable>GetModuleListByVirtualQuery.exe 3436
Process DLLs dumper using VirtualQueryEx+GetMappedFileName
By Ivanlef0u
BE M4D !

[*] Dumping Dlls
MappedFile : \Device\HarddiskVolume1\ProgHack\c\LdrpHashTable\HideDll.exe (Addre
ss : 0x400000 - SizeOfImage : 0x960)
MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\msvcrt.dll (Address : 0x77be0000 - SizeOfImage : 0x58000)
MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\kernel32.dll (Address : 0x7c800000 - SizeOfImage : 0x106000)
MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\ntdll.dll (Address : 0x7c910000 - SizeOfImage : 0xb9000)

C:\ProgHack\c\LdrpHashTable>

Cool on retrouve bien notre ntdll.dll ! D’après ce que je sais, le seul moyen pour planquer notre module c’est de hooker le syscall ZwQueryVirtualMemory en kernel-land …

Au final vous avez compris qu’il existe plusieurs manières pour retrouver les DLLs d’un processus. On a vu que pour être sur de nos infos il faut utiliser ZwQueryVirtualMemory mais dans le cas ou cette API est hookée et que l’attaquant oublie la LdrpHashTable on peut encore s’en sortir. Bien sur ce n’est pas la technique ultime mais il est toujours bon de l’avoir sous le bras, c’est n’est pas difficile à comprendre et à implémenter en plus.

Peut être qu’un jour le monstrueux DLL Hell arrêtera de nous poser des problèmes.

Vous trouverez le code est les binaires ici :
http://ivanlef0u.fr/repo/LdrpHashTable.rar

Pour finir une petite selection de son parce que vous le valez bien

An Inch Above Sand du dernier album ‘What We All Come To Need’ de Pelican.

An Inch Above Sand

Postponed. End de l’EP ‘Samsara’ de Deviniance.

Postponed. End

Hordes To War de l’album ‘All Shall Fall’ de Immortal.

Hordes To War

Enfin Bleed de l’album ‘Obzen’ de Meshuggah parce que ca rigole zéro :

Bleed

12 comments décembre 28th, 2009

Previous Posts


Calendar

juillet 2014
L Ma Me J V S D
« fév    
 123456
78910111213
14151617181920
21222324252627
28293031  

Posts by Month

Posts by Category