Log in

View Full Version : Hidden Kernel Module (Driver) detection techniques


Ramsey
June 20th, 2009, 00:53
I am trying to develop a tool that can detect the presence of a specific kernel driver rootkit. I know for sure it is unlinked from the PsLoadedModuleList and does not hook any API calls (ZwQuerySystemInformation). Obviously, because it has modified the linked list it does not need to mess w/ any API calls.

I was curious as to how I can proceed in detecting the presence of this rookit given the information presented above.

Kayaker
June 20th, 2009, 02:35
Hi

As a start, how does the rootkit fare against these two strategies?

http://www.security.org.sg/code/kproccheck.html
http://www.security.org.sg/code/sdtrestore.html

GamingMasteR
June 20th, 2009, 05:09
Hi,

If the rootkit is unlinked from PsLoadedModuleList only then you can detect it's device/driver object by scanning the device/driver directory .

you can start from this example i made :
Code:
#define NUMBER_HASH_BUCKETS 37
#define OBJECT_TO_OBJECT_HEADER(o) ((POBJECT_HEADER)CONTAINING_RECORD((o), OBJECT_HEADER, Body))

typedef struct _OBJECT_DIRECTORY_ENTRY {
struct _OBJECT_DIRECTORY_ENTRY *ChainLink;
PVOID Object;
} OBJECT_DIRECTORY_ENTRY, *POBJECT_DIRECTORY_ENTRY;

typedef struct _OBJECT_DIRECTORY {
struct _OBJECT_DIRECTORY_ENTRY *HashBuckets[ NUMBER_HASH_BUCKETS ];
struct _OBJECT_DIRECTORY_ENTRY **LookupBucket;
BOOLEAN LookupFound;
USHORT SymbolicLinkUsageCount;
struct _DEVICE_MAP *DeviceMap;
} OBJECT_DIRECTORY, *POBJECT_DIRECTORY;

static UNICODE_STRING DirectoryUnicode = RTL_CONSTANT_STRING(L"Directory";

VOID WalkDirectory(POBJECT_DIRECTORY Directory, POBJECT_TYPE Type)
{
ULONG Bucket;
POBJECT_DIRECTORY_ENTRY DirectoryEntry;
POBJECT_DIRECTORY_ENTRY DirectoryEntryNext;
PVOID Object;
POBJECT_HEADER ObjectHeader;
POBJECT_TYPE ObjectType;
POBJECT_NAME_INFORMATION ObjectName;
ULONG dwRetLength;

for(Bucket = 0; Bucket < NUMBER_HASH_BUCKETS; Bucket++)
{
DirectoryEntry = DirectoryEntryNext = Directory->HashBuckets[Bucket];
while (MmIsAddressValid(DirectoryEntryNext))
{
Object = DirectoryEntryNext->Object;
ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
ObjectType = ObjectHeader->ObjectType;
ObQueryNameString(Object, NULL, 0, &dwRetLength);
ObjectName = (POBJECT_NAME_INFORMATION)ExAllocatePool(NonPagedPool, dwRetLength);
ObQueryNameString(Object, ObjectName, dwRetLength, &dwRetLength);
if (ObjectType == Type)
{
DbgPrint("%wZ: %wZ -> %p", &Type->ObjectTypeName, ObjectName, Object);
}
else
{
if (RtlCompareUnicodeString(&ObjectType->ObjectTypeName, &DirectoryUnicode, FALSE) == 0)
{
WalkDirectory((POBJECT_DIRECTORY)Object, Type);
}
}
ExFreePool(ObjectName);
DirectoryEntryNext = DirectoryEntryNext->ChainLink;
}
}
}


VOID ScanDirectory(PWCHAR DirName, POBJECT_TYPE DirType)
{
UNICODE_STRING DirectoryName = RTL_CONSTANT_STRING(DirName);
OBJECT_ATTRIBUTES ObjectAttributes = RTL_INIT_OBJECT_ATTRIBUTES(&DirectoryName, OBJ_CASE_INSENSITIVE);
NTSTATUS Status;
HANDLE Handle = NULL;
POBJECT_DIRECTORY Directory = NULL;
PDRIVER_OBJECT *Objects = NULL;


Status = ZwOpenDirectoryObject(&Handle, DIRECTORY_QUERY, &ObjectAttributes);
if (NT_SUCCESS (Status))
{
Status = ObReferenceObjectByHandle(Handle, FILE_READ_ACCESS, NULL, KernelMode, (PVOID *)&Directory, NULL);
if (NT_SUCCESS (Status))
{
WalkDirectory(Directory, DirType);
ObDereferenceObject(Directory);
}
ZwClose(Handle);
}
}


NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
ScanDirectory(L"\\Driver", *IoDriverObjectType);
ScanDirectory(L"\\Device", *IoDeviceObjectType);
return STATUS_UNSUCCESSFUL;
}

darawk
June 22nd, 2009, 19:14
You can also search for a signature of the DRIVER_OBJECT structure, and the DRIVER_ENTRY structure in the PsLoadedModuleList (it's almost identical to the structure used to link the dll list in a process). Another way to search for drivers (though it won't detect all of them) is to enumerate device objects and then look at their attached drivers. Basically, just look for any and every structure that points to either of the two driver structures previously mentioned and enumerate them and follow the pointers.

You can also scan for MZ headers in kernel space and then grab the name of the module out of the export directory and compare it to lists obtained through higher level methods. This can be tricky though because you may run into fragments of modules that have been unloaded, you will need to come up with a way to validate the 'activeness' of the image.

Ramsey
July 2nd, 2009, 04:47
Thanks for the replies & the sample (GamingMasteR)

I am able to get a pointers to the DEVICE_OBJECT & DRIVER_OBJECT....How would I go about copying the driver from memory (so I can later dump it to a file) ?

GamingMasteR
July 2nd, 2009, 09:12
Quote:
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


Use DriverStart and DriverSize for base address and size, make sure every memory page in the driver region is resident in memory before reading it .

cpuZ
March 8th, 2011, 23:12
Hello guys, new to the forum and this thread.

GamingMaster
"make sure every memory page in the driver region is resident in memory before reading it ."

Can you explain more about this because I am working on a driver dumping function which uses ZwMapViewOfSection but I am getting different data with Root Repeal and Rku dumps. Are they doing something differently? What method would you recommend?

Regards,
cpuZ

Indy
March 9th, 2011, 09:37
Module by definition can not be hidden.

cpuZ
March 9th, 2011, 14:25
Hello Indy

Thanks for the response but I think I was not very clear in my posting intention. I am not concerned with detecting drivers or hiding them for that matter whatsoever, detecting would be the job of antirootkit and hiding that of malware/rootkits. I am simply wondering what is the recommended way or best method to dump a visible driver from kernel memory to disk. I figured I would ask here since this is an advanced programming board where some of you have already done this. Any suggestions?

Regards,
cpuZ

Kayaker
March 9th, 2011, 19:01
Hi

I think it would depend on where/when you're dumping the driver image. If in a PsSetLoadImageNotifyRoutine callback then you're detecting driver loading and should have a virgin PE image to work with. Nothing should be paged out at that point, including the INIT section, which often contains important rootkit behaviour one wants to know about.

If dumping from an existing loaded image, INIT might be paged out for good (PAGEABLE, DISCARDABLE), and data sections will be initialized. There could also be paged out sections, which in Softice you had to use the PAGEIN command on before dumping with Icedump for example. You might have to "touch" those pages before dumping, not sure of the recommended way of doing that offhand.

I would create a dedicated system thread for managing file output requests, scheduling a work queue or similar dispatching method to dump a copy of the driver image whenever the system gets around to it, and leaving the driver to continue loading (or prevent it from loading if you so choose from within a PsSetLoadImageNotifyRoutine callback).

Not sure if that answers all your questions offhand, but it's a start.

Cheers,
Kayaker

cpuZ
March 9th, 2011, 19:26
Thanks for the very detail oriented response Kayaker. I have read some of your previous posts beforehand, good stuff. I am only concerned with dumping existing images which are initialized and loaded, not at all with virgin images caught in notify callbacks (PsSetLoadImageNotifyRoutine) so I have opted for a simplistic method in which I use a memory descriptor list, lock the pages down and then read the memory to an allocated nonpaged memory pool with RtlCopyMemory and then transfer this buffer to an indirect buffer which I will later parse out pertinent information.

This seems to work fine and I haven't had any issues thus far. As far as system stability is concerned I am using try/except SEH frames around calls to RtlCopyMemory and also I have locked the pages to memory with MmMapLockedPagesSpecifyCache(BugCheck = FALSE) so I should receive NULL back in the event of failure, access mode is KernelMode. From what I have read using MmMapLockedPages with access mode set to KernelMode causes the system to issue a bugcheck on failure so I avoid that memory management API altogether and stick to the specify cache variant.

Thanks again for your information. If there is any concerns about my current method please feel free to correct me in the event that system stability could be in jeopardy. I know that copying live kernel memory is never a 100% safe thing to do in the first place.

Regards,
cpuZ

Kayaker
March 10th, 2011, 00:26
Thanks. It sounds like you've got a good working method there for the purposes. Please feel free to post any code snippets you feel might be of interest to others.

Regards,
Kayaker

Indy
March 10th, 2011, 09:08
cpuZ
Quote:
Any suggestions?

Module exists if it is described in the loader. Either it is associated with the file map. Otherwise, the module does not exist. All you think about this makes no sense