Posts filed under 'RE'

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

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

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

EMET failed

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

12 comments novembre 1st, 2009

ProcessIoPortHandlers

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 :

  1. Un driver pouvant recevoir des IOCTLs, il sert principalement à appeler ZwSetInformationProcess sur ProcessIoPortHandlers et définit la routine de gestion des I/Os.
  2. 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.
  3. 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 :

  1. 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.
  2. 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

8 comments septembre 26th, 2009

Debugging with LEDs

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

    11 comments août 14th, 2009

    Return of the SMM

    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 :

    1. 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 :
      mtrr_caching

      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 :
      mtrr_mapping2

      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.
      mtrr_struct

      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.

    2. 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.
    3. 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

    6 comments mars 29th, 2009

    IDT Sniffer

    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 :
    idt

    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.
    idtgates

    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 :
    protect

    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 :
        1. IDT'' passe en R/W.
        2. 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.
        3. On active le TF flags de l’EFlags pour passer en single step.
        4. Retour d’exception avec IRET.
        5. 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 :

    1. 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.
    2. 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.
    3. 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.
    4. A partir d’ici 2 cas se présentent :
      1. Le compteur de l’entrée IDT0[i] n’a pas atteint 8. On restaure IDT'' à l’aide de FirstIDT.
      2. 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.
    5. IDT'' repasse en RO. Flush des caches pour que tout ca soit propre.
    6. On peut penser à ajouter ici une vérification du CR0.WP.
    7. 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 :

    1. 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''.
    2. L’instruction écrit dans IDT''
    3. 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

    3 comments février 26th, 2009

    Playing with DynamoRio

    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 ). 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 :
    bloc_cache

    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 :
    flow

    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 !

    4 comments janvier 6th, 2009

    Previous Posts


    Calendar

    novembre 2024
    L Ma Me J V S D
    « fév    
     123
    45678910
    11121314151617
    18192021222324
    252627282930  

    Posts by Month

    Posts by Category