Posts filed under 'C:'
Il y a quelques jours, la team MSRC de Microsoft a sortit EMET (Enhanced Mitigation Evaluation Toolkit). Un outil aidant à la gestion des différentes protections fournies par Windows, comme le DEP. Je me suis dit qu’il serait intéressant d’y jeter un oeil pour voir si toutes les protections mises en place sont solides.
EMET est constitué de plusieurs binaires :
- EMET_conf.exe qui permet de configurer les binaires qu’on veut protéger.
- EMET_launcher.exe et EMET_launcher64.exe qui vont initialiser le processus à protéger
- EMET.dll la DLL qui est injecté dans le processus pour mettre en place la protection.
En fait EMET se base sur l’Image File Execution Options. Cette feature de Windows permet d’exécuter un binaire à la place d’un autre, cela est indiqué par la clé registre ‘HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options’. On spécifie une sous-clé contenant le nom du processus et les valeurs qui vont déterminer le comportement à avoir quand ce processus sera lancé. EMET utilise la valeur « Debugger » pour faire appel au launcher qui va aller lire « HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EMET » pour parser les options à appliquer au niveau de la protection.
Une des features qui m’a le plus intéressé est l’anti-allocation en 0. Je cite :
NULL page allocation
This blocks attackers from being able to take advantage of NULL dereferences in user mode. It functions by allocating the first page of memory before the program starts. Right now the exploitation techniques for these types of vulnerabilities are only theoretical. However, this mitigation will protect you even if that changes. Please note this protection does not impact kernel mode NULL dereferences as the current version of EMET only supports user mode mitigations.
J’ai disass EMET.dll et je suis tombé sur le code suivant :
int __fastcall sub_110E0(int a1)
{
int v1; // eax@3
HMODULE v2; // eax@6
FARPROC v3; // eax@7
int result; // eax@9
int v5; // [sp+4h] [bp-4h]@1
v5 = a1;
if ( sub_11070(L"heap_allocations") == 1 )
sub_11190();
v1 = sub_11070(L"null_allocation");
if ( v1 == 1 )
VirtualAlloc((LPVOID)v1, 0x400u, 0x3000u, v1);
if ( sub_11070(L"enable_dep") == 1 )
{
v2 = GetModuleHandleW(L"Kernel32.dll");
if ( v2 )
{
v3 = GetProcAddress(v2, "SetProcessDEPPolicy");
if ( v3 )
((int (__stdcall *)(signed int))v3)(3);
}
}
result = sub_11070(L"sehop");
if ( result == 1 )
{
GetModuleHandleExW(5, sub_110E0, &v5);
result = sub_11610();
dword_1B6B8 = 1;
}
return result;
}
On voit très bien que la protection ‘anti-allocation-en-0′ est basé sur cet appel à VirtualAlloc() :
VirtualAlloc(1, 1024, MEM_COMMIT|MEM_RESERVE, PAGE_NOACCESS);
Sachant cela je me code un petit binaire qui effectue exactement la même opération et là c’est le drame, VirtuallAlloc renvoie 0, indiquant une erreur de type ‘ERROR_INVALID_PARAMETER (00000057)’.
Je trace alors un peu le code de VirtuaAlloc pour tomber sur ceci :
LPVOID __stdcall VirtualAlloc(LPVOID lpAddress, DWORD dwSize, DWORD flAllocationType, DWORD flProtect)
{
return VirtualAllocEx((HANDLE)0xFFFFFFFF, lpAddress, dwSize, flAllocationType, flProtect);
}
LPVOID __stdcall VirtualAllocEx(HANDLE hProcess, LPVOID lpAddress, DWORD dwSize, DWORD flAllocationType, DWORD flProtect)
{
int v5; // eax@2
int v7; // [sp+Ch] [bp-20h]@2
CPPEH_RECORD ms_exc; // [sp+14h] [bp-18h]@2
if ( lpAddress && (unsigned int)lpAddress < 0x1000) )
{
SetLastError(0x57u);
}
else
{
v5 = NtAllocateVirtualMemory(hProcess, &lpAddress, 0, &dwSize, flAllocationType, flProtect);
v7 = v5;
ms_exc.disabled = -1;
if ( v5 >= 0 )
return lpAddress;
BaseSetLastNTError(v5);
}
return 0;
}
En fait si VirtualAlloc me renvoie une erreur c’est parce que le paramètre ‘lpAddress’ est inférieur à 0×1000). On regarde la doc :
lpAddress [in, optional]
The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, the address is rounded down to the next page boundary. To determine the size of a page and the allocation granularity on the host computer, use the GetSystemInfo function. If this parameter is NULL, the system determines where to allocate the region.
On nous dit que si ce paramètre est à 0 alors le système choisit l’adresse de l’allocation, sinon l’utilisateur peut spécifier une adresse.
Apparemment une adresse inférieure à 0×1000 n’est pas apprécié par VirtualAlloc c’est pourquoi dans EMET.dll l’appel échoue. On peut donc tout naturellement allouer de la mémoire en 0 dans un processus protéger avec EMET grâce ce code :
//
// Allocate NULL address in userland memory space
//
BOOL AllocateNullPage()
{
NTSTATUS Status;
DWORD Addr=1;
DWORD Len=0x1000;
Status=ZwAllocateVirtualMemory(GetCurrentProcess(),
Addr,
0,
&Len,
MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE);
if(!NT_SUCCESS(Status))
{
printf("[-] Error with ZwAllocateVirtualMemory : 0x%x\n", Status);
return FALSE;
}
return TRUE;
}
On utilise directement ZwAllocateVirtualMemory qui nous évite de passer par VirtualAlloc.
Donc, quand je lis dans le readme.rtf de EMET :
EMET has been tested with the following OS:
32 bit: Windows XP, Server 2003, Vista, Server 2008 and Windows 7
64 bit: Vista and Windows 7 and Windows 2008 R2
Je me dis : failed ! (pour info je suis sous WinXP SP3).
Pour corriger le tir il suffirait de faire appel directement à ZwAllocateVirtualMemory pour mettre la page en 0 en PAGE_NOACCESS.
Enfin je trouve qu’EMET ressemble pas mal à WehnTrust.
Sinon hdmoore s’est fait plaisir en lâchant les adresses d’opcodes intéressantes pour l’exploitation au sein de la DLL
novembre 1st, 2009
Ce post décrit une feature du noyau NT très peu documentée, la gestion des I/Os d’un process tournant sous V8086. Je vous conseille d’abord de (re)lire ce post qui discute de la gestion des exceptions et interruptions d’un processus 16 bits, faites attention notamment aux différents endroits ou le noyau prend la main pour gérer ces évènements, notamment pour une émulation par l’API win32 ou bien pour une reinjection en virtual mode.
Peut-être que certains l’on remarqué en lisant les .h du WDK, notamment le fichier %DDK_INC_PATH%\ntddk.h, dans l’enum PROCESSINFOCLASS on peut voir l’identifiant ProcessIoPortHandlers avec un commentaire à coté disant qu’il est accessible uniquement en KernelMode. Ce qui est marrant c’est que même googlant dessus on ne trouve pas grand chose d’intéressant (j’ai p-e mal cherché btw !). Ce post à pour but de montrer quel est son rôle et comment l’utiliser, nous arrivons ainsi à mettre en place un moyen de communication assez original entre notre process et le noyau qui change des IOCTLs et autres appels systèmes tellement classiques :]
Il faut d’abord savoir que ce ProcessInformationClass est uniquement accessible par l’API native ZwSetInformationProcess, on ne peut donc pas demande à un process existant des informations sur cette feature avec ZwQueryInformationProcess. De plus un contrôle est effectué dans la fonction non-exportée du noyau PspSetProcessIoHandler pour vérifier que l’appelant est bien en KernelMode, donc le seul moyen d’utiliser cette feature est d’avoir un driver à notre disposition.
D’ailleurs le fait d’utiliser un driver n’est pas pratique puisque ZwSetInformationProcess demande un handle sur le process cible et que les handles en kernel-mode comme tout le mode le sait, c’est mal vu qu’il sont spécifique à un processus alors que noyau lui ne l’est pas. Bref rien de méchant mais au lieu d’un handle une référence sur la structure EPROCESS aurait suffit. D’un coté le handle permet d’avoir une vérification au niveau des droits de l’objet mais on s’en fou un peu vu qu’on est déjà dans le noyau et qu’on justement le droit de tout faire sur tous les objets ! Oui cela était mon whine inutile du jour..
D’abord un point sur les I/Os supporté par l’architecture x86 :
- Les I/Os provenant des instructions IN et OUT qui peuvent lire ou écrire des bytes, word et doubleword. AL/AX/EAX est le registre source ou destination de l’I/O.
- Les I/Os effectués avec les instructions INS/INSB/INSW/INSD et OUTS/OUTSB/OUTSW/OUTSD qui sont des I/Os utilisant les zone mémoires ES:(E)DI ou ES:(E)SI comme destination/source, DX contient le numéro de port. La particularité de ces instructions est quelles peuvent être précédées du prefixe REP, ECX indiquant dans ce cas le nombre de répétitions. Attention au Direction Flag (DF) qui indique le sens de la copie/lecture :p
Comme je l’ai dans mon autre post sur le VDM, les I/Os en V8086 provoquent forcément une exception de type General Protection Fault (#GP).
Rentrons dans le vif du sujet, que permet donc cette feature ? Elle fournit justement la possibilité d’ajouter des handlers qui vont trappés les I/Os de votre process 16 bits. Vu comme cela ce n’est très pas utile, surtout que de nos jours plus personne ne dev en 16 bits mais à l’époque (c’est à dire en l’an 2337 avant la guerre des barbus) cela devait servir à quelque chose. Arrivé ici vous devriez comprendre que ce post ne sert pas à grand chose et qu’il ne vous apportera strictement rien. Je dis ça, mais c’est certainement mieux que les posts de newsoft sur l’échec de la sécu ! Merde je vais me faire blacklister de la blogosphère en disant ça
Maintenant passons à l’implémentation, d’abord on a besoin de connaitre le forme du paramètre ProcessInformation de ZwSetInformationProcess. Il s’agit d’une structure PROCESS_IO_PORT_HANDLER_INFORMATION définie par :
//
// SetProcessInformation Structure for ProcessSetIoHandlers info class
//
typedef struct _PROCESS_IO_PORT_HANDLER_INFORMATION {
BOOLEAN Install; // true if handlers to be installed
ULONG NumEntries;
ULONG Context;
PEMULATOR_ACCESS_ENTRY EmulatorAccessEntries;
} PROCESS_IO_PORT_HANDLER_INFORMATION, *PPROCESS_IO_PORT_HANDLER_INFORMATION;
On voit qu’il s’agit simplement d’un wrapper pour un tableau de structures plus intéressantes, les EMULATOR_ACCESS_ENTRY :
//
// Structures used by the kernel drivers to describe which ports must be
// hooked out directly from the V86 emulator to the driver.
//
typedef enum _EMULATOR_PORT_ACCESS_TYPE {
Uchar,
Ushort,
Ulong
} EMULATOR_PORT_ACCESS_TYPE, *PEMULATOR_PORT_ACCESS_TYPE;
//
// Access Modes
//
#define EMULATOR_READ_ACCESS 0x01
#define EMULATOR_WRITE_ACCESS 0x02
typedef struct _EMULATOR_ACCESS_ENTRY {
ULONG BasePort;
ULONG NumConsecutivePorts;
EMULATOR_PORT_ACCESS_TYPE AccessType;
UCHAR AccessMode;
UCHAR StringSupport;
PVOID Routine;
} EMULATOR_ACCESS_ENTRY, *PEMULATOR_ACCESS_ENTRY;
Quelques détails sur les membres de EMULATOR_ACCESS_ENTRY :
- BasePort TODO
- NumConsecutivePorts est le nombre de ports à partir de BasePort qui auront le même handler.
- AccessType spécifie si notre handler est pour gérer les I/Os sur des bytes, words ou doublewords.
- AccessMode permet de définir si on veut trapper les IN* ou OUT*.
- StringSupport est un booléan indiquant si notre handler gère les IN*/OUT* ou INS*/OUTS*.
- Routine est un pointeur sur notre handler.
Concernant le format de Routine, cela depend de 2 choses :
- I/O String ou pas.
- Taille de l’I/O (1, 2 ou 4).
Au final on a donc 6 prototypes de handlers possibles :
//
// These are the various function prototypes of the routines that are
// provided by the kernel driver to hook out access to io ports.
//
typedef
NTSTATUS
(*PDRIVER_IO_PORT_UCHAR ) (
IN ULONG_PTR Context,
IN ULONG Port,
IN UCHAR AccessMode,
IN OUT PUCHAR Data
);
typedef
NTSTATUS
(*PDRIVER_IO_PORT_UCHAR_STRING ) (
IN ULONG_PTR Context,
IN ULONG Port,
IN UCHAR AccessMode,
IN OUT PUCHAR Data,
IN ULONG DataLength
);
typedef
NTSTATUS
(*PDRIVER_IO_PORT_USHORT ) (
IN ULONG_PTR Context,
IN ULONG Port,
IN UCHAR AccessMode,
IN OUT PUSHORT Data
);
typedef
NTSTATUS
(*PDRIVER_IO_PORT_USHORT_STRING ) (
IN ULONG_PTR Context,
IN ULONG Port,
IN UCHAR AccessMode,
IN OUT PUSHORT Data,
IN ULONG DataLength // number of words
);
typedef
NTSTATUS
(*PDRIVER_IO_PORT_ULONG ) (
IN ULONG_PTR Context,
IN ULONG Port,
IN UCHAR AccessMode,
IN OUT PULONG Data
);
typedef
NTSTATUS
(*PDRIVER_IO_PORT_ULONG_STRING ) (
IN ULONG_PTR Context,
IN ULONG Port,
IN UCHAR AccessMode,
IN OUT PULONG Data,
IN ULONG DataLength // number of dwords
);
Dans le cas d’un IN* exécuté par le process 16 bits il est possible de modifier la valeur lue par l’instruction. Je ne sais pas à quoi ca peut servir mais c’est marrant
De son coté le noyau maintient des handlers pour les cas suivants :
- Ecriture ou lecture d’un port avec une I/O ‘classique’.
- Ecriture ou lecture d’un port à l’aide d’une String I/O.
- I/O sur un byte ou un word ou un doubleword.
- Les I/O sur des doubleword sont découpés en double I/O sur des word s’il n’y a pas de handler existant.
- Les I/O sur les word sont découpés en double I/O sur les bytes s’il n’y a pas de handler existant.
Ce qui nous amène à ces structures, notez que dans le tableau IoFunctions l’indice 0 correspond aux fonctions handler des IN* alors que l’indice 1 est celui des OUT*:
//
// Vdm Objects and Io handling structure
//
typedef struct _VDM_IO_HANDLER_FUNCTIONS {
PDRIVER_IO_PORT_ULONG UlongIo;
PDRIVER_IO_PORT_ULONG_STRING UlongStringIo;
PDRIVER_IO_PORT_USHORT UshortIo[2];
PDRIVER_IO_PORT_USHORT_STRING UshortStringIo[2];
PDRIVER_IO_PORT_UCHAR UcharIo[4];
PDRIVER_IO_PORT_UCHAR_STRING UcharStringIo[4];
} VDM_IO_HANDLER_FUNCTIONS, *PVDM_IO_HANDLER_FUNCTIONS;
typedef struct _VDM_IO_HANDLER {
struct _VDM_IO_HANDLER *Next;
ULONG PortNumber;
VDM_IO_HANDLER_FUNCTIONS IoFunctions[2];
} VDM_IO_HANDLER, *PVDM_IO_HANDLER;
Pour retrouver ces structures le noyau passe par champ VdmObjects de l’EPROCESS (offset 0×158 sous XP). Ce champ pointe sur une structure VDM_PROCESS_OBJECTS de la forme :
typedef struct _VDM_PROCESS_OBJECTS {
PVDM_IO_LISTHEAD VdmIoListHead;
KAPC QueuedIntApc;
KAPC QueuedIntUserApc;
FAST_MUTEX DelayIntFastMutex;
KSPIN_LOCK DelayIntSpinLock;
LIST_ENTRY DelayIntListHead;
PVDMICAUSERDATA pIcaUserData;
PETHREAD MainThread;
PVDM_TIB VdmTib;
PUCHAR PrinterState;
PUCHAR PrinterControl;
PUCHAR PrinterStatus;
PUCHAR PrinterHostState;
USHORT AdlibStatus;
USHORT AdlibIndexRegister;
USHORT AdlibPhysPortStart;
USHORT AdlibPhysPortEnd;
USHORT AdlibVirtPortStart;
USHORT AdlibVirtPortEnd;
USHORT AdlibAction;
USHORT VdmControl;
ULONG PMCliTimeStamp;
} VDM_PROCESS_OBJECTS, *PVDM_PROCESS_OBJECTS;
Je ne vais pas détaillé tous les champs de cette structure. Celui qui nous intéresse est VdmIoListHead de type PVDM_IO_LISTHEAD dont voici la définitition :
typedef struct _VDM_IO_LISTHEAD {
PVDM_IO_HANDLER VdmIoHandlerList;
ERESOURCE VdmIoResource;
ULONG Context;
} VDM_IO_LISTHEAD, *PVDM_IO_LISTHEAD;
On retombe sur nos pattes vu que VmdIoHandlerList pointe sur une liste de VDM_IO_HANDLER.
Toujours dans le noyau mais cette fois au niveau des fonctions qui sont mises en oeuvres, sachant qu’une I/O en V8086 lève une exception de type #GP on passe donc par le handler KiTrap0D. Ce handler vérifie que c’est un process VDM à l’aide du champ VdmObjects de l’EPROCESS, si ce pointeur est différent de NULL alors il appel la routine VdmDispatchOpcodeV86_try. Ensuite on traverse Ki386DispatchOpcode qui utilise les tables OpcodeIndex et OpcodeDispatch pour appeler les handlers d’opcodes correspondant à l’instruction fautive. Seul les handlers suivants appelent Ki386VdmDispatchIo :
OpcodeINBimm
OpcodeINWimm
OpcodeOUTBimm
OpcodeOUTWimm
OpcodeINB
OpcodeINW
OpcodeOUTB
OpcodeOUTW
OpcodeINBimmV86
OpcodeINWimmV86
OpcodeOUTBimmV86
OpcodeOUTWimmV86
OpcodeINBV86
OpcodeINWV86
OpcodeOUTBV86
OpcodeOUTWV86
Ki386VdmDispatchIo utilise Ps386GetVdmIoHandler pour vérifier la présence d’un handler I/O, s’il en existe un alors on passe dans VdmDispatchIoToHandler ou VdmDispatchUnalignedIoToHandler. Cette dernière fonction se charge d’appeler notre handler d’I/O avec les bons paramètres. Note à moi même, regarder à quoi correspondent les fonctions Ki386AdlibEmulation et Ki386AdlibDirectIo dans Ki386VdmDispatchIo.
Du coté de ZwSetInformationProcess on fait appel à PspSetProcessIoHandlers, Psp386InstallIoHandler puis Psp386InsertVdmIoHandlerBlock.
Pour finir, j’ai codé un POC. Il est constitué de 3 élèments :
- Un driver pouvant recevoir des IOCTLs, il sert principalement à appeler ZwSetInformationProcess sur ProcessIoPortHandlers et définit la routine de gestion des I/Os.
- Un process user-land, qui communique avec le driver pour lui fournir le PID du process sur lequel il doit ouvrir le handle passé à ZwSetInformationProcess.
- Un programme 16 bits compilé avec RadAsm qui utilise l’instruction ‘repo outsb’ pour écrire une string sur un port réservé 0×74.
Pour l’utilisation vous chargez le driver, vous lancez le binaire 16 bits qui se met en attente puis vous spécifiez le PID au programme user-land (cherchez le process ntvdm.exe) afin que le driver mette en place le handler d’I/O.
Bien sur il s’agit juste d’un POC, l’idée est de faire transiter des infos plus intéressantes vers notre driver avec cette méthode. Cependant la méthode reste lourde, vu qu’il ne suffit pas d’exécuter du code 16 bits. Pour info vous pouvez le faire de 2 manières :
- Mettre le prefixe de taille d’opérande (0×66) qui inverse le sens du bit D du segment selector. De fait qu’on soit avec un segment de code user-land/kernel-land avec le bit D (Default) à 1 les opérandes et les adresses sont sur 32 bits.
- Mettre en place une LDT avec un segment de code ayant le bit D à 1 dans son segment descriptor.
Mais de toute façon exécuter du code 16 bits ne suffit pas car il faut un process wraper par ntvdm.exe pour que cette feature soit applicable. On pourrait très bien imaginer un programme 16 bits servant de proxy avec un autre programme user-land, mais les IPC sont plutôt rares entre les 2 mondes
Donc pour le moment le programme 16 bits doit tout faire de lui même, pas très pratique vous me direz. Surement qu’une vraie solution existe afin d’avoir quelque chose de souple (tant qu’on touche pas le fond à injecter du code 16 bits dans la mémoire basse de ntvdm.exe tout va bien :p) mais pour le moment je ne vois pas. D’un coté je suis pratiquement sûr que personne n’osera jamais utiliser ce code :]
Finalement rien d’impressionnant mais je voulais attirer votre attention sur une feature quasiment inconnue du noyau. J’ai pu remarquer de nombreuses autres fonctionnalités perdue dans le kernel qui datent des premières versions et qui restent là pour des raisons de compatibilités. C’est beau d’arriver en 2010 et de voir que même votre Windows 7 est capable d’exécuter du code 16 bits, surtout que le code de ntvdm.exe et de ses libs est lui aussi très vieux. Cela me rappel un peu le problème de MS09-048 qui ne se verra pas patcher à cause de problèmes architecturaux trop lourd à corriger. On se rend compte de la lourdeur des systèmes actuels qui font tourner des codes développés il y plusieurs années par des personnes qui pour la plupart ont prit leur retraite à l’heure actuelle. Ainsi toute aptitude de maintenance est perdue et je pense que cela va aller en s’empirant dans les années à venir car les systèmes augmentent en complexité et reposent de plus en plus sur la couche inférieure. J’ai peur du futur !
Vous trouverez les codes et binaires de ce POC ici :
http://ivanlef0u.fr/repo/ProcessIoPortHandlers.rar
septembre 26th, 2009
Dans la série ‘j’touche le fond et je creuse encore ! ‘ je vous propose une méthode sympa pour debugger vos codes critiques. Récemment j’ai été amené à dev du code noyau tournant dans un contexte critique par rapport à l’OS. Comprendre par là que je tournait à des IRQL >= DIRQL, comme si j’étais dans une Interrupt Service Routine (ISR) déclénché par une interruption extérieur. Dans ces cas lorsqu’on veut débugger ces routines on ne peut pas forcément utiliser des fonctions comme DbgPrint ou KdPrint, même si la doc dit que c’est possible on perd beaucoup en performances et si comme moi vous debuggez votre machine depuis la liaison série l’OS se retrouve vite à genoux.
Alors après on peut très bien me dire de mettre des message de debug en queue et de les envoyer dans un worker thread qui balancera des DbgPrint à un IRQL de PASSIVE_LEVEL, ce n’est pas difficile à implémenter c’est juste que je n’avais pas envie de faire. de la même manière, mettre des breakpoints dans le code ou bien à l’aide du kernel-debugger n’est pas forcément très pratique non plus vu que l’OS notifiera à chaque fois le debugger dès qu’il en verra un.
De plus j’avais juste besoin de savoir si un codepath particulier était prit donc un simple signal me disant « oui ou non » me suffisait. Je me suis dit entre 2 trolls sur IRC concernant l’OS des barbus (qui ne marche pas très bien apparemment) qu’utiliser les LEDs du clavier comme signal pourrait être une solution, certes bizarre, mais efficace.
L’idée est simple, il s’agit juste de demander au code de faire clignoter une LED à un moment précis. Dans un premier temps je me suis donc documenté rapidement sur le contrôleur clavier i8042 pour savoir que 2 I/Os port sont utilisés :
- Le Read-input buffer sur 1 byte. Port 0×60
- Le Write-ouput buffer, 1 byte. Port 0×60
- Le Status-register, 1 byte uniquement accessible en lecture. Port 0×64
- Le Control-register, 1 byte en lecture/écriture.
Pour le controleur i8042 le Read-input et Write-ouput sont en fait confondus. Le Control-register quand à lui est uniquement accessible en envoyant une commande sur le Status-register qui sert alors de byte de commande lorsqu’on l’écrit. Attention il s’agit alors d’une commande pour le contrôleur clavier et non le clavier lui même !
Justement, la commande qui m’intéresse est celle qui permet de définir les LEDS clavier. Après quelques recherches sur le net je tombe sur The PS/2 Keyboard Interface et The AT Keyboard Controler. La commande qui m’intéresse est donc ’0xED (Set/Reset LEDs)’, comme il s’agit du clavier nous devons écrire cette commande dans le Write-ouput buffer
Si 1, SROLL_LOCK est allumée. Éteinte sinon.
Si 1, NUM_LOCK est allumée. Éteinte sinon.
Si 1, CAPS_LOCK est allumée. Éteinte sinon.
Maintenant il ne reste plus qu’a envoyer la commande puis les données. Ce qui se traduit en asm par :
#define KBD_DATA 0x60 // I/O port for keyboard data
#define KBD_STATUS 0x64 // I/O port for keyboard status (read)
#define KBD_CMD_SET_LEDS 0xED // Set keyboard leds
#define SCROLL_LOCK 1
#define NUM_LOCK 2
#define CAPS_LOCK 4
xor eax, eax
mov al, KBD_CMD_SET_LEDS
out KBD_DATA , al
mov al, Leds
out KBD_DATA, al
Oulà, pas trop vite, on oublie que pour écrire dans le Write-ouput buffer il faut d’abord attendre que le controleur nous dise que nous pouvons le faire. Pour cela il faut que le bit 1 du Status-register soit à 0, de plus pour une lecture il faut que le bit 0 soit à 1. On doit donc poller le Status-register afin de savoir quand on peut lire ou écrire le port 0×60. La routine est donc simplement :
kbd_wait:
in al, KBD_STATUS
test al, 2
jne kbd_wait
ret
Pour tester au lieu de me faire chier directement avec un driver, j’ai demandé à ce que l’IOPL de mon process soit modifié, pour Windows il faut que le token du process possède le privilège SeTcbPrivilege. Celui-ci peut être attribuer en allant dans Panneau de configuration -> Outils d’administration -> Stratégie de sécurité locale -> Stratégies locales -> Attribution des droits utilisateurs et en ajoutant votre utilisateur dans la stratégie « Agir en tant que partie du système d’exploitation » (après avoir ajouter votre user il faut vous que relanciez votre session).
Je vous laisse les sources maintenant à vous de vous amuser avec, en tout cas cela m’a bien servit dans mon driver pour tracer mon code.
Vous trouverez les sources ici :
http://ivanlef0u.fr/repo/kbd-leds.rar
Sinon quelques liens à lire :
Updates: Autoruns v9.53, ProcDump v1.3, Process Monitor v2.6 | New Mark’s Blog post: The Case of the Temporary Registry Profiles | Download Windows Internals 5 sample chapter
VMWare Cloudburst
VMware ring3 detection (RF handling)
Preventing the exploitation of user mode heap corruption vulnerabilities
août 14th, 2009
Omg j’ai retrouvé l’url de mon blog, je m’en veux d’avoir été absent si longtemps mais j’avais d’autres projets. Vous allez rire mais depuis quelques temps je suis complètement absorbé par des challenges linux. Ce sont les challenges de intruded et de overthewire, il s’agit d’exploiter des failles applicatives sur différentes binaires. On se connecte en ssh sur le serveur et le binaire level-n est SUID level-n+1 ainsi lorsqu’on arrive à lancer un /bin/sh on peut aller lire le fichier qui contient le password du level suivant.
En fait, je dépose les soluces sur le wiki de Oldschool avec d’autres on essaye de réussir le plus de levels possibles. C’est uniquement dans le but d’apprende et de partager le plus de techniques d’exploitation puis tant qu’à faire, motiver d’autres personnes a faire ces challenges. Autant clarifier les choses, si vous criez au sploil n’aller pas sur le site et ne venez pas couiner !
Je me suis surtout concentré sur les levels de Utumno et de Maze, des serveurs de intruded. Certains sont vraiment intéressants comme ce BOF dans lequel on ne contrôle qu’un saved ebp ou bien même ce heap overflow sous l’allocateur ptmalloc. Ca reste assez classique mais ca fait toujours du bien de se creuser la tête dessus et ca me change de Windows
Sinon, IDT Sniffer marche, je n’ai plus qu’à finir l’interface avec l’user-land et la gestion du filtrages des exceptions avant de le release.
Enfin je vous propose du bon hardcore avec le groupe The Carrier et ‘Panicstricken’ de leur album ‘One Year Later’.
Panicstricken
I chose death over life, wanted to meet my maker I was dying inside.
Pain too great to deal with me made me try to take my life. But now I’ve seen the light, I’ve got a second chance at life.
I’m not retracting my old tracks.
I’m never going back.
To the locked doors and the blinding lights and the uncertainty of whether or not it will be a good or bad day. I never want to wonder what life would be like without me in it, because I’m alive.
I’ve taken what I’ve learned with me, it’s all I know, and I’ll been kickin’ my old shit out the front fucking door. Because I don’t need it anymore.
I’m taking steps forward one foot at a time, making sure not to fall.
And it will be the last trip of my life. Reaching up while the ship sinks to the bottom of this dead ocean.
A thousand stars couldn’t shine through all the shit we’ve been through, in this world that has no ending. I’m never going back to blinding lights.
juin 28th, 2009
Encore un post qui parle du SMM, c’est la mode en cette saison. Pour ceux qui débarquent, je vous conseil d’abord de lire ce post pour vous mettre dans le bain. Je tiens juste d’emblée à préciser que le SMM c’est joli, c’est puissant, c’est funny mais qu’au final c’est quand même de la branlette intellectuelle. Il faut savoir que beaucoup de choses faisables en SMM le sont déjà en ring 0. Mis à part le cas très spécifique de la corruption d’hyperviseurs comme l’équipe de Invisible Things Lab l’a montré en profitant d’un bug permettant d’atteindre la SMRAM je ne vois pas en quoi le SMM est utile pour un attaquant. Attention je dis cela parce qu’il est plus simple actuellement de réaliser certaines attaques indétectables, comme par exemple un keylogger, sans avoir à se mettre en SMM. Finalement je rejoins l’opinion d’autres personnes qui disent qu’il faut prendre en compte le du risque SMM mais qu’on a déjà assez de choses non-secure à traiter pour le moment. Donc au final pas de quoi paniquer.
Justement parlons de ce fameux bug. Loïc Duflot a montré à CansecWest une nouvelle attaque visant le mode SMM. Sachant qu’actuellement les chipsets de type northbridge sont bridés afin que personne n’accède à la SMRAM il devient quasiment impossible à l’utilisateur de lire ou d’écrire dans cette mémoire. Cette attaque consiste à modifier la politique du cache au niveau de la SMRAM. On dit au CPU de ne pas aller chercher le code SMM dans la DRAM mais dans son cache directement, cache que l’on contrôle bien sur. Chose marrante c’est précisément ce même bug qu’on utiliser les personnes d’ITL pour atteindre la SMRAM et ainsi infecter l’hyperviseur de XEN. Les grands esprits se rencontrent :]
L’attaque se déroule en 3 étapes et nécessite au moins d’être admin sur la b0x :
- On modifie la gestion du cache à l’aide des MTRRs (Memory Type Range Register). Ils se présentent sous la forme d’un ensemble de MSRs, leur taille est donc de 64 bits. Un MTRR sert à associer un espace de mémoire physique à un type de cache. Les différents types de cache sont :
Je ne vais pas tous les décrire, sachez que ceux qui nous intéressent sont les types UC et WB.
Il existe 2 types de MTRR ceux qui sont constants et ceux qui sont variables. Pour le moment seul les constants nous intéresse car ils sont utilisés pour décrire la gestion du cache pour le premier mega de la mémoire physique, comme le montre le schéma suivant :
C’est précisément dans ce premier mega de mémoire qu’est situé la SMRAM. Elle est normalement située à l’adresse physique 0xA0000. Pour connaitre quel est le type de cache appliqué à la SMRA il suffit de lire la doc :
Register IA32_MTRR_FIX64K_00000 :
Maps the 512-KByte address range from 0H to 7FFFFH. This range is divided into eight 64-KByte sub-ranges.
Registers IA32_MTRR_FIX16K_80000 and IA32_MTRR_FIX16K_A0000 :
Maps the two 128-KByte address ranges from 80000H to BFFFFH. This range is divided into sixteen 16-KByte sub-ranges, 8 ranges per register.
Registers IA32_MTRR_FIX4K_C0000 through IA32_MTRR_FIX4K_F8000 :
Maps eight 32-KByte address ranges from C0000H to FFFFFH. This range is divided into sixty-four 4-KByte sub-ranges, 8 ranges per register.
C’est donc le MSR IA32_MTRR_FIX16K_A0000 (0×259) qui détermine que politique de cache est appliquée à la SMRAM. La structure d’un MTRR est simple, le MSR est découpé en champ de 8 bits qui décrivent le caching sur une plage de mémoire. Dans le cas du MSR IA32_MTRR_FIX16K_A0000 chacun de ses champs décrit un espace mémoire de taille 16KB.
Il existe 2 encodage différent pour le champ Type, le plus simple utilise uniquement les 2 premiers bits pour définir le type de chacun, les autres sont reservés et mit à 0. Le second encodage, dit étendu n’est dispo que pour les CPU AMD et ne nous intéresse pas ici. Maintenant avec le LKD on peut lire la valeur du MSR IA32_MTRR_FIX16K_A0000.
lkd> rdmsr 259
msr[259] = 00000000`00000000
Cela montre que pour le moment la SMRAM est donc UC. Tout accès en écriture ou lecture dans cette mémoire ne sera jamais mis en cache. Ce qui fait que tout le code SMM est chargé depuis la DRAM. Ainsi la demande d’accès à la mémoire physique de la SMRAM passe forcément par le northbridge qui contrôle si le CPU est bien en mode SMM.
Pour le moment l’utilisateur ne peut toujours pas accéder par lui même à la SMRAM car le chipset verrouille le bit qui sert à aiguiller l’accès entre la SMRAM (dans la DRAM) et l’adresse du buffer vidéo (je rappel que par défaut ce bit oriente l’accès mémoire sur le buffer vidéo). Ce bit d’aiguillage est mise à jour lorsque que chipset est certain que le CPU est en mode SMM.
Maintenant on va modifier cela pour que la zone mémoire sur l’intervalle [0xA0000, 0xA0000+0x4000] soit en WB. C’est à dire que toutes les lectures et écritures seront mises en cache si possible. On définit donc les 2 premiers bits du IA32_MTRR_FIX16K_A0000 à la valeur 0×6.
-
On balance une SMI sur le CPU. Le CPU va donc accéder à la SRAM et du fait de la nouvelle politique de cache mise en place, la SMRAM va donc se retrouver dans le cache. Le SMI handler est exécuter normalement depuis le cache.
-
Sachant que notre SMRAM est dans le cache, l’accès à la mémoire physique 0xA0000 ne passe plus par le northbridge ! Aucun contrôle n’est donc réalisé sur le mode du CPU. Il suffit donc de mapper cet espace mémoire physique dans notre espace virtuel pour y écrire/lire comme si de rien n’était, kewl =] Attention en utilisant la pagination d’autres bits, notamment la PAT (Page Attribute Table), doivent prit en compte. Ce sont les bits CD et NW du CR0, les bits PCD et PNW du PDE/PTE qui sont concernés. En fait on voudrait éviter de tomber sur une combinaison entre le MTRR et la PAT qui rendrait la mémoire non cachable lorsqu’on la mappe.
Concernant l’implémentation d’un exploit sous Windows. Il est possible de lire des MSR depuis le user-land avec l’API ZwSystemDebugControl, modifier le IA32_MTRR_FIX16K_A0000 ne pose donc pas de problème. On peut aussi générer un SMI à l’aide d’une instruction ‘OUT 0×0, 0xB2′ du moment qu’on à modifier l’IOPL courant à l’aide de l’API ZwSetInformationProcess et du SeTcbPrivilege. On peut réaliser tout ceci depuis le ring3. Reste à mapper l’adresse physique 0xA000, on pourrait penser qu’utiliser le \Device\PhysicalMemory est suffisant mais ce dernier n’est qu’en lecture seule sous WinXP. En plus il n’est pas possible de contrôler les flags de caching pour les pages qui mappent la SMRAM en user-land.
Il existe aussi l’API native ZwSystemDebugControl avec ControlCode SysDbgReadPhysical (10) qui permet de lire de la mémoire physique depuis l’user-land. Voici une fonction la mettant en oeuvre :
ULONG ReadPhysicalMem(PHYSICAL_ADDRESS Addr, PVOID Buff, ULONG Size)
{
NTSTATUS Status;
DWORD RetBytes;
SYSDBG_PHYSICAL PhysMem;
PhysMem.Address=Addr;
PhysMem.Buffer=Buff;
PhysMem.Request=Size;
Status=ZwSystemDebugControl(SysDbgReadPhysical, &PhysMem, sizeof(PhysMem), NULL, 0, &RetBytes);
if(!NT_SUCCESS(Status))
{
printf("Error with ZwSystemDebugControl (SysDbgReadPhysical) : 0x%x\n", Status);
return 0;
}
return RetBytes;
}
Par contre je n’ai pas compris mais l’API renvoie STATUS_UNSUCCESSFUL (0xC0000001) lorsqu’on essaye de lire l’adresse physique 0xA0000. Surement que le noyau refuse qu’on lise dans le premier mega de mémoire physique je n’ai pas investit plus dans cette direction
Jusqu’ici on voit que pour atteindre la SMRAM depuis l’user-land c’est la misère. Alors solution bourrin, on sort le driver. Le code est quasiment le même qu’en user-land sauf qu’on utilise MmMapIoSpace pour mapper la SMRAM dans notre espace virtuel noyau. Après si l’exploit marche on peut aussi bien lire et écrire dans la SMRAM mise en cache.
Une fois qu’on arrive à écrire dans la SMRAM mise en cache tout est gagné. On peut reloger la SMRAM sur une zone mémoire qu’on contrôle. Par contre cette modification ne peut se faire qu’en SMM.
Pour réaliser cela, on modifie le SMI handler dans le cache afin qu’il exécute un shellcode qui va modifier la valeur du champ SMBASE. Ce champ est situé à l’adresse SMBASE+0x7EF8. Notre shellcode va mette à jour le champ SMBASE pour qu’il pointe sur la zone de mémoire physique avec notre SMRAM custom. Une fois qu’on à fait cela on relance une SMI, le handler va exécuter notre shellcode, le champ SMBASE est donc modifié et on flush le cache pour que la valeur soit mise à jour dans la DRAM. Lors d’un prochaine SMI ce sera notre custom SMRAM qui sera prise en compte, yeepee
J’ai codé 2 programmes qui réalisent l’attaque cité plus haut afin de dumper la SMRAM. Le premier est en user-land et tente de lire la SMRAM depuis le cache avec le \Device\PhysicalMemory. Le second est un driver qui utilise MmMapIoSpace. Par contre, par manque de temps et surtout de motivation je n’ai pas vraiment testé mes binaires. C’est surtout du au fait que l’attaque n’est pas réalisable depuis une VM puisque les VM comme VMware et Virtual PC qui n’utilisent pas la virtualisation hardware émule le mode SMM et que je n’ai pas trop envie de le tester sur ma machine de taff. Bref c’est du code de base si vous voulez investir le sujet, si vous avez besoin d’aide n’hésitez pas à me contacter !
En parlant de Virtual PC 2k7, j’ai remarqué que ce dernier n’aimait pas trop qu’on touche à ses MTRR. Une modification du IA32_MTRR_FIX16K_A0000 n’est pas prise à compte par la VM ce qui en fait une astuce de plus pour détecter Virtual PC. Toujours pratique dans le cas d’un rk kernel-land qui voudrait éviter d’être exécuter sur cette VM.
Pendant que je suis dans la SMM, je vous conseil de lire l’excellent article de chpie qui installe un keylogger SMM sous VMware. En fait VMware (tout comme Virtual PC 2k7) émule un northbridge de type Intel 440BX AGPset 82443BX, dont la documentation est disponible ici. Ce qui est cool, c’est que le bit D_LCK est mis à jours 0. Ce qui veut dire que le bit D_OPEN peut être modifié librement par l’utilisateur ! Je me suis donc codé un petit dumper pour SMRAM. Le principe est simple on écrit le SMRAM Control Register du northbridge pour qu’il nous laisse accéder à la SMRAM librement. L’outil va écrire un .bin sur le disque qui correspond à l’interval de mémoire [0xA0000, 0xA0000+0x20000] que IDA se fera un plaisir d’analyser. Pour vous dire on retrouve des choses assez marrantes dans la SMRAM
Ce qui faut retenir dans tout ca, c’est que jouer avec le SMM c’est cool, cela permet de comprendre plein de choses sur le fonctionnement de notre machine. Par contre en terme de fonctionnalités, le SMM reste limité, même si on dit que c’est super-furtif on est loin de voir apparaître des malwares qui infectent le SMM (je dis ça mais ils sont bien arrivés dans nos MBR les petits batards). En fait la grosse difficulté est du au fait qu’on est très proche du matériel et qu’il n’existe pas forcément de documentation pour tous les chipsets northbridge grand public. Pour mon laptop par exemple je n’ai accès à aucune datasheet concernant mon northbridge. Donc en termes de généricité on est vite limité. Enfin comme je l’ai dit au début pas besoin d’aller se planquer dans la SMRAM pour poser un keylogger ou sniffer réseau qui bypass une bonne partie des AV/FW, alors pourquoi faire compliqué quand on peut faire simple :]
Voici l’url pour les binaires et codes :
http://ivanlef0u.fr/repo/smm_attack.rar
Pour finir, quelques liens intéressants :
!exploitable
An Analysis of Conficker’s Logic and Rendezvous Points
Anti-Debugging Series
Emulation/AV Awareness
Cons and thing
Microsoft GdiPlus EMF GpFont.SetData Integer Overflow
Guide des principes fondamentaux de l’enquête informatique pour Windows
Pushing the Limits of Windows: Paged and Nonpaged Pool
Plongeon dans les appels systèmes Windows
Et pour info le coding du tool IDT Sniffer avance tranquillement :p
mars 29th, 2009
Certains disent que je suis mort, d’autres que j’ai muté pour devenir reverser Java voir même spécialiste UML. Que nenni j’ai tenté de faire le gens ordinaire pendant quelques temps, vous savez : faire du social, parler à des gens, sortir et toutes ces conneries. Cela ne m’a pas beaucoup réussi donc ‘back to the roots’. Pour la peine vous prendrez bien un peu de bon son pour réveillez vos neurones. Du bon post-metal sludge avec Rosetta et Red In Tooth And Claw :
Red in tooth and claw
Une fois revenu aux vraies valeurs, c’est à dire IRC et les trollkores. J’ai beaucoup discuté avec Babo0n sur la possibilité de crée un outil pour surveiller l’IDT notamment lorsque celle-ci est modifiée par des modules noyau qui servent à la protection de binaire comme Themida ou Starforce. Ce post présente juste les réflexions sur l’implémentation d’un outil capable de mettre en place une surveillance de l’IDT, le code devrait être fournit dans un prochain post si j’arrive à le finir :]
On voudrait donc être capable de voir quelles routines de l’IDT sont modifiées par ces drivers puis ensuite de contrôler ceux qui leur est passé. En fait on se placerait comme un proxy filtrant entre l’IDT et le module. Évidemment il n’existe aucun mécanisme natif sur architecture x86 pour faire cela nativement.
Plaçons un peu le contexte. On travaille sur des systèmes Windows en IA-32 SMP avec ou sans PAE. On souhaite hooker uniquement les exceptions pour le moment, les interruptions peuvent aussi être gérées de la même manière mais on s’en fou. Nous n’avons pas droit aux jeux d’instruction VMX et SVM, donc pas de virtualisation pour des raisons de portabilité matérielle.
Juste un rappel sur la forme d’un IDT et de ses structures, cela sera utile pour la suite. Les 32 premières entrées de l’IDT sont réservées pour les exceptions, le reste est pour les interruptions. Voici les détails des exceptions que l’on a :
Chacune des entrées de l’IDT est formée par l’une des gates suivante. En général nous n’avons que des trap ou interrupt gates.
A noter que la différence entre une trap gate et une interrupt gate est la gestion de l’IF flag de l’EFlags. Ce flag contrôle le masquage des interruptions, s’il est à 0 plus aucune interruption n’est délivrée sur le core. Lorsqu’une interrupt gate est appelée l’IF flag est automatique mit à 0, il est ensuite remit à 1 lors du retour avec l’instruction IRET. Une trap gate quand à elle ne modifie pas l’IF flag. Sous Windows on ne trouve que des interrupt gates.
Sur un core l’IDT est stocké dans le registre IDTR qui peut être accéder avec l’instruction LIDT et écrit à l’aide de SIDT. L’IDTR est un registre de 48 bits composé d’un couple (base (32), limit(16)) définissant l’IDT.
Pour la suite du post nous considérons uniquement un système non-SMP, nous n’avons qu’une seule IDT à gérer. Bien entendu l’outil final devra supporter des systèmes avec plusieurs cores. L’idée que je présente par la suite pour résoudre ce problème, n’est qu’une parmi d’autres, si vous en voyez de plus simple n’hésitez pas
L’idée consiste donc à faire travailler le système sur une copie de l’IDT qui lui sert de tampon pour prévenir les tentatives d’écriture. Ce tampon est alloué en lecture seule. On redéfinit donc l’IDT du système sur ce tampon, lorsqu’une écriture intervient une exception est générée. Notre module regarde la valeur inscrite et note l’adresse de la routine. Ensuite il place sa propre routine à la place de celle qui allait se faire hooker. Cette routine sert à notifier l’utilisateur qu’une exception surveillée à eu lieu et de router cette exception sur le handler du module qui voulait placer le hook.
Maintenant commencons à parler d’implémentation. D’abord définissons quelques termes :
- FirstIDT : IDT originale, accessible en R/W par défaut sous Win
- IDT’ : copie de FirstIDT en R/W
- IDT'' : mapping de IDT’ en RO
- IDT0 : structure de type IDT vide (0) gérée par notre driver.
On ne peut pas directement copier IDT en RO à cause de l’API kernel. On doit la copier et ensuite la remapper en RO.
Ensuite il faut prendre en compte le bit WP (bit 16) du CR0. Si il est à 1 alors quelque soit le CPL et quelque soit les privilèges des pages leurs droits sont respectés. S’il est à 0 alors on peut écrire dans n’importe quelle page du moment qu’on est en ring0.
En ce qui nous concerne, il nous faut WP=1 pour pouvoir mettre en place une page ring0 en R0. Heureusement c’est le cas par défaut sous Window mais une simple modification de ce bit fait tomber tout l’appli ! Hélas il nous est impossible de contrôler sa modification. Pour info on retrouve la modification de ce bit lors du hooking de SSDT. Voici un tableau récapitulatif :
Extrait intel volume 3A chap 4 :
Starting with the P6 family, Intel processors allow user-mode pages to be write-protected against supervisor-mode access. Setting CR0.WP = 1 enables supervisor-mode sensitivity to user-mode, write protected pages. Supervisor pages which are read-only are not writable from any privilege level (if CR0.WP = 1). This supervisor write-protect feature is useful for implementing a “copy-on-write” strategy used by some operating systems, such as UNIX*, for task creation (also called forking or spawning). When a new task is created, it is possible to copy the entire address space of the parent task. This gives the child task a complete, duplicate set of the parent’s segments and pages. An alternative copy-on-write strategy saves memory space and time by mapping the child’s segments and pages to the same segments and pages used by the parent task. A private copy of a page gets created only when one of the tasks writes to the page. By using the WP flag and marking the shared pages as read-only, the supervisor can detect an attempt to write to a user-level page, and can copy the page at that time.
IDT'' devient l’IDT de notre core. Lors d’une tentative d’écriture une exception de type #PF est levée. Comme en kernel-land il n’existe pas de mécanisme de gestion des exceptions global comme les VEH nous devons mettre en place notre propre #PF handler. Notre module doit donc modifier le page fault handler de IDT'' avant de l’assigner sur le core. Ce handler sert de filtre et permet de savoir si une tentative d’écriture sur IDT'' a eu lieu. Si non alors en appel le gestionnaire de Windows KitTrap0E. Si oui alors on applique un traitement spécial à l’exception. Ce qu’on veut c’est noter la valeur inscrite par le module dans IDT'', or pour le moment nous avons une exception, c’est à dire que l’EIP pointe sur l’instruction fautive du fait qu’on est une fault et non une trap. Il nous faut donc retrouver la valeur qui allait être inscrite dans IDT''. Pour cela il existe plusieurs solutions :
- On émule l’instruction, pour cela il nous faut une libraire d’émulation x86 ou bien un désassembleur assez puissant pour faire ressortir suffisamment d’information sur la sémantique de l’instruction. Je ne connais aucune lib d’émulation x86 publique, par contre le moteur de disass BeaEngine est un bon candidat. Une fois qu’on a émulé l’instruction on note la valeur qui allait être inscrite au même endroit dans IDT0 ensuite on retourne avec IRET.
- On exécute l’instruction dans un environnement controlé. En gros on laisse l’instruction être exécuté par le CPU mais on est capable de reprendre la main après. On peut faire cela de plusieurs façons :
- A l’aide du TF flag (bit 8 ) de l’EFlags on passe en single step. Lorsque l’instruction à finie de s’exécuter on une trap de type #DB. Pour nous cela implique de réaliser les opérations suivantes :
- IDT'' passe en R/W.
- On récupère l’EIP sur la pile, il pointe sur l’instruction fautive. On calcule sa taille à l’aide d’un LDE ou d’une lib de disass et on ajoute la somme EIP+instrlen dans une variable globale.
- On active le TF flags de l’EFlags pour passer en single step.
- Retour d’exception avec IRET.
- On va décrire le traitement de l’int 1 plus loin.
- On peut aussi fabriquer une environnement qui nous permet de prendre la main après l’exécution de l’instruction. Avec un code qui fonctionne comme Bee-Lee. Mais cela pose le même problème qu’avec l’utilisation du TF comme nous allons le voir.
On écarte l’utilisation d’une lib d’émulation pour la suite. On garde uniquement la solution avec le TF, vous allez comprendre pourquoi par la suite.
A ce moment l’instruction fautive écrit dans IDT'' (IDT’ est donc lui aussi modifié) et génère une except de type #DB avec l’EIP qui pointe sur l’instruction suivante. En fait précédemment nous avons du hooké le handler pour les #DB (KiTrap01).
On réalise ensuite les traitements suivants :
- On vérifie que le saved EIP correspond bien à notre EIP mit en variable globale. Si non alors on appel le handler de Win KiTrap01.
- Si l’EIP est celui d’une instruction qui a écrit dans IDT'' alors on note la valeur dans l’entrée équivalente de IDT0. Soit i l’indice de l’entrée inscrite, on sait aussi que sizeof(IDTENTRY)=8, il faut donc entre 8 écritures de 1 byte et 1 écriture de 8 bytes (pouvant être faite à l’aide des instructions MMX par exemple) pour définir une nouvelle entrée dans l’IDT. On compare IDT'' avec IDT pour trouver le nombre de bytes écrit.
- IDT0[i] est mit à jour avec le ou les bytes écrit dans IDT''. A chaque entrée de IDT0 est associé un compteur permettant de savoir quand l’écriture de l’entrée est terminée, ce compteur va de 1 à 8.
- A partir d’ici 2 cas se présentent :
- Le compteur de l’entrée IDT0[i] n’a pas atteint 8. On restaure IDT'' à l’aide de FirstIDT.
- Le compteur de l’entrée IDT0[i] vaut 8. On place dans IDT’[i] (donc aussi dans IDT'') notre propre handler de sniffing qui route vers la routine placé dans IDT0[i]. FirstIDT[i] se voit aussi inscrire cette valeur afin d’avoir un état cohérent par la suite si notre handler se faisait écraser. IDT0[i] est mit à 0 ainsi que son compteur.
- IDT'' repasse en RO. Flush des caches pour que tout ca soit propre.
- On peut penser à ajouter ici une vérification du CR0.WP.
- Retour d’exception avec IRET.
Ok normalement tout cela ne fonctionne pas trop mal. Reste un gros problème, l’écriture des handlers qu’on a du hooké, le #PF et le #DB. Ceux-ci doivent rester valident même en cas d’écrasement. On comprend bien que si un module écrase ces entrées en 2 fois (2*4 bytes, le plus courant) elles seront invalide. Souvenez vous plus haut, les bits du pointeur sur le handler sont repartis dans les 2 dwords, en modifier un sans l’autre crée donc un état incohérent. Du fait qu’on a forcément du besoin du #PF et de #DB ou de #BP (cas avec Bee-Lee) il nous trouver une solution pour éviter leur écrasement lors qu’un module veut les hooker.
Une solution simple est de déplacer l’IDT lorsqu’on identifie une écriture sur l’un de ces handlers. Cette IDT sera une copie de IDT'' et ne sera assigné sur le core uniquement pour traiter l’écriture dans un ces handlers. Cela donne le scénario suivant :
- Dans notre #PF on détecte une tentative d’écriture sur l’un des handlers critique. On déplace l’IDT sur une copie de IDT''.
- L’instruction écrit dans IDT''
- On arrive dans notre #DB qui fait la même chose que précédemment en plus de remettre l’IDT du core sur IDT''. Evidemment nos routines de #DB et de #PF sont capables de router les exceptions vers celles du modules lorsqu’elles se font écraser.
Voilà, j’entends les couinements de Babo0n au fond mais ca doit tenir la route. Vous remarquerez que c’est un peu compliqué pour juste sniffer l’IDT et j’aurais peut être du faire un schéma pour que vous visualisiez tout cela mais j’avais plus d’encre Une dernière chose à propos des informations que renvoient les routines de sniffing. Il serait intéressant de rapporter des infos sur les registres, la pile, le thread et le processus ainsi qu’une possibilité de filtrage que l’utilisateur détermine, à voir.
Pour finir, je fais donc appel à vous ! Si vous ne comprenez pas des choses ou que certaines ne sont pas clair n’hésitez pas me poser des questions ! Je vais essayer de coder cela dans les prochains jours mais c’est loin d’être simple et je voudrais être sur de ce que je fais. Hé oui parfois faut réfléchir avant de pisser du code …
Au final ca serait un tool sympa pour aider au reverse de certaines protects un peu violente. A noter qu’à l’aide de la virtualisation hardware tout ceci est beaucoup plus simple à réaliser car il suffit de mettre des flags à 1 pour demander à l’hyperviseur de prendre la main lorsqu’une exception arrive !
Enfin quelques lectures et tools qui valent la peine :
Implementing SMM PS/2 Keyboard sniffer
Return-oriented exploiting
New Tool: VMMap v1.0 | Mark speaking at Microsoft TechEd 2009
Understanding the kernel address space on 32-bit Windows Vista
Attacking Intel TXT: paper and slides
Le blog de Kris Kaspersky
février 26th, 2009
Et non ce blog n’est pas le mort, depuis le temps que je l’ai délaissé, je commençais à m’en vouloir. Disons que j’ai été prit par mal de taff en plus de pas faire grand chose de bien au niveau de mes projets perso et puis il y a eu une pénurie de chocapicz à cause de la crise Alors au lieu de poster pour rien dire d’intéressant ou même de la merde comme une grosse tapz, j’ai préféré fermer ma gueule. Ce post est consacré à une technologie récente mais encore trop peu connue par les gens qui font des vrais choses (comprendre : pas des projets java et du .Net) : la Dynamic Binary Instrumentamention (DBI).
Pour illustrer mes propos je vais me baser sur le framework DynamoRio, développé par le Mit et les laboratoires HP, récemment acquit par VmWare. La dernière version est disponible sur le site govirtual.org, un site dédié à la recherche en virtualisation. La première fois ou j’ai découvert le DBI fut en lisant le paper de Skape ‘Memalyze: Dynamic Analysis of Memory Access Behavior in Software‘ (et aussi là). Il existe d’autres frameworks permettant de faire du DBI comme PIN qui est notamment utilisé par l’outil Saffron présenté par Danny Quist et Valsmith durant la conf à BH2K7 Covert Debugging. Un des outils les plus connu qui fonctionne avec du DBI est Valgring.
Maintenant passons aux choses sérieux. Le DBI c’est quoi ? On pourrait le voir comme une machine virtuelle orientée analyse de code et instrumentation de code. Quand je dis instrumentations c’est à des fins d’analyses, de tracing et de mesure de performances ainsi que de modification en temps réel du code. En gros le code d’un thread est passé dans une moulinette qui offre différentes actions possible sur ce dernier. L’outil de DBI va se placer à des endroits stratégiques, en général aux branchements, pour contrôler le flux d’exécution. Attention en aucun le DBI ne va pas faire de l’émulation du code (sauf cas particuliers), le code est toujours exécuté nativement sur le processeur.
Dans le cadre de DynamoRio, celui-ci se présente sous la forme d’une librairie, dynamorio.dll, qui va surveiller les actions importantes d’un processus et de ses threads pour appliquer son instrumentation. Sous Windows, c’est une DLL, dynamorio.dll qui se retrouve injecté dans un processus au moment de son initialisation. Cette DLL est chargée par drpreinject.dll qui se retrouve injecté dans tous les process grâce à la clé HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs. Celle clé contient le path de drpreinject.dll. drpreinject.dll va ensuite consulter les infos stockée dans HKEY_LOCAL_MACHINE\SOFTWARE\VMware,Inc.\DynamoRIO\ pour savoir si elle doit charger dynamorio.dll. Cette clé contient des dossiers nommés par des process. Chez moi j’ai pas exemple dans la clé stockée dans HKEY_LOCAL_MACHINE\SOFTWARE\VMware,Inc.\DynamoRIO\test.exe les valeurs :
DYNAMORIO_AUTOINJECT : C:\ProgHack\c\dynamorio\lib32\debug\dynamorio.dll
DYNAMORIO_LOGDIR : C:\ProgHack\c\dynamorio\logs
DYNAMORIO_OPTIONS : -code_api -no_hide -client_lib "C:\ProgHack\c\dynamorio\me\dyn.dll;0;"
DYNAMORIO_RUNUNDER : 1
Pour enregistrer un binaire auprès de DynamoRio on utilise l’outil drdeploy.exe en faisant (j’expliquerais cette ligne de commande plus en détail par le suite) :
drdeploy.exe -reg test.exe -root C:\ProgHack\c\dynamorio\ -ops "-no_hide" -client C:\ProgHack\c\dynamorio\me\dyn.dll 0 ""
Par défaut dynamorio.dll se cache de la liste de modules chargés (unlikage dans les doubles listes chainées : InLoadOrderModuleList InMemoryOrderModuleList et InitializationOrderModuleList du champ Ldr de type PEB_LDR_DATA du PEB) pour désactiver cela on utilise l’option -no_hide. L’analyse du code commence quand à elle après le call à dynamorio!dynamorio_app_take_over par drpreinject.dll. Le premier code analysé n’est pas celui de notre binaire mais celui de kernel32!FreeLibrary appelé par DynamoRio pour décharger drpreinject.dll.
Ca c’était pour la partie Windows et injection de DLL. Commençons à parler du DynamoRio lui même. J’ai eu du mal à prendre la doc en main (même si c’est moins difficile que celle de metasm) avec l’aide de tutoriaux, ce n’est pas évident. J’ai tout de même réussit à comprendre une bonne partie des fonctionnalités.
La librairie va commencer à analyser le code sous forme de blocs, ces blocs sont presque de la même forme que ceux générés par l’analyse de IDA. Un bloc est une séquence d’instructions se terminant par une 1 unique instruction de branchement : jmp, jcc, call ou ret. Par exemple, les instructions suivantes :
(Le dissass a été effectué avec l'API de DynamoRio)
TAG 0x7c925e4a
+0 L3 8b ff mov %edi -> %edi
+2 L3 55 push %ebp %esp -> %esp (%esp)
+3 L3 8b ec mov %esp -> %ebp
+5 L3 83 7d 08 00 cmp 0x08(%ebp) $0x00000000
+9 L3 0f 85 82 45 03 00 jnz $0x7c95a3db
+15 L3 8b 4d 0c mov 0x0c(%ebp) -> %ecx
+18 L3 85 c9 test %ecx %ecx
+20 L3 56 push %esi %esp -> %esp (%esp)
+21 L3 57 push %edi %esp -> %esp (%esp)
+22 L3 74 33 jz $0x7c925e95
+24 L3 66 8b 39 data16 mov (%ecx) -> %di
+27 L3 0f b7 c7 movzx %di -> %eax
+30 L3 6a 02 push $0x00000002 %esp -> %esp (%esp)
+32 L3 99 cdq %eax -> %edx %eax
+33 L3 5e pop %esp (%esp) -> %esi %esp
+34 L3 f7 fe idiv %esi %edx %eax -> %edx %eax
+36 L3 85 d2 test %edx %edx
+38 L3 75 32 jnz $0x7c925ea4
+40 L3 66 8b 71 02 data16 mov 0x02(%ecx) -> %si
+44 L3 53 push %ebx %esp -> %esp (%esp)
+45 L3 0f b7 c6 movzx %si -> %eax
+48 L3 6a 02 push $0x00000002 %esp -> %esp (%esp)
+50 L3 99 cdq %eax -> %edx %eax
+51 L3 5b pop %esp (%esp) -> %ebx %esp
+52 L3 f7 fb idiv %ebx %edx %eax -> %edx %eax
+54 L3 5b pop %esp (%esp) -> %ebx %esp
+55 L3 85 d2 test %edx %edx
+57 L3 75 1f jnz $0x7c925ea4
+59 L3 66 3b fe data16 cmp %di %si
+62 L3 77 1a jnbe $0x7c925ea4
+64 L3 66 85 ff data16 test %di %di
+67 L3 74 0e jz $0x7c925e9d
+69 L3 83 79 04 00 cmp 0x04(%ecx) $0x00000000
+73 L3 74 0f jz $0x7c925ea4
+75 L3 33 c0 xor %eax %eax -> %eax
+77 L3 5f pop %esp (%esp) -> %edi %esp
+78 L3 5e pop %esp (%esp) -> %esi %esp
+79 L3 5d pop %esp (%esp) -> %ebp %esp
+80 L3 c2 08 00 ret $0x00000008 %esp (%esp) -> %esp
+83 L3 3b c7 cmp %eax %edi
+85 L3 0f 8c a9 00 00 00 jl $0x7c925e36
+91 L3 56 push %esi %esp -> %esp (%esp)
+92 L3 57 push %edi %esp -> %esp (%esp)
+93 L3 e8 b6 00 00 00 call $0x7c925e4a %esp -> %esp (%esp)
END 0x7c925e4a
Seront transformées par les blocs :
TAG 0x7c925e4a
+0 L3 8b ff mov %edi -> %edi
+2 L3 55 push %ebp %esp -> %esp (%esp)
+3 L3 8b ec mov %esp -> %ebp
+5 L3 83 7d 08 00 cmp 0x08(%ebp) $0x00000000
+9 L3 0f 85 82 45 03 00 jnz $0x7c95a3db
END 0x7c925e4a
TAG 0x7c925e59
+0 L3 8b 4d 0c mov 0x0c(%ebp) -> %ecx
+3 L3 85 c9 test %ecx %ecx
+5 L3 56 push %esi %esp -> %esp (%esp)
+6 L3 57 push %edi %esp -> %esp (%esp)
+7 L3 74 33 jz $0x7c925e95
END 0x7c925e59
TAG 0x7c925e62
+0 L3 66 8b 39 data16 mov (%ecx) -> %di
+3 L3 0f b7 c7 movzx %di -> %eax
+6 L3 6a 02 push $0x00000002 %esp -> %esp (%esp)
+8 L3 99 cdq %eax -> %edx %eax
+9 L3 5e pop %esp (%esp) -> %esi %esp
+10 L3 f7 fe idiv %esi %edx %eax -> %edx %eax
+12 L3 85 d2 test %edx %edx
+14 L3 75 32 jnz $0x7c925ea4
END 0x7c925e62
TAG 0x7c925e72
+0 L3 66 8b 71 02 data16 mov 0x02(%ecx) -> %si
+4 L3 53 push %ebx %esp -> %esp (%esp)
+5 L3 0f b7 c6 movzx %si -> %eax
+8 L3 6a 02 push $0x00000002 %esp -> %esp (%esp)
+10 L3 99 cdq %eax -> %edx %eax
+11 L3 5b pop %esp (%esp) -> %ebx %esp
+12 L3 f7 fb idiv %ebx %edx %eax -> %edx %eax
+14 L3 5b pop %esp (%esp) -> %ebx %esp
+15 L3 85 d2 test %edx %edx
+17 L3 75 1f jnz $0x7c925ea4
END 0x7c925e72
TAG 0x7c925e85
+0 L3 66 3b fe data16 cmp %di %si
+3 L3 77 1a jnbe $0x7c925ea4
END 0x7c925e85
TAG 0x7c925e8a
+0 L3 66 85 ff data16 test %di %di
+3 L3 74 0e jz $0x7c925e9d
END 0x7c925e8a
TAG 0x7c925e8f
+0 L3 83 79 04 00 cmp 0x04(%ecx) $0x00000000
+4 L3 74 0f jz $0x7c925ea4
END 0x7c925e8f
TAG 0x7c925e95
+0 L3 33 c0 xor %eax %eax -> %eax
+2 L3 5f pop %esp (%esp) -> %edi %esp
+3 L3 5e pop %esp (%esp) -> %esi %esp
+4 L3 5d pop %esp (%esp) -> %ebp %esp
+5 L3 c2 08 00 ret $0x00000008 %esp (%esp) -> %esp
END 0x7c925e95
TAG 0x7c925d85
+0 L3 3b c7 cmp %eax %edi
+2 L3 0f 8c a9 00 00 00 jl $0x7c925e36
END 0x7c925d85
TAG 0x7c925d8d
+0 L3 56 push %esi %esp -> %esp (%esp)
+1 L3 57 push %edi %esp -> %esp (%esp)
+2 L3 e8 b6 00 00 00 call $0x7c925e4a %esp -> %esp (%esp)
END 0x7c925d8d
Ces blocs sont mit en mémoire dans le ‘basic bloc cache’. Leur code est ensuite exécuté par DynamoRio qui se charge soit de crée un nouveau bloc si celui n’existe pas soit de crée un bloc plus gros constitué de blocs déjà existant. Ce second type d’exécution va donc crée des nouveau blocs à partir d’anciens, ces nouveaux blocs sont mit en mémoire dans le ‘trace cache’. DynamoRio améliore ainsi les performances car il n’a plus besoin d’effectuer lui même les transitions entre ces blocs, cela est souvent utilisé lorsqu’il y a des boucles dans le code. Avec un schéma ca donne ça :
Attentions, je précise bien que le binaire va être exécuté à partir de ces blocs et non plus depuis le code original !
C’est sur ces blocs que DynamoRio propose à l’utilisateur d’agir, il peut par exemple simplement les analyser ou y ajouter/modifier/changer des instructions. En fait si on prend l’architecture de DynamoRio :
Le client n’a accès qu’aux fonctions fournies par la partie supérieure du schéma. Il ne s’agit pas vraiment de fonction mais plus précisément d’events qui seront notifiés au client sous forme de callbacks. La partie client vient s’interfacer avec la DLL dynamiorio.dll présente en mémoire. Le client se retrouve aussi sous la forme d’une DLL qui est chargée cette fois ci par dynamiorio.dll. Le path de cette DLL est passé par le paramètre -client_lib de drdeploy.exe. Le client possède une fonction d’initialisation qui va être appelé par dynamorio pour mettre en place les différents callbacks. Voici le client le plus minimaliste que l’on puisse faire :
#include <windows.h>
#include "dr_api.h"
void event_exit(void)
{
// empty client
}
DR_EXPORT void dr_init(client_id_t id)
{
// empty client
dr_register_exit_event(event_exit);
}
La fonction dr_init de notre DLL est ainsi appelée par dynamorio, c’est dans cette fonction qu’on place l’enregistrement de nos différentes callbacks. En voici les 2 principales :
- dr_register_bb_event() : Enregistre une callback qui est appelée après la création de chaque bloc.
- dr_register_trace_event() : Enregistre une callback qui est appelé après la création d’un bloc dans le trace-bloc. Lorsque des blocs sont ajoutés dans un trace-bloc, la routine qui gère les basics-bloc est rappelée avec le paramètre for_trace à true. Cela permet de savoir si un bloc va être ajouté dans un trace-bloc. Cela permet d’effectuer des modifications différentes en fonction des 2 cas.
Allons un peu plus loin. DynamoRio propose 2 manières de jouer avec le code au sein des blocs. La première met en avant l’utilisation des meta-instructions, ce sont des instructions de la même forme que les autres sauf quelles ne seront pas exécutées comme faisant partie de l’application mais par dynamorio (elles ne seront pas ajouté dans de nouveau bloc). Il est par exemple possible de mettre en place un « clean-call », qui va appeler notre routine dans le contexte de l’application actuel mais sans en modifier l’état, les meta-instructions travaillant sur une copie du contexte.
Il est aussi possible de manipuler directement le code des blocs, c’est comme si on modifait le code du binaire en mémoire mais depuis les blocs. On ne touche donc toujours pas au code original en mémoire ! Par contre cela pose un problème important dans le cas ou les nouvelles instructions créent une exception. DynamoRio doit savoir comment retomber sur ses pattes. Pour cela on met en place une « translation » allant des nouvelles instructions vers les anciennes. Si une exception intervient dans ces instructions, alors DynamoRio présente à l’application les instructions qui suivent la translation et qui doivent être considérées comme fautives. Par exemple, si je décide de modifier l’instruction : ‘inc dword ptr [ebx]‘ par ‘add dword ptr [ebx], 1′ alors je dis à DynamoRio qu’en cas de faute sur cette nouvelle instruction l’application devra considérer que c’est l’instruction « inc dword ptr [ebx] » qui a faulté. On met en place une translation avec l’API instr_set_translation. Bon, j’avoue, cela n’est pas super évident à comprendre et la doc est pas super clair à ce propos, d’ailleurs je ne suis même pas complètement sur de l’avoir parfaitement compris
Toujours dans le cadre d’une exception, DynamoRio propose 2 manières de gérer les translations. Si l’on a renvoyé DR_EMIT_DEFAULT lors d’une des 2 callbacks de créations de blocs, alors cette callback elle rappelée avec le paramètre « translating » à true. Si l’on a renvoyé DR_EMIT_STORE_TRANSLATIONS alors DynamoRio saute cet étage de rappel à la callback du basic-bloc.
Tout ce que j’ai dit plus haut représente les fondements de DynamoRio, bien entendu il existe d’autres fonctionnalités comme des events qui supportent la création de nouveaux threads (dr_register_thread_init_event()) ou le chargement de modules (dr_register_module_load_event()). Cependant avec ces bases, nous sommes en mesure des faire des choses sympas.
On va réaliser un client simple qui va aller hooker l’API native ZwQuerySystemInformation. L’idée est donc de trouver le bloc qui correspond au wrapper de ce syscall dans ntdll. Pour rappel ils sont de la forme :
mov eax,
mov edx, SharedUserData!SystemCallStub
call dword ptr [edx]
ret
Notre hook va posséder la forme d’un inline hook, avec donc un « jmp MyZwQuerySystemInformation » qui se chargera d’appeler la fonction originale puis de filtrer les résultats pour, par exemple, cacher un process.
Le prototype de la callblack que l’on enregistre avec dr_register_bb_event est :
dr_emit_flags_t (*)(void * drcontext, void * tag, instrlist_t * bb, bool for_trace, bool translating);
J’ai déjà expliqué les paramètres for_trace et translating. Tag est un identifiant unique pour le bloc, en fait c’est tout simplement l’adresse de sa première instruction. bb représente la liste des instructions du bloc et drcontext est une structure opaque représentant l’état de l’application. Dans notre callback on va donc récupérer l’adresse virtuelle qui correspond à chacune des instructions du bloc et vérifier si l’une d’entre elles est l’adresse de l’API ZwQuerySystemInformation dans ntdll. Pour cela on fait une boucle while sur la liste des instruction en retrouvant leur adresse avec l’API instr_get_app_pc(). Une fois qu’on a trouvé l’adresse nous créons une instructions de saut sur MyZwQuerySystemInformation avec le code : in_jmp=INSTR_CREATE_jmp(drcontext, opnd_create_pc((app_pc)MyZwQuerySystemInformation)); Enfin on insère cette instruction au début du bloc avec l’API instrlist_prepend. Au final on le code :
dr_emit_flags_t event_block(void * drcontext, void * tag, instrlist_t * bb, bool for_trace, bool translating)
{
instr_t *instr, *in_jmp, *in_call;
opnd_t call;
//dr_fprintf(drout, "event_block | tag : 0x%x | tid : %d | for_trace : %s | translating : %s\n", tag, //dr_get_thread_id(drcontext), for_trace ? "true" : "false", translating ? "true" : "false");
//instrlist_disassemble(drcontext, tag, bb, drout);
//
// We dont care about translation
//
if(for_trace)
return DR_EMIT_DEFAULT;
instr=instrlist_first(bb);
while(instr!=NULL)
{
//
// ZwQuerySystemInformation bloc
//
if((FARPROC)instr_get_app_pc(instr)==pZwQuerySystemInformation)
{
dr_fprintf(drout, "Syscall ZwQuerySystemInformation found !\nReplacing code ...\n");
in_jmp=INSTR_CREATE_jmp(drcontext, opnd_create_pc((app_pc)MyZwQuerySystemInformation));
instr_set_translation(in_jmp, instr_get_app_pc(instr));
instrlist_prepend(bb, in_jmp);
instrlist_disassemble(drcontext, tag, bb, drout);
return DR_EMIT_STORE_TRANSLATIONS;
}
instr=instr_get_next(instr);
}
return DR_EMIT_DEFAULT;
}
Du fait qu’on a modifié le code de l’API ZwQuerySystemInformation (attention uniquement dans le bloc, pas en mémoire je rappel) on ne plus y faire appel dans la fonction MyZwQuerySystemInformation (argh stack-overflow !). On recode donc directement la fonction avec le wrapper de syscall. Ce qui donne :
NTSTATUS __stdcall MyZwQuerySystemInformation(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength)
{
dr_fprintf(drout, "Calling ZwQuerySystemInformation\n"
"SystemInformationClass : 0x%x\n"
"SystemInformation : 0x%x\n"
"SystemInformationLength : 0x%x\n"
"ReturnLength : 0x%x\n",
SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength);
__asm
{
pop ebp
mov eax, 0xAD
mov edx, 0x7FFE0300
call dword ptr [edx]
ret 0x10
}
}
Voilà, maintenant c’est prêt, on lance le tout et on peut voir :
Syscall ZwQuerySystemInformation found !
Replacing code ...
TAG 0x7c91d910
+0 L4 e9 07 b6 9c ec jmp $0x10001000
+5 L3 b8 ad 00 00 00 mov $0x000000ad -> %eax
+10 L3 ba 00 03 fe 7f mov $0x7ffe0300 -> %edx
+15 L3 ff 12 call (%edx) %esp -> %esp (%esp)
END 0x7c91d910
Hop un hook d’api native sans modification de la mémoire !
Je m’arrête là pour aujourd’hui, j’espère vous avoir convaincu d’utiliser plus souvent cette techno pour le profiling et l’instrumentation de vos programmes. Personnellement j’aurais aimé l’utilisé dans un cadre plus offensif car pouvoir modifier du code sans toucher à la mémoire du binaire est toujours cool. Mais les requis de bases et la diminution (légère) de perfs ne m’ont pas emballé. En tout cas je n’irais pas foutre DynamoRio dans un rootkit. Qui plus est la lib ne fonctionne pas en kernel-land, ouais je sais, je me plains.
Encore une mauvaise nouvelle. J’ai testé un binaire packé avec UPX sur DynamoRio et il s’est lamentablement planté, dommage il aurait été intéressant d’appliquer ce genre de techno sur de tels binaires.
Bref, une techno super intéressante qui mérite qu’on s’y attarde mais qui demande à devenir plus mature dans le futur. Il faut bien sur reconnaitre le travail impressionnant réalisés par ses auteurs mais il manque encore des petites choses pour la rentre plus accessible.
Vous trouvez les codes+binaires ici :
http://ivanlef0u.fr/repo/DynamoRio.rar
Sinon, je vous conseil de tester Bochs, un émulateur de machine x86, pour ma part j’ai réussi à y installer un Windows XP et ça ne marche pas trop mal.
Aller aussi jouer avec NativeClient de Google, ca à l’air de bien défoncer.
Peter Ferrie lache un paper intéressant sur différentes méthodes d’anti-unpacking.
A signaler le retour de Arteam.
Le meilleur pour la fin : L33chma, je kiff ton boul !
Et surtout n’oubliez pas :
BE MAD !
janvier 6th, 2009
Putain, ça fait 1000 ans que j’ai pas bloggé, je suis surchargé en ce moment. Vous allez vous foutre de ma gueule mais je suis en train d’étudier des choses comme java/CPP/sql/xml/uml, disons que ce ne sont pas les choses les plus inintéressantes mais c’est très très chiant à bosser. J’ai tellement de projets et de rapports à rendre dessus que je n’ai même plus le temps de jouer avec mon Windows
Bref, je passe vite fait pour vous montrer une feature marrant de Windbg (d’ailleur la version 6.10.3.233 est sortie il y moins d’une semaine). Il s’agit d’une extension aidant au debugging de binaire VDM. Je crois qu’un exemple sera plus parlant qu’autre chose :
0:003> !vdmexts.load
0:003> !vdmexts.help
WOW commands are not currently available.
------------- VDMEXTS Debug Extension help:--------------
help [cmd] - Displays this list or gives details on command
ApiProfClr - Clears the api profiling table
ApiProfDmp [options] - Dumps the api profiling table
at 0xXXXX - shows name associated with hex atom #
bp <addr> - Sets a vdm breakpoint
bd/be <n> - Disables/enables vdm breakpoint 'n'
bl - Lists vdm breakpoints
chkheap - Checks WOW kernel's global heap
cia - Dump cursor/icon alias list
d<b|w|d> <addr> [len] - Dump vdm memory
ddemem - Dump dde memory thunks
ddte <addr> - Dump dispatch table entry pointed to by <addr>
denv <bProt> <selEnv> - Dump environment for current task or given selector/segment
df [vector] - Dump protect mode fault handler address
dfh [fh [pdb]] - Dump DOS file handles for current or given PDB
dg <sel> - Dump info on a selector
ddh [seg] - Dump DOS heap chain starting at <seg>:0000
dgh [sel|ownersel] - Dump WOW kernel's global heap
dhdib [@<address>] - Dump dib.drv support structures (DIBINFO)
di [vector] - Dump protect mode interrupt handler address
dma - Dump virtual DMA state
dpd - Dump DPMI DOS memory allocations
dpx - Dump DPMI extended memory allocations
dsft [sft] - Dump all or specified DOS system file tables
dt [-v] <addr> - Dump WOW Task Info
dwp <addr> - Dump WOWPORT structure pointed to by <addr>
e<b|w|d> <addr> <data> - Edit vdm memory
filter [options] - Manipulate logging filter
fs <text to find> - Find text in 16:16 memory (case insensitive)
glock <sel> - Increments the lock count on a moveable segment
gmem - Dumps Global/heap memory alloc'd by wow32
gunlock <sel> - Decrements the lock count on a moveable segment
hgdi16 [-v] <h16> - Returns 32-bit GDI handle for <h16>
hgdi32 [-v] <h32> - Returns 16-bit GDI handle for <h32>
ica - Dump Interrupt Controller state
k - Stack trace
kb - Stack trace with symbols
LastLog - Dumps Last Logged WOW APIs from Circular Buffer
lg [#num] [count] - Dumps NTVDM history log
lgr [#num] [count] - Dumps NTVDM history log (with regs)
lgt [1|2|3] - Sets NTVDM history log timer resolution
lm <sel|modname> - List loaded modules
ln [addr] - Determine near symbols
LogFile [path] - Create/close toggle for iloglevel capture to file
(path defaults to c:\ilog.log)
MsgProfClr - Clears the msg profiling table
MsgProfDmp [options] - Dumps the msg profiling table
ntsd - Gets an NTSD prompt from the VDM prompt
r - Dump registers
rmcb - Dumps dpmi real mode callbacks
SetLogLevel xx - Sets the WOW Logging Level
StepTrace - Toggles Single Step Tracing On/Off
sx - Displays debugging options
sx<d|e> <flag> - Disables/enables debugging options
timer - Display 8253 timer 0 information
u [addr] [len] - Unassemble vdm code with symbols
wc <hwnd16> - Dumps the window class structure of <hwnd16>
ww <hwnd16> - Dumps the window structure of <hwnd16>
x <symbol> - Get symbol's value
-------------- i386 specific commands
fpu - Dump 487 state
pdump - Dumps profile info to file \profile.out
pint - Sets the profile interval
pstart - Causes profiling to start
pstop - Causes profiling to stop
vdmtib [addr] - Dumps the register context in the vdmtib
where [options] can be displayed with 'help <cmd>'
Avec ça on peut voir facilement les exeptions et interrupt handlers mis en place par le binaire ntvdm.exe avec les couples (segment:offset) :
0:000> !vdmexts.df ; Dump protect mode fault handler address
00: 00C7:00001200
01: 00C7:00001205
02: 00C7:0000120A
03: 00C7:0000120F
04: 00C7:00001214
05: 00C7:00001219
06: 00C7:0000121E
07: 00C7:00001223
08: 00C7:00001228
09: 00C7:0000122D
0A: 00C7:00001232
0B: 00C7:00001237
0C: 00C7:0000123C
0D: 00C7:00001241
0E: 00C7:00001246
0F: 00C7:0000124B
10: 00C7:00001250
11: 00C7:00001255
12: 00C7:0000125A
13: 00C7:0000125F
14: 00C7:00001264
15: 00C7:00001269
16: 00C7:0000126E
17: 00C7:00001273
18: 00C7:00001278
19: 00C7:0000127D
1A: 00C7:00001282
1B: 00C7:00001287
1C: 00C7:0000128C
1D: 00C7:00001291
1E: 00C7:00001296
1F: 00C7:0000129B
0:000> !vdmexts.di ; Dump protect mode interrupt handler address
00: 00C7:00000D00
01: 00C7:00001307
02: 00C7:00000D0A
03: 00C7:00001307
04: 00C7:00000D14
05: 00C7:00000D19
06: 00C7:00000D1E
07: 00C7:00000D23
08: 00C7:00000D28
09: 00C7:00000D2D
0A: 00C7:00000D32
0B: 00C7:00000D37
0C: 00C7:00000D3C
0D: 00C7:00000D41
0E: 00C7:00000D46
0F: 00C7:00000D4B
10: 00C7:00001393
11: 00C7:00000D55
12: 00C7:00000D5A
13: 00C7:00002870
14: 00C7:00000D64
15: 00C7:000014A7
16: 00C7:00000D6E
17: 00C7:00000D73
18: 00C7:00000D78
19: 00C7:00001308
1A: 00C7:00000D82
1B: 00C7:00000D87
1C: 00C7:00000D8C
1D: 00C7:00000D91
1E: 00C7:00000D96
1F: 00C7:00000D9B
20: 00C7:00000DA0
21: 00C7:000004E6
22: 00C7:00000DAA
23: 00C7:00000DAF
24: 00C7:00000DB4
25: 00C7:000029CD
26: 00C7:00002A85
27: 00C7:00000DC3
28: 00C7:000012B8
29: 00C7:00000DCD
2A: 00C7:0000315E
2B: 00C7:00000DD7
2C: 00C7:00000DDC
2D: 00C7:00000DE1
2E: 00C7:00000DE6
2F: 00C7:00000DEB
30: 00C7:00001307
31: 00C7:000012DC
32: 00C7:00000DFA
33: 00C7:0000155D
34: 00C7:00000E04
35: 00C7:00000E09
36: 00C7:00000E0E
37: 00C7:00000E13
38: 00C7:00000E18
39: 00C7:00000E1D
3A: 00C7:00000E22
3B: 00C7:00000E27
3C: 00C7:00000E2C
3D: 00C7:00000E31
3E: 00C7:00000E36
3F: 00C7:00000E3B
40: 00C7:00000E40
41: 00C7:00001307
42: 00C7:00000E4A
43: 00C7:00000E4F
44: 00C7:00000E54
45: 00C7:00000E59
46: 00C7:00000E5E
47: 00C7:00000E63
48: 00C7:00000E68
49: 00C7:00000E6D
4A: 00C7:00000E72
4B: 00C7:00000E77
4C: 00C7:00000E7C
4D: 00C7:00000E81
4E: 00C7:00000E86
4F: 00C7:00000E8B
50: 00C7:00000E90
51: 00C7:00000E95
52: 00C7:00000E9A
53: 00C7:00000E9F
54: 00C7:00000EA4
55: 00C7:00000EA9
56: 00C7:00000EAE
57: 00C7:00000EB3
58: 00C7:00000EB8
59: 00C7:00000EBD
5A: 00C7:00000EC2
5B: 00C7:00000EC7
5C: 00C7:0000318B
5D: 00C7:00000ED1
5E: 00C7:00000ED6
5F: 00C7:00000EDB
60: 00C7:00000EE0
61: 00C7:00000EE5
62: 00C7:00000EEA
63: 00C7:00000EEF
64: 00C7:00000EF4
65: 00C7:00000EF9
66: 00C7:00000EFE
67: 00C7:00000F03
68: 00C7:00000F08
69: 00C7:00000F0D
6A: 00C7:00000F12
6B: 00C7:00000F17
6C: 00C7:00000F1C
6D: 00C7:00000F21
6E: 00C7:00000F26
6F: 00C7:00000F2B
70: 00C7:00000F30
71: 00C7:00000F35
72: 00C7:00000F3A
73: 00C7:00000F3F
74: 00C7:00000F44
75: 00C7:00000F49
76: 00C7:00000F4E
77: 00C7:00000F53
78: 00C7:00000F58
79: 00C7:00000F5D
7A: 00C7:00000F62
7B: 00C7:00000F67
7C: 00C7:00000F6C
7D: 00C7:00000F71
7E: 00C7:00000F76
7F: 00C7:00000F7B
80: 00C7:00000F80
81: 00C7:00000F85
82: 00C7:00000F8A
83: 00C7:00000F8F
84: 00C7:00000F94
85: 00C7:00000F99
86: 00C7:00000F9E
87: 00C7:00000FA3
88: 00C7:00000FA8
89: 00C7:00000FAD
8A: 00C7:00000FB2
8B: 00C7:00000FB7
8C: 00C7:00000FBC
8D: 00C7:00000FC1
8E: 00C7:00000FC6
8F: 00C7:00000FCB
90: 00C7:00000FD0
91: 00C7:00000FD5
92: 00C7:00000FDA
93: 00C7:00000FDF
94: 00C7:00000FE4
95: 00C7:00000FE9
96: 00C7:00000FEE
97: 00C7:00000FF3
98: 00C7:00000FF8
99: 00C7:00000FFD
9A: 00C7:00001002
9B: 00C7:00001007
9C: 00C7:0000100C
9D: 00C7:00001011
9E: 00C7:00001016
9F: 00C7:0000101B
A0: 00C7:00001020
A1: 00C7:00001025
A2: 00C7:0000102A
A3: 00C7:0000102F
A4: 00C7:00001034
A5: 00C7:00001039
A6: 00C7:0000103E
A7: 00C7:00001043
A8: 00C7:00001048
A9: 00C7:0000104D
AA: 00C7:00001052
AB: 00C7:00001057
AC: 00C7:0000105C
AD: 00C7:00001061
AE: 00C7:00001066
AF: 00C7:0000106B
B0: 00C7:00001070
B1: 00C7:00001075
B2: 00C7:0000107A
B3: 00C7:0000107F
B4: 00C7:00001084
B5: 00C7:00001089
B6: 00C7:0000108E
B7: 00C7:00001093
B8: 00C7:00001098
B9: 00C7:0000109D
BA: 00C7:000010A2
BB: 00C7:000010A7
BC: 00C7:000010AC
BD: 00C7:000010B1
BE: 00C7:000010B6
BF: 00C7:000010BB
C0: 00C7:000010C0
C1: 00C7:000010C5
C2: 00C7:000010CA
C3: 00C7:000010CF
C4: 00C7:000010D4
C5: 00C7:000010D9
C6: 00C7:000010DE
C7: 00C7:000010E3
C8: 00C7:000010E8
C9: 00C7:000010ED
CA: 00C7:000010F2
CB: 00C7:000010F7
CC: 00C7:000010FC
CD: 00C7:00001101
CE: 00C7:00001106
CF: 00C7:0000110B
D0: 00C7:00001110
D1: 00C7:00001115
D2: 00C7:0000111A
D3: 00C7:0000111F
D4: 00C7:00001124
D5: 00C7:00001129
D6: 00C7:0000112E
D7: 00C7:00001133
D8: 00C7:00001138
D9: 00C7:0000113D
DA: 00C7:00001142
DB: 00C7:00001147
DC: 00C7:0000114C
DD: 00C7:00001151
DE: 00C7:00001156
DF: 00C7:0000115B
E0: 00C7:00001160
E1: 00C7:00001165
E2: 00C7:0000116A
E3: 00C7:0000116F
E4: 00C7:00001174
E5: 00C7:00001179
E6: 00C7:0000117E
E7: 00C7:00001183
E8: 00C7:00001188
E9: 00C7:0000118D
EA: 00C7:00001192
EB: 00C7:00001197
EC: 00C7:0000119C
ED: 00C7:000011A1
EE: 00C7:000011A6
EF: 00C7:000011AB
F0: 00C7:000011B0
F1: 00C7:000011B5
F2: 00C7:000011BA
F3: 00C7:000011BF
F4: 00C7:000011C4
F5: 00C7:000011C9
F6: 00C7:000011CE
F7: 00C7:000011D3
F8: 00C7:000011D8
F9: 00C7:000011DD
FA: 00C7:000011E2
FB: 00C7:000011E7
FC: 00C7:000011EC
FD: 00C7:000011F1
FE: 00C7:000011F6
FF: 00C7:000011FB
Je sais, je balance tout cela sans explications mais je connais quelqu’un que ça intéressa au plus haut point. Je continuerais l’exploration de la machine virtuelle DOS prochainement, il faut juste que je trouve du temps pour le faire. Sinon pour ceux qui se le demande Abyss n’est pas mort, je projette aussi d’en faire quelque chose de marrant dans le futur :]
novembre 25th, 2008
J’avais dit que je coderais une version générique pour mon outil TaskPwdDmp, c’est fait ! Après quelques retours me signalant que mon programme ne marchait à cause de l’utilisation d’offsets harcodés pour retrouver la fonction schedsvc!DecryptCredentials je me suis décidé à faire quelque chose de propre. Au lieu de fournir une liste de tous les offets possible de Windows XP à 2003 avec toutes les versions de DLLs possibles j’ai préféré opter pour une autre méthode plus simple.
La fonction DecryptCredentials de schedsvc.dll n’est pas exportée, le seul moyen donc de connaître son adresse précise est d’utiliser les symbols fournit par Microsoft sur leur site. Ainsi des programmes comme IDA, WinDbg ou Process Explorer utilisent ses symbols pour rendre le debugging/dissasembling/tracing/monitoring de binaires Windows plus aisé.
L’idée est donc simple, rendre l’outil TaskPwdDmp capable d’utiliser lui aussi les symbols pour obtenir l’adresse d’une fonction précise. En utilisant le moteur d’aide au debugging qui se charge d’obtenir la bonne version de symbols de manière dynamique on évite de se prendre la tête avec la version de l’OS, du Service Pack, de la localisation et de schedsvc.dll.
Les symbols sont en général représentés par des fichiers d’extensions .pdb. Par défaut Visual Studio génère un fichier .pdb pour chaque fichier .obj, en fonction des options de compilations bien sur. Microsoft met à disposition ses fichiers .pdb pour presque toutes les versions des binaires de son OS x86 ou x64, allant de Windows 2000 jusqu’à Windows Server 2008.
L’outil va donc aller chercher sur le net les symbols pour la DLL schedsvc pour lire l’adresse de la fonction DecryptCredentials. Cela rajoute donc une contrainte qui demande d’avoir l’accès au net mais de l’autre coté nous obtenons plus de fiabilité.
Pour obtenir les informations d’aide au debugging on utilise la librairie DbgHelp.dll. Cette DLL dont la dernière version est fournie dans les Debugging Tools permet de manipuler toutes les APIs de gestion des symbols, pour être plus précis : l’application des fichiers .pdb aux modules chargés en mémoire, aux stackframes, aux threads ainsi que l’aide du parsing entre les fichiers sources et binaires associés. D’un autre coté, nous avons aussi la DLL symsrv.dll qui elle à pour rôle de gérer le symbol server et le symbol store, les endroits ou sont disponibles les symbols et ou les stocker.
Il faut d’abord initialiser le moteur de gestion des symbols avec l’API SymInitialize, cette API demande le handle du process auquel seront appliquées toutes les APIs suivante ainsi qu’un argument spécifiant ou non si on doit chargé tous les symbols de tous les modules. Dans notre cas nous n’avons besoin que des symbols d’un seul module, ceux de schedsvc.dll, nous allons donc éviter de demander à DbgHelp.dll de retrouver tous les symbols. Pour cela, il est possible de spécifier à l’aide de SymSetOptions le flag SYMOPT_DEFERRED_LOADS qui demande au gestionnaire de symbols de les charger quand ceux ci sont nécessaire, cela évite de consommer trop d’espace mémoire et d’attendre 1000 ans qu’ils soient tous mappés.
Ensuite avec SymLoadModuleEx nous demandons de charger les symbols pour le module schedsvc.dll du binaire svchost.exe qui l’héberge. C’est à ce moment qu’intervient le gestionnaire symsrv, voyant que le .pdb n’existe pas dans le symbol store montré par la variable d’environnement _NT_SYMBOL_PATH , il va directement télécharger le .pdb (s’il existe !) depuis le symbol server qui lui aussi peut être définit avec NT_SYMBOL_PATH. En général ce symbol server est ‘http://msdl.microsoft.com/download/symbols’, on peut d’ailleur voir les requêtes tentées par DbgHelp.dll (visiblent sous forme de messages de debug) avant de faire appel à symsrv.dll :
DBGHELP: _NT_SYMBOL_PATH: SRV*C:\WINDOWS\TEMP*http://msdl.microsoft.com/download/symbols
DBGHELP: Symbol Search Path: .;SRV*C:\WINDOWS\TEMP*http://msdl.microsoft.com/download/symbols
DBGHELP: .\schedsvc.pdb - file not found
DBGHELP: .\dll\schedsvc.pdb - file not found
DBGHELP: .\symbols\dll\schedsvc.pdb - file not found
SYMSRV: schedsvc.pdb from http://msdl.microsoft.com/download/symbols: 56991 bytes - 0 percent copied
DBGHELP: schedsvc - public symbols
C:\WINDOWS\TEMP\schedsvc.pdb\21D8A0C07CFF463CA338812BD32887191\schedsvc.pdb
Normalement symsrv nous demande dans une DialogBox d’accepter une licence, pour éviter cela on crée le fichier ‘symsrv.yes’ dans le même répertoire que symsrv.dll. Pour plus d’infos voir la ProcessEula dans symsrv.dll.
Après l’API SymFromName est capable à partir d’un nom de retrouver toutes les infos symboliques associées, on obtient une structure SYMBOL_INFO qui contient notamment l’adresse en mémoire.
typedef struct _SYMBOL_INFO
{
ULONG SizeOfStruct;
ULONG TypeIndex;
ULONG64 Reserved[2];
ULONG Index;
ULONG Size;
ULONG64 ModBase;
ULONG Flags;
ULONG64 Value;
ULONG64 Address;
ULONG Register;
ULONG Scope;
ULONG Tag;
ULONG NameLen;
ULONG MaxNameLen;
TCHAR Name[1];
} SYMBOL_INFO, *PSYMBOL_INFO;
A partir de là 2 choix s’offrent à nous.
- Le binaire qui injecte la DLL va télécharger les symbols sur le net et envoyer l’information à la DLL injectée dans svchost.exe. Cela demande de mettre un place un mécanisme d’IPC.
- La dll injectée retrouve elle même les symbols sur le net directement depuis le processus svchost.exe.
Au début j’ai opté pour la 2ème solution parce que c’était plus simple à implémenter, qui plus est,dans le cas ou il existe un firewall sur le b0x, une connexion extérieure depuis un processus svchost.exe est relativement plus « normale » que celle provenant d’un binaire inconnu.
Après de nombreux essais mon code dans la DLL injectée ne marchait pas alors qu’un binaire stand-alone avec le même code fonctionnait quand à lui. De ce que j’ai pu voir, il existe plusieurs classes dans symsrv.dll qui sont : StoreWinHTTP, StoreHTTP, StoreWinInet et StoreUNC. Ces classes définissent quelles DLLs seront utilisées pour accéder au net, par exemple StoreWinHTTP utilise la DLL winhttp.dll et StoreWinInet utilise wininet.dll. En regardant avec Process Monitor j’ai vu que mon binaire stand-alone utilisait la librairie wininet alors que le même code dans la DLL injectée tentait une connexion avec winhttp.dll. Bref, j’ai un bug quantique et je n’arrive pas à trouver comment symsrv choisit quelle DLL il va utiliser, c’est très bizzare.
Changement de direction donc, retour à la 1ère solution. Le binaire va retrouver les symbols sur le net et les communiquer à la DLL. La communication entre les 2 processes est réalisée avec un named pipe qu’on crée avec CreateNamedPipe, on se place en écoute avec ConnectNamedPipe. Le processus d’injection écrit dans le pipe l’adresse de DecryptCredentials avec un simple WriteFile. La DLL connaissant l’adresse peut maintenant hooker la fonction et le binaire principal faire appel à la fonction ITask::GetAccountInformation pour trigger le hook.
Au final on a donc le code suivant qui retrouve l’adresse de DecryptCredentials :
/*++
Routine Description:
RetrieveDecryptCredentialsAddress
Arguments:
Task Scheduler svchost'pid.
Return Value:
Address of DecryptCredentials. 0 otherwise.
--*/
DWORD RetrieveDecryptCredentialsAddress(DWORD TaskSrvPid)
{
PSYMBOL_INFO Symbol;
HANDLE hProcess;
ULONG64 SymBase;
ULONG64 Address=0;
SymSetOptions(SYMOPT_UNDNAME|SYMOPT_DEBUG|SYMOPT_DEFERRED_LOADS|SYMOPT_PUBLICS_ONLY);
hProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, TaskSrvPid);
if(hProcess==NULL)
{
printf("Error with OpenProcess : %lu\n", GetLastError());
return 0;
}
//
// Set _NT_SYMBOL_PATH for the current process
//
SetEnvironmentVariable("_NT_SYMBOL_PATH", "SRV*C:\\WINDOWS\\TEMP*http://msdl.microsoft.com/download/symbols");
if(!SymInitialize(hProcess, NULL, TRUE))
{
printf("Error with SymInitialize : %lu\n", GetLastError());
CloseHandle(hProcess);
return 0;
}
//
// Load symbol module from database
//
SymBase=SymLoadModuleEx(hProcess, NULL, TaskSchedDll, NULL, (ULONG64)GetRemoteHandle(TaskSrvPid, TaskSchedDll), 0, NULL, 0);
//
// If the module is already loaded, the return value is zero and GetLastError returns ERROR_SUCCESS
//
if((SymBase==0) && (GetLastError()!=ERROR_SUCCESS))
{
printf("Error with SymLoadModuleEx : %lu\n", GetLastError());
SymCleanup(GetCurrentProcess());
CloseHandle(hProcess);
return 0;
}
//
// Allocate symbol struct
//
Symbol=(PSYMBOL_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SYMBOL_INFO)+MAX_SYM_NAME);
if(Symbol==NULL)
{
printf("Cannot allocate Symbol struct\n");
SymCleanup(GetCurrentProcess());
CloseHandle(hProcess);
return 0;
}
RtlZeroMemory(Symbol, sizeof(SYMBOL_INFO)+MAX_SYM_NAME);
Symbol->SizeOfStruct=sizeof(SYMBOL_INFO);
Symbol->MaxNameLen=MAX_SYM_NAME;
//
// Retrieve address of DecryptCredentials
//
if(!SymFromName(hProcess, "DecryptCredentials", Symbol))
{
printf("Error with SymFromName : %lu\n", GetLastError());
CloseHandle(hProcess);
HeapFree(GetProcessHeap(), 0, Symbol);
SymCleanup(GetCurrentProcess());
return 0;
}
Address=Symbol->Address;
printf("DecryptCredentials function is at : 0x%I64x\n", Address);
//
// Clean
//
SymCleanup(hProcess);
HeapFree(GetProcessHeap(), 0, Symbol);
CloseHandle(hProcess);
return (DWORD)Address;
}
A noter que le 5ème paramètre de SymLoadModuleEx est l’ImageBase du module dans le process svchost.exe, d’ou ma fonction GetRemoteHandle qui va retrouver l’ImageBase de schedsvc.dll à distance avec CreateToolhelp32Snapshot (option TH32CS_SNAPMODULE).
Pour finir nous avons donc la sortie :
C:\sym>TaskPwdDmp.exe
Windows Task Scheduler credentials dumper
By Ivanlef0u, thanks to Nicolas Ruff
BE M4D!
Works on Windows XP and 2003
Retrieves schedsvc!DecryptCrendetials address with symbols
/|
\`O.o'
=(_|_)=
U
There is 1 tasks in queue
Current tasks are :
Calculatrice
schedsvc.dll found in process [svchost.exe:1744]
Task Scheduler svchost pid is : 1744
DecryptCredentials function is at : 0x76b22962
Injecting Dll TaskPwdDmpDll.dll in process : 1744
FullDllPath : C:\sym\TaskPwdDmpDll.dll
[Msg from PID 1744]-> Dll injected in process 1744
[Msg from PID 1744]-> schedsvc dll is at : 0x76B10000
[Msg from PID 1744]-> Listenning thread created with TID : 856
[Msg from PID 1744]-> schedsvc!DecryptCredentials is at: 0x76b22962
Dumping credentials ...
[Msg from PID 1744]-> Credentials fu:fr
Dll successfully unloaded
Vous trouverez le binaire ici :
http://ivanlef0u.fr/repo/TaskPwdDmpSymbols.rar
Voilà j’espère que cette solution vous plait, j’attends vos retours avec impatience.
Merci à 0vercl0k pour les beta-tests :p
octobre 27th, 2008
Les auteurs du fameux journal technique uninformed sortent leur dernier opus disponible ici. Au programme :
Can you find me now? Unlocking the Verizon Wireless xv6800 (HTC Titan) GPS de Skywing.
Using dual-mappings to evade automated unpackers de Skape. Un court article expliquant comment bypasser les unpackers automatiques qui retrouvent l’OEP en regardant les pages mémoire exécutés par le binaire. Du fait que ces tools manipulent uniquement des adresses virtuelles, il est possible de mettre en place une zone mémoire unique possédant 2 adresses virtuelles dans le contexte du packer. Ainsi d’un coté le packer écrit le code dans une mémoire virtuelle alors qu’il sera exécuté dans une autre, dans la mémoire physique ce sont la ou les mêmes pages qui sont écrites et exécutées. L’outil d’unpacking automatique ni voit que du feu et croit qu’il s’agit de code provenant du packer.
Analyzing local privilege escalations in win32k de mxatone (Han but kikoo !! sale pourriture communiste @#!!!). Article décrivant en partie le fonctionnement du système GDI win32.sys de Windows. Ensuite mxatone nous montre des faiblesses au niveau de 3 API et l’art de les exploiter, un régal !
Exploiting Tomorrow’s Internet Today: Penetration testing with IPv6 de H D Moore.
Sinon de mon coté je continue d’étudier la gestion du mode virtuel sous Windows, j’espère pour avancer rapidement, je prépare aussi une mise à jour pour rendre plus générique mon outil qui permet de dumper les credentials stockés pour la gestion des tâches planifiées. Restez connecté !
Bonne lecture à tous.
octobre 12th, 2008
Next Posts
Previous Posts