· 8 years ago · Mar 14, 2018, 07:50 PM
1Rootkit analysis
2Use case on HideDRV
3Version 1.6
42016-10-27
5Author: Paul Rascagnères
6
7HideDRV – Rootkit analysis
82
9INDEX
10Preparation ........................................................................................... 5
11Driver signature enforcement & Patch guard........................................................ 6
12Global architecture of the driver ...................................................................... 7
13File-system mini-filter...................................................................................13
14Registry callbacks ........................................................................................21
15Process creation callbacks..............................................................................24
16Payload injection.........................................................................................25
17Chapter abstract..........................................................................................26
18Context ..........................................................................................28
19Device and Symbolic link ...............................................................................28
20SSDT hooks ..........................................................................................30
21Chapter abstract..........................................................................................32
22HideDRV – Rootkit analysis
233
24DISCLAIMER
25The purpose of this article is to provide a first good overview of the kernel mechanisms and how to
26handle a rootkit analysis.
27This document is issued by Sekoia Cybersecurity and intends to share reverse engineering best practices
28in order to help organizations in their security jobs. If you are interested in reverse engineering, malware
29analysis, advanced rootkit analysis or Windows internal working, CERT Sekoia can assist you either for
30investigations or for trainings.
31Contact us at contact@sekoia.fr
32HideDRV – Rootkit analysis
334
34INTRODUCTION
35ESET’s experts published a complete and interesting white paper on the toolkit of a well-known APT
36actor identified as Sednit (aka APT28, Fancy Bear, Sofacy, STRONTIUM, Tsar Team…). The paper can be
37downloaded there. This group seems to be the author of several major media hacks such as the attack of
38the German parliament in December 2014 or the compromise of TV5Monde in April 2015. Thanks to a
39great collaboration with ESET, CERT Sekoia got access to the rootkit samples in order to perform its own
40investigation.
41The name of the rootkit discovered by ESET is HIDEDRV. This name was chosen by the developer and is
42present in several comments in the driver file (FsFlt.sys). CERT Sekoia frequently deals with malware and
43rootkits analysis. Sometimes, several people ask us for tricks for kernel analysis and debugging. After a
44quick overview of the drivers, we found out that this sample was a perfect case study for beginners.
45Therefore, we decided to publish a deep analysis on this rootkit. People interested in discovering and
46practising Windows kernel analysis and rootkit debugging might like it. Indeed, this sample is wonderful
47for beginners:
48- no packer;
49- no obfuscation;
50- no advanced or undocumented trick;
51- really small (< 100 functions);
52- all the classical features (such as registries hiding, files hiding, code injection from the kernel).
53And there is the icing on the cake: the developers let a lot of comments for helping and guiding the
54analyst ;).
55This article describes how to deal with rootkit analysis step by step: laboratory setup, Windows kernel
56architecture and API, Windows protection, Windows 10 64 bits… The purpose is to provide a tutorial of
57the “state of the art†of rootkit analysis on modern x64 Windows systems.
58As usual we love feedbacks, so don’t be afraid to contact us!
59HideDRV – Rootkit analysis
605
61X64 ROOTKIT ANALYSIS
62PREPARATION
63This article was created based on the following configuration:
64- Host: Windows 10 TH2 – x64
65- Guest: Windows 10 TH2 – x64
66- Hypervisor: Hyper-V
67- Sample hash: 4bfe2216ee63657312af1b2507c8f2bf362fdf1d63c88faba397e880c2e39430
68- Sample file name: fsflt.sys
69- Debugger: WinDBG x64
70- Disassembler: IDA Pro
71WinDBG is the Microsoft debugger. It allows userland and kernel debugging on Windows environments.
72If you are not familiar with WinDBG, we recommend reading this article first: http://windbg.info/doc/1-
73common-cmds.html.
74To perform Windows kernel analysis, we need a specific setup of the Virtual Machine and WinDBG in
75order to remotely debug the VM from the host. We strongly recommend the following Microsoft
76documentation available there: https://msdn.microsoft.com/enus/library/windows/hardware/hh439378(v=vs.85).aspx.
77Additionnaly, we need:
78- To disable the driver signature enforcement (see next chapter):
79Bcdedit.exe -set TESTSIGNING ON
80- To create some registry keys in order to load the rootkit on demand (we will see why later):
81[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt]
82"Description"="FsFtl minifilter"
83"DependOnService"="FltMgr"
84"Group"="FSFilter Content Screener"
85"ImagePath"="system32\\Drivers\\fsflt.sys"
86"DisplayName"="FsFlt"
87"Tag"=dword:00000001
88"ErrorControl"=dword:00000001
89"Type"=dword:00000002
90"Start"=dword:00000003
91[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt\Enum]
92"NextInstance"=dword:00000001
93"Count"=dword:00000001
94"0"="Root\\\\LEGACY_minifilter\\\\0000"
95[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt\Instances]
96"DefaultInstance"="minifilter Instance"
97[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt\Instances\minifilter
98Instance]
99"Flags"=dword:00000000
100"Altitude"="262100"
101HideDRV – Rootkit analysis
1026
103[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt\Parameters]
104[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt\Parameters\c1]
105@="\\Device\\HarddiskVolume1\\Sekoia\\ApplicationDummy.dll"
106[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt\Parameters\c2]
107@="\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\services\\FsFlt"
108[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt\Parameters\c3]
109@="\\Device\\HarddiskVolume1\\Sekoia\\ApplicationDummy.dll"
110[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FsFlt\Parameters\c4]
111@=\\Device\\HarddiskVolume1\\Sekoia
112Note: you must adapt the HarddiskVolume1 to your system and point to the C drive. The directory
113C:\Sekoia must exist and must contain an ApplicationDummy.dll file.
114DRIVER SIGNATURE ENFORCEMENT & PATCH GUARD
115
116Driver signature enforcement
117Since Windows 7 x64, Microsoft implemented the driver signature enforcement. When this feature is
118enabled, the drivers must be signed by a trusted vendor in order to be loaded by the system. More
119information about this topic can be found on the Microsoft web site: https://msdn.microsoft.com/enus/library/windows/hardware/ff544865%28v=vs.85%29.aspx.
120This validation is performed by CI.dll
121(Code Integrity). The rootkit developers need to sign their rootkits or to bypass the signature control. If
122you are interested in the driver signature bypasses, we recommend watching our talk during SyScan360
123there: https://www.youtube.com/watch?v=lVEaw5SW2DE. In our case, we didn’t have the dropper of
124the driver so we could not identify the signature bypass technique that was used. At this stage, to
125perform the analysis, we have to disable the driver signature as explained previously.
126
127Patch guard
128Since Windows 7 x64, Microsoft implemented the Patch Guard (aka Kernel Patch Protection – KKP). The
129purpose of the security feature is to protect the integrity of the kernel’s code and to avoid modification
130of sensitive tables such as the System Service Dispatch Table (SSDT), the Interrupt Descriptor Table (IDT)
131or Global Descriptor Table (GDT). A lot of rootkits performed inline hooks or table modifications in order
132to modify the behaviour of the kernel. If a driver performs this kind of thing with Patch Guard enabled,
133the system crashes and the user gets a Blue Screen of Death. Generally, in the 64 bits version of modern
134Windows rootkit, the developers use legitimate callback procedures provided by Microsoft (the rootkit
135Turla is an exception however where the developers bypassed Patch Guard and performed inline hooks
136in the kernel’s code directly).
137HideDRV – Rootkit analysis
1387
139GLOBAL ARCHITECTURE OF THE DRIVER
140
141First steps
142WinDBG can be automatically stopped when the driver is loaded (after pressing CTRL+Break to break
143the system):
144kd> sxe ld FsFlt.sys
145Using a previously described setup, you can easily load the driver by starting the FsFlt service:
146C:\Windows\System32> sc start FsFlt
147The debugger will be automatically stopped at the driver loading:
148kd> !lmi FsFlt
149Loaded Module Info: [fsflt]
150 Module: fsflt
151 Base Address: fffff80198af0000
152 Image Name: fsflt.sys
153 Machine Type: 34404 (X64)
154 Time Stamp: 5305a705 Thu Feb 20 07:56:05 2014
155 Size: d000
156 CheckSum: b745
157Characteristics: 22
158Debug Data Dirs: Type Size VA Pointer
159 CODEVIEW 58, 82f8, 72f8 RSDS - GUID: {5DBF95F0-1907-435C-996B1A16AE85070C}
160 Age: 1e, Pdb:
161d:\!work\etc\hideinstaller_kis2013\Bin\Debug\win7\x64\fsflt.pdb
162 Symbol Type: DEFERRED - No error - symbol load deferred
163 Load Report: no symbols loaded
164You can notice the name of the .pdb path: kis2013. This could be a reference to Kaspersky Internet
165Security 2013. At this point, you can set a breakpoint on the DriverEntry() function of the driver:
166HideDRV – Rootkit analysis
1678
168kd> bp fsflt+0xb064
169kd> g
170Breakpoint 0 hit
171fsflt+0xb064:
172fffff801`98afb064 4883ec28 sub rsp,28h
173Note: the default base address in IDA Pro is 0x10000. The base address of the loading module is
1740xfffff800198af0000 as you can see in the !lmi command output (so the offset in argument to the
175breakpoint is IDA Pro Address – 0x10000).
176Static overview of the rootkit design
177As explained, the entry point of the rootkit is at offset 0x1b064 (DriverEntry()). The core of the
178driver is located at 0x11010 (Core()). This chapter will focus on the workflow of this Core() function.
179Note: If you are lost, do not hesitate to read the debug provided by the developer:
180To display the debug, you can use DebugView from SysInternals: https://technet.microsoft.com/enus/sysinternals/bb896647.aspx
181
182HideDRV – Rootkit analysis
1839
184Step 1
185First, (offset 0x11500), the driver gets the registry stored value in
186\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\services\FsFlt\Parameters\c4 thanks to the
187ZwOpenKey(), ZwQueryKey() and ZwEnumerateKey() APIs. Then the driver checks if the
188value is an existing directory with the ZwCreateFile() function with the CreateOptions
189argument at “FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NOALERTâ€:
190If the directory does not exist, the driver stops itself. (That's why we created the C:\Sekoia directory).
191In kernel space, the most common way to allocate memory is to use ExAllocatePoolWithTag()
192function. Here is the prototype:
193PVOID ExAllocatePoolWithTag(
194 _In_ POOL_TYPE PoolType,
195 _In_ SIZE_T NumberOfBytes,
196 _In_ ULONG Tag
197);
198The first argument is the pool type (paged, non-paged, etc.), the second argument is the size of the pool
199and the last argument is the tag name. The tag name is a four characters’ value. We identified two
200different tag names in the rootkit:
201- DCBA
202- rbRN
203HideDRV – Rootkit analysis
20410
205Step 2
206The rootkit registers a file-system minifilter. This feature will be described in detail later.
207Step 3
208The rootkit parses the configuration stored in the registry in order to get the value of the files, the
209directories and the registry keys to hide and the path of the library to inject in explorer.exe. The
210parsing is performed at offset 0x12e30 and the values are stored in global variables (FilePath,
211RegPath and DLLPath in the screenshot):
212HideDRV – Rootkit analysis
21311
214In each callback (registry/file/directory), these global variables are used to check if the accessed element
215returned by the callback matches one of the rules stored in registry. Here is the debug output of the
216configuration parsing left by the developer:
217HIDEDRV: >>>>>>>>Hide rules>>>>>>>> rules
218HIDEDRV: File rules: \Device\HarddiskVolume1\Sekoia\ApplicationDummy.dll
219HIDEDRV: Registry rules: \REGISTRY\MACHINE\SYSTEM\CurrentControlSet\services\FsFlt
220HIDEDRV: Inject dll: \Device\HarddiskVolume1\Sekoia\ApplicationDummy.dll
221HIDEDRV: Folder rules: \Device\HarddiskVolume1\Sekoia
222HIDEDRV: <<<<<<<<XXXXX<<<<<<<< rules
223HIDEDRV: <<<<<<<<Hide rules<<<<<<<< rules
224The driver callbacks use the full volume path, that’s why the content in registry must start by
225\Device\HarddiskVolumeX (the kernel view) and not by c:\ (the userland view). The comparison
226function is at the offset 0x13360. This function has 2 arguments:
227- a string (the name to be checked returned by the callback)
228- an integer:
229o 1: check if the string matches the file rules global variable;
230o 2: check if the string matches the registry rules global variable;
231o 3: check if the string matches the folder rules global variable.
232
233HideDRV – Rootkit analysis
23412
235Step 4
236The rootkit starts the file-system minifilter registered previously. This feature will be described in detail
237later.
238Step 5
239The rootkit registers and starts the registry callbacks at offset 0x144a0. This feature will be described in
240detail later.
241Step 6
242The rootkit registers and starts the process creation callbacks at offset 0x15030. This feature will be
243described in detail later. However, this function starts with an interesting action:
244The rootkit deletes the file C:\Windows\System32\sysprep\CRYPTBASE.dll. We don’t
245exactly know why the driver removes this file. However, the library and this path are frequently used to
246bypass UAC. You can find in Metasploit the code of this kind of privilege escalation:
247https://github.com/rapid7/metasploitframework/blob/master/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Inject.cpp.
248We
249assume that the rootkit removes the trace of a privilege escalation previously realised.
250Step 7
251Finally, the rootkit defines an event that will be used to perform the library injection in the process
252explorer.exe. This features will be described in detail later.
253HideDRV – Rootkit analysis
25413
255FILE-SYSTEM MINI-FILTER
256Static analysis
257A file-system mini-filter is basically registered and started using two functions:
258- FltRegisterFilter() (MSDN documentation: https://msdn.microsoft.com/enus/library/windows/hardware/ff544305(v=vs.85).aspx)
259- FltStartFiltering() (MSDN documentation: https://msdn.microsoft.com/enus/library/windows/hardware/ff544569(v=vs.85).aspx)
260From a reverse engineering point of view, the first one in the most interesting, in particular the second
261argument (structure) used in the function: FLT_REGISTRATION. Here is the prototype of the
262structure:
263typedef struct _FLT_REGISTRATION {
264 USHORT Size;
265 USHORT Version;
266 FLT_REGISTRATION_FLAGS Flags;
267 const FLT_CONTEXT_REGISTRATION *ContextRegistration;
268 const FLT_OPERATION_REGISTRATION *OperationRegistration;
269 PFLT_FILTER_UNLOAD_CALLBACK FilterUnloadCallback;
270 PFLT_INSTANCE_SETUP_CALLBACK InstanceSetupCallback;
271 PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK InstanceQueryTeardownCallback;
272 PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownStartCallback;
273 PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownCompleteCallback;
274 PFLT_GENERATE_FILE_NAME GenerateFileNameCallback;
275 PFLT_NORMALIZE_NAME_COMPONENT NormalizeNameComponentCallback;
276 PFLT_NORMALIZE_CONTEXT_CLEANUP NormalizeContextCleanupCallback;
277#if FLT_MGR_LONGHORN
278 PFLT_TRANSACTION_NOTIFICATION_CALLBACK TransactionNotificationCallback;
279 PFLT_NORMALIZE_NAME_COMPONENT_EX NormalizeNameComponentExCallback;
280#endif
281#ifdef FLT_MFG_WIN8
282 PFLT_SECTION_CONFLICT_NOTIFICATION_CALLBACK SectionNotificationCallback;
283#endif
284} FLT_REGISTRATION, *PFLT_REGISTRATION;
285Here is the view of this structure in IDA Pro:
286HideDRV – Rootkit analysis
28714
288The 5 callbacks functions do not contain relevant codes. The interesting code in located in the
289FLT_OPERATION_REGISTRATION structure. Here is the prototype of the structure:
290typedef struct _FLT_OPERATION_REGISTRATION {
291 UCHAR MajorFunction;
292 FLT_OPERATION_REGISTRATION_FLAGS Flags;
293 PFLT_PRE_OPERATION_CALLBACK PreOperation;
294 PFLT_POST_OPERATION_CALLBACK PostOperation;
295 PVOID Reserved1;
296} FLT_OPERATION_REGISTRATION, *PFLT_OPERATION_REGISTRATION;
297The content can also be viewedin IDA Pro:
298The PreOperation()function (0x14100) contains the code used to get the file/directory name
299returned by the callback in order to compare it with the path stored in the registry. Here is the assembly
300code:
301HideDRV – Rootkit analysis
30215
303If the callback name returned matches a value in registry, the code modifies the contents of the callback
304data structure by setting STATUS_NOT_FOUND via the FltSetCallbackDataDirty() API. The
305file or the directory will finally be hidden to the user:
306
307HideDRV – Rootkit analysis
30816
309Overview of the minifilter structure
310The Windows kernel frequently uses linked lists. The file system minifilters uses circular doubly linked
311list. Here is the definition of this kind of list:
312typedef struct _LIST_ENTRY {
313 struct _LIST_ENTRY *Flink;
314 struct _LIST_ENTRY *Blink;
315} LIST_ENTRY, *PLIST_ENTRY;
316The schema of the linked filters can be seen below:
317
318HideDRV – Rootkit analysis
31917
320Dynamic analysis with WinDBG
321Thanks to the previous schema, we can analyse and identify the file-system minifilters with WinDBG. The
322fltkd extension allows to list the registered filter (and get the address of the ListHead in red) and
323the configuration of these filters:
324kd> !fltkd.filters
325Filter List: ffffe001b9e730c0 "Frame 0"
326 FLT_FILTER: ffffe001b9e8ba90 "WdFilter" "328010"
327 FLT_INSTANCE: ffffe001ba3cd780 "WdFilter Instance" "328010"
328 FLT_INSTANCE: ffffe001ba47f010 "WdFilter Instance" "328010"
329 FLT_INSTANCE: ffffe001ba8c8640 "WdFilter Instance" "328010"
330 FLT_INSTANCE: ffffe001ba982640 "WdFilter Instance" "328010"
331 FLT_FILTER: ffffe001b9b42010 "FsFlt" "262100"
332 FLT_INSTANCE: ffffe001ba8366f0 "minifilter Instance" "262100"
333 FLT_INSTANCE: ffffe001ba8306f0 "minifilter Instance" "262100"
334 FLT_INSTANCE: ffffe001ba83c6f0 "minifilter Instance" "262100"
335 FLT_INSTANCE: ffffe001ba8416f0 "minifilter Instance" "262100"
336 FLT_FILTER: ffffe001b9240320 "storqosflt" "244000"
337 FLT_FILTER: ffffe001b954a5e0 "FileCrypt" "141100"
338 FLT_FILTER: ffffe001bb694720 "luafv" "135000"
339 FLT_INSTANCE: ffffe001bb69b010 "luafv" "135000"
340 FLT_FILTER: ffffe001ba4b2cb0 "npsvctrig" "46000"
341 FLT_INSTANCE: ffffe001ba98c710 "npsvctrig" "46000"
342 FLT_FILTER: ffffe001b9e85010 "FileInfo" "45000"
343 FLT_INSTANCE: ffffe001ba3cdb40 "FileInfo" "45000"
344 FLT_INSTANCE: ffffe001ba46e420 "FileInfo" "45000"
345 FLT_INSTANCE: ffffe001baa45310 "FileInfo" "45000"
346 FLT_INSTANCE: ffffe001ba721b40 "FileInfo" "45000"
347 FLT_FILTER: ffffe001b9e88580 "Wof" "40700"
348 FLT_INSTANCE: ffffe001ba462b90 "Wof Instance" "40700"
349 FLT_INSTANCE: ffffe001ba73c640 "Wof Instance" "40700"
350kd> !fltkd.filter ffffe001b9b42010
351FLT_FILTER: ffffe001b9b42010 "FsFlt" "262100"
352 FLT_OBJECT: ffffe001b9b42010 [02000000] Filter
353 RundownRef : 0x000000000000000a (5)
354 PointerCount : 0x00000001
355 PrimaryLink : [ffffe001b9240330-ffffe001b9e8baa0]
356 Frame : ffffe001b9e73010 "Frame 0"
357 Flags : [00000002] FilteringInitiated
358HideDRV – Rootkit analysis
35918
360 DriverObject : ffffe001bc5768d0
361 FilterLink : [ffffe001b9240330-ffffe001b9e8baa0]
362 PreVolumeMount : 0000000000000000 (null)
363 PostVolumeMount : 0000000000000000 (null)
364 FilterUnload : fffff80198af13f0 fsflt+0x13f0
365 InstanceSetup : fffff80198af14b0 fsflt+0x14b0
366 InstanceQueryTeardown : fffff80198af14d0 fsflt+0x14d0
367 InstanceTeardownStart : fffff80198af14f0 fsflt+0x14f0
368 InstanceTeardownComplete : fffff80198af14f0 fsflt+0x14f0
369 ActiveOpens : (ffffe001b9b421c8) mCount=0
370 Communication Port List : (ffffe001b9b42218) mCount=0
371 Client Port List : (ffffe001b9b42268) mCount=0
372 VerifierExtension : 0000000000000000
373 Operations : ffffe001b9b422c0
374 OldDriverUnload : 0000000000000000 (null)
375 SupportedContexts : (ffffe001b9b42140)
376 VolumeContexts : (ffffe001b9b42140)
377 InstanceContexts : (ffffe001b9b42140)
378 FileContexts : (ffffe001b9b42140)
379 StreamContexts : (ffffe001b9b42140)
380 StreamHandleContexts : (ffffe001b9b42140)
381 TransactionContext : (ffffe001b9b42140)
382 (null) : (ffffe001b9b42140)
383 InstanceList : (ffffe001b9b42078)
384 FLT_INSTANCE: ffffe001ba8366f0 "minifilter Instance" "262100"
385 FLT_INSTANCE: ffffe001ba8306f0 "minifilter Instance" "262100"
386 FLT_INSTANCE: ffffe001ba83c6f0 "minifilter Instance" "262100"
387 FLT_INSTANCE: ffffe001ba8416f0 "minifilter Instance" "262100"
388We can see few callbacks to the FsFlt driver. Sadly, the fltkd extension hides few elements and we
389cannot find the PreOperation() function. We used a trick to get it by parsing the kernel structures
390as described in the previous schema:
391kd> dt _FLT_FILTER ffffe001b9b42010
392FLTMGR!_FLT_FILTER
393 +0x000 Base : _FLT_OBJECT
394 +0x030 Frame : 0xffffe001`b9e73010 _FLTP_FRAME
395 +0x038 Name : _UNICODE_STRING "FsFlt"
396 +0x048 DefaultAltitude : _UNICODE_STRING "262100"
397 +0x058 Flags : 2 ( FLTFL_FILTERING_INITIATED )
398 +0x060 DriverObject : 0xffffe001`bc5768d0 _DRIVER_OBJECT
399 +0x068 InstanceList : _FLT_RESOURCE_LIST_HEAD
400 +0x0e8 VerifierExtension : (null)
401HideDRV – Rootkit analysis
40219
403 +0x0f0 VerifiedFiltersLink : _LIST_ENTRY [ 0x00000000`00000000 -
4040x00000000`00000000 ]
405 +0x100 FilterUnload : 0xfffff801`98af13f0 long +0
406 +0x108 InstanceSetup : 0xfffff801`98af14b0 long +0
407 +0x110 InstanceQueryTeardown : 0xfffff801`98af14d0 long +0
408 +0x118 InstanceTeardownStart : 0xfffff801`98af14f0 void +0
409 +0x120 InstanceTeardownComplete : 0xfffff801`98af14f0 void +0
410 +0x128 SupportedContextsListHead : (null)
411 +0x130 SupportedContexts : [7] (null)
412 +0x168 PreVolumeMount : (null)
413 +0x170 PostVolumeMount : (null)
414 +0x178 GenerateFileName : (null)
415 +0x180 NormalizeNameComponent : (null)
416 +0x188 NormalizeNameComponentEx : (null)
417 +0x190 NormalizeContextCleanup : (null)
418 +0x198 KtmNotification : (null)
419 +0x1a0 SectionNotification : (null)
420 +0x1a8 Operations : 0xffffe001`b9b422c0 _FLT_OPERATION_REGISTRATION
421 +0x1b0 OldDriverUnload : (null)
422 +0x1b8 ActiveOpens : _FLT_MUTEX_LIST_HEAD
423 +0x208 ConnectionList : _FLT_MUTEX_LIST_HEAD
424 +0x258 PortList : _FLT_MUTEX_LIST_HEAD
425 +0x2a8 PortLock : _EX_PUSH_LOCK
426kd> dt _FLT_OPERATION_REGISTRATION 0xffffe001`b9b422c0
427FLTMGR!_FLT_OPERATION_REGISTRATION
428 +0x000 MajorFunction : 0 ''
429 +0x004 Flags : 0
430 +0x008 PreOperation : 0xfffff801`98af4100 _FLT_PREOP_CALLBACK_STATUS +0
431 +0x010 PostOperation : (null)
432 +0x018 Reserved1 : (null)
433kd> u 0xfffff801`98af4100
434fsflt+0x4100:
435fffff801`98af4100 4c89442418 mov qword ptr [rsp+18h],r8
436fffff801`98af4105 4889542410 mov qword ptr [rsp+10h],rdx
437fffff801`98af410a 48894c2408 mov qword ptr [rsp+8],rcx
438fffff801`98af410f 4883ec38 sub rsp,38h
439fffff801`98af4113 c744242000000000 mov dword ptr [rsp+20h],0
440fffff801`98af411b 48c744242800000000 mov qword ptr [rsp+28h],0
441fffff801`98af4124 488b442440 mov rax,qword ptr [rsp+40h]
442fffff801`98af4129 488b4010 mov rax,qword ptr [rax+10h]
443HideDRV – Rootkit analysis
44420
445We can confirm that the defined PreOperation() function is at FsFlt+0x4100 as mentioned
446previously.
447Limitations
448The implementation made by the rootkit developer has several limitations. The most interesting is the
449fact that files and directories implementing hiding feature is performed using their full volume paths (for
450example: \Device\HarddiskVolume1\Sekoia\ApplicationDummy.dll). By changing the
451volume name, we bypass the protection and we can access to the hidden artefacts. An easy way to
452change the volume name is to create a Shadow Copy of the drive. The volume path pattern of a Shadow
453Copy is \Device\HarddiskVolumeShadowCopyX. By mounting it, we are able to access to the
454protection files and directories. If you use live forensics tools with Shadow Copy feature to retrieve the
455artefacts (such as FastIR Collector: https://github.com/SekoiaLab/Fastir_Collector) the rootkit is simply
456inefficient.
457HideDRV – Rootkit analysis
45821
459REGISTRY CALLBACKS
460Static analysis
461A registry access callback is basically registered and started with one function:
462- CmRegisterCallbackEx() (MSDN documentation: https://msdn.microsoft.com/enus/library/windows/hardware/ff541921(v=vs.85).aspx)
463
464From a reverse engineering point of view, the first argument of this function is the most interesting. It
465contains the function to be executed by the callback:
466The callback function is RegCallBacksFunction() (0x4870). This function checks if the accessed
467registry name matches the value to be hidden. If the result is successful, the function performs a second
468check on the process path that tries to access to this registry:
469HideDRV – Rootkit analysis
47022
471If the process path that ends with services.exe is available to the hidden registry, the rootkit does
472not hide it. However, the callback function changes the contents of the callback data structure to
473STATUS_NOT_FOUND:
474
475Overview of the registry callbacks structure
476The schema of the registry callback linked list looks like this:
477
478
479HideDRV – Rootkit analysis
48023
481Dynamic analysis with WinDBG
482We can list the registry callbacks thanks to WinDBG. The first step is to get the number of defined
483callbacks:
484kd> dd nt!CmpCallBackCount L1
485fffff803`6db02be0 00000002
486On our virtual machine, we have 2 registry callbacks listed into a _LIST_ENTRY list:
487kd> dps nt!CallbackListHead L2
488fffff803`6dafa700 ffffc000`92eb3bb0
489fffff803`6dafa708 ffffc000`9605c710
490kd> dt nt!_LIST_ENTRY fffff803`6dafa700
491[ 0xffffc000`92eb3bb0 - 0xffffc000`9605c710 ]
492 +0x000 Flink : 0xffffc000`92eb3bb0 _LIST_ENTRY [ 0xffffc000`9605c710 -
4930xfffff803`6dafa700 ]
494 +0x008 Blink : 0xffffc000`9605c710 _LIST_ENTRY [ 0xfffff803`6dafa700 -
4950xffffc000`92eb3bb0 ]
496kd> dps 0xffffc000`9605c710 L8
497ffffc000`9605c710 fffff803`6dafa700 nt!CallbackListHead
498ffffc000`9605c718 ffffc000`92eb3bb0
499ffffc000`9605c720 0069006e`00000000
500ffffc000`9605c728 01d1d67d`e400c771
501ffffc000`9605c730 ffffc000`952b4a80
502ffffc000`9605c738 fffff801`98af4870 fsflt+0x4870
503ffffc000`9605c740 00650065`000c000c
504ffffc000`9605c748 ffffc000`9498ca70
505We can see that one of the callbacks refers to FsFlt+0x4870. It matches the offset mentioned
506previously.
507Limitations
508The implementation by the rootkit developer has several limitations. By simply copying regedit.exe
509on the desktop and by renaming it to services.exe, the user can execute it and see the hidden
510registry key because the executable file path will end with services.exe and the rootkit will think
511that it’s the real services.exe process.
512HideDRV – Rootkit analysis
51324
514PROCESS CREATION CALLBACKS
515
516Static analysis
517A process creation callback is basically registered and started with one function:
518- PsSetCreateProcessNotifyRoutine() (MSDN documentation:
519https://msdn.microsoft.com/en-us/library/windows/hardware/ff559951(v=vs.85).aspx)
520Once again, from a reverse engineering point of view, the first argument of the function is the most
521interesting since it contains the function name to be executed when a process is created or deleted:
522In our sample, the function is ProcessCallbacks() (0x15280). This function checks if the process
523name is explorer.exe. If so, the rootkit sets an event in order to inject the library (.dll) configured in
524the registry.
525Dynamic analysis with WinDBG
526We can list the process creation and deletion callbacks thanks to WinDBG. The first step is to get the
527number of callbacks:
528kd> dd nt!PspCreateProcessNotifyRoutineCount L1
529fffff803`6defadcc 00000006
530kd> dd nt!PspCreateProcessNotifyRoutineExCount L1
531fffff803`6defadc8 00000002
532On the system, we have 8 process callbacks. With a small script, we can list them all:
533kd> .for (r $t0=0; $t0 < 8; r $t0=$t0+1) { r $t1=poi($t0 * 8 +
534nt!PspCreateProcessNotifyRoutine); .if ($t1 == 0) { .continue }; r $t1 = $t1 &
5350xFFFFFFFFFFFFFFF0; dps $t1+8 L1;}
536ffffe001`b9248688 fffff803`6d8db7e0 nt!ViCreateProcessCallback
537ffffe001`b9239348 fffff801`96687290 cng!CngCreateProcessNotifyRoutine
538ffffe001`b95f7df8 fffff801`96c970a0 WdFilter!MpCreateProcessNotifyRoutineEx
539ffffe001`b9e8e1f8 fffff801`96488da0 ksecdd!KsecCreateProcessNotifyRoutine
540ffffe001`ba312748 fffff801`96fc30d0 tcpip!CreateProcessNotifyRoutineEx
541ffffe001`ba372f58 fffff801`9633d7b0 CI!I_PEProcessNotify
542HideDRV – Rootkit analysis
54325
544ffffe001`b92cb2e8 fffff801`988daba0 peauth+0x2aba0
545ffffe001`bada52d8 fffff801`98af5280 fsflt+0x5280
546On the results above, the last callback points to the ProcessCallbacks() function mentioned
547previously. As WinDBG scripting language is not really user-friendly, here is the explanation:
548.for (r $t0=0; $t0 < 8; r $t0=$t0+1) #a loop of 8 iterations (our 8 callbacks)
549{
550 r $t1=poi($t0 * 8 + nt!PspCreateProcessNotifyRoutine);
551 #For each callback, get the pointer to the process callback structure
552 .if ($t1 == 0) {
553 .continue
554 };
555 r $t1 = $t1 & 0xFFFFFFFFFFFFFFF0; #Apply a mask on the pointer
556 dps $t1+8 L1; #the callback function is at the offset 0x8
557}
558PAYLOAD INJECTION
559To perform the library injection in the process explorer.exe, the rootkit uses the APC (Asynchronous
560Procedure Calls). You can find a document on the feature on MSDN: https://msdn.microsoft.com/frfr/library/windows/desktop/ms681951(v=vs.85).Aspx.
561The technique was used in the well-known rootkit TLD3/TLD4. The driver uses the API
562KeStackAttachProcess() and KeUnstackDetachProcess() in order to attach the current
563driver thread to the address space of the explorer.exe process. The driver gets the base address of
564kernel32.dll and particularly the address of LoadLibrary() in explorer.exe. Then, the
565driver allocates memory in the process to copy the library path. Finally, the rootkit executes
566KeInitializeApc() and KeInsertQueueAPC() to execute LoadLibrary() in order to load
567and execute the library.
568As a tutorial, you can read the source code of TLD3 in order to understand the techniques in use:
569http://pastebin.com/UpvGUw19.
570HideDRV – Rootkit analysis
57126
572CHAPTER ABSTRACT
573With one driver, we browsed explanations on file-system, registry callbacks, process creation callbacks
574and code injection via APC. These techniques are really common in rootkit analysis. The biggest missing
575piece concerns the network capabilities. This rootkit does not implement NDIS Filter or WFP (Windows
576Filtering Platform). The network capabilities are really common; for example, this mechanism was
577implemented in the Turla rootkit. In order to be as complete as possible, here is the way to investigate
578and to list the WPF callbacks with WinDBG:
579kd> dp netio!gWfpGlobal L1
580fffff801`96a63258 ffffe001`b9e025b0
581kd> u netio!FeInitCalloutTable L10
582NETIO!FeInitCalloutTable:
583fffff801`96a22490 4053 push rbx
584fffff801`96a22492 4883ec20 sub rsp,20h
585fffff801`96a22496 488b05bb0d0400 mov rax,qword ptr [NETIO!gWfpGlobal (fffff801`96a63258)]
586fffff801`96a2249d 33c9 xor ecx,ecx
587fffff801`96a2249f ba57667043 mov edx,43706657h
588fffff801`96a224a4 48898848010000 mov qword ptr [rax+148h],rcx
589fffff801`96a224ab 48898850010000 mov qword ptr [rax+150h],rcx
590fffff801`96a224b2 b900400100 mov ecx,14000h
591fffff801`96a224b7 4c8b059a0d0400 mov r8,qword ptr [NETIO!gWfpGlobal (fffff801`96a63258)]
592fffff801`96a224be 4981c050010000 add r8,150h
593fffff801`96a224c5 e8223dfeff call NETIO!WfpPoolAllocNonPaged (fffff801`96a061ec)
594fffff801`96a224ca 488bd8 mov rbx,rax
595kd> dps ffffe001`b9e025b0+0x150 L1
596ffffe001`b9e02700 ffffe001`b9e07000
597kd> !pool ffffe001`b9e07000
598Pool page ffffe001b9e07000 region is Nonpaged pool
599*ffffe001b9e07000 : large page allocation, tag is WfpC, size is 0x14000 bytes
600Pooltag WfpC : WFP callouts, Binary : netio.sys
601kd> u NETIO!InitDefaultCallout
602NETIO!InitDefaultCallout:
603fffff801`96a2251c 4053 push rbx
604fffff801`96a2251e 4883ec20 sub rsp,20h
605fffff801`96a22522 4c8d051f150400 lea r8,[NETIO!gFeCallout (fffff801`96a63a48)]
606fffff801`96a22529 ba57667043 mov edx,43706657h
607fffff801`96a2252e b950000000 mov ecx,50h
608fffff801`96a22533 e8b43cfeff call NETIO!WfpPoolAllocNonPaged
609HideDRV – Rootkit analysis
61027
611fffff801`96a22538 488bd8 mov rbx,rax
612fffff801`96a2253b 4885c0 test rax,rax
613kd> r $t0=ffffe001b9e07000; .for( r $t1=0; @$t1 < 0x30; r $t1=@$t1+1) {dps
614@$t0+2*@$ptrsize L2; r $t0=@$t0+0x50;}
615ffffe001`b9e07010 00000000`00000000
616ffffe001`b9e07018 00000000`00000000
617ffffe001`b9e07060 fffff801`971ab5c0 tcpip!IPSecInboundTransportFilterCalloutClassifyV4
618ffffe001`b9e07068 fffff801`9712b060 tcpip!IPSecAleConnectCalloutNotify
619ffffe001`b9e070b0 fffff801`971ab700 tcpip!IPSecInboundTransportFilterCalloutClassifyV6
620ffffe001`b9e070b8 fffff801`9712b060 tcpip!IPSecAleConnectCalloutNotify
621ffffe001`b9e07100 fffff801`971aaf70 tcpip!IPSecOutboundTransportFilterCalloutClassifyV4
622ffffe001`b9e07108 fffff801`9712b060 tcpip!IPSecAleConnectCalloutNotify
623ffffe001`b9e07150 fffff801`971b30d0 tcpip!IPSecOutboundTransportFilterCalloutClassifyV6
624ffffe001`b9e07158 fffff801`9712b060 tcpip!IPSecAleConnectCalloutNotify
625ffffe001`b9e071a0 fffff801`971b2990 tcpip!IPSecInboundTunnelFilterCalloutClassifyV4
626ffffe001`b9e071a8 fffff801`9712b060 tcpip!IPSecAleConnectCalloutNotify
627ffffe001`b9e071f0 fffff801`971b2a50 tcpip!IPSecInboundTunnelFilterCalloutClassifyV6
628ffffe001`b9e071f8 fffff801`9712b060 tcpip!IPSecAleConnectCalloutNotify
629[…]
630ffffe001`b9e07c90 fffff801`97037500 tcpip!WfpAlepSetOptionsCalloutClassify
631ffffe001`b9e07c98 fffff801`9707ce80 tcpip!FllAddGroup
632ffffe001`b9e07ce0 00000000`00000000
633HideDRV – Rootkit analysis
63428
635BONUS: DIFFERENCE BETWEEN
636THE X86 & THE X64 VERSIONS
637CONTEXT
638We decided to add a small chapter concerning the x86 version of HIDEDRV. This version contains 2 major
639difference with the x64 version:
640- The driver creates a device and a symbolic link;
641- Instead of using file system minifilters to hide elements, the driver defines 3 SSDT (System Service
642Dispatch Table) hooks.
643As we wrote, the SSDT hooks are not possible in Windows x64 (except when bypassing Patch Guard).
644This constraint does not exist in x86. This approach is not popular anymore but it’s interesting to keep it
645in mind and be able to analyse it.
646For those interested in this x86 sample, the associated hash is:
647b1900cb7d1216d1dbc19b4c6c8567d48215148034a41913cc6e59958445aebde
648DEVICE AND SYMBOLIC LINK
649On the x86 version of the rootkit, the developer created a driver device and a symbolic link:
650HideDRV – Rootkit analysis
65129
652In the analysed sample, the device and symbolic link are not used. The symbolic links are usually created
653in order to receive notification (IOCTL) from the user space via the DeviceIoControl() API.
654We can list the device thanks to WinDBG:
655kd> !object \Device
656Object: 88e0f030 Type: (85253e90) Directory
657 ObjectHeader: 88e0f018 (new version)
658 HandleCount: 0 PointerCount: 232
659 Directory Object: 88e010e8 Name: Device
660 Hash Address Type Name
661 ---- ------- ---- ----
662 00 85fb9738 Device KsecDD
663 862092d8 Device Beep
664 86004388 Device Ndis
665 […]
666 28 854762e8 Device dfsflt
667 86214578 Device Null
668 852d77f0 Device 00000010
669 852c7030 Device 00000003
670 […]
671HideDRV – Rootkit analysis
67230
673SSDT HOOKS
674Overview of the SSDT
675The SSDT is the table that contains the addresses of the functions to be executed when a syscall is made.
676Here is the schema of the table:
677To understand how it works, we can look the assembly code of the function NtQueryKey():
678kd> u ntdll!NtQueryKey
679ntdll!ZwQueryKey:
68077ca60e8 b8f4000000 mov eax,0F4h
68177ca60ed ba0003fe7f mov edx,offset SharedUserData!SystemCallStub
68277ca60f2 ff12 call dword ptr [edx]
68377ca60f4 c21400 ret 14h
68477ca60f7 90 nop
685The function executes a system call with the argument 0xF4. We can get the function executed when this
686system call is performed:
687kd> dps KiServiceTable+0xf4*4 L1
688826b816c 82886cae nt!NtQueryKey
689Note: a second table (KeServiceDescriptorTableShadow) exists. The table contains a pointer to
690KiServiceTable (the same as previously) and to W32perviceTable (syscall for the GUI threads).
691
692HideDRV – Rootkit analysis
69331
694Static analysis
695In the function SSDT_Hook() (0x13490), the rootkit replaces 3 functions addresses in the
696KiServiceTable. These functions are used to read registry value, get file information and get
697directory information:
698The malicious code functions have the same purpose as the callbacks previously described:
699- if the accessed file, directory or registry must be hidden, the rootkit returns that the element does
700not exist;
701- if the accessed file, directory or registry is not in the list, the rootkit executes the original function
702of the SSDT (previously saved in a global variable).
703-
704Dynamic analysis with WinDBG
705The SSDT hook can be directly identified with WinDBG:
706kd> dps nt!KeServiceDescriptorTable L3
707827a39c0 826b7d9c nt!KiServiceTable
708827a39c4 00000000
709827a39c8 00000191
710kd> .shell -ci "dps nt!KiServiceTable L0x191" find "FsFlt"
711826b7f6c 92af9160 FsFlt+0x2160
712826b8118 92afac00 FsFlt+0x3c00
713826b82c0 92afa9c0 FsFlt+0x39c0
714.shell: Process exited
715HideDRV – Rootkit analysis
71632
717CHAPTER ABSTRACT
718The SSDT hooks are becoming rarer. The same approach can be performed in the IDT (Interrupt
719Description Table). The table is used to identify the function address to execute when an interrupt is
720called. We can display the table thanks to WinDBG:
721kd> !idt -a
722Dumping IDT: 80b95400
7233255d61800000000:82677fc0 nt!KiTrap00
7243255d61800000001:82678150 nt!KiTrap01
7253255d61800000002:Task Selector = 0x0058
7263255d61800000003:826785c0 nt!KiTrap03
7273255d61800000004:82678748 nt!KiTrap04
728[…]
729As explained previously, this approach was popular a few years ago on x86 platform but it tends to
730become rarer today due to Patch Guard.
731HideDRV – Rootkit analysis
73233
733CONCLUSION
734This document has been written as a “hands on†for reverse engineering beginners and willing to
735leverage his experience on rootkits.
736The use case of the document is a very interesting case study for basic rootkit techniques. Using
737different tools and tricks, we overviewed the main features of the rootkit such as filesystem
738manipulation, registry and process callbacks, code injection and even network manipulation.