In the first part of his series on the Necurs rootkit, Peter Ferrie looked at what it does during start-up and when it is not loaded as a boot-time driver. This time, he looks at what Necurs does when it is loaded as a boot-time driver.
Copyright © 2014 Virus Bulletin
In the first part of this series on the Necurs rootkit , we looked at what it does during start-up and when it is not loaded as a boot-time driver. This time, we will look at what Necurs does when it is loaded as a boot-time driver.
When Necurs is loaded as a boot-time driver, it remains resident in memory (unlike when it is loaded as a standard driver). It sets every entry in its IRP table to point to a single routine (described below). It attempts to create a new ‘\Device\NtSecureSys’ device and a ‘\??\NtSecureSys’ symbolic link to the device. The symbolic link allows the user-mode component to communicate with the kernel mode component, and to send I/O control requests to it.
The rootkit attempts to retrieve the address of the ObRegisterCallbacks() function. This API was introduced in Windows Vista. If the rootkit is running on a platform that supports the API, then it registers callbacks for process and thread objects, intending to intercept process and thread creation events before they occur. The rootkit registers itself using an altitude of ‘20101’. The altitude describes how low in the stack the callback should be placed. The rootkit uses a value in the reserved region of ‘FSFilter System’, corresponding to a level that is even lower than the lowest documented level.
If the rootkit is running on a platform that does not support the ObRegisterCallbacks() API, then it queries the build number of the currently running version of Windows. The rootkit is specifically interested in builds 2600 (Windows XP), 3790 (Windows 2003) and 6000 (Windows Vista SP0). The rootkit uses the build number to determine the function indexes that correspond to the NtOpenProcess() and NtOpenThread() functions in the Service Descriptor Table. The rootkit allocates memory for the entire service table, then maps and locks the pages so that they can be read without issue. It saves the pointers to the original NtOpenProcess() and NtOpenThread() functions, and replaces them with rootkit-specific versions.
The rootkit attempts to access the ‘DB1’ registry value under the ‘\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services\<random numbers>’ key that it created previously . If the value doesn’t exist, the rootkit creates it later. If the value does exist, the rootkit requires the data – an array of zero-terminated Unicode strings – to be at least four bytes long and even in length. The rootkit uses this array when determining whether a registry access request should be allowed.
The rootkit registers a callback for registry operations, but does so using the CmRegisterCallback() function, which is documented as being obsolete for Windows Vista and later. It adds the current thread handle to a thread array that it carries, and sets the reference count to one. The array is used for access control for the rootkit functionality. Any thread handles which appear in the array are allowed to request that the rootkit performs certain actions or queries certain information.
The rootkit creates a file system filter device for the device that hosts the rootkit file, and attempts to attach the filter to the top of the file system stack so that it is the first device to receive all requests. If that request fails (which can occur if the subsystem has not yet been initialized), the rootkit creates a thread that runs once every 100ms to attempt to register the device. The thread runs until it succeeds.
The rootkit attempts to access the ‘DB0’ registry value under the ‘\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services\<random numbers>’ key. If the value doesn’t exist, the rootkit creates it later. If the value does exist, the rootkit requires the data to be a multiple of 16 bytes in length. The data is an array of MD5 hash values that form a whitelist of MD5 hashes of memory images. The rootkit uses this array when determining whether an already-loaded driver should be allowed to remain loaded.
The rootkit attempts to access the ‘DB2’ registry value under the ‘\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services\<random numbers>’ key. If the value doesn’t exist, the rootkit creates it later. If the value does exist, then the rootkit requires the data – an array of FNV-1 hash values that form a whitelist of driver names – to be a multiple of eight bytes in length. The rootkit uses this array when determining whether a driver should be allowed to load.
The rootkit requests the list of loaded modules, then examines each entry in the list. It is interested in two key entries: win32k.sys and itself. The rootkit also pays attention to the order in which they have been loaded. If the ‘win32k.sys’ module is in the list, the rootkit sets a flag which is checked later. If the rootkit module is seen, then the blacklist and whitelist behaviour is enabled, if the ‘DB0’ and ‘DB2’ registry values exist.
If the blacklist behaviour is enabled, the rootkit performs a case-insensitive comparison of the module name with each entry in the following list (sorted for easier reading – the original unsorted list was likely created by adding the names as they were found):
If a match is found, the rootkit writes some code at the module’s entrypoint, which causes it to return immediately with a STATUS_UNSUCCESSFUL result, in turn causing the driver to be unloaded by Windows, if the code is executed. It does not stop the driver from running if it was already active. If the module’s name is not on the blacklist, then the rootkit will check the flags field for the undocumented ‘VP’ device status. If the flag is set, then the rootkit always allows it. Otherwise, it checks the whitelist.
The check for a whitelist entry is complicated. It begins with the rootkit allocating a block of memory that is equal in size to the module being checked. The entire contents of the module are then copied to the block of memory, and the copied image is relocated as though it were loaded to a fixed base of 0x10000. The rootkit supports two kinds of relocation items: IMAGE_REL_BASED_HIGHLOW and IMAGE_REL_BASED_DIR64. The imports table is parsed, but all entries are zeroed out. The rootkit calculates the MD5 hash of the headers and each of the sections, and then searches for a match in the MD5 whitelist.
There is a vulnerability in the way in which the rootkit calculates the hash of the sections, which means that a knowledgeable person could alter an allowed driver in such a way that the original MD5 hash would be retained, but entirely different code could be executed. This technique could be used to bypass the protections of the rootkit and then uninstall it. However, we will not go into the details here.
If the MD5 hash matches one of the entries in the MD5 whitelist, the rootkit allows the driver to remain in memory. Otherwise, it performs the same code alteration as for the blacklisted drivers. This creates a race condition whereby a just-loaded driver might be caught by the code change and then exit, but a driver that loaded just a little earlier might complete its entry routine and thus escape the effect of the alteration. However, it is clear that once the rootkit has loaded, no unrecognized drivers can be loaded, and no updated drivers can be installed.
If the whitelist does not exist, the rootkit will create it by initiating a new thread to gather the information. The thread waits until the ntdll.dll file can be opened, meaning that the file system driver has become active. The thread makes an attempt once every 200ms until it succeeds. At that point, all of the critical system drivers will have been loaded, which the rootkit considers sufficient time to allow before creating the whitelist of allowed drivers.
The rootkit enumerates each of the entries in the ‘\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services’ registry hive. The driver is not added to the whitelist if it has no ‘Type’ registry value, or if the driver type is not a kernel driver, a file system driver, or a ‘recogniser’ driver. If the driver’s path is ‘system32\<driver name>’, then the rootkit will reformat the path to ‘\systemroot\system32\<driver name>’. If the driver has no ‘ImagePath’ registry value, then the rootkit will supply ‘\SystemRoot\System32\Drivers\<driver name>.sys’. Otherwise, the rootkit will accept the ‘ImagePath’ value, regardless of what it contains.
The rootkit checks whether the driver name is among the blacklisted names, and will not add it to the whitelist if it is. Otherwise, the rootkit opens the file, reads the entire file into memory, relocates it to a fixed base of 0x10000, and calculates the MD5 hash, as described above. The rootkit then attempts to find the resource section in the image. Interestingly, it supports 64-bit files in this routine, even though such files are excluded explicitly during the MD5 calculation, so the code-path is never executed. The rootkit parses the resource section to find the version information item, and the digital certificate. If either target is found, the rootkit searches the version information and/or the digital certificate for references to any entry in the following list (which is sorted for easier reading):
Check Point Software Technologies Ltd
CJSC Returnil Software
Comodo Security Solutions
Doctor Web Ltd
ESET, spol. s r.o.
FRISK Software International Ltd
G DATA Software
Panda Software International
Quick Heal Technologies
Sophos Plc Sunbelt Software
Any driver that references any of the names on the list will not be added to the whitelist, but if the driver has not been excluded, the rootkit will add the MD5 hash to the MD5 whitelist. The rootkit also calculates the FMV-1 hash of the driver path, and adds that to the FMV-1 whitelist.
After examining each of the services in the registry, the rootkit performs the same checks for each of the files in the ‘\SystemRoot\System32\Drivers’ directory, and each of the DLLs in the ‘\SystemRoot\System32’ directory. After examining each of the DLLs, the rootkit waits until the ‘win32k.sys’ module appears in the loaded module list. At that point, it queries the list of loaded modules again, and adds all of the entries that are not on the blacklist, as described above. There is some duplicated code here, whereby the rootkit calculates the FMV-1 hash of the driver path, and adds that to the FMV-1 whitelist again. This is harmless though, since the duplicated entries will be removed later.
If the rootkit is running on a version of Windows prior to Windows Vista, the rootkit adds the ‘ntldr’ and ‘boot.ini’ files manually to the FMV-1 whitelist. Otherwise, it adds the ‘bootmgr’ and ‘\SystemRoot\System32\winload.exe’ files manually to the FMV-1 whitelist. The rootkit sorts the MD5 and FMV-1 whitelists, and removes any duplicated entries. It then writes the ‘DB0’ and ‘DB2’ registry values with the contents of the MD5 and FMV-1 whitelists, respectively. The rootkit also registers a callback which receives control when an image is loaded, before the image gains execution control. The callback watches for ‘win32k.sys’ being loaded, and sets the flag that the whitelisting thread checks (if it is not set already). If the loaded file can be opened, the rootkit reads the entire file into memory and then performs the whitelist check, as described above. Otherwise, the rootkit performs only the MD5 hash check on the in-memory image. If the image fails the verification, the rootkit performs the same code alteration as for the blacklisted drivers.
Next time, we will look at the different IRP functions, and the details of the rootkit’s stealthing abilities.
 Ferrie, P. The curse of Necurs, part 1. Virus Bulletin. April 2014, p.4. http://www.virusbtn.com/ pdf/magazine/2014/201404.pdf.