?? 在nt系列操作系統里讓自己“消失”.txt
字號:
在NT系列操作系統里讓自己“消失”
創建時間:2004-03-06
文章屬性:原創
文章提交:SoBeIt (kinsephi_at_hotmail.com)
===================[ 在NT系列操作系統里讓自己“消失”]==================
SoBeIt
作者:Holy_Father <holy_father@phreaker.net>
版本:1.2 english
日期:05.08.2003
=====[ 1. 內容 ]============================================
1. 內容
2. 介紹
3. 文件
3.1 NtQueryDirectoryFile
3.2 NtVdmControl
4. 進程
5. 注冊表
5.1 NtEnumerateKey
5.2 NtEnumerateValueKey
6. 系統服務和驅動
7. 掛鉤和擴展
7.1 權限
7.2 全局掛鉤
7.3 新進程
7.4 DLL
8. 內存
9. 句柄
9.1 命名句柄并獲得類型
10. 端口
10.1 Netstat, OpPorts和FPortWinXP下
10.2 OpPorts在Win2k和NT4下, FPort在Win2k下
11. 結束
=====[ 2. 介紹 ]==================================================
這篇文檔是在Windows NT操作系統下隱藏對象、文件、服務、進程等的技術。這種方法是基于Windows API函數的掛鉤。
這篇文章中所描述的技術都是從我寫rootkit的研究成果,所以它能寫rootkit更有效果并且更簡單。這里也同樣包括了我的實踐。
在這篇文檔中隱藏對象意味著改變某些用來命名這些對象的系統函數,使它們將忽略這些對象的名字。這樣一來我們改動的那些函數的返回值表示這些對象根本就不存在。
最基本的方法(除去少數不同的)是我們用原始的參數調用原始的函數,然后我們改變它們的輸出。
在這篇文章里將描述隱藏文件、進程、注冊表鍵和鍵值、系統服務和驅動、分配的內存還有句柄。
=====[ 3. 文件 ]========================================
在有很多種隱藏文件使系統無法發現的可能。我們只使用改變API的方法,而沒使用那些比如涉及到文件系統的技術。這樣會更容易些因為我們無法知道文件系統工作的獨特性。
=====[ 3.1 NtQueryDirectoryFile ]=============================
在WINNT里在某些目錄中尋找某個文件的方法是枚舉它里面所有的文件和它的子目錄下的所有文件。文件的枚舉是使用NtQueryDirectoryFile函數。
NTSTATUS NtQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG FileInformationLength,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan
);
對我們來說重要的參數是FileHandle,FileInformation和FileInformationClass。FileHandle是從NtOpenFile獲得的目錄對象句柄。FileInformation是一個指針,指向函數要寫入需要的數據的已分配內存。FileInformationClass決定寫入FileImformation的記錄的類型。
FileInformationClass是一個變化的枚舉類型,我們只需要其中4個值來枚舉目錄內容:
#define FileDirectoryInformation 1
#define FileFullDirectoryInformation 2
#define FileBothDirectoryInformation 3
#define FileNamesInformation 12
要寫入FileInformation的FileDirecoryInformation記錄的結構:
typedef struct _FILE_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;
FileFullDirectoryInformation:
typedef struct _FILE_FULL_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
WCHAR FileName[1];
} FILE_FULL_DIRECTORY_INFORMATION, *PFILE_FULL_DIRECTORY_INFORMATION;
FileBothDirectoryInformation:
typedef struct _FILE_BOTH_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
UCHAR AlternateNameLength;
WCHAR AlternateName[12];
WCHAR FileName[1];
} FILE_BOTH_DIRECTORY_INFORMATION, *PFILE_BOTH_DIRECTORY_INFORMATION;
FileNamesInformation:
typedef struct _FILE_NAMES_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;
這個函數在FileInformation中寫入這些結構的一個列表。對我們來說在這些結構類型中只有3個變量是重要的。
NextEntryOffset是這個列表中項的偏移地址。第一個項在地址FileInformation+0處,所以第二個項在地址是FileInformation+第一個項的NextEntryOffset。最后一個項的NextEntryOffset是0。
FileName是文件全名。
FileNameLength是文件名長度。
如果我們想要隱藏一個文件,我們需要分別通知這4種類型,對每種類型的返回記錄我們需要和我們打算隱藏的文件比較名字。如果我們打算隱藏第一個記錄,我們可以把后面的結構向前移動,移動長度為第一個結構的長度,這樣會導致第一個記錄被改寫。如果我們想要隱藏其它任何一個,只需要很容易的改變上一個記錄的NextEntryOffset的值就行。如果我們要隱藏最后一個記錄就把它的NextEntryOffset改為0,否則NextEntryOffset的值應為我們想要隱藏的那個記錄和前一個的NextEntryOffset值的和。然后修改前一個記錄的Unknown變量的值,它是下一次搜索的索引。把要隱藏的記錄之前一個記錄的Unknown變量的值改為我們要隱藏的那個記錄的Unkown變量的值即可。
如果沒有原本應該可見的記錄被找到,我們就返回STATUS_NO_SUCH_FILE。
#define STATUS_NO_SUCH_FILE 0xC000000F
=====[ 3.2 NtVdmControl ]========================================
不知什么原因DOS的枚舉NTVDM能夠通過函數NtVdmControl也能獲得文件的列表。
NTSTATUS NtVdmControl(
IN ULONG ControlCode,
IN PVOID ControlData
);
ConcrolCode標明了在緩沖區ControlData中申請數據的子函數。如果ControlCode為VdmDiretoryFile那么這個函數的功能將和FileInformation設置為FileBothDirectoryInformation的函數NtQueryDirectoryFile功能一樣。
#define VdmDirectoryFile 6
這時的ControlData的用法就和FileInformation一樣。這里唯一的不同就是我們不知道緩沖區的長度。所以我們需要手動來計算它的長度。我們把所有記錄的NextEntryOffset和最后一個記錄的FileNameLength還有0X5E(最后一個記錄除去文件名的長度)。隱藏的方法和前面提到的使用NtQueryDirectoryFile的方法一樣。
=====[ 4. 進程 ]========================================
各種進程信息是通過NtQuerySystemInformation獲取的。
NTSTATUS NtQuerySystemInformation(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
SystemInformationClass標明了我們想要獲得的信息的類別,SystemInformation是一個指向函數輸出緩沖區的指針,SystemInformationLength是這個緩沖區的長度,ReturnLength是寫入字節的數目。
對于正在運行的進程的枚舉我們使用設置為SystemProcessesAndThreadsInformation的SystemInformationClass。
#define SystemInformationClass 5
在SystemInformation的緩沖區中返回的數據結構是:
typedef struct _SYSTEM_PROCESSES {
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved1[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters; // Windows 2000特有的
SYSTEM_THREADS Threads[1];
} SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;
隱藏進程和隱藏文件方法基本一樣,就是改動我們需要隱藏的記錄的前一個記錄的NextEntryDelta。通常我們不用隱藏第一個記錄,因為它是空閑進程(Idle process)。
=====[ 5. 注冊表 ]========================================
Windows的注冊表是一個很大的樹形數據結構,對我們來說里面有兩種重要的記錄類型需要隱藏。一種類型是注冊表鍵,另一種是鍵值。因為注冊表的結構,隱藏注冊表鍵不象隱藏文件或進程那么麻煩。
=====[ 5.1 NtEnumerateKey ]===============================
因為注冊表的結構我們不能請求某個指定部分所有鍵的列表。我們只能在注冊表某個部分通過查詢指定鍵的索引以獲得它的信息。這里提供了NtEnumerateKey。
NTSTATUS NtEnumerateKey(
IN HANDLE KeyHandle,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG KeyInformationLength,
OUT PULONG ResultLength
);
KeyHandle是已經用索引標明我們想要從中獲取信息的子鍵的句柄。KeyInformationClass標明了返回信息類型。數據最后寫入KeyInformaiton緩沖區,緩沖區長度為KeyInformationLength。寫入的字節數由ResultLength返回。
我們需要意識到的最重要的東西是如果我們隱藏了某個鍵,在這個鍵之后的所有鍵的索引都會改變。因為我們是通過高位的索引來獲取鍵的信息,并通過低位的索引來請求這個鍵。所以我們必須記錄之前有多少個記錄被隱藏,然后返回正確的值。
讓我們來看個例子。假設我們在注冊表中有一些鍵名字是A,B,C,D,E和F。它們的索引從0開始,也就是說索引4對應鍵E。現在我們如果想要隱藏鍵B,被掛鉤過的應用程序用索引4調用NtEnumerateKey時我們應該返回F鍵的信息因為有一個索引改變了?,F在問題是我們不知道是否會有索引被改變。如果我們不注意索引的改變而對于索引4的請求仍然返回鍵E而不是鍵F的話,很有可能在我們用索引1請求時什么都返回不了或者返回鍵C。這兩種情況都會導致錯誤。這就是為什么我們要注意索引的改變。
現在如果我們通過用索引0到Index重新調用函數來記錄轉移我們可能會等待一段時間(在1GHz處理器上普通的注冊表就得等10秒種那么長的時間)。所以我們不得不想出一種更加巧妙的方法。
我們知道鍵是按字母排序的(除了引用外)。如果我們忽略引用(我們不需要隱藏)我們能使用以下方法記錄改變。我們通過字母排序列出我們想要隱藏的鍵名的列表(使用RtlCompareUnicodeString),然后當應用程序調用NtEnumerateKey時我們不需要用不可變的變量重新調用它,而能夠找到用索引標明的記錄的名字。
NTSTATUS RtlCompareUnicodeString(
IN PUNICODE_STRING String1,
IN PUNICODE_STRING String2,
IN BOOLEAN CaseInSensitive
);
String1和String2是將要比較的字符串,CaseInSensitive在不忽略大小寫時被設置為True。
函數結果描述String1和String2的關系:
result > 0: String1 > String2
result = 0: String1 = String2
result < 0: String1 < String2
現在我們需要找到一個邊緣項。我們在列表中對用索引標明的鍵按字母比較名字。邊緣項是在我們列表中最后一個較短的名字。我們知道轉移最多是我們列表中邊緣項的數量。但并不是所有我們列表中的項都是注冊表中有效的鍵。所以我們不得不請求我們列表中達到邊緣項的所有的在注冊表中這個部分的項。這些通過調用NtOpenKey來完成。
NTSTATUS NtOpenKey(
OUT PHANDLE KeyHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -