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
Bypassing PatchGuard on Windows x64
Subverting PatchGuard Version 2
PatchGuard Reloaded: A Brief Analysis of PatchGuard Version 3
5 comments mars 30th, 2010