|
|
Hidden Rootkit Process Detection |
|
|
|
|
|
|
|
|
|
|
|
|
|
Rootkits are one of the advanced species in today's every
changing technical world. They are known for their sophisticated
techniques to hide their presence often evading their detection from
top notch Antiviruses and detection tools. Antivirus solutions often
hit the wall when it comes to Rootkit detection and there is a
greater need for dedicated Anti-Rootkit tools.
Rootkits use combination of user land and kernel level techniques
to evade their detection. In this article we will throw light on how
userland Rootkits work under the hood and different techniques which
can be used to detect such Rootkits. Though these methods are
effective only against user land Rootkits, in some cases they can
even detect kernel based Rootkits unless they haven't taken proper
care to remove all those traces. |
|
|
|
|
Userland Rootkits use different techniques to hide their process
and to prevent its termination. One such method is to hook the
NtOpenProcess function (OpenProcess API internally calls
NtOpenProcess) and return negative result whenever Anti-Rootkit
application try to open such process. As a result Rootkit process
will remain hidden from any process viewer tools.
This is just one of the method and often you will find more such
internal functions such as NtQuerySystemInformation being hooked to
filter out their process from the list.
|
|
|
|
|
Detection of hidden process is equally challenging as
Rootkit can
employ one or more methods to cover its presence. Here are some of
the very effective methods to detect such userland Rootkit processes.
All these detection methods work on common approach. First they
get the list of all running processes using standard API functions
such as EnumProcesses or Process32First. Then one or more special
methods mentioned below are used to enumerate the processes. Finally
this new process list is compared with previously obtained list and
any new process found in this new list is detected as hidden rootkit process.
|
|
|
|
This is very effective method to detect any hidden userland rootkit
processes. One of the lesser-known methods of enumerating the processes
is to use NtQuerySystemInformation function by passing first parameter
as SystemProcessesAndThreadsInformation. The drawback of this method is
that it can be easily circumvented by hooking the
NtQuerySystemInformation function and then by tampering with the
results.
The NtQuerySystemInformation is basically stub having few lines of
code to transition from user to kernel land. It finally calls the
NtQuerySystemInformation function within the kernel. So the trick here
is to implement the NtQuerySystemInformation without directly calling
the function.
Here is the sample code that shows how one can directly implement
NtQuerySystemInformation on various platforms. On Windows2000,
INT 2E
and from XP onwards 'sysenter' instruction is used to transition from
user to kernel.
|
|
__declspec(naked)
NTSTATUS __stdcall DirectNTQuerySystemInformation
(ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength)
{
//For Windows 2000
if( OSMajorVersion == 5 && OSMinorVersion == 0 )
{
__asm
{
mov eax, 0x97
lea edx, DWORD PTR ss:[esp+4]
INT 0x2E
ret 0x10
}
}
//For Windows XP
if( OSMajorVersion == 5 && OSMinorVersion == 1 )
{
__asm
{
mov eax, 0xAD
call SystemCall_XP
ret 0x10
SystemCall_XP:
mov edx, esp
sysenter
}
}
//For Windows Vista & Longhorn
if( OSMajorVersion == 6 && OSMinorVersion == 0 )
{
__asm
{
mov eax, 0xF8
call SystemCall_VISTA
ret 0x10
SystemCall_VISTA:
mov edx, esp
sysenter
}
}
//For Windows 7
if( OSMajorVersion == 6 && OSMinorVersion == 1 )
{
__asm
{
mov eax, 0x105
call SystemCall_WIN7
ret 0x10
SystemCall_WIN7:
mov edx, esp
sysenter
}
}
}
}
|
|
This technique can discover any userland rootkit process and only
way for rootkit process to defeat against this technique is to move into
kernel. However, due to low-level implementation, there is slight risk
in using this method in production code. |
|
|
|
|
This method was first used by BlackLight and it turned out to be very
effective yet simple. Here, it enumerates through process id from 0 to
0x41DC and then check if that process exist by calling OpenProcess
function. Then this list of discovered processes are compared with
normal process list got using standard enumeration functions (such as
Process32First, EnumProcesses functions).
During the testing, it is found that some process id on server
machines were more than magic number 0x41DC. So in order to be effective
the magic number is doubled to take care of all possible running
processes on latest operating systems.
Here is the sample code that implements PIDB method:
|
|
for(int i=0; i < 0x83B8; i+=4)
{
//These are system idle and system processes
if( i == 0 || i==4 )
{
continue;
}
hprocess = OpenProcess
(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, i);
if( hprocess == NULL )
{
if( GetLastError() != ERROR_INVALID_PARAMETER)
{
// If the error code is other than
// ERROR_INVALID_PARAMETER that means this
// process exists but we are not able to open.
//check if this process is already discovered
//using standard API functions.
if( IsHiddenProcess(i) )
{
printf("\n Hidden process found %d", i);
}
}
continue;
}
dwExitCode = 0;
GetExitCodeProcess(hprocess, &dwExitCode);
// check if this is active process...
// only active process will return error
// code as ERROR_NO_MORE_ITEMS
if( dwExitCode == ERROR_NO_MORE_ITEMS )
{
//check if this process is already discovered
if( IsHiddenProcess(i) )
{
printf("\n Hidden process found %d", i);
}
}
CloseHandle(hprocess);
}
|
|
Though this is very effective method, rootkit can easily defeat this
technique by hooking OpenProcess or its native version NTOpenProcess
function and then returning NULL with error code as
ERROR_INVALID_PARAMETER. To defend against such tricks anti-rootkit
softwares can call NtOpenProcess using direct system call method as
shown in "Detection of Hidden Process using Direct NT System Call
Implemenation". |
|
|
|
|
Any windows process when run will have lot of open handles realted to
process, thread, named objects, file, port, registry, etc. that can be
used to detect hidden process. One can use the native API function. The
effective way to enumerate handles is to use NtQuerySystemInformation
with first parameter as SystemHandleInformation. It lists the handles
from all running processes in the system. For each enumerated handle, it
provides information such as handle, handle type and process id of the
owning process. Hence, by enumerating through all the handles and then
using the associated process id, one can detect all possible hidden
processes that are not revealed through standard API functions.
There is one interesting system process called CSRSS.EXE, which holds
the handles to all running processes. So instead of going through all
the different handles, one can just scroll through the process handles
of CSRSS.EXE process. Interestingly this method can, not only detect
userland hidden processes but also some of the rootkit processes which
have used kernel land techniques without taking care of hiding process
handles within CSRSS.EXE process.
Here is the code snippet, which can demonstrate this method:
|
|
PVOID bufHandleTable = malloc(dwSize);
status = NtQuerySystemInformation
(SystemHandleInformation, bufHandleTable, dwSize, 0);
SYSTEM_HANDLE_INFORMATION *HandleInfo =
(SYSTEM_HANDLE_INFORMATION *) bufHandleTable;
// Process handles within CSRSS will not have handle
// to following processes system idle process, system
// process, smss.exe, csrss.exe.
for(int i=0; i< HandleInfo->NumberOfHandles; i++)
{
int pid = HandleInfo->Handles[i].UniqueProcessId;
// For XP & 2K3 : HANDLE_TYPE_PROCESS = 0x5
// For Vista & Longhorn : HANDLE_TYPE_PROCESS = 0x6
if( HandleInfo->Handles[i].ObjectTypeIndex ==
HANDLE_TYPE_PROCESS)
{
//check if this process id is that of CSRSS.EXE process.
if( IsCSRSSProcess(pid) )
{
hprocess = OpenProcess(PROCESS_DUP_HANDLE, false, pid);
if( hprocess )
{
if( DuplicateHandle(hprocess,
(HANDLE)HandleInfo->Handles[i].Handle,
GetCurrentProcess(),
&tprocess,
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, 0))
{
targetPid = GetProcessId(tprocess);
//check if this is hidden process
if( IsHiddenProcess(targetPid) )
{
printf("\n Found hidden process %d", targetPid);
}
}
}// End of if( hprocess )
} // End of if( IsCSRSSProcess(pid) )
} // End of if
} // End of for-loop
|
|
Since the CSRSS.EXE is not first process started when Windows boots,
it does not contains handles to already started processes such as system
idle process(pid=0), system process (pid=4), smss.exe and its process
itself.
On Windows Vista system it is possible to more than one CSRSS.EXE
process in case of multiple users logged in. Same situation arises on XP
system, when more than one user is operating through 'Switch User'
mechanism. In such case, one has to check if the enumerated process
belongs to any of these CSRSS process ids. The function IsCSRSSProcess()
above does exactly the same by comparing the discovered process id with
list of all running CSRSS.EXE processes.
One more way is to enumerate all thread handles within CSRSS process
instead of process handles, as most rootkits are aware of this
technique. The CSRSS process not only has process handles but also
thread handles for every running processes. Once the thread handle is
known, one can use GetProcessIdOfThread function to get process id
associated with that thread after duplicating it.
Though any rootkit process can defeat this technique by hooking
NtQuerySystemInformation or NtOpenProcess function, it can easily be
circumvented by using direct implementation of these native API
functions as described in the "Detection of Hidden Process using Direct
NT System Call Implemenation".
|
|
|
|
|
There exists several other userland methods to detect hidden rootkit
processes, but they are not as effective as the ones described above.
However they can be used on need basis and often to target specific
rootkit.
One such method is to enumerate through all the open Windows created
by the processes within the system using EnumWindows API function and
then calling the GetWindowThreadProcessId function to get the process id
associated with that Window.
Here is the sample code that does the same...
|
|
//Setup the callback function to enumerate through windows
EnumWindows(EnumWindowsProc, NULL);
//This is callback function to enumerate windows
BOOL CALLBACK EnumWindowsProc(HWND hwnd, PARAM lParam)
{
DWORD procId;
GetWindowThreadProcessId(hwnd, &procId);
if( IsHiddenProcess(procId) )
{
printf("Found hidden process %d", procId);
}
}
|
|
There exist several other ways to detect the hidden processes in
user land and new ways are being discovered everyday. Though these
detection techniques can be easily defeated from kernel land, they
present simple and less risky mechanism to uncover the userland
rootkits. |
|
|
|
|
1. Detection
of Hidden Processes 2.
Hiding
Rootkit process from CSRSS Handle Enumeration Method |
|
|
|
|
|
|
|
|
|
|
|
|
|
|