Bonjour à tous,
Je profite de mes petites vacances pour attaquer la programmation de driver.
Cela me trottait dans la tête depuis pas mal de temps enfaite, je vous dévoile alors aujourd'hui mes premier pas dans ce nouveau monde.
Conseils préliminaires :
- Je vous recommande encore une fois, l'utilisation massive de machines virtuels, et de snapshots.
Tout simplement, parce que les Blues Screen Of Death (abrégé BSOD) arrivent très rapidement.
- Ensuite, tout comme dans l'userland (ring3), des outils de debugs sont les bienvenus, je vous conseille donc d'installer les "Debugging Tools for Windows".
- Pour pouvoir mener vos test sur les drivers, je vous recommande "Driver Loader" qui va permettre d'enregistrer votre driver au sein du système.
Vous pouvez ensuite le lancer avec aisance par le biais de la commande "net" :
net start votredriver
Le programme "WinObj" peut aussi vous être utile.
- Et bien sur, le WDK (Windows Driver Kit)!
Cette iso va contenir de la documentation très bien faite, les librairies nécessaires au développement de vos drivers, ..tous ce que vous aurez besoins pour coder vos drivers en tout cas :).
Je cite :
"The Windows Driver Kit (WDK) is a fully integrated driver development system for the Microsoft Windows family of operating systems. It contains the Windows DDK and tests that Microsoft uses to test the stability and reliability of the Windows operating system."
source : http://www.microsoft.com/whdc/devtools/WDK/AboutWDK.mspx.
Voilà tout pour le moment.
Je vous propose à présent, quelques lignes sur la configuration du debuggeur de kernel (kd).
Debug your fucking vm.
Pour pouvoir debugguer vos futurs driver, ou votre kernel, vous avez besoin d'un serveur prêt à être debugguer, ou une machine virtuel.
En ce qui me concerne j'ai choisis la solution machine virtuel, cela est bien plus simple.
Afin de connecter le kd à votre machine virtuel il va falloir tout d'abord ajouter un Port série à votre vm.
On choisit ensuite de faire transiter les données par un named pipe.
On configure la connection de la named pipe avec le port série.
N'oublions pas de cliquer sur le bouton "Advanced" et de cochez "Yield CPU on poll".
Une fois configurer notre port série, on va modifier un petit peu le boot.ini pour que la machine supporte le remote debugging.
Vous ouvrez donc le boot.ini avec notepad++ par exemple, vous copiez collez la ligne dans le [Operating systems] qui existe déjà, vous vous retrouvez avec deux lignes identiques.
Vous rajoutez sur l'une des deux cela :
/debug /debugport=COM1:
Je me retrouve avec un boot.ini qui ressemble à cela :
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professionnel" /noexecute=optin /fastdetect /kernel=oemkrnl.exe
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professionnel" /noexecute=optin /fastdetect /kernel=oemkrnl.exe /debug /debugport=COM1:
A présent, on redemare notre vm..et là au boot, nous avons le choix entre un démarrage classsique, et un démarrage avec le debug activé!
Il est temps de sortir WinDbg.
Vous allez dans le menu "File"->"Kernel Debug" et on va le configurer pour que les données entre la vm et le debug transite par le named pipe.
On valide, et Break (CTR+Break), et vous devriez vous retrouvez avec le prompt du kd.
Je vous conseil aussi de télécharger les symbols : http://msdl.microsoft.com/download/symbols.
Ensuite vous vous creez une variable d'environnement qui porte le nom :
_NT_SYMBOL_PATH
et comme valeur :
SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols
Vous êtes donc prêt à debugguer vos drivers !
Ceci dit je vous donne un peu de documentations :
- Si vous voulez connecter virtualpc a windbg -> http://therecyclebin.wordpress.com/2007/06/11/kernel-debugging-windbg-and-virtual-pc/.
- Windbg -> http://software.rkuster.com/.
- WDK -> http://connect.microsoft.com/.
Introduction
"One Ring to rule them all, One Ring to find them, One Ring to bring them all and in the darkness bind them." The Fellowhip of the Ring, J. R. R. TOLKIEN.
Je vais consacrer ces quelques lignes à présenter un peu le ring0.
Comme vous le savez peut être déjà tous, les processeurs mettent à disposition 4 niveaux de privilèges appelés rings (ring0 , .. , ring3).
Le système windows en utilise que deux d'entre eux à savoir :
- L'userland ou le ring3 (abrégé r3).
- Et le kerneland ou le ring0 (abrégé r0).
Le ring0 va permettre la gestion de la mémoire virtuelle par exemple, ou encore la gestion d'un matériel connecté à votre ordinateur ; c'est enfaite le coeur du système.
Comme la citation le laisse entrevoir, dans le ring0 c'est nous le maître :).
En revanche le ring3 est ce qui est le plus classique, les exécutables classiques, notre bon vieux client irc, ou notre player de musique préférée ; ceci dit cette espace est restreint à des règles.
On retrouve souvent le fonctionnement suivant : une application est exécutée depuis l'userland, celle-ci va envoyer des informations à un driver qui lui va pouvoir agir en conséquence, en lui renvoyant si nécessaire des informations de retours.
If faut savoir aussi que la taille des registres de nos processeurs s'élève à 32bits (architecture x86), nous avons donc accès a 2^32 adresses mémoire, soit 4go : 2 pour l'userland (de 0x00000000 à 0xBFFFFFFF) et 2 pour le kerneland (de 0xC0000000 à 0xFFFFFFFF).
A présent notre but va être de coder des programmes capables de survivre dans ce monde où il n'est question que de BSOD (mais nan :)).
On appellera ces programmes des drivers.
Et pourquoi pas arriver vers un rootkit de base commandé de l'userland ? :).
Place au coding à présent.
Message venu du nouveau monde.
Après cette brève introduction au ring0, nous allons pouvoir aborder la programmation de driver.
Le point d'entré d'un driver est celui-ci :
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
}
Avec pDriverObject qui est un pointeur sur une structure du type DRIVER_OBJECT.
Celle-ci représente le driver chargé, et le second argument pRegistryPath est une chaine de caractères UNICODE de la forme suivante :
\Registry\Machine\System\CurrentControlSet\Services\NomDuDriver
Ensuite si vous voulez déchargez un driver, il faut spécifier une fonction d'unload par le biais du membre DriverUnload de la structure DRIVER_OBJECT.
pDriverObject->DriverUnload = VotreFonction;
Et enfin, on utilise l'équivalent de printf() mais au niveau kerneland à savoir : DbgPrint().
Les messages seront bien sur visible grâce à un outil utilisé dans des articles précédents : DebugView.
Ceci nous amène donc à la compilation!
Euh..On compile cela comment?
J'y viens.:)
Tout d'abord j'utiliserais le compilateur disponible avec le wdk, cela évitera de se casser la tête.
Deux fichiers sont nécéssaires à la compilation :
- le makefile.
- et un fichier "sources" (c'est enfaite le nom des fichiers de projets sous le wdk).
Le fichier makefile est composé d'une seule ligne :
!INCLUDE $(NTMAKEENV)\makefile.def
Ensuite le fichier sources est décomposé de la façon suivante :
TARGETNAME = LeNomDuDriver
TARGETPATH = obj
TARGETTYPE = DRIVER
INCLUDES = %BUILD%\inc
LIBS = %BUILD%\lib
SOURCES = VotreSource.c
Nous sommes donc prêt à compiler notre premier driver, émotion au rendez vous !
Nous ouvrons le build environment xp.
On se déplace dans notre dossier contenant les trois fichiers, et on lance la commande build.
Oh..un .sys :), il s'agit de notre driver.
Mais comment le lance t-on?
Patience, nous y voilà.
Dans les premières lignes de ce modeste article, je vous proposais plusieurs outils, dont DriverLoader.
DriverLoader va nous permettre d'enregistrer notre driver, et de pouvoir le lancer.
Nous demarrons DebugView, on lance DriverLoader, et on lance notre driver.
Et voilà, notre helloWorld fonctionne :).
A présent, just for phun, nous allons provoquer un BSOD, histoire d'en réver pendant une semaine.
On va aller écrire n'importe quoi à n'importe qu'elle adresse enfaite :).
Resultat en image :
Enfin bon, a présent passons aux choses sérieuses.
E.T téléphone kernel.
Maintenant notre but va être de faire communiquer une application lancée en ring3 et un driver en ring0.
Nous commencerons par le driver.
Pour mener à bien notre quête, nous allons utiliser les IOCTLs codes et les IRPs.
Mais nous en parlerons un peu plus bas.
Afin de pouvoir communiquer avec l'exterieur, nous avons besoin de creer un "device" une sorte d'interface.
NTSTATUS
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject
);
N'oublions pas que les chaines mis en jeux sont des chaines unicodes.
Ensuite il est nécéssaire de creer un symlink entre le nom de notre interface, et le nom visible de l'userland.
Ce symlink permettra d'avoir accès à notre device depuis le r3.
NTSTATUS
IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName
);
Après ces étapes, nous allons "handler" des fonctions à déclencher selon la réception des différentes IRP.
Je m'explique, une IRP est enfaite un paquet d'information envoyé à un driver, il existe plusieurs type d'IRP (voir documentation).
A l'intérieur de la structure DRIVER_OBJECT il existe un membre nommé MajorFunction qui est tableau de pointeur.
kd> dt nt!_DRIVER_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 Void
+0x010 DriverSize : Uint4B
+0x014 DriverSection : Ptr32 Void
+0x018 DriverExtension : Ptr32 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING
+0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
+0x028 FastIoDispatch : Ptr32 _FAST_IO_DISPATCH
+0x02c DriverInit : Ptr32 long
+0x030 DriverStartIo : Ptr32 void
+0x034 DriverUnload : Ptr32 void
+0x038 MajorFunction : [28] Ptr32 long
Ce tableau nous permet alors d'handler des fonctions qui vont réagir selon les différentes IRPs reçus.
Par exemple, lorsque l'on va ouvrir un handle sur notre device (dans notre application ring3) par le biais de la fonction CreateFile(), le driver va recevoir
une IRP_MJ_CREATE, et quand on va fermer le handle, il va recevoir une IRP_MJ_CLOSE.
C'est ici que les IOCTLs rentrent en jeux.
En effet pour pouvoir déclencher tel ou tel action, nous avons besoin d'une sorte de "code" qui serait recuperé par le driver, et qui agirait en conséquence.
Nous allons donc forger des IOCTLs, par le biais de la macro CTL_CODE().
On va donc creer notre propre "code" qui sera reconnus par le driver.
#define IOCTL_LOL\
CTL_CODE( SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)
par exemple.
Il faut savoir qu'il existe plusieurs type de transfert pour nos données, en ce qui nous concerne nous utiliserons la METHOD_BUFFERED.
Bon à présent lors de l'envoi de l'IOCTL par notre appli r3, le driver va alors recevoir une IRP_MJ_DEVICE_CONTROL.
La fonction qui sera "handler" à cette irp va alors s'occuper de récuperer des informations sur le type d'IOCTL reçus.
Pour cela nous utiliserons la fonction suivante :
PIO_STACK_LOCATION
IoGetCurrentIrpStackLocation(
IN PIRP Irp
);
Cette fonction va permettre de récupérer tous ce dont on aura besoin sur l'action demandée.
Ensuite nous allons lire le membre Parameters.DeviceIoControl.IoControlCode de la structure IO_STACK_LOCATION.
Ce membre nous indique le "code" envoyé : celui forgé ; notre IOCTLs.
Il nous reste plus qu'a mettre en place un switch sur ce membre, et agir en conséquence :).
Il est aussi possible de passer des arguments, il suffit de lire le membre AssociatedIrp.SystemBuffer de la structure IRP et d'y écrire dedans.
Ce buffer fait office de buffer d'entré et de sortie, mais ceci est propre à la METHOD_BUFFERED.
Screenshot pour la route :
On peut aussi à ce moment là, utiliser l'outil WinObj pour constater l'existence de notre device avec un symlink.
Ouf, vous pouvez soufler un peu, nous arrivons à la fin.
Comme vous vous en doutez surrement, ces IOCTLs vont donc être massivement employer dans les rootkits par exemple, afin de faire communiquer le driver et l'application userland.
Pas d'exemple concret?:(
Mais si!
How to hide a fucking process with DKOM (Direct Kernel Object Manipulation).
Dans cette partie nous allons nous amusez à cacher un processus au système :).
Je vous explique un peu notre plan :
- Notre application userland, va recuperer le nom du processus que l'on veut cacher.
- L'appli va envoyer une IOCTL au driver, avec un argument : le nom du processus.
- Le driver va donc recuperer le nom du processus. (c'est là que ça devient intéréssant)
- Nous allons récupérer un pointeur sur une structure EPROCESS grâce à la fonction IoGetCurrentProcess().
- Une fois récupérer notre pointeur, nous allons parcourir les structures grâce au membre ActiveProcessLinks qui est enfaite une double liste chainée.
- Nous allons comparer le nom du processus récupéré avec le membre ImageFileName.
- Si il n'y a aucune différence nous nous trouvons dans la structure EPROCESS concernant le processus à cacher.
- A présent il nous reste juste à modifier la structure précédente et suivante afin que les membres Flink et Blink des listes chainées ne pointent plus sur la structure du processus à caché.
On va en quelques sortes "sauter" la structure à caché.
Voilà à présent je vous file 2 structures qui vont être utile dans la compréhension du code :
kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void //PID du processus, à l'offset 0x084.
+0x088 ActiveProcessLinks : _LIST_ENTRY //ce qui nous interesse :)) à l'offset 0x088.
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : Uint4B
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : Uint4B
+0x114 ForkInProgress : Ptr32 _ETHREAD
+0x118 HardwareTrigger : Uint4B
+0x11c VadRoot : Ptr32 Void
+0x120 VadHint : Ptr32 Void
+0x124 CloneRoot : Ptr32 Void
+0x128 NumberOfPrivatePages : Uint4B
+0x12c NumberOfLockedPages : Uint4B
+0x130 Win32Process : Ptr32 Void
+0x134 Job : Ptr32 _EJOB
+0x138 SectionObject : Ptr32 Void
+0x13c SectionBaseAddress : Ptr32 Void
+0x140 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x148 Win32WindowStation : Ptr32 Void
+0x14c InheritedFromUniqueProcessId : Ptr32 Void
+0x150 LdtInformation : Ptr32 Void
+0x154 VadFreeHint : Ptr32 Void
+0x158 VdmObjects : Ptr32 Void
+0x15c DeviceMap : Ptr32 Void
+0x160 PhysicalVadList : _LIST_ENTRY
+0x168 PageDirectoryPte : _HARDWARE_PTE
+0x168 Filler : Uint8B
+0x170 Session : Ptr32 Void
+0x174 ImageFileName : [16] UChar //Nom du process
+0x184 JobLinks : _LIST_ENTRY
+0x18c LockedPagesList : Ptr32 Void
+0x190 ThreadListHead : _LIST_ENTRY
+0x198 SecurityPort : Ptr32 Void
+0x19c PaeTop : Ptr32 Void
+0x1a0 ActiveThreads : Uint4B
+0x1a4 GrantedAccess : Uint4B
+0x1a8 DefaultHardErrorProcessing : Uint4B
+0x1ac LastThreadExitStatus : Int4B
+0x1b0 Peb : Ptr32 _PEB
+0x1b4 PrefetchTrace : _EX_FAST_REF
+0x1b8 ReadOperationCount : _LARGE_INTEGER
+0x1c0 WriteOperationCount : _LARGE_INTEGER
+0x1c8 OtherOperationCount : _LARGE_INTEGER
+0x1d0 ReadTransferCount : _LARGE_INTEGER
+0x1d8 WriteTransferCount : _LARGE_INTEGER
+0x1e0 OtherTransferCount : _LARGE_INTEGER
+0x1e8 CommitChargeLimit : Uint4B
+0x1ec CommitChargePeak : Uint4B
+0x1f0 AweInfo : Ptr32 Void
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
+0x238 LastFaultCount : Uint4B
+0x23c ModifiedPageCount : Uint4B
+0x240 NumberOfVads : Uint4B
+0x244 JobStatus : Uint4B
+0x248 Flags : Uint4B
+0x248 CreateReported : Pos 0, 1 Bit
+0x248 NoDebugInherit : Pos 1, 1 Bit
+0x248 ProcessExiting : Pos 2, 1 Bit
+0x248 ProcessDelete : Pos 3, 1 Bit
+0x248 Wow64SplitPages : Pos 4, 1 Bit
+0x248 VmDeleted : Pos 5, 1 Bit
+0x248 OutswapEnabled : Pos 6, 1 Bit
+0x248 Outswapped : Pos 7, 1 Bit
+0x248 ForkFailed : Pos 8, 1 Bit
+0x248 HasPhysicalVad : Pos 9, 1 Bit
+0x248 AddressSpaceInitialized : Pos 10, 2 Bits
+0x248 SetTimerResolution : Pos 12, 1 Bit
+0x248 BreakOnTermination : Pos 13, 1 Bit
+0x248 SessionCreationUnderway : Pos 14, 1 Bit
+0x248 WriteWatch : Pos 15, 1 Bit
+0x248 ProcessInSession : Pos 16, 1 Bit
+0x248 OverrideAddressSpace : Pos 17, 1 Bit
+0x248 HasAddressSpace : Pos 18, 1 Bit
+0x248 LaunchPrefetched : Pos 19, 1 Bit
+0x248 InjectInpageErrors : Pos 20, 1 Bit
+0x248 VmTopDown : Pos 21, 1 Bit
+0x248 Unused3 : Pos 22, 1 Bit
+0x248 Unused4 : Pos 23, 1 Bit
+0x248 VdmAllowed : Pos 24, 1 Bit
+0x248 Unused : Pos 25, 5 Bits
+0x248 Unused1 : Pos 30, 1 Bit
+0x248 Unused2 : Pos 31, 1 Bit
+0x24c ExitStatus : Int4B
+0x250 NextPageColor : Uint2B
+0x252 SubSystemMinorVersion : UChar
+0x253 SubSystemMajorVersion : UChar
+0x252 SubSystemVersion : Uint2B
+0x254 PriorityClass : UChar
+0x255 WorkingSetAcquiredUnsafe : UChar
+0x258 Cookie : Uint4B
kd> dt nt!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY //Pointe sur la struct suivante.
+0x004 Blink : Ptr32 _LIST_ENTRY //Pointe sur la struct precedente.
Je pense que vous êtes apte à comprendre le code fournis :).
Un petit screenshot pour la route :
A présent il nous reste à aborder l'application ring3.
Celle-ci est extremement simple à comprendre/coder.
En effet, on ouvre un handle sur notre device avec CreateFile(), nous envoyons nos IOCTLs grâce à la fonction DeviceIoControl().
Voilà tout!
Et je compile ça comment?
J'ai utilisé le compilateur disponible dans le wdk.
Seul le fichier "sources" diffère, je vous propose le mien :
TARGETNAME=votreAppli
TARGETTYPE=PROGRAM
INCLUDES=
SOURCES=VotreAppli.c
UMTYPE=console
UMBASE=0x04000000
USE_MSVCRT=1
Vous pouvez dorénavant compiler l'application utilisé pour communiquer avec notre driver !
Mais..cette technique n'a pas de "point" faible?
Hum, à mon grand regret après avoir lus un article disponible sur le blog d'un amis (lilxam), celui-ci nous montre comment "bypasser" ce hide de process :).
Enfin bon, je ne m'avous pas vaincus, je reviendrais avec une meilleure technique :))).
Il est venus le temps de filer les codes :
- HelloWorld.c
- BSOD.c
- HideFuckingProcessR3.c
- HideFuckingProcessR0.c
Et voilà c'est finit pour aujourd'hui !
Encore merci aux personnes qui m'auraient filer un coup de main et toussa :).
cya.
PS : Achetez vous "Subverting kernel windows" écrit par le créateur rootkit.com, un superbe livre de chevet :).
jeudi 21 février 2008
First steps into ring0.
Libellés :
DKOM,
EPROCESS,
hidding with DKOM,
hide process,
kd,
kernel,
remote debugging,
ring0,
ring3,
rootkit r0,
subverting windows kernel
Inscription à :
Publier les commentaires (Atom)
2 commentaires:
Très bon tutoriel. Très pratique, la plupart sont en anglais et vu que c'est technique, c'est très difficile à comprendre.
Merci beaucoup !!
Merci ÉNORMÉMENT 0ver'.
C'est vraiment un article super.
Merci encore.
Enregistrer un commentaire