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

saga's blog

突出重點(diǎn),系統(tǒng)全面,不留死角

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

公告

QQ:34O859O5

常用鏈接

留言簿(15)

搜索

  •  

積分與排名

  • 積分 - 212858
  • 排名 - 124

最新評(píng)論

閱讀排行榜

評(píng)論排行榜

轉(zhuǎn)載學(xué)習(xí)
原文:http://blog.csdn.net/SpiderF/archive/2005/04/05/336594.aspx

在NT系列操作系統(tǒng)里讓自己“消失”

1. 內(nèi)容
2. 介紹
3. 文件
    3.1 NtQueryDirectoryFile
    3.2 NtVdmControl
4. 進(jìn)程
5. 注冊(cè)表
    5.1 NtEnumerateKey
    5.2 NtEnumerateValueKey
6. 系統(tǒng)服務(wù)和驅(qū)動(dòng)
7. 掛鉤和擴(kuò)展
    7.1 權(quán)限
    7.2 全局掛鉤
    7.3 新進(jìn)程
    7.4 DLL
8. 內(nèi)存
9. 句柄
    9.1 命名句柄并獲得類型
10. 端口
    10.1 Netstat, OpPorts和FPortWinXP下
    10.2 OpPorts在Win2k和NT4下, FPort在Win2k下
11. 結(jié)束

 

=====[ 2. 介紹 ]==================================================

    這篇文檔是在Windows NT操作系統(tǒng)下隱藏對(duì)象、文件、服務(wù)、進(jìn)程等的技術(shù)。這種方法是基于Windows API函數(shù)的掛鉤。
    這篇文章中所描述的技術(shù)都是從我寫rootkit的研究成果,所以它能寫rootkit更有效果并且更簡單。這里也同樣包括了我的實(shí)踐。
    在這篇文檔中隱藏對(duì)象意味著改變某些用來命名這些對(duì)象的系統(tǒng)函數(shù),使它們將忽略這些對(duì)象的名字。這樣一來我們改動(dòng)的那些函數(shù)的返回值表示這些對(duì)象根本就不存在。
    最基本的方法(除去少數(shù)不同的)是我們用原始的參數(shù)調(diào)用原始的函數(shù),然后我們改變它們的輸出。
    在這篇文章里將描述隱藏文件、進(jìn)程、注冊(cè)表鍵和鍵值、系統(tǒng)服務(wù)和驅(qū)動(dòng)、分配的內(nèi)存還有句柄。

 

=====[ 3. 文件 ]========================================

    在有很多種隱藏文件使系統(tǒng)無法發(fā)現(xiàn)的可能。我們只使用改變API的方法,而沒使用那些比如涉及到文件系統(tǒng)的技術(shù)。這樣會(huì)更容易些因?yàn)槲覀儫o法知道文件系統(tǒng)工作的獨(dú)特性。


=====[ 3.1 NtQueryDirectoryFile ]=============================

    在WINNT里在某些目錄中尋找某個(gè)文件的方法是枚舉它里面所有的文件和它的子目錄下的所有文件。文件的枚舉是使用NtQueryDirectoryFile函數(shù)。


    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
    );


    對(duì)我們來說重要的參數(shù)是FileHandle,FileInformation和FileInformationClass。FileHandle是從NtOpenFile獲得的目錄對(duì)象句柄。FileInformation是一個(gè)指針,指向函數(shù)要寫入需要的數(shù)據(jù)的已分配內(nèi)存。FileInformationClass決定寫入FileImformation的記錄的類型。
    FileInformationClass是一個(gè)變化的枚舉類型,我們只需要其中4個(gè)值來枚舉目錄內(nèi)容:

    #define FileDirectoryInformation 1
    #define FileFullDirectoryInformation 2
    #define FileBothDirectoryInformation 3
    #define FileNamesInformation 12


要寫入FileInformation的FileDirecoryInformation記錄的結(jié)構(gòu):

    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;


    這個(gè)函數(shù)在FileInformation中寫入這些結(jié)構(gòu)的一個(gè)列表。對(duì)我們來說在這些結(jié)構(gòu)類型中只有3個(gè)變量是重要的。
    NextEntryOffset是這個(gè)列表中項(xiàng)的偏移地址。第一個(gè)項(xiàng)在地址FileInformation+0處,所以第二個(gè)項(xiàng)在地址是FileInformation+第一個(gè)項(xiàng)的NextEntryOffset。最后一個(gè)項(xiàng)的NextEntryOffset是0。
    FileName是文件全名。
    FileNameLength是文件名長度。

    如果我們想要隱藏一個(gè)文件,我們需要分別通知這4種類型,對(duì)每種類型的返回記錄我們需要和我們打算隱藏的文件比較名字。如果我們打算隱藏第一個(gè)記錄,我們可以把后面的結(jié)構(gòu)向前移動(dòng),移動(dòng)長度為第一個(gè)結(jié)構(gòu)的長度,這樣會(huì)導(dǎo)致第一個(gè)記錄被改寫。如果我們想要隱藏其它任何一個(gè),只需要很容易的改變上一個(gè)記錄的NextEntryOffset的值就行。如果我們要隱藏最后一個(gè)記錄就把它的NextEntryOffset改為0,否則NextEntryOffset的值應(yīng)為我們想要隱藏的那個(gè)記錄和前一個(gè)的NextEntryOffset值的和。然后修改前一個(gè)記錄的Unknown變量的值,它是下一次搜索的索引。把要隱藏的記錄之前一個(gè)記錄的Unknown變量的值改為我們要隱藏的那個(gè)記錄的Unkown變量的值即可。

    如果沒有原本應(yīng)該可見的記錄被找到,我們就返回STATUS_NO_SUCH_FILE。

    #define STATUS_NO_SUCH_FILE 0xC000000F


=====[ 3.2 NtVdmControl ]========================================

    不知什么原因DOS的枚舉NTVDM能夠通過函數(shù)NtVdmControl也能獲得文件的列表。

    NTSTATUS NtVdmControl(       
        IN ULONG ControlCode,
        IN PVOID ControlData
    );

    ConcrolCode標(biāo)明了在緩沖區(qū)ControlData中申請(qǐng)數(shù)據(jù)的子函數(shù)。如果ControlCode為VdmDiretoryFile那么這個(gè)函數(shù)的功能將和FileInformation設(shè)置為FileBothDirectoryInformation的函數(shù)NtQueryDirectoryFile功能一樣。

    #define VdmDirectoryFile 6

    這時(shí)的ControlData的用法就和FileInformation一樣。這里唯一的不同就是我們不知道緩沖區(qū)的長度。所以我們需要手動(dòng)來計(jì)算它的長度。我們把所有記錄的NextEntryOffset和最后一個(gè)記錄的FileNameLength還有0X5E(最后一個(gè)記錄除去文件名的長度)。隱藏的方法和前面提到的使用NtQueryDirectoryFile的方法一樣。

 

=====[ 4. 進(jìn)程 ]========================================

    各種進(jìn)程信息是通過NtQuerySystemInformation獲取的。   

    NTSTATUS NtQuerySystemInformation(
        IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
        IN OUT PVOID SystemInformation,
        IN ULONG SystemInformationLength,
        OUT PULONG ReturnLength OPTIONAL
    );

   
    SystemInformationClass標(biāo)明了我們想要獲得的信息的類別,SystemInformation是一個(gè)指向函數(shù)輸出緩沖區(qū)的指針,SystemInformationLength是這個(gè)緩沖區(qū)的長度,ReturnLength是寫入字節(jié)的數(shù)目。
    對(duì)于正在運(yùn)行的進(jìn)程的枚舉我們使用設(shè)置為SystemProcessesAndThreadsInformation的SystemInformationClass。

    #define SystemInformationClass 5


    在SystemInformation的緩沖區(qū)中返回的數(shù)據(jù)結(jié)構(gòu)是:

    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;


    隱藏進(jìn)程和隱藏文件方法基本一樣,就是改動(dòng)我們需要隱藏的記錄的前一個(gè)記錄的NextEntryDelta。通常我們不用隱藏第一個(gè)記錄,因?yàn)樗强臻e進(jìn)程(Idle process)。

 

=====[ 5. 注冊(cè)表 ]========================================

    Windows的注冊(cè)表是一個(gè)很大的樹形數(shù)據(jù)結(jié)構(gòu),對(duì)我們來說里面有兩種重要的記錄類型需要隱藏。一種類型是注冊(cè)表鍵,另一種是鍵值。因?yàn)樽?cè)表的結(jié)構(gòu),隱藏注冊(cè)表鍵不象隱藏文件或進(jìn)程那么麻煩。


=====[ 5.1 NtEnumerateKey ]===============================

    因?yàn)樽?cè)表的結(jié)構(gòu)我們不能請(qǐng)求某個(gè)指定部分所有鍵的列表。我們只能在注冊(cè)表某個(gè)部分通過查詢指定鍵的索引以獲得它的信息。這里提供了NtEnumerateKey。

    NTSTATUS NtEnumerateKey(
        IN HANDLE KeyHandle,
        IN ULONG Index,
        IN KEY_INFORMATION_CLASS KeyInformationClass,
        OUT PVOID KeyInformation,
        IN ULONG KeyInformationLength,
        OUT PULONG ResultLength
    );


    KeyHandle是已經(jīng)用索引標(biāo)明我們想要從中獲取信息的子鍵的句柄。KeyInformationClass標(biāo)明了返回信息類型。數(shù)據(jù)最后寫入KeyInformaiton緩沖區(qū),緩沖區(qū)長度為KeyInformationLength。寫入的字節(jié)數(shù)由ResultLength返回。
    我們需要意識(shí)到的最重要的東西是如果我們隱藏了某個(gè)鍵,在這個(gè)鍵之后的所有鍵的索引都會(huì)改變。因?yàn)槲覀兪峭ㄟ^高位的索引來獲取鍵的信息,并通過低位的索引來請(qǐng)求這個(gè)鍵。所以我們必須記錄之前有多少個(gè)記錄被隱藏,然后返回正確的值。
    讓我們來看個(gè)例子。假設(shè)我們?cè)谧?cè)表中有一些鍵名字是A,B,C,D,E和F。它們的索引從0開始,也就是說索引4對(duì)應(yīng)鍵E。現(xiàn)在我們?nèi)绻胍[藏鍵B,被掛鉤過的應(yīng)用程序用索引4調(diào)用NtEnumerateKey時(shí)我們應(yīng)該返回F鍵的信息因?yàn)橛幸粋€(gè)索引改變了。現(xiàn)在問題是我們不知道是否會(huì)有索引被改變。如果我們不注意索引的改變而對(duì)于索引4的請(qǐng)求仍然返回鍵E而不是鍵F的話,很有可能在我們用索引1請(qǐng)求時(shí)什么都返回不了或者返回鍵C。這兩種情況都會(huì)導(dǎo)致錯(cuò)誤。這就是為什么我們要注意索引的改變。
    現(xiàn)在如果我們通過用索引0到Index重新調(diào)用函數(shù)來記錄轉(zhuǎn)移我們可能會(huì)等待一段時(shí)間(在1GHz處理器上普通的注冊(cè)表就得等10秒種那么長的時(shí)間)。所以我們不得不想出一種更加巧妙的方法。
    我們知道鍵是按字母排序的(除了引用外)。如果我們忽略引用(我們不需要隱藏)我們能使用以下方法記錄改變。我們通過字母排序列出我們想要隱藏的鍵名的列表(使用RtlCompareUnicodeString),然后當(dāng)應(yīng)用程序調(diào)用NtEnumerateKey時(shí)我們不需要用不可變的變量重新調(diào)用它,而能夠找到用索引標(biāo)明的記錄的名字。

    NTSTATUS RtlCompareUnicodeString(      
        IN PUNICODE_STRING String1,
        IN PUNICODE_STRING String2,
        IN BOOLEAN  CaseInSensitive 
    );

    String1和String2是將要比較的字符串,CaseInSensitive在不忽略大小寫時(shí)被設(shè)置為True。
    函數(shù)結(jié)果描述String1和String2的關(guān)系:

        result > 0:    String1 > String2
        result = 0:    String1 = String2
        result < 0:    String1 < String2

現(xiàn)在我們需要找到一個(gè)邊緣項(xiàng)。我們?cè)诹斜碇袑?duì)用索引標(biāo)明的鍵按字母比較名字。邊緣項(xiàng)是在我們列表中最后一個(gè)較短的名字。我們知道轉(zhuǎn)移最多是我們列表中邊緣項(xiàng)的數(shù)量。但并不是所有我們列表中的項(xiàng)都是注冊(cè)表中有效的鍵。所以我們不得不請(qǐng)求我們列表中達(dá)到邊緣項(xiàng)的所有的在注冊(cè)表中這個(gè)部分的項(xiàng)。這些通過調(diào)用NtOpenKey來完成。


    NTSTATUS NtOpenKey(
        OUT PHANDLE KeyHandle,
        IN ACCESS_MASK DesiredAccess,
        IN POBJECT_ATTRIBUTES ObjectAttributes
    );

    KeyHandle是高位的鍵的句柄,我們使用NtEnumerateKey的這個(gè)值。DesaireAccess是訪問權(quán)力。KEY_ENUMERATE_SUB_KEYS是它的正確的值。ObjectAttributes描述了我們要打開的子鍵(包括了它的名字)。

    #define KEY_ENUMERATE_SUB_KEYS 8

    如果NtOpenKey返回0表示打開成功,意味著這個(gè)來自我們列表中的鍵是存在的。被打開的鍵通過NtClose來關(guān)閉。

    NTSTATUS NtClose(
        IN HANDLE Handle
    );

   
    對(duì)每次NtEnumareteKey的調(diào)用我們要計(jì)算的改變,數(shù)量上等同于我們列表中存在于注冊(cè)表指定部分的鍵的數(shù)量。然后我們把改變的數(shù)量加到變量Index,最后調(diào)用原始的NtEnumerateKey。
    我們使用KeyInformationClass的KeyBasicInformation來獲得用索引標(biāo)明的鍵的名字。   

    #define KeyBasicInformation 0

    NtEnumerateKey在KeyInformation緩沖區(qū)中返回這個(gè)結(jié)構(gòu):

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

    這里我們只需要的東西是Name和它的長度NameLength。   
    如果沒有被轉(zhuǎn)移的索引的記載我們就返回錯(cuò)誤STATUS_EA_LIST_INCONSISTENT。

    #define STATUS_EA_LIST_INCONSISTENT 0x80000014


=====[ 5.2 NtEnumerateValueKey ]============================

    注冊(cè)表鍵值不是按字母分類的。幸運(yùn)的是在一個(gè)鍵里鍵值的數(shù)目比較少,所以我們可以通過重調(diào)的方法來獲得改變的數(shù)目。用來獲取一個(gè)鍵值信息的API是NtEnumerateValueKey。

    NTSTATUS NtEnumerateValueKey(
        IN HANDLE KeyHandle,
        IN ULONG Index,
        IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
        OUT PVOID KeyValueInformation,
        IN ULONG KeyValueInformationLength,
        OUT PULONG ResultLength
    );

    KeyHandle也是等級(jí)高的鍵的句柄。Index是所給鍵中鍵值的索引。KeyValueInformationClass描述信息的類型,保存在KeyValueInformation緩沖區(qū)中,緩沖區(qū)以字節(jié)為大小為KeyValueInformationLength。寫入字節(jié)的數(shù)量返回在ResultLength中。
    我們通過用0到Index的所有索引重調(diào)函數(shù)計(jì)算轉(zhuǎn)移。鍵值的名字通過把KeyValueInformationClass設(shè)置為KeyValueBasicInformation來獲取。
   
    #define KeyValueBasicInformation 0


    然后我們獲取在KeyValueInformation緩沖區(qū)中接下來的數(shù)據(jù)結(jié)構(gòu):

    typedef struct _KEY_VALUE_BASIC_INFORMATION {
        ULONG TitleIndex;
        ULONG Type;
        ULONG NameLength;
        WCHAR Name[1];
    } KEY_VALUE_BASIC_INFORMATION, *PKEY_VALUE_BASIC_INFORMATION;

    這里我們只對(duì)Name和NameLength感興趣。

   
    如果這里沒有被轉(zhuǎn)移的索引記載我們就返回錯(cuò)誤STATUS_NO_MORE_ENTRIES。

    #define STATUS_NO_MORE_ENTRIES 0x8000001A

 

=====[ 6. 系統(tǒng)服務(wù)和驅(qū)動(dòng) ]====================================

    系統(tǒng)服務(wù)和驅(qū)動(dòng)是通過4個(gè)獨(dú)立的API函數(shù)枚舉的。它們?cè)诿總€(gè)Windows版本中的聯(lián)系都不一樣。所以我們必須掛鉤所有4個(gè)函數(shù)。

    BOOL EnumServicesStatusA(
        SC_HANDLE hSCManager,
        DWORD dwServiceType,
        DWORD dwServiceState,
        LPENUM_SERVICE_STATUS lpServices,
        DWORD cbBufSize,
        LPDWORD pcbBytesNeeded,
        LPDWORD lpServicesReturned,
        LPDWORD lpResumeHandle
    );

    BOOL EnumServiceGroupW(
        SC_HANDLE hSCManager,
        DWORD dwServiceType,
        DWORD dwServiceState,
        LPBYTE lpServices,
        DWORD cbBufSize,
        LPDWORD pcbBytesNeeded,
        LPDWORD lpServicesReturned,
        LPDWORD lpResumeHandle,
        DWORD dwUnknown
    );

    BOOL EnumServicesStatusExA(
        SC_HANDLE hSCManager,
        SC_ENUM_TYPE InfoLevel,
        DWORD dwServiceType,
        DWORD dwServiceState,
        LPBYTE lpServices,
        DWORD cbBufSize,
        LPDWORD pcbBytesNeeded,
        LPDWORD lpServicesReturned,
        LPDWORD lpResumeHandle,
        LPCTSTR pszGroupName
    );

    BOOL EnumServicesStatusExW(
        SC_HANDLE hSCManager,
        SC_ENUM_TYPE InfoLevel,
        DWORD dwServiceType,
        DWORD dwServiceState,
        LPBYTE lpServices,
        DWORD cbBufSize,
        LPDWORD pcbBytesNeeded,
        LPDWORD lpServicesReturned,
        LPDWORD lpResumeHandle,
        LPCTSTR pszGroupName
    );


    這里最重要的是lpService,它指向保存服務(wù)列表的緩沖區(qū)。而指向結(jié)果中記錄個(gè)數(shù)的lpServicesReturned也很重要。輸出緩沖區(qū)中的數(shù)據(jù)結(jié)構(gòu)取決于函數(shù)類型。函數(shù)EnumServicesStatusA和
EnumServicesGroupW返回這個(gè)結(jié)構(gòu):

    typedef struct _ENUM_SERVICE_STATUS {
        LPTSTR lpServiceName;
        LPTSTR lpDisplayName;
        SERVICE_STATUS ServiceStatus;
    } ENUM_SERVICE_STATUS, *LPENUM_SERVICE_STATUS;

    typedef struct _SERVICE_STATUS {
        DWORD dwServiceType;
        DWORD dwCurrentState;
        DWORD dwControlsAccepted;
        DWORD dwWin32ExitCode;
        DWORD dwServiceSpecificExitCode;
        DWORD dwCheckPoint;
        DWORD dwWaitHint;
    } SERVICE_STATUS, *LPSERVICE_STATUS;

函數(shù)EnumServicesStatusExA和EnumServicesStatusExW返回這個(gè):

    typedef struct _ENUM_SERVICE_STATUS_PROCESS {
        LPTSTR lpServiceName;
        LPTSTR lpDisplayName;
        SERVICE_STATUS_PROCESS ServiceStatusProcess;
    } ENUM_SERVICE_STATUS_PROCESS, *LPENUM_SERVICE_STATUS_PROCESS;

    typedef struct _SERVICE_STATUS_PROCESS {
        DWORD dwServiceType;
        DWORD dwCurrentState;
        DWORD dwControlsAccepted;
        DWORD dwWin32ExitCode;
        DWORD dwServiceSpecificExitCode;
        DWORD dwCheckPoint;
        DWORD dwWaitHint;
        DWORD dwProcessId;
        DWORD dwServiceFlags;
    } SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS;


    我們只對(duì)lpServiceName感興趣因?yàn)樗窍到y(tǒng)服務(wù)的名字。所有記錄都有靜態(tài)的大小,所以我們想要隱藏一個(gè)的話就需要將之后所有記錄向前移它的大小。這里我們必須區(qū)分SERVICE_STATUS和SERVICE_STATUS_PROCESS的大小。

 

=====[ 7. 動(dòng)態(tài)掛鉤和擴(kuò)展 ]=====================================

    為達(dá)到預(yù)想的效果我們需要掛鉤所有正在運(yùn)行的進(jìn)程和所有將要被創(chuàng)建的進(jìn)程。所有新進(jìn)程都必須在它們運(yùn)行第一條指令前被掛鉤,否則它們就能夠在被掛夠前看到被隱藏的對(duì)象。
   

=====[ 7.1 權(quán)限 ]=============================================

    首先我們得知道我們至少獲得管理員administrator權(quán)限來獲得進(jìn)入所有正在運(yùn)行的進(jìn)程。最好的可能是將我們的進(jìn)程當(dāng)做系統(tǒng)服務(wù)來運(yùn)行,因?yàn)樗\(yùn)行與SYSTEM用戶權(quán)限下。為安裝服務(wù)我們首先得獲取特殊的權(quán)限。
    獲取SeDebugPrivilege的權(quán)限是很有用的,通過調(diào)用OpenProcessToken、LookupPrivilegeValue
和AdjustTokenPrivileges來完成。

    BOOL OpenProcessToken(
        HANDLE ProcessHandle,
        DWORD DesiredAccess,
        PHANDLE TokenHandle
    );

    BOOL LookupPrivilegeValue(
        LPCTSTR lpSystemName,
        LPCTSTR lpName,
        PLUID lpLuid
    );

    BOOL AdjustTokenPrivileges(
        HANDLE TokenHandle,
        BOOL DisableAllPrivileges,
        PTOKEN_PRIVILEGES NewState,
        DWORD BufferLength,
        PTOKEN_PRIVILEGES PreviousState,
        PDWORD ReturnLength
    );


    代碼如下:

    #define SE_PRIVILEGE_ENABLED    0x0002
    #define TOKEN_QUERY        0x0008
    #define TOKEN_ADJUST_PRIVILEGES    0x0020

    HANDLE hToken;
    LUID DebugNameValue;
    TOKEN_PRIVILEGES Privileges;
    DWORD dwRet;

    OpenProcessToken(GetCurrentProcess(),
             TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,hToken);
    LookupPrivilegeValue(NULL,"SeDebugPrivilege",&DebugNameValue);
    Privileges.PrivilegeCount=1;
    Privileges.Privileges[0].Luid=DebugNameValue;
    Privileges.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hToken,FALSE,&Privileges,sizeof(Privileges),
                  NULL,&dwRet);
    CloseHandle(hToken);


=====[ 7.2 全局掛鉤 ]=======================================

    枚舉進(jìn)程通過前面提到的API函數(shù)NtQuerySystemInformation來完成。因?yàn)橄到y(tǒng)中還有一些內(nèi)部native進(jìn)程,所以使用重寫函數(shù)第一個(gè)指令的方法來掛鉤。對(duì)每個(gè)正在運(yùn)行的進(jìn)程我們需要做的都一樣。首先在目標(biāo)進(jìn)程里分配一部分內(nèi)存用來寫入我們用來掛鉤函數(shù)的新代碼,然后把每個(gè)函數(shù)開始的5個(gè)字節(jié)改為跳轉(zhuǎn)指令(jmp),這個(gè)跳轉(zhuǎn)會(huì)轉(zhuǎn)為執(zhí)行我們的代碼。所以當(dāng)被掛鉤的函數(shù)被調(diào)用時(shí)跳轉(zhuǎn)指令能立刻被執(zhí)行。我們需要保存每個(gè)函數(shù)開始被改寫的指令,需要它們來調(diào)用被掛鉤函數(shù)的原始代碼。保存指令的過程在"掛鉤Windows API"的3.2.3節(jié)有描述。
    首先通過NtOpenProcess打開目標(biāo)進(jìn)程并獲取句柄。如果我們沒有足夠權(quán)限的話就會(huì)失敗。

    NTSTATUS NtOpenProcess(
        OUT PHANDLE ProcessHandle,
        IN ACCESS_MASK DesiredAccess,
        IN POBJECT_ATTRIBUTES ObjectAttributes,
        IN PCLIENT_ID ClientId OPTIONAL
    );

    ProcessHandle是指向保存進(jìn)程對(duì)象句柄的指針。DesiredAccess應(yīng)該被設(shè)置為PROCESS_ALL_ACCESS。我們要在ClientId結(jié)構(gòu)里設(shè)置UniqueProcess為目標(biāo)進(jìn)程的PID,UniqueThread應(yīng)該為0。被打開的句柄可以通過NtClose關(guān)閉。

    #define PROCESS_ALL_ACCESS 0x001F0FFF

    現(xiàn)在我們?yōu)槲覀兊拇a分配部分內(nèi)存。這通過NtAllocateVirtualMemory來完成。

    NTSTATUS NtAllocateVirtualMemory(
        IN HANDLE ProcessHandle,
        IN OUT PVOID BaseAddress,
        IN ULONG ZeroBits,
        IN OUT PULONG AllocationSize,
        IN ULONG AllocationType,
        IN ULONG Protect
    );

    ProcessHandle是來自NtOpenProcess相同參數(shù)。BaseAddress是一個(gè)指針,指向被分配虛擬內(nèi)存基地址的開始處,它的輸入?yún)?shù)應(yīng)該為NULL。AllocationSize指向我們要分配的字節(jié)數(shù)的變量,同樣它也用來接受實(shí)際分配的字節(jié)數(shù)大小。最好把AllocationType在設(shè)置成MEM_COMMIT之外再加上MEM_TOP_DOWN因?yàn)閮?nèi)存要在接近DLL地址的盡可能高的地址分配。

    #define MEM_COMMIT    0x00001000
    #define MEM_TOP_DOWN    0x00100000   


    然后我們就可以通過調(diào)用NtWriteVirtualMemory來寫入我們的代碼。

    NTSTATUS NtWriteVirtualMemory(
        IN HANDLE ProcessHandle,
        IN PVOID BaseAddress,
        IN PVOID Buffer,
        IN ULONG BufferLength,
        OUT PULONG ReturnLength OPTIONAL
    );

    BaseAddress是NtAllocateVirtualMemory返回的地址。Buffer指向我們要寫入的字節(jié),BufferLength是我們要寫入的字節(jié)數(shù)。

    現(xiàn)在我們來掛鉤單個(gè)進(jìn)程。被加載入所有進(jìn)程的動(dòng)態(tài)鏈接庫只有ntdll.dll。所以我們要檢查被導(dǎo)入進(jìn)程要掛鉤的函數(shù)是否來自ntdll.dll。但是這些來自其它DLL的函數(shù)所在的內(nèi)存可能已經(jīng)被分配,這時(shí)重寫它的代碼會(huì)在目標(biāo)進(jìn)程里導(dǎo)致錯(cuò)誤。這就是我們必須去檢查我們要掛鉤的函數(shù)來自的動(dòng)態(tài)鏈接庫是否被目標(biāo)進(jìn)程加載的原因。
    我們需要通過NtQueryInformationProcess獲取目標(biāo)進(jìn)程的PEB(進(jìn)程環(huán)境塊)。

    NTSTATUS NtQueryInformationProcess(
        IN HANDLE ProcessHandle,
        IN PROCESSINFOCLASS ProcessInformationClass,
        OUT PVOID ProcessInformation,
        IN ULONG ProcessInformationLength,
        OUT PULONG ReturnLength OPTIONAL
    );

    我們把ProcessInformationClass設(shè)置為ProcessBasicInformation,然后PROCESS_BASIC_INFORMATION結(jié)構(gòu)會(huì)返回到ProcessInformation緩沖區(qū)中,大小為給定的ProcessInformationLength。

    #define ProcessBasicInformation 0

    typedef struct _PROCESS_BASIC_INFORMATION {
        NTSTATUS ExitStatus;
        PPEB PebBaseAddress;
        KAFFINITY AffinityMask;
        KPRIORITY BasePriority;
        ULONG UniqueProcessId;
        ULONG InheritedFromUniqueProcessId;
    } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

    PebBaseAddress就是我們要尋找的東西。在PebBaseAddress+0C處是PPEB_LDR_DATA的地址。這些通過調(diào)用NtReadVirtualMemory來獲得。

    NTSTATUS NtReadVirtualMemory(
        IN HANDLE ProcessHandle,
        IN PVOID BaseAddress,
        OUT PVOID Buffer,
        IN ULONG BufferLength,
        OUT PULONG ReturnLength OPTIONAL
    );

    變量和NtWriteVirtualMemory的很相似。
    在PPEB_LDR_DATA+01C處是InInitializationOrderModuleList的地址。它是被加載進(jìn)進(jìn)程的動(dòng)態(tài)鏈接庫的列表。我們只對(duì)這個(gè)結(jié)構(gòu)中的一些部分感興趣。

    typedef struct _IN_INITIALIZATION_ORDER_MODULE_LIST {
        PVOID Next,
        PVOID Prev,
        DWORD ImageBase,
        DWORD ImageEntry,
        DWORD ImageSize,
        ...
    );

    Next是指向下一個(gè)記錄的指針,Prev指向前一個(gè),最后一個(gè)記錄的會(huì)指向第一個(gè)。ImageBase是內(nèi)存中模塊的地址,ImageEntry是模快的入口點(diǎn),ImageSize是它的大小。
   
    對(duì)所有我們想要掛鉤的庫我們需要獲得它們的ImageBase(比方調(diào)用GetModuleHandle或者LoadLibrary)。然后把這個(gè)ImageBase和InInitializationOrderModuleList的ImageBase比較。
    現(xiàn)在我們已經(jīng)為掛鉤準(zhǔn)備就緒。因?yàn)槲覀兪菕煦^正在運(yùn)行的進(jìn)程,所以可能我們正在改寫代碼的同時(shí)代碼被執(zhí)行,這時(shí)就會(huì)導(dǎo)致錯(cuò)誤。所以首先我們就得停止目標(biāo)進(jìn)程里的所有線程。它的所有線程列表可以通過設(shè)置了SystemProcessAndThreadInformation的NtQuerySystemInformation來獲得。有關(guān)這個(gè)函數(shù)的描述參考第4節(jié)。但是還得加入SYSTEM_THREADS結(jié)構(gòu)的描述,用來保存線程的信息。

    typedef struct _SYSTEM_THREADS {
        LARGE_INTEGER KernelTime;
        LARGE_INTEGER UserTime;
        LARGE_INTEGER CreateTime;
        ULONG WaitTime;
        PVOID StartAddress;
        CLIENT_ID ClientId;
        KPRIORITY Priority;
        KPRIORITY BasePriority;
        ULONG ContextSwitchCount;
        THREAD_STATE State;
        KWAIT_REASON WaitReason;
    } SYSTEM_THREADS, *PSYSTEM_THREADS;

    對(duì)每個(gè)線程調(diào)用NtOpenThread獲取它們的句柄,通過使用ClientId。

    NTSTATUS NtOpenThread(
        OUT PHANDLE ThreadHandle,
        IN ACCESS_MASK DesiredAccess,
        IN POBJECT_ATTRIBUTES ObjectAttributes,
        IN PCLIENT_ID ClientId
    );

    我們需要的句柄被保存在ThreadHandle。我們需要把DesiredAccess設(shè)置為THREAD_SUSPEND_RESUME。

    #define THREAD_SUSPEND_RESUME 2

    ThreadHandle用來調(diào)用NtSuspendThread。

    NTSTATUS NtSuspendThread(
        IN HANDLE ThreadHandle,
        OUT PULONG PreviousSuspendCount OPTIONAL
    );


    被掛起的進(jìn)程就可以被改寫了。我們按照"掛鉤Windows API"里3.2.2節(jié)里描述的方法處理。唯一的不同是使用其它進(jìn)程的函數(shù)。

    掛鉤完后我們就可以調(diào)用NtResumeThread恢復(fù)所有線程的運(yùn)行。

    NTSTATUS NtResumeThread(
        IN HANDLE ThreadHandle,
        OUT PULONG PreviousSuspendCount OPTIONAL
    );


=====[ 7.3 新進(jìn)程 ]================================================

    感染所有正在運(yùn)行的進(jìn)程并不能影響將要被運(yùn)行的進(jìn)程。我們可以每隔一定時(shí)間獲取一次進(jìn)程的列表,然后感染新的列表里的進(jìn)程。但這種方法很不可靠。
    更好的方法是掛鉤新進(jìn)程開始時(shí)肯定會(huì)調(diào)用的函數(shù)。因?yàn)樗邢到y(tǒng)中正在運(yùn)行的進(jìn)程都已經(jīng)被掛鉤,所以這種方法不會(huì)漏掉任何新的進(jìn)程。我們可以掛鉤NtCreateThread,但這不是最簡單的方法。我們可以掛鉤NtResumeThread,因?yàn)樗彩敲慨?dāng)新進(jìn)程創(chuàng)建時(shí)被調(diào)用,它在NtCreateThread之后被調(diào)用。
    唯一的問題在于,這個(gè)函數(shù)并不只在新進(jìn)程被創(chuàng)建時(shí)調(diào)用。但我們能很容易解決這點(diǎn)。NtQueryInformationThread能給我們指定線程是屬于哪個(gè)進(jìn)程的信息。最后我們要做的就是檢查進(jìn)程是否已經(jīng)被掛鉤了。這通過讀取我們要掛鉤的函數(shù)的開始5個(gè)字節(jié)來完成。

    NTSTATUS NtQueryInformationThread(
        IN HANDLE ThreadHandle,
        IN THREADINFOCLASS ThreadInformationClass,
        OUT PVOID ThreadInformation,
        IN ULONG ThreadInformationLength,
        OUT PULONG ReturnLength OPTIONAL
    );

    ThreadInformationClass是信息分類,在這里它被設(shè)置為ThreadBasicInformation。ThreadInformation是保存結(jié)果的緩沖區(qū),大小按字節(jié)計(jì)算為ThreadInformationLength。

    #define ThreadBasicInformation 0

    對(duì)ThreadBasicInformation返回這個(gè)結(jié)構(gòu):

    typedef struct _THREAD_BASIC_INFORMATION {
        NTSTATUS ExitStatus;
        PNT_TIB TebBaseAddress;
        CLIENT_ID ClientId;
        KAFFINITY AffinityMask;
        KPRIORITY Priority;
        KPRIORITY BasePriority;
    } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

    ClientId是線程所屬進(jìn)程的PID。

    現(xiàn)在我們來感染新進(jìn)程。問題就是新進(jìn)程的地址空間中只有ntdll.dll,其他的模塊在調(diào)用NtResumeThread之后被加載。有幾種方法可以解決這個(gè)問題,比方說我們可以掛鉤一個(gè)名為LdrInitializeThunk的API函數(shù),它在進(jìn)程初始化時(shí)被調(diào)用。

    NTSTATUS LdrInitializeThunk(
        DWORD Unknown1,
        DWORD Unknown2,
        DWORD Unknown3
    );

    首先我們先運(yùn)行原始的代碼,然后掛鉤新進(jìn)程里所有要掛鉤的函數(shù)。但最好對(duì)LdrInitializeThunk解除掛鉤,因?yàn)檫@個(gè)函數(shù)在之后要被調(diào)用很多次,我們并不需要重新再掛鉤所有的函數(shù)。這時(shí)在程序執(zhí)行第一個(gè)指令前所有工作已經(jīng)完成。這就是為什么在我們掛鉤它之前它沒有機(jī)會(huì)調(diào)用任何一個(gè)被掛鉤過的函數(shù)的原因。
    對(duì)自己掛鉤和動(dòng)態(tài)掛鉤正在運(yùn)行的進(jìn)程一樣,只是這里我們不需要關(guān)心正在運(yùn)行的線程。


=====[ 7.4 DLL ]================================================

    系統(tǒng)中每個(gè)進(jìn)程都是一份ntdll.dll拷貝。這意味著我們可以在進(jìn)程初始化階段掛鉤這個(gè)模塊里的任意一個(gè)函數(shù)。但是來自其它模塊比如kernel32.dll或advapi32.dll的函數(shù)該怎么辦呢?還有一些進(jìn)程只有ntdll.dll,其他模塊都是在進(jìn)程被掛鉤之后在運(yùn)行過程中才被動(dòng)態(tài)加載的。這就是我們還得掛鉤加載新模塊的函數(shù)LdrLoadDll的原因。
   
    NTSTATUS LdrLoadDll(
        PWSTR szcwPath,
        PDWORD pdwLdrErr,     
        PUNICODE_STRING pUniModuleName,
        PHINSTANCE pResultInstance
    );

    這里對(duì)我們來說最重要的是pUniModuleName,它保存模塊名字。當(dāng)調(diào)用成功后pResultInstance保存模塊地址。
    我們首先調(diào)用原始的LdrLoadDll然后掛鉤被加載模塊里所有函數(shù)。

 

=====[ 8. 內(nèi)存 ]===========================================

    當(dāng)我們正在掛鉤一個(gè)函數(shù)時(shí)我們會(huì)修改它開始的字節(jié)。通過調(diào)用NtReadVirtualMemory任何人都可以檢測(cè)出函數(shù)被掛鉤。所以我們還要掛鉤NtReadVirtualMemory來防止檢測(cè)。

    NTSTATUS NtReadVirtualMemory(
        IN HANDLE ProcessHandle,
        IN PVOID BaseAddress,
        OUT PVOID Buffer,
        IN ULONG BufferLength,
        OUT PULONG ReturnLength OPTIONAL
    );

    我們修改了我們掛鉤的函數(shù)開始的字節(jié)并且為我們新的代碼分配了內(nèi)存。我們就需要檢查時(shí)候有人讀取了這些代碼。如果我們的代碼出現(xiàn)在BaseAddress到BaseAddress+BufferLength中我們就需要在緩沖區(qū)中改變它的一些字節(jié)。
    如果有人在我們分配的內(nèi)存中查詢字節(jié)我們就返回空的緩沖區(qū)和錯(cuò)誤STATUS_PARTIAL_COPY。這個(gè)值用來表示被請(qǐng)求的字節(jié)并沒有完全被拷貝到緩沖區(qū)中,它也同樣被用在當(dāng)請(qǐng)求了未分配的內(nèi)存時(shí)。這時(shí)ReturnLength應(yīng)該被設(shè)為0。

    #define STATUS_PARTIAL_COPY 0x8000000D

    如果有人查詢被掛鉤的函數(shù)開始的字節(jié)我們就調(diào)用原始代碼并拷貝原始代碼里開始的那些字節(jié)到緩沖區(qū)中。
    現(xiàn)在新進(jìn)程已無法通過讀取它的內(nèi)存來檢測(cè)是否被掛鉤了。同樣如果你調(diào)試被掛鉤的進(jìn)程調(diào)試器也會(huì)用問題,它會(huì)顯示原始代碼,但卻執(zhí)行我們的代碼。

    為了使隱藏更完美,我們還要掛鉤NtQueryVirtualMemory。這個(gè)函數(shù)用來獲取虛擬內(nèi)存的信息。我們掛鉤它來防止探測(cè)我們分配的虛逆內(nèi)存。

    NTSTATUS NtQueryVirtualMemory(
        IN HANDLE ProcessHandle,
        IN PVOID BaseAddress,
        IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
        OUT PVOID MemoryInformation,
        IN ULONG MemoryInformationLength,
        OUT PULONG ReturnLength OPTIONAL
    );

    MemoryInformationClass標(biāo)明了返回?cái)?shù)據(jù)的類別。我們對(duì)開始的2種類型感興趣。

    #define MemoryBasicInformation 0
    #define MemoryWorkingSetList 1

    對(duì)MemoryBasicInformation返回這個(gè)結(jié)構(gòu):

    typedef struct _MEMORY_BASIC_INFORMATION {
        PVOID BaseAddress;
        PVOID AllocationBase;
        ULONG AllocationProtect;
        ULONG RegionSize;
        ULONG State;
        ULONG Protect;
        ULONG Type;
    } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

    每個(gè)區(qū)段都有它的大小RegionSize和它的類型Type。空閑內(nèi)存的類型是MEM_FREE。(區(qū)段對(duì)象就是文件映射對(duì)象,是可被映射到一個(gè)進(jìn)程的虛逆地址空間的對(duì)象)

    #define MEM_FREE 0x10000

    如果我們代碼之前一個(gè)區(qū)段的類型是MEM_FREE我們就在它的RegionSize加上我們代碼的區(qū)段的大小。如果我們代碼之后的區(qū)段的類型也是MEM_FREE那么就在之前區(qū)段的RegionSize上再加上之后的空閑區(qū)段的大小。
    如果我們代碼之前的區(qū)段是其它類型,我們就對(duì)我們代碼的區(qū)段返回MEM_FREE。它的大小根據(jù)之后的區(qū)段來計(jì)算。

    對(duì)MemoryWorkingSetList返回這個(gè)結(jié)構(gòu):

    typedef struct _MEMORY_WORKING_SET_LIST {
        ULONG NumberOfPages;
        ULONG WorkingSetList[1];
    } MEMORY_WORKING_SET_LIST, *PMEMORY_WORKING_SET_LIST;

    NumberOfPages是WorkingSetList中列項(xiàng)的數(shù)目。這個(gè)數(shù)字應(yīng)該減少一些。我們?cè)赪orkingSetList中找到我們代碼的區(qū)段然后把之后記錄前移。WorkingSetList是按DWORD排列的數(shù)組,每個(gè)元素的高20位標(biāo)明了區(qū)段地址,低12位是標(biāo)志。

 

=====[ 9. 句柄 ]=========================================

    用類SystemHandleInformation來調(diào)用NtQuerySystemInformation會(huì)在_SYSTEM_HANDLE_INFORMATION_EX結(jié)構(gòu)中獲取所有被打開的句柄的數(shù)組。

    #define SystemHandleInformation 0x10

    typedef struct _SYSTEM_HANDLE_INFORMATION {
        ULONG ProcessId;
        UCHAR ObjectTypeNumber;
        UCHAR Flags;
        USHORT Handle;
        PVOID Object;
        ACCESS_MASK GrantedAccess;
    } SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

    typedef struct _SYSTEM_HANDLE_INFORMATION_EX {
        ULONG NumberOfHandles;
        SYSTEM_HANDLE_INFORMATION Information[1];
    } SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;

    ProcessId標(biāo)明了擁有句柄的進(jìn)程。ObjectTypeNumber是句柄類型。NumberOfHandles是Information數(shù)組中元素的數(shù)量。隱藏其中一項(xiàng)是很麻煩的,我們要去掉所有之后的元素并減少NumberOfHandles。去掉之后所有元素是必須的,因?yàn)閿?shù)組中句柄是按ProcessId分組的。這意味著一個(gè)來自同一個(gè)進(jìn)程中的所有句柄都在一塊兒。對(duì)于一個(gè)進(jìn)程變量Handle的數(shù)量是不斷增加的。
    現(xiàn)在回想一下這個(gè)函數(shù)(NtQuerySystemInformation)使用SystemProcessAndThreadsInformation類來調(diào)用時(shí)返回的結(jié)構(gòu)_SYSTEM_PROCESSES。這里我們能夠看到每個(gè)進(jìn)程都有它自己的句柄的數(shù)量在HandleCount中。如果我們想要做得更完美我們就應(yīng)該修改HandleCount,因?yàn)橛肧ystemProcessesAndThreadsInformation類調(diào)用這個(gè)函數(shù)時(shí)隱藏了不少句柄。但校正是非常浪費(fèi)時(shí)間的。在系統(tǒng)正常運(yùn)行的一小段時(shí)間里就會(huì)有很多句柄正在打開或關(guān)上。所以在對(duì)這個(gè)函數(shù)兩次緊挨著的調(diào)用句柄的數(shù)量被更改是很正常的,所以我們根本不需要改變HandleCount。


=====[ 9.1 命名句柄并獲取類型 ]===================================

    隱藏句柄很麻煩,但找出哪個(gè)句柄該被隱藏更困難一些。比方說我們要隱藏一個(gè)進(jìn)程就要隱藏它的所有句柄并隱藏所有和它有聯(lián)系的句柄。我們比較句柄的ProcessId參數(shù)和想要隱藏的進(jìn)程的PID,如果它們相等就隱藏這個(gè)句柄。但是其它進(jìn)程的句柄在我們能比較任何東西之前不得不先命名。系統(tǒng)中句柄的數(shù)量通常很龐大,所以最好在嘗試命名之前先比較句柄類型。命名類型可以為我們不感興趣的句柄省不少時(shí)間。
    命名句柄和句柄類型通過調(diào)用NtQueryObject來完成。

    NTSTATUS ZwQueryObject(
        IN HANDLE ObjectHandle,
        IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
        OUT PVOID ObjectInformation,
        IN ULONG ObjectInformationLength,
        OUT PULONG ReturnLength OPTIONAL
    );

    ObjectHandle是我們想要獲取有關(guān)信息的句柄,ObjectInformationClass是信息類型,保存在以字節(jié)計(jì)算長度為ObjectInformationLength的緩沖區(qū)ObjectInformation中。
    我們對(duì)OBJECT_INFORMATION_CLASS使用的類是ObjectNameInformation和ObjectAllTypesInformation。ObjectNameInfromation類在緩沖區(qū)中返回OBJECT_NAME_INFORMATION結(jié)構(gòu),而ObjectAllTypesInformation類返回OBJECT_ALL_TYPES_INFORMATION結(jié)構(gòu)。

    #define ObjectNameInformation 1
    #define ObjectAllTypesInformation 3

    typedef struct _OBJECT_NAME_INFORMATION {
        UNICODE_STRING Name;
    } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

    Name決定了句柄的名字。


    typedef struct _OBJECT_TYPE_INFORMATION {
        UNICODE_STRING Name;
        ULONG ObjectCount;
        ULONG HandleCount;
        ULONG Reserved1[4];
        ULONG PeakObjectCount;
        ULONG PeakHandleCount;
        ULONG Reserved2[4];
        ULONG InvalidAttributes;
        GENERIC_MAPPING GenericMapping;
        ULONG ValidAccess;
        UCHAR Unknown;
        BOOLEAN MaintainHandleDatabase;
        POOL_TYPE PoolType;
        ULONG PagedPoolUsage;
        ULONG NonPagedPoolUsage;
    } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

    typedef struct _OBJECT_ALL_TYPES_INFORMATION {
        ULONG NumberOfTypes;
        OBJECT_TYPE_INFORMATION TypeInformation;
    } OBJECT_ALL_TYPES_INFORMATION, *POBJECT_ALL_TYPES_INFORMATION;

    Name決定類型對(duì)象名字,類型對(duì)象緊跟在每個(gè)OBJECT_TYPE_INFORMATION結(jié)構(gòu)后面。下一個(gè)OBJECT_TYPE_INFORMATION結(jié)構(gòu)跟在這個(gè)Name后面,間隔4個(gè)字節(jié)。
   
   
    SYSTEM_HANDLE_INFORMATION結(jié)構(gòu)中的ObjectTypeNumber是TypeInformation數(shù)組中的索引。

    比較困難的是獲取其他進(jìn)程中句柄的名字。這里有兩種命名的可能性。一是通過調(diào)用NtDuplicateObject把句柄拷貝到我們的進(jìn)程中然后命名它。這種方法對(duì)某些特殊類型的句柄會(huì)失敗。但由于它失敗的次數(shù)比較少,所以我們采用這種方法。

    NtDuplicateObject(
        IN HANDLE SourceProcessHandle,
        IN HANDLE SourceHandle,
        IN HANDLE TargetProcessHandle,
        OUT PHANDLE TargetHandle OPTIONAL,
        IN ACCESS_MASK DesiredAccess,
        IN ULONG Attributes,
        IN ULONG Options
    );

    SourceHandle是我們想要拷貝的句柄,SourceProcessHandle是擁有SourceHandle的進(jìn)程的句柄。TargetProcessHandle是想要拷貝到的進(jìn)程的句柄,在這里是我們進(jìn)程的句柄。TargetHandle是指向保存原始句柄拷貝的指針。DesiredAccess應(yīng)該被設(shè)為PROCESS_QUERY_INFORMATION,Attributes和Options設(shè)為0。

    第二種命名方法對(duì)所有句柄都有效,就是使用系統(tǒng)驅(qū)動(dòng)。源代碼可以在http://rootkit.host.sk的OpHandle項(xiàng)目里找到。

 

=====[ 10. 端口 ]==========================================

    枚舉打開端口最簡單的方法是調(diào)用AllocateAndGetTcpTableFromStack和AllocateAndGetUdpTableFromStack函數(shù),或者AllocateAndGetTcpExTableFromStack和AllocateAndGetUdpExTableFromStack函數(shù),它們都來自iphlpapi.dll。帶Ex的函數(shù)從Windows XP才開始有效。


    typedef struct _MIB_TCPROW {
        DWORD dwState;
        DWORD dwLocalAddr;
        DWORD dwLocalPort;
        DWORD dwRemoteAddr;
        DWORD dwRemotePort;
    } MIB_TCPROW, *PMIB_TCPROW;

    typedef struct _MIB_TCPTABLE {
        DWORD dwNumEntries;
        MIB_TCPROW table[ANY_SIZE];
    } MIB_TCPTABLE, *PMIB_TCPTABLE;

    typedef struct _MIB_UDPROW {
        DWORD dwLocalAddr;
        DWORD dwLocalPort;
    } MIB_UDPROW, *PMIB_UDPROW;

    typedef struct _MIB_UDPTABLE {
        DWORD dwNumEntries;
        MIB_UDPROW table[ANY_SIZE];
    } MIB_UDPTABLE, *PMIB_UDPTABLE;

    typedef struct _MIB_TCPROW_EX
    {
        DWORD dwState;
        DWORD dwLocalAddr;
        DWORD dwLocalPort;
        DWORD dwRemoteAddr;
        DWORD dwRemotePort;
        DWORD dwProcessId;
    } MIB_TCPROW_EX, *PMIB_TCPROW_EX;

    typedef struct _MIB_TCPTABLE_EX
    {
        DWORD dwNumEntries;
        MIB_TCPROW_EX table[ANY_SIZE];
    } MIB_TCPTABLE_EX, *PMIB_TCPTABLE_EX;

    typedef struct _MIB_UDPROW_EX
    {
        DWORD dwLocalAddr;
        DWORD dwLocalPort;
        DWORD dwProcessId;
    } MIB_UDPROW_EX, *PMIB_UDPROW_EX;

    typedef struct _MIB_UDPTABLE_EX
    {
        DWORD dwNumEntries;
        MIB_UDPROW_EX table[ANY_SIZE];
    } MIB_UDPTABLE_EX, *PMIB_UDPTABLE_EX;

    DWORD WINAPI AllocateAndGetTcpTableFromStack(
        OUT PMIB_TCPTABLE *pTcpTable,
        IN BOOL bOrder,
        IN HANDLE hAllocHeap,
        IN DWORD dwAllocFlags,
        IN DWORD dwProtocolVersion;
    );

    DWORD WINAPI AllocateAndGetUdpTableFromStack(
        OUT PMIB_UDPTABLE *pUdpTable,
        IN BOOL bOrder,
        IN HANDLE hAllocHeap,
        IN DWORD dwAllocFlags,
        IN DWORD dwProtocolVersion;
    );

    DWORD WINAPI AllocateAndGetTcpExTableFromStack(
        OUT PMIB_TCPTABLE_EX *pTcpTableEx,
        IN BOOL bOrder,
        IN HANDLE hAllocHeap,
        IN DWORD dwAllocFlags,
        IN DWORD dwProtocolVersion;
    );

    DWORD WINAPI AllocateAndGetUdpExTableFromStack(
        OUT PMIB_UDPTABLE_EX *pUdpTableEx,
        IN BOOL bOrder,
        IN HANDLE hAllocHeap,
        IN DWORD dwAllocFlags,
        IN DWORD dwProtocolVersion;
    );

   

    還有另外一種方法。當(dāng)程序創(chuàng)建了一個(gè)套接字并開始監(jiān)聽時(shí),它就會(huì)有一個(gè)為它和打開端口的打開句柄。我們?cè)谙到y(tǒng)中枚舉所有的打開句柄并通過NtDeviceIoControlFile把它們發(fā)送到一個(gè)特定的緩沖區(qū)中,來找出這個(gè)句柄是否是一個(gè)打開端口的。這樣也能給我們有關(guān)端口的信息。因?yàn)榇蜷_句柄太多了,所以我們只檢測(cè)類型是File并且名字是\Device\Tcp或\Device\Udp的。打開端口只有這種類型和名字。

    如果你看一下iphlpapi.dll里函數(shù)的代碼,就會(huì)發(fā)現(xiàn)這些函數(shù)同樣調(diào)用NtDeviceIoControlFile并發(fā)送到一個(gè)特定緩沖區(qū)來獲得系統(tǒng)中所有打開端口的列表。這意味著我們要想隱藏端口只需要掛鉤NtDeviceIoControlFile函數(shù)。

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

    我們感興趣的成員變量有這幾個(gè):FileHandle標(biāo)明了要通信的設(shè)備的句柄,IoStatusBlock指向接收最后完成狀態(tài)和請(qǐng)求操作信息的變量,IoControlCode是指定要完成的特定的I/O控制操作的數(shù)字,InputBuffer包含了輸入的數(shù)據(jù),長度為按字節(jié)計(jì)算的InputBufferLength,相似的還有OutputBuffer和OutputBufferLength。

      
=====[ 10.1 WinXP下使用Netstat OpPorts FPort ]=========================

    在Windoes XP獲得所有打開端口的列表可以使用一些軟件比方OpPorts、FPort和Netstat。
    這里程序用IoControlCode0x000120003調(diào)用了NtDeviceIoControlFile兩次。輸出緩沖區(qū)在第二次調(diào)用時(shí)被填滿。FileHandle的名字這里總是\Device\Tcp。InputBuffer因不同類型的調(diào)用而不同:

    1) 為獲得MIB_TCPROW數(shù)組InputBuffer看起來是這樣:

第一次調(diào)用:
0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00

第二次調(diào)用:
0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00


    2) 為獲得MIB_UDPROW數(shù)組:

第一次調(diào)用:
0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00

第二次調(diào)用:
0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00


    3) 為獲得MIB_TCPROW_EX數(shù)組:

第一次調(diào)用:
0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00

第二次調(diào)用:
0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00
0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00


    4) 為獲得MIB_UDPROW_EX數(shù)組:

第一次調(diào)用:
0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00

第二次調(diào)用:
0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00
0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00


    你可以看到緩沖區(qū)只有少數(shù)字節(jié)不同。我們現(xiàn)在比較清晰地簡要說明一下:
   
    我們感興趣的調(diào)用是InputBuffer[1]為0x04且InputBuffer[17]為0x01。只有使用這些輸入數(shù)據(jù)才能使OutputBuffer里為我們想要的表。如果我們想要獲得TCP端口信息我們就把InputBuffer[0]設(shè)為0x00,想獲得UDP端口信息就把它設(shè)為0x01。如果我們還需要額外的輸出表(MIB_TCPROW_EX或MIB_UDPROW_EX)我們就在第二次調(diào)用里把InfputBufer[16]設(shè)為0x02。

    如果我們發(fā)現(xiàn)使用了這幾個(gè)參數(shù)的調(diào)用我們就修改輸出緩沖區(qū)。獲取輸出緩沖區(qū)中ROW的數(shù)量可以很容易根據(jù)ROW的大小分開IoStatusBlock結(jié)構(gòu)里的Infotmation變量。隱藏其中一個(gè)ROW就會(huì)變的容易,只需要用之后的ROW改寫他并刪掉最后一個(gè)ROW。不要忘了修改OutputBufferLength和IoStatusBlock。


=====[ 10.2 Win2k和NT4下使用OpPorts, Win2k下使用FPort ]==========================

    我們用IoControlCode0x00210012調(diào)用NtDeviceIoControlFile來判斷這個(gè)擁有類型File和名字\Device\Tcp或\Device\Udp是否是打開端口的句柄。

    所以最先我們比較IoControlCode然后是類型和句柄名字。如果這些都符合就接著比較輸入緩沖區(qū)長度,它應(yīng)該和結(jié)構(gòu)TDI_CONNECTION_IN長度一樣,為0x18。輸出緩沖區(qū)的結(jié)構(gòu)是TDI_CONNECTION_OUT。

    typedef struct _TDI_CONNETION_IN
    {
        ULONG UserDataLength,
        PVOID UserData,
        ULONG OptionsLength,
        PVOID Options,
        ULONG RemoteAddressLength,
        PVOID RemoteAddress
    } TDI_CONNETION_IN, *PTDI_CONNETION_IN;

    typedef struct _TDI_CONNETION_OUT
    {
        ULONG State,
        ULONG Event,
        ULONG TransmittedTsdus,
        ULONG ReceivedTsdus,
        ULONG TransmissionErrors,
        ULONG ReceiveErrors,
        LARGE_INTEGER Throughput
        LARGE_INTEGER Delay,
        ULONG SendBufferSize,
        ULONG ReceiveBufferSize,
        ULONG Unreliable,
        ULONG Unknown1[5],
        USHORT Unknown2
    } TDI_CONNETION_OUT, *PTDI_CONNETION_OUT;


    具體判斷句柄是不是一個(gè)打開端口的方法請(qǐng)參考OpPorts的源代碼,在http://rookit.host.sk上可以找到。我們現(xiàn)在來隱藏指定端口。我們已經(jīng)比較過了InputBufferLength和IoControlCode,現(xiàn)在來比較RemoteAddressLength,對(duì)打開端口來說它總是3或4。最后要做的是比較OutputBufferBuffer里的ReceiveTsdus,用網(wǎng)絡(luò)上的端口和要隱藏的端口列表比較。區(qū)別TCP和UDP的做法是句柄的名字不一樣。在刪除了OutputBuffer、修改IoStatusBlock并返回STATUS_INVALID_ADDRESS后我們就已經(jīng)隱藏了這個(gè)端口了。

      
=====[ 11. 結(jié)束語 ]===============================================

    具體細(xì)節(jié)請(qǐng)參考Hacker defender rootkit version 1.0.0的源代碼,在http://rootkit.host.skhttp://www.rootkit.com都可以找到。
    在將來我還會(huì)加入更多有關(guān)的技術(shù)。這篇文檔的更新版本會(huì)改進(jìn)現(xiàn)有的方法和并加入新的思想。
    特別感謝Ratter提供了很多完成這篇文檔和Hacker defender代碼所需要的技術(shù)。

===================================[ End ]==============================

后記:

    其實(shí)只要我們對(duì)Windows的內(nèi)核有一定程度的了解我們都知道單純靠掛鉤函數(shù)是不能真正做到隱藏的,這樣做只不過是欺騙操作系統(tǒng)的使用者,卻欺騙不了操作系統(tǒng)自己。線程要想被運(yùn)行就必須獲得時(shí)間片,將自己加入調(diào)度鏈表中,從而暴露自己。內(nèi)核維護(hù)一組被稱為調(diào)度程序數(shù)據(jù)庫的數(shù)據(jù)結(jié)構(gòu)來做出線程調(diào)度的決策。其中最重要的結(jié)構(gòu)是調(diào)度程序就緒隊(duì)列(KiDispatckerReadyListHead)。它里面有64個(gè)DWORD,分別對(duì)應(yīng)于32個(gè)線程優(yōu)先級(jí)的隊(duì)列,隊(duì)列包含處于就緒狀態(tài)的線程,正在等待調(diào)度執(zhí)行。還有兩個(gè)隊(duì)列KiWaitInListHead和KiWaitOutListHead保存著處于等待狀態(tài)的線程。可以很簡單的枚舉這3個(gè)鏈表中的所有元素從而列出系統(tǒng)中的所有線程。因此要想徹底從Windows系統(tǒng)里“消失”就要從Windows的內(nèi)核下手(Windows的內(nèi)核只負(fù)責(zé)線程調(diào)度,其它功能由執(zhí)行程序組件完成)。這個(gè)功能還有待完成。

    水平有限,歡迎大家指出錯(cuò)漏之處。QQ:27324838 Email:kinvis@hotmail.com

posted on 2008-06-10 21:15 saga.constantine 閱讀(1212) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 轉(zhuǎn)的貼
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久精彩免费视频| 一本色道久久88综合日韩精品 | 久久久久久久成人| 久久精品国产一区二区电影| 午夜日韩电影| 久久人体大胆视频| 欧美久久久久| 国产精品视频网站| 国产自产精品| 99pao成人国产永久免费视频| 亚洲视频导航| 久久久久久91香蕉国产| 亚洲高清av在线| 亚洲日韩第九十九页| 亚洲小说欧美另类社区| 久久精品一本久久99精品| 免费看亚洲片| 国产精品私房写真福利视频 | 亚洲一区不卡| 久久亚洲国产成人| 一区二区欧美日韩| 久久国产精品免费一区| 欧美精品免费视频| 国产欧美一区二区精品性| 亚洲国产精品t66y| 欧美亚洲免费高清在线观看| 欧美福利一区二区| 亚洲欧美卡通另类91av| 欧美福利精品| 国内自拍亚洲| 午夜精品久久久久久久| 亚洲国产成人av在线| 久久成人亚洲| 国产精品嫩草久久久久| 亚洲精品久久久久久下一站 | 亚洲日本电影在线| 久久久精彩视频| 国产精品久久久久久久app| 亚洲国产精品激情在线观看| 久久国产欧美日韩精品| 亚洲午夜伦理| 欧美日韩免费观看一区三区| 亚洲国产精品高清久久久| 久久久久久久尹人综合网亚洲 | 欧美一区二区三区免费在线看| 欧美精品久久久久久久免费观看| 精品成人久久| 久久久久五月天| 亚洲欧美一区在线| 国产精品一区二区三区久久| 亚洲午夜激情| 日韩亚洲欧美一区| 欧美精品在线观看| 亚洲美女毛片| 亚洲每日更新| 欧美日本在线| 一区二区三区欧美成人| 一本色道**综合亚洲精品蜜桃冫| 欧美老女人xx| 亚洲网站在线| 亚洲视频成人| 国产精品尤物| 久久久久国产一区二区| 久久蜜桃精品| 亚洲精品国产精品国自产观看| 欧美激情欧美激情在线五月| 欧美mv日韩mv国产网站app| 日韩午夜中文字幕| 一区二区三区欧美成人| 国产欧美日韩综合一区在线观看 | 国户精品久久久久久久久久久不卡| 欧美在线观看www| 久久成人这里只有精品| 在线日韩欧美视频| 亚洲精品国产日韩| 欧美先锋影音| 久久精品国产96久久久香蕉| 久久国产精品一区二区三区| 亚洲国产日本| 一区二区日韩| 狠狠综合久久| 亚洲免费观看在线观看| 国产片一区二区| 欧美激情中文不卡| 欧美日韩国语| 久久噜噜噜精品国产亚洲综合| 久久久人成影片一区二区三区观看| 在线观看国产一区二区| 亚洲国产二区| 国产精品免费久久久久久| 蜜月aⅴ免费一区二区三区 | 欧美韩国日本综合| 亚洲影院在线观看| 久久亚洲综合网| 亚洲私拍自拍| 久久偷看各类wc女厕嘘嘘偷窃| 一二美女精品欧洲| 久久国产一区二区| 欧美a级大片| 欧美一区二区播放| 欧美国产综合| 狂野欧美性猛交xxxx巴西| 欧美日韩亚洲国产精品| 久久久久国产精品麻豆ai换脸| 欧美欧美天天天天操| 久热爱精品视频线路一| 国产精品久久久久9999| 亚洲国产精品www| 国模精品一区二区三区色天香| 日韩一级二级三级| 亚洲欧洲在线免费| 久久岛国电影| 久久国产精品一区二区| 欧美日韩一级片在线观看| 免费亚洲一区二区| 国产日韩欧美麻豆| 中日韩高清电影网| 在线亚洲免费| 欧美极品在线观看| 欧美激情欧美激情在线五月| 精品成人在线视频| 欧美在线视频在线播放完整版免费观看| 在线视频欧美精品| 欧美激情亚洲国产| 亚洲第一黄色| 亚洲国产精品电影| 久久免费黄色| 美女主播一区| 黄色在线一区| 久久黄色级2电影| 久久精品最新地址| 国产亚洲精品bt天堂精选| 亚洲男人第一av网站| 欧美一级视频精品观看| 国产精品美女www爽爽爽| 一区二区三区高清在线 | 久久av一区二区三区| 国产精品一级久久久| 欧美一级欧美一级在线播放| 欧美一区二区三区免费看| 国产日韩欧美夫妻视频在线观看| 亚洲在线视频一区| 久久国产免费看| 在线成人性视频| 欧美a级片网站| 一区二区久久| 小嫩嫩精品导航| 国产一区二区三区网站| 久久久夜夜夜| 日韩一区二区精品葵司在线| 午夜视频在线观看一区二区| 国产一区二区三区免费在线观看| 久久精品日产第一区二区| 免费在线一区二区| 99视频+国产日韩欧美| 欧美三区视频| 亚洲欧美日本国产专区一区| 久久一二三区| 亚洲久久成人| 国产精品中文在线| 麻豆精品视频在线观看视频| 亚洲精品视频一区| 久久av一区二区三区漫画| 亚洲高清不卡在线观看| 欧美日韩综合在线| 久久精品一区| 99视频精品在线| 狠狠操狠狠色综合网| 欧美日韩国产高清视频| 先锋影音久久| 亚洲区在线播放| 久久久.com| 亚洲午夜精品网| 精品福利免费观看| 欧美日韩亚洲三区| 久久精品中文| 国产精品99久久久久久久女警 | 国产精品一区二区三区四区五区| 久久久激情视频| 亚洲午夜一级| 欧美福利电影在线观看| 久久精品一区二区三区不卡| 99综合电影在线视频| 黄色一区二区三区| 国产精品视频内| 欧美日韩一区二区在线观看视频| 久久精品系列| 亚洲欧美亚洲| 99国产精品自拍| 亚洲高清免费| 久久视频国产精品免费视频在线| 中文网丁香综合网| 亚洲精品久久久久中文字幕欢迎你| 国产视频久久久久| 国产精品都在这里| 欧美美女bb生活片| 免费黄网站欧美| 久久久91精品国产一区二区精品| 一卡二卡3卡四卡高清精品视频| 欧美高清日韩|