|
The callback tables located in the DRIVER_OBJECT of the file system drivers are patched: The IRP handlers of the needed drivers are replaced. This includes replacing the pointers to base function handlers (DRIVER_OBJECT->MajorFunction) as well as replacing pointers to the drivers unload procedure (DRIVER_OBJECT->DriverUnload).
The following functions are handled:
IRP_MJ_CREATE IRP_MJ_CREATE_NAMED_PIPE IRP_MJ_CREATE_MAILSLOT IRP_MJ_DIRECTORY_CONTROL -> IRP_MN_QUERY_DIRECTORY
For a more detailed description of the redirection of file operations refer to the source [2].
----[ 2.3 - Slanret (IERK, Backdoor-ALI)
The source code for this is unavailable -- it was originally disco- vered by some administrator on his network. It is a normal driver ("ierk8243.sys") which periodically causes BSODs, and is visible as a service called "Virtual Memory Manager".
"Slanret is technically just one component of a root kit. It comes with a straightforward backdoor program: a 27 kilobyte server called "Krei" that listens on an open port and grants the hacker remote access to the system. The Slanret component is a seven kilobyte cloaking routine that burrows into the system as a device driver, then accepts commands from the server instructing it on what files or processes to conceal." [3]
----[ 3. Stealth on disk, in registry and in memory
The lower the I/O interception in a rootkit is performed, the harder it usually is to detect it's presence. One would think that a reliable place for interception would be the low-level disk operations (read/write sectors). This would require handling all filesystems that might be on the hard disk though: FAT16, FAT32, NTFS.
While FAT was relatively easy to deal with (and some old DOS stealth viruses used similar techniques) an implementation of something similar on WinNT is a task for maniacs.
A second place to hook would be hooking dispatch functions of file- system drivers: Patch DriverObject->MajorFunction and FastIoDispatch in memory or patch the drivers on disk. This has the advantage of being re- latively universal and is the method used in HE4HookInv.
A third possibility is setting a filter on a filesyste driver (FSD). This has no advantages in comparison with the previous method, but has the drawback of being more visible (Filemon uses this approach). The functions Zw*, Io* can then be hooked either by manipulating the Ke- ServiceDescriptorTable or directly patching the function body. It is usually quite easy to detect that pointers in KeServiceDescriptorTable point to strange locations or that the function body of a function has changed. A filter driver is also easy to detect by calling IoGetDevice- ObjectPointer and then checking DEVICE_OBJECT->StackSize.
All normal drivers have their own keys in the registry, namely in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services.
The abovementioned rootkits can hide registry keys, but obviously, if the system is booted "cleanly", an administrator can see anything that was hidden. One can also load a rootkit using ZwSetSystemInformation( SystemLoadAndCallimage) without the need to create any registry keys. An example of this technique can be found in [6].
A rootkit loader in a separate file is too unstealthy. It might be a smarter move to patch that call into some executable file which is part of the system boot. One can use any driver or user-mode program that works with sufficient privileges, or any DLL linked to by it. One has to ask one question though: If the newly introduced changes need to be hidden anyway, why make two similar but differing procedures (for hiding changes to a file as well as hiding the existance of a file) instead of limiting our- selves to one ?
In most cases one can target null.sys. Implementing it's functionality is as easy as "hello world", and that is why it is usually replaced with a trojan. But if we are going to have a procedure for hiding changes to a file, we can replace ANY driver with a trojan that will substitute the content of the replaced file with the original content to everyone (incl- uding the kernel). Upon startup, it will copy itself to some allocated memory area and start a thread there.
This will make the trojan almost unnoticeable in memory: No system utility can see the driver any more, as it is just an anonymous memory page amongst many. We do not even need a thread, using intercepted IRP dispatch functions of some driver (DriverObject->MajorFunction[IRP_MJ_xxx]). We can also use IoQueueWorkItem and KeInsertQueueDpc, so no additional threads in SYSTEM will be visible in the task manager. After this is done the trojan can unload the driver it was started from, and reload it in a clean (unchanged) variant. As a result, high levels of stealth will be achieved by relatively simple means. The original content of the manipu- lated file could for example be stored in the trojan's file after the trojan itself.
It will then be sufficient to hook all FSD requests (IRP and FastIO) and upon access change the position (and size of the file). (CurrentIrpStackLocation->Parameters.*.ByteOffset)
--[ 4 - My variant: The thorny path
----[ 4.1 - Shell
I originally intended to do something similarily simple as standard user-mode code: Just pass a socket handle for stdin/stdout/stderr to the newly created cmd.exe process. I did not find a way to open a useful socket from a driver though, as the interface with the AFD driver (kmode core of winsock) is undocumented. Reverse-engineering it's usage was not an option either as due to changes between versions my technique would be unreliable. I had to find a different way.
First variant =============
We could start our code in the context of some process, using a shell- code quite similar to that used in exploits. The code could wait for a TCP connection and start cmd.exe with redirected I/O.
I chose this way when I tired of trying to start a full-fledged win32 process from a driver. The shellcode is position-independent, searches for kernel32.dll in memory and loads the winsock library. All that needs to be done is injecting the shellcode into the address space of a process and pass control to the entry point of the shellcode. However, in the process of doing this the normal work of the process must not be interrupted, be- cause a failure in a critical system process will lead to a failure of the whole system.
So we need to allocate memory, write shellcode there, and create a thread with EIP = entry point of the shellcode. Code to do this can be found in the attached file shell.cpp. Unfortunately, when CreateProcess is called from the thread started in this way it failed, most probably because something that CreateProcess relies upon was not initialized pro- poerly in the context of our thread. We thus need to call CreateProcess from a thread context which has everything that CreateProcess needs ini- tialized -- we're going to take a thread which belongs to the process we are intruding into (I used SetThreadContext for that). One needs to re- store the state of the thread prior to the interruption so it can contiue it's normal operation.
So we need to: Save thread context via GetThreadContext, set the EIP to our context via SetThreadContext, wait for the code to complete, and then restore the original cont again. The rest is just a usual shellcode for Windows NT (full code in dummy4.asm).
One unsolved problem remains: If the thread is in waiting state, it will not run until it wakes up. Using ZwAlertThread does not yield any re- sult if the thread is in a nonalertable wait state. Fortunately, the thread in services.exe worked without a problem -- this does not imply it will stay like this in the future though, so I continued my research:
Second variant ==============
Things are not as easy as [4] makes them sound. Creating a full- fledged win32-process requires it's registration in the CSRSS subsystem. This is accomplished by using CsrClientCallServer(), which receives all necessary information about the process (handles, TID, PID, flags). The functions calls ZwRequestWaitReplyPort, which receives a handle of a pre- viously opened port for connection with CSRSS.
This port is not open in the SYSTEM process context. Opening it never succeeded (ZwConnectPort returned STATUS_PORT_CONNECTION_REFUSED). Play- ing with SECURITY_QUALITY_OF_SERVICE didn't help. While disassembling ntdll.dll I saw that ZwConnectPort calls were preceded by ZwCreateSection. But there was no time and no desire to play with sections. Here is the code that didn't work:
VOID InformCsrss(HANDLE hProcess,HANDLE hThread,ULONG pid,ULONG tid) { CSRMSG csrmsg; HANDLE hCurProcess; HANDLE handleIndex; PVOID p;
_asm int 3;
UNICODE_STRING PortName; RtlInitUnicodeString(&PortName,L"\\Windows\\ApiPort"); static SECURITY_QUALITY_OF_SERVICE QoS = {sizeof(QoS), SecurityAnonymous, 0, 0}; /*static SECURITY_QUALITY_OF_SERVICE QoS = {0x77DC0260, (_SECURITY_IMPERSONATION_LEVEL)2, 0x120101, 0x10000};*/ DWORD ret=ZwConnectPort(&handleIndex,&PortName,&QoS,NULL, NULL,NULL,NULL,NULL);
if (!ret) { RtlZeroMemory(&csrmsg,sizeof(CSRMSG));
csrmsg.ProcessInforma 上一页 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] ... 下一页 >> |