07 - File System Rootkit Acheron
Blackclowns magazine Issue 1 Article 7
Ivanlef0u <ivanlef0u.tuxfamily.org>
1 - Intro
Dans le post sur mon blog "POC IRP Hooking", j'avais montré comment intercepter les requêtes faites au driver NTFS en modifiant directement les pointeur des fonctions IRP_MJ_XXX dans la table des MajorFunction du driver \filesystem\ntfs. L'avantage de cette technique est quelle évite de poser un hook sur les fonctions natives et bypass donc les anti-rk qui ne vérifient pas l'intégrité du driver NTFS. Cependant comme pour tout hook, nous modifions les résultats renvoyé par la fonction originale qui, dans mon cas, était NtfsFsdDirectoryControl, contrôlant les IRP_MJ_DIRECTORY_CONTROL passées au driver.
Le problème c'est qu'après l'appel à cette fonction certains champs de l'IRP ne sont plus valable, en effet plus bas, dans la device stack, un driver à normalement du faire appel à IoCompleteRequest pour renvoyé le résultat, de ce fait accéder comme je le faisait au buffer représenté par l'IRP était risqué. Comme le DDK le précise :
When the IRP is completed, the system unlocks and frees all of the MDLs that are associated with the IRP. The system unlocks the MDLs before it queues the I/O completion routine and frees them after the I/O completionroutine executes.
Le buffer représenté par l'IRP à donc des chances au retour d'être invalide, ce qui peut causer pas mal de soucis, notamment des jolis BSOD. Enfin les anti-rk actuels ont l'habitude de vérifier que les pointeurs de fonctions dans la table des MajorFunctions sont bien à l'intérieur du driver, c'est à dire entre ImageBase et ImageBase+SizeOfImage.
Bref il fallait améliorer la chose pour la rendre plus propre et moins détectable. Pour cela, on va corrompre le système avec les outils qu'il nous fournit, Acheron est né !
2 - Devices Architecture
Pour commencer il faut savoir que les drivers ne communiquent pas directement avec le système, ils le font à travers des interfaces (devices). Le driver crée les devices et peut en posséder plusieurs. Il existe différents type de devices :
- Physical Device Object (PDO) : représente un device de driver de bus.
- Functional Device Object (FDO) : est un device de driver "fonctionnel" c'est à dire agissant sur les IRP.
- Filter Device Object (FiDO) : device qui filtre les IRP.
- Control Device Object (CDO) : device qui sert d'interface de control avec le driver.
En général, un driver possède un device nommé, qui sert d'interface avec l'userland permettant au driver de recevoir des IOCTL. Les drivers non-nommés servent à travailler sur les IRP envoyés soit par les appels natifs, soit par les interruptions hardware, soit par les autres drivers.
Pour mieux comprendre l'agencement de ces devices regardons les devices associés au gestionnaire de volume principal.
lkd> !drvobj \driver\ftdisk
Driver object (842d3ae0) is for:
\Driver\Ftdisk
Driver Extension List: (id , addr)
Device Object list:
842f7d88 842d3900
Le device object à l'adresse 0x842d3900 nommé FtControl sert de device de communication avec l'userland (CD0), utilisé par le le gestionnaire de disques.
lkd> !devobj 842d3900
Device object (842d3900) is for:
FtControl \Driver\Ftdisk DriverObject 842d3ae0
Current Irp 00000000 RefCount 0 Type 00000012 Flags 00000840
Dacl e1446394 DevExt 842d39b8 DevObjExt 842d3a70
ExtensionFlags (0000000000)
AttachedTo (Lower) 843aff10
Le second device nommé HarddiskVolume1 correspond au device de type VDO (Volume Device Object) représenté par le symbolic link C:.
lkd> !object \global??\C:
Object: e14bd0d0 Type: (843c8040) SymbolicLink
ObjectHeader: e14bd0b8 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: e10016b0 Name: C:
Target String is '\Device\HarddiskVolume1'
Drive Letter Index is 3 (C:)
lkd> !devobj 842f7d88
Device object (842f7d88) is for:
HarddiskVolume1 \Driver\Ftdisk DriverObject 842d3ae0
Current Irp 00000000 RefCount 1284 Type 00000007 Flags 00001150
Vpb 842fa790 Dacl e1446394 DevExt 842f7e40 DevObjExt 842f7f28 Dope 843ce500
DevNode 842f7bb0
ExtensionFlags (0000000000)
AttachedDevice (Upper) 842f7928 \Driver\VolSnap
Device queue is not busy.
Au dessus du device HarDiskVolume1, on trouve le device type FiDO VolSnap, Microsoft Shadow Copy Provider qui permet de crée un backup en temps réel d'un disque en utilisant le Shadow Copy service, généralement non activé mais qui peut servir aux sofs de backup de disques.
Le lien entre le VDO HardiskVolume1 et le driver de système de fichier est réalisé avec le VPB (Volume Parameter Block), qui est un champ (offset 0x24) de la structure DEVICE_OBJECT.
A VPB serves as the link between a volume device object and the device object that a file system driver creates to represent a mounted file system instance for that volume
lkd> dt nt!_DEVICE_OBJECT
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 ReferenceCount : Int4B
+0x008 DriverObject : Ptr32 _DRIVER_OBJECT
+0x00c NextDevice : Ptr32 _DEVICE_OBJECT
+0x010 AttachedDevice : Ptr32 _DEVICE_OBJECT
+0x014 CurrentIrp : Ptr32 _IRP
+0x018 Timer : Ptr32 _IO_TIMER
+0x01c Flags : Uint4B
+0x020 Characteristics : Uint4B
+0x024 Vpb : Ptr32 _VPB
+0x028 DeviceExtension : Ptr32 Void
+0x02c DeviceType : Uint4B
+0x030 StackSize : Char
+0x034 Queue : __unnamed
+0x05c AlignmentRequirement : Uint4B
+0x060 DeviceQueue : _KDEVICE_QUEUE
+0x074 Dpc : _KDPC
+0x094 ActiveThreadCount : Uint4B
+0x098 SecurityDescriptor : Ptr32 Void
+0x09c DeviceLock : _KEVENT
+0x0ac SectorSize : Uint2B
+0x0ae Spare1 : Uint2B
+0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
+0x0b4 Reserved : Ptr32 Void
lkd> dt nt!_VPB
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 Flags : Uint2B
+0x006 VolumeLabelLength : Uint2B
+0x008 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x00c RealDevice : Ptr32 _DEVICE_OBJECT
+0x010 SerialNumber : Uint4B
+0x014 ReferenceCount : Uint4B
+0x018 VolumeLabel : [32] Uint2B
On peut obtenir plus d'informations sur le VPB avec la commande !vpb du kd.
lkd> !vpb 842fa790
Vpb at 0x842fa790
Flags: 0x1 mounted
DeviceObject: 0x842bf770 <- FDO du driver \FileSystem\Ntfs
RealDevice: 0x842f7d88 <- HarddiskVolume1, VDO crée par \driver\ftdisk
RefCount: 1284
Volume Label:
On peut résumer la device stack du driver ftdisk avec le schéma suivant :
+--------------------------------+
| |
| (noname) |
| FiDO du driver \filesystem\sr |
| |
+--------------------------------+
/\
||
||
+--------------------------------+ +--------------------------------+
| | | |
| (noname) | | (noname) |
| FDO du driver \filesystem\Ntfs | | FiDO du driver \driver\VolSnap |
| | | |
+--------------------------------+ +--------------------------------+
/\ /\
|| ||
|| ||
++---------------++--------------++
||
+-------------------------------+
| |
| \Device\HardiskVolume1 |
| VDO du driver \driver\ftisk |
| |
+-------------------------------+
On s'intéresse plus particulèrement au driver \filesystem\ntfs, en jouant un peu avec le kd dessus on retrouve :
kd> !drvobj \filesystem\ntfs
Driver object (80e4f240) is for:
\FileSystem\Ntfs
Driver Extension List: (id , addr)
Device Object list:
80e7a030 80e2b040
Le driver ntfs possède 2 devices :
kd> !devobj 80e7a030
Device object (80e7a030) is for:
\FileSystem\Ntfs DriverObject 80e4f240
Current Irp 00000000 RefCount 0 Type 00000008 Flags 00000000
DevExt 80e7a0e8 DevObjExt 80e7a890
ExtensionFlags (0000000000)
AttachedDevice (Upper) 80e7b978 \FileSystem\sr
Device queue is not busy.
Un unnamed device qui est donc le FDO du driver Ntfs.
kd> !devobj 80e2b040
Device object (80e2b040) is for:
Ntfs \FileSystem\Ntfs DriverObject 80e4f240
Current Irp 00000000 RefCount 1 Type 00000008 Flags 00000040
Dacl e126aafc DevExt 00000000 DevObjExt 80e2b0f8
ExtensionFlags (0000000000)
AttachedDevice (Upper) 80e2bbc8 \FileSystem\sr
Device queue is not busy.
Et un named device qui sert de CDO pour le driver Ntfs.
Si on résume par un schéma on a :
+--------------------------------+ +--------------------------------+
| | | |
| \Ntfs | | (noname) |
| CDO du driver \filesystem\Ntfs | | FDO du driver \filesystem\Ntfs |
| | | |
+--------------------------------+ +--------------------------------+
/\ /\
|| ||
|| ||
++---------------++--------------++
||
+-------------------------------+
| |
| \Filesytem\Ntfs |
| driver Ntfs |
| |
+-------------------------------+
3 - Concept
L'idée est donc la suivante, toujours dans le but de modifier le comportement du système de fichier il est possible de construire notre propre Filter Driver pour s'attacher au dessus des driver filesystem. Ces derniers possédant le même format de requête, il nous est facile de filtrer les IRP entrant et sortant pour modifier au besoin les résultats.
Ma vm possède la liste suivante de driver filesystem :
kd> !object \filesystem
Object: e125e040 Type: (80e97b68) Directory
ObjectHeader: e125e028 (old version)
HandleCount: 0 PointerCount: 21
Directory Object: e1004ec8 Name: FileSystem
Hash Address Type Name
---- ------- ---- ----
00 80e4f240 Driver Ntfs
01 ffbdd2c0 Driver NetBIOS
02 80e4fc40 Driver sr
05 80d832a0 Driver Rdbss
15 80ca2500 Driver Msfs
17 80ce6358 Driver MRxSmb
18 ffbca040 Device UdfsCdRomRecognizer
19 80cb2a70 Driver Srv
24 80e2b490 Driver Mup
80e90180 Driver RAW
25 ffbcbf38 Driver Npfs
ffbb5950 Driver Fs_Rec
26 e125ef58 Directory Filters
30 80d3e390 Driver MRxVPC
31 80db8650 Driver Cdfs
32 80d446d0 Device FatCdRomRecognizer
ffbd1040 Device CdfsRecognizer
80e3d4a0 Driver FltMgr
34 80d44808 Device FatDiskRecognizer
36 80ccc740 Device UdfsDiskRecognizer
Ntfs est bien evidemment le filesystem pour les disques ntfs, Cdfs correspond au driver pour le file system des cd-rom. Pour les autres je vous laisse deviner :]
Notre driver va donc enumérer les devices des différents drivers filesystem puis chercher si il existe un ou plusieurs FDO. Si oui, alors il attache son device au dessus qui permettra par la suite de "rerouter" les IRP afin de regarder ce quelles contiennent.
4 - Choosing Implementation Type
4.1 - Minifilter
Il existe plusieurs façcon d'implementer un filter driver. La plus simple consiste à utiliser un driver natif sous Windows. On appel cela un minifilter car notre driver n'est pas obligé de tout faire, il peut sélectionner sur quels drivers il va s'attacher, les types d'IRP qu'il veut analyser, le reste étant gérer par le driver du Filter Manager (FltMgr). Le problème, c'est que le driver doit être installer avec un .inf et s'exécute sous forme de service, ce qui n'est pas vraiment pratique n'y très furtif.
4.1 - Raw Filter Driver
On peut aussi coder notre filter driver, complêtement autonome, qui démarre juste avec un appel à NtLoadDriver et qui va s'attacher tout seul sur les drivers filesystem. Evidemment cela est un peu plus compliqué que de coder un minifilter. C'est un peu comme si on codait un driver d'AV qui intercepterait les les requêtes sur le diques afin de vérifer si ceux-ci ne possède pas de contenu malsain.
C'est ce dernier type de driver que nous choisirons.
5 - Construction
Notre driver, va donc s'attacher aux FDO des filesystem existant, l'avantage par rapport au fait de s'attacher au volume est que si on un disque dur et un hd extern tout deux en ntfs, on est sûr de filter les requêtes qui leurs seront transmises
Heureusement le DDK nous fournit une API, IoRegisterFsRegistrationChange qui nous permet d'enregistrer une fonction callback qui sera exécutée sur tous les filesystem au départ puis dès qu'un nouveau filesytem sera devenu actif.
Notre fonctions callback, récupère ainsi un pointeur sur le device CDO actif du driver filesystem (par exemple le CDO Ntfs du driver \filesystem\Ntfs). Il nous suffit par la suite de récupérer la liste des autres devices unnamed avec l'API IoEnumerateDeviceObjectList et d'attacher le notre avec IoAttachDeviceToDeviceStackSafe. Vous voyez c'est pas difficile :)
Notre driver pouvant recevoir des Fast I/O, c'est à dire des IRP qui s'éxécuteront obligatoirement dans le context du thread appelant, on oblige toutes les Fast I/O qui existent à renvoyer false, afin que les IRP passent par les fonctions "normales" du driver, celles de la table des IRP_MJ_xxx.
Jusqu'ici, nous avons réussit à attacher les devices de notre driver sur les devices des filesystem actif et à obliger les IRP à passer par les fonctions IRP_MJ_xxx de notre driver. A partir de là on contrôle entièrement les requêtes, il nous faut choisir celles qu'on va modifier.
Comme on veut cacher un ou des fichiers, on va handler les IRP de type IRP_MJ_DIRECTORY_CONTROL qui servent à recupérer la liste des fichiers d'un dossier, appelé par l'API native NtQueryDirectoryFile. Les autres types seront transmit aux devices inférieurs avec un simple IoSkipCurrentIrpStackLocation / IoCallDriver.
Maitenant qu'on filtre les IRP IRP_MJ_DIRECTORY_CONTROL il faut qu'on modifie non pas la demande mais le résultat qui proviendra du FDO du driver ntfs. Il existe une feature super pratique de DDK pour réaliser se genre d'opération, cela s'appel une completion routine. Les completion routines sont mises dans l'IO_STACK_LOCATION de l'IRP et sont appelées au moment de la completion, c'est à dire quand le driver FDO prévient qu'il a finit de manipuler l'IRP en appelant IoCompleteRequest. Une completion routine se place très simplement en copiant les paramètre de l'IRP dans la next IO_STACK_LOCATION avec la fonction IoCopyCurrentIrpStackLocationToNext (heWwWWWwW!) puis on remplace notre IO_STACK_LOCATION avec notre completion routine en appelant IoSetCompletionRoutineEx.
Le petit problème c'est que comme le dit la doc :
When the IRP is completed, the system unlocks and frees all of the MDLs that are associated with the IRP. The system unlocks the MDLs before it queues the I/O completion routine and frees them after the I/O completion routine executes.
Le fait est, que les buffer userland sont traité en METHOD_NEITHER, c'est à dire que c'est à l'utilisateur de faire en sorte que ceux-si soit valides au moment au le driver les manipule .
METHOD_NEITHER :
With this method, the highest-level driver must determine whether to set up buffered or direct access to user data on receipt of the request, possibly must lock down the user buffer, and must wrap its access to the user buffer in a structured exception handler (see Handling Exceptions). Otherwise, the originating user-mode caller might change the buffered data before the driver can use it, or the caller could be swapped out just as the driver is accessing the user buffer.
Ce qui veut dire qu'au moment au notre completion routine sera appelée rien ne nous garantira que les pages mémoire contenant le buffer de destination de l'appel à la fonction NtfsFsdDirectoryControl seront en mémoire, les pages pouvant être swappées. Pour empêcher se léger problème, en fonction du type d'I/O effectuer on ajoute un MDL qui garantira que les pages de seront pas out swappées. Maintenant dans notre completion routine on peut manipuler le buffer sans problème.
Notre completion routine va servir à vérifier si la liste des fichiers renvoyé contient ou non des fichiers qu'on voudrait cacher. On va handler 6 InformationClass qui peuvent être choisit au moment de l'appel à NtQueryDirectoryFile et qui permettent de retrouver la liste des fichiers d'un dossier :
- FileDirectoryInformation
- FileFullDirectoryInformation
- FileBothDirectoryInformation
- FileNamesInformation
- FileIdBothDirectoryInformation
- FileIdFullDirectoryInformation
Dans chaque cas NtQueryDirectoryFile nous renvoie une liste de structures spécifiques qu'on va scanner. Si on trouve une structure qui représente un fichier qu'on veut cacher alors on l'unlink et on l'efface. Pour code, je me suis pas fait chier, j'ai choisit de cacher les fichiers qui commencent par un PREFIX précis, par exemple '$'.
Concernant le code, celui ci est commenté en anglais (gruUtz0gz0gFfS!), j'avais commencé à implémention la fonction chargé de filtre les IRP_MJ_CREATE, provenenant des appels de NtCreateFile, NtOpenFile ...etc Mais j'ai décidé de la laisser en commentaires. En effet même si un fichier reste "caché", il est toujours possible d'ouvrir un handle dessus, d'ou l'intérêt de gérer les IRP qui servent à la création de de handles.
Je n'ai pas implémenté la gestion des File Reference Number, en gros il faudrait filtrer les IRP_MJ_CREATE en ouvrant un handle sur le FileObject crée avec ObOpenObjectByPointer puis en checkant le nom du fichier avec un ZwQueryFileInformation avec l'InformationClass FileNameInformation, on vérifie ainsi si c'est un de nos fichiers, si oui on renvoie que l'opération d'ouverture de handle a foirée, en théorie :p
Un truc cool aussi à faire serait de gérer les IRP de type IRP_MJ_QUERY_INFORMATION provenant de l'appel natif NtQueryInformationFile qui sert notamment à déctecter les ADS, il suffit de vérifier si le FileInformationClass de l'IO_STACK_LOCATION de l'IRP est positionner sur FileStreamInformation.
Un petit mot sur la furtivité, j'ai juste mit dans mon code une fonction permettant d'unlinker le driver de la PsLoadedModuleList.
6 - Conclusion
L'avantage évident d'avoir un rookit fonctionnant de cette façon c'est que nous tirons profit des features que le système nous offre. Ansi, aucune fonction n'est hookée, aucune table n'est modifiée, nous gardons le système vierge.
Pour un anti-rootkit, il est donc difficile de différencier ce module, d'un module normal du système. C'est sur cet aspect que joue principalement ce rootkit, en effet, ce dernier n'ayant pas de comportement apparememnt "suspect" (il ne fait que filtrer les requètes sur les driver filesystem, tout comme le font beaucoup de driver d'AV), il est assez difficile de déterminer, même pour un expert si il est malvaillant.
Au final, Acheron est loin d'être un file system rootkit parfait, une lecture en raw sur le disque permet de retrouver les fichiers, mais il montre qu'il est faisaible de crée un rootkit sans altérer le fonctionnement de l'OS en mettant des hooks partout.
Un vrai truc serait de coder un filter driver mais qui se placerais au dessus du driver de disque, à vous de jouer :)
7 - Greetz
B0nd et Fantomas pour leur aide.
All d4 fuckingz mofo qui pensent que win est le meilleur OS :p
TH1S TOOLKITZ W4S SPONSOR3D BYZ :
- SHiTKiTTENz
,-~-, ,-~~~~-, /\ /\
(\ / ,-, \ ,' ', / ~~ \
\'-' / \ \ / _ # <0 0> \
'--' \ \/ .' '. # = Y =/
\ / \ \ `#-..O.-'
Sh1TtInG >> O \ \ \ `\ \\
0n >> 0 ) /> / \ \\
y0u >> 0o / /`/ /`__ \ \\__
T4pZ >> o00o (____)))_))) \__)))
- Pok3Mon
, .::.
Pok3Mon .;:**' AMC
` 0
.:XHHHHk. db. .;;. dH MX 0
oMMMMMMMMMMM ~MM dMMP :MMMMMR MMM MR ~MRMN
QMMMMMb "MMX MMMMMMP !MX' :M~ MMM MMM .oo. XMMM 'MMM
`MMMM. )M> :X!Hk. MMMM XMM.o" . MMMMMMM X?XMMM MMM>!MMP
'MMMb.dM! XM M'?M MMMMMX.`MMMMMMMM~ MM MMM XM `" MX MMXXMM
~MMMMM~ XMM. .XM XM`"MMMb.~*?**~ .MMX M t MMbooMM XMMMMMP
?MMM> YMMMMMM! MM `?MMRb. `""" !L"MMMMM XM IMMM
MMMX "MMMM" MM ~%: !Mh.""" dMI IMMP
'MMM. IMX
~M!M IMP
#blackclowns undergroundz community
sept. 02 17:46:00 <Kototama> tu fais quoi a part coder pouik__ ?
sept. 02 17:46:16 <pouik__> je bois ...
Références
- Microsoft Windows Internals Fourth Edition
- Chapter 10. Storage Management Volume Management
- Chapter 12. File Systems File System Driver Architecture
- POC IRP Hooking http://www.ivanlef0u.tuxfamily.org/?p=45
- IRP HOOKING http://www.ivanlef0u.tuxfamily.org/?p=44
- Proper way to hide files/directories aka the FsFilter way aka bypass Flister http://www.rootkit.com/newsread.php?newsid=251
- Different ways of handling IRPs - cheat sheet (part 1 and 2) http://support.microsoft.com/?scid=kb%3Ben-us%3B320275&x=8&y=15 http://support.microsoft.com/?scid=kb%3Ben-us%3B326315&x=14&y=11
- Neither I/O Operations http://msdn2.microsoft.com/en-us/library/ms793505.aspx
- Using MDLs http://msdn2.microsoft.com/en-us/library/aa489506.aspx
- The Windows Driver Model Simplifies Management of Device Driver I/O Requests http://www.microsoft.com/msj/0199/windowsdriver/windowsdriver.aspx
- Handling IRPs: What Every Driver Writer Needs to Know http://msdn2.microsoft.com/en-us/library/ms810023.aspx
- KdPrint/DbgPrint and UNICODE_STRING/ANSI_STRING LOL? http://alter.org.ua/ru/docs/nt_kernel/kdprint_ustr/
- Clandestine File System Driver http://www.rootkit.com/newsread.php?newsid=386
begin 644 Acheron_code.tar.gz
M'XL("!;#=D<``T%C:&5R;VY?8V]D92YT87(`[%Q[<]M&DL^_5)6^P\BI.*17
M(BG;LJ^LT%L4"3F(^0H?<;))E@4!0Q$1"'#QL,1U=)_]NGMF\"8IW]G9V]J@
...
M:5/BJ`FO)BH4ZX17'C%"MV=7OEW"JY`FG8*:\&JV0I%3"LS5S(X^70!/@65.
M%X#@.6QM%`NG*4>+C0J23@%KB^S/&@^\!EX#KX'7P&O@-?`:>`V\!E[_H]=_
)`7A"`5P`<`T`
`
end