• <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

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

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

            公告

            QQ:34O859O5

            常用鏈接

            留言簿(15)

            搜索

            •  

            積分與排名

            • 積分 - 210899
            • 排名 - 122

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

             

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

                在有很多種隱藏文件使系統(tǒng)無(wú)法發(fā)現(xiàn)的可能。我們只使用改變API的方法,而沒(méi)使用那些比如涉及到文件系統(tǒng)的技術(shù)。這樣會(huì)更容易些因?yàn)槲覀儫o(wú)法知道文件系統(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ì)我們來(lái)說(shuō)重要的參數(shù)是FileHandle,FileInformation和FileInformationClass。FileHandle是從NtOpenFile獲得的目錄對(duì)象句柄。FileInformation是一個(gè)指針,指向函數(shù)要寫(xiě)入需要的數(shù)據(jù)的已分配內(nèi)存。FileInformationClass決定寫(xiě)入FileImformation的記錄的類(lèi)型。
                FileInformationClass是一個(gè)變化的枚舉類(lèi)型,我們只需要其中4個(gè)值來(lái)枚舉目錄內(nèi)容:

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


            要寫(xiě)入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中寫(xiě)入這些結(jié)構(gòu)的一個(gè)列表。對(duì)我們來(lái)說(shuō)在這些結(jié)構(gòu)類(lèi)型中只有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是文件名長(zhǎng)度。

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

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

                #define STATUS_NO_SUCH_FILE 0xC000000F


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

                不知什么原因DOS的枚舉NTVDM能夠通過(guò)函數(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ū)的長(zhǎng)度。所以我們需要手動(dòng)來(lái)計(jì)算它的長(zhǎng)度。我們把所有記錄的NextEntryOffset和最后一個(gè)記錄的FileNameLength還有0X5E(最后一個(gè)記錄除去文件名的長(zhǎng)度)。隱藏的方法和前面提到的使用NtQueryDirectoryFile的方法一樣。

             

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

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

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

               
                SystemInformationClass標(biāo)明了我們想要獲得的信息的類(lèi)別,SystemInformation是一個(gè)指向函數(shù)輸出緩沖區(qū)的指針,SystemInformationLength是這個(gè)緩沖區(qū)的長(zhǎng)度,ReturnLength是寫(xiě)入字節(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ù)形數(shù)據(jù)結(jié)構(gòu),對(duì)我們來(lái)說(shuō)里面有兩種重要的記錄類(lèi)型需要隱藏。一種類(lèi)型是注冊(cè)表鍵,另一種是鍵值。因?yàn)樽?cè)表的結(jié)構(gòu),隱藏注冊(cè)表鍵不象隱藏文件或進(jìn)程那么麻煩。


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

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


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

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

                #define KEY_ENUMERATE_SUB_KEYS 8

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

                NTSTATUS NtClose(
                    IN HANDLE Handle
                );

               
                對(duì)每次NtEnumareteKey的調(diào)用我們要計(jì)算的改變,數(shù)量上等同于我們列表中存在于注冊(cè)表指定部分的鍵的數(shù)量。然后我們把改變的數(shù)量加到變量Index,最后調(diào)用原始的NtEnumerateKey。
                我們使用KeyInformationClass的KeyBasicInformation來(lái)獲得用索引標(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和它的長(zhǎng)度NameLength。   
                如果沒(méi)有被轉(zhuǎn)移的索引的記載我們就返回錯(cuò)誤STATUS_EA_LIST_INCONSISTENT。

                #define STATUS_EA_LIST_INCONSISTENT 0x80000014


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

                注冊(cè)表鍵值不是按字母分類(lèi)的。幸運(yùn)的是在一個(gè)鍵里鍵值的數(shù)目比較少,所以我們可以通過(guò)重調(diào)的方法來(lái)獲得改變的數(shù)目。用來(lái)獲取一個(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描述信息的類(lèi)型,保存在KeyValueInformation緩沖區(qū)中,緩沖區(qū)以字節(jié)為大小為KeyValueInformationLength。寫(xiě)入字節(jié)的數(shù)量返回在ResultLength中。
                我們通過(guò)用0到Index的所有索引重調(diào)函數(shù)計(jì)算轉(zhuǎn)移。鍵值的名字通過(guò)把KeyValueInformationClass設(shè)置為KeyValueBasicInformation來(lái)獲取。
               
                #define KeyValueBasicInformation 0


                然后我們獲取在KeyValueInformation緩沖區(qū)中接下來(lái)的數(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感興趣。

               
                如果這里沒(méi)有被轉(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)是通過(guò)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ù)類(lèi)型。函數(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)限來(lái)獲得進(jìn)入所有正在運(yùn)行的進(jìn)程。最好的可能是將我們的進(jìn)程當(dāng)做系統(tǒng)服務(wù)來(lái)運(yùn)行,因?yàn)樗\(yùn)行與SYSTEM用戶權(quán)限下。為安裝服務(wù)我們首先得獲取特殊的權(quán)限。
                獲取SeDebugPrivilege的權(quán)限是很有用的,通過(guò)調(diào)用OpenProcessToken、LookupPrivilegeValue
            和AdjustTokenPrivileges來(lái)完成。

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

                #define PROCESS_ALL_ACCESS 0x001F0FFF

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

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

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

                #define MEM_COMMIT    0x00001000
                #define MEM_TOP_DOWN    0x00100000   


                然后我們就可以通過(guò)調(diào)用NtWriteVirtualMemory來(lái)寫(xiě)入我們的代碼。

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

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

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

                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)鏈接庫(kù)的列表。我們只對(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ì)所有我們想要掛鉤的庫(kù)我們需要獲得它們的ImageBase(比方調(diào)用GetModuleHandle或者LoadLibrary)。然后把這個(gè)ImageBase和InInitializationOrderModuleList的ImageBase比較。
                現(xiàn)在我們已經(jīng)為掛鉤準(zhǔn)備就緒。因?yàn)槲覀兪菕煦^正在運(yùn)行的進(jìn)程,所以可能我們正在改寫(xiě)代碼的同時(shí)代碼被執(zhí)行,這時(shí)就會(huì)導(dǎo)致錯(cuò)誤。所以首先我們就得停止目標(biāo)進(jìn)程里的所有線程。它的所有線程列表可以通過(guò)設(shè)置了SystemProcessAndThreadInformation的NtQuerySystemInformation來(lái)獲得。有關(guān)這個(gè)函數(shù)的描述參考第4節(jié)。但是還得加入SYSTEM_THREADS結(jié)構(gòu)的描述,用來(lái)保存線程的信息。

                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獲取它們的句柄,通過(guò)使用ClientId。

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

                我們需要的句柄被保存在ThreadHandle。我們需要把DesiredAccess設(shè)置為T(mén)HREAD_SUSPEND_RESUME。

                #define THREAD_SUSPEND_RESUME 2

                ThreadHandle用來(lái)調(diào)用NtSuspendThread。

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


                被掛起的進(jìn)程就可以被改寫(xiě)了。我們按照"掛鉤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)程開(kāi)始時(shí)肯定會(huì)調(diào)用的函數(shù)。因?yàn)樗邢到y(tǒng)中正在運(yùn)行的進(jìn)程都已經(jīng)被掛鉤,所以這種方法不會(huì)漏掉任何新的進(jìn)程。我們可以掛鉤NtCreateThread,但這不是最簡(jiǎn)單的方法。我們可以掛鉤NtResumeThread,因?yàn)樗彩敲慨?dāng)新進(jìn)程創(chuàng)建時(shí)被調(diào)用,它在NtCreateThread之后被調(diào)用。
                唯一的問(wèn)題在于,這個(gè)函數(shù)并不只在新進(jìn)程被創(chuàng)建時(shí)調(diào)用。但我們能很容易解決這點(diǎn)。NtQueryInformationThread能給我們指定線程是屬于哪個(gè)進(jìn)程的信息。最后我們要做的就是檢查進(jìn)程是否已經(jīng)被掛鉤了。這通過(guò)讀取我們要掛鉤的函數(shù)的開(kāi)始5個(gè)字節(jié)來(lái)完成。

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

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

                #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)在我們來(lái)感染新進(jìn)程。問(wèn)題就是新進(jìn)程的地址空間中只有ntdll.dll,其他的模塊在調(diào)用NtResumeThread之后被加載。有幾種方法可以解決這個(gè)問(wèn)題,比方說(shuō)我們可以掛鉤一個(gè)名為L(zhǎng)drInitializeThunk的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)完成。這就是為什么在我們掛鉤它之前它沒(méi)有機(jī)會(huì)調(diào)用任何一個(gè)被掛鉤過(guò)的函數(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ù)。但是來(lái)自其它模塊比如kernel32.dll或advapi32.dll的函數(shù)該怎么辦呢?還有一些進(jìn)程只有ntdll.dll,其他模塊都是在進(jìn)程被掛鉤之后在運(yùn)行過(guò)程中才被動(dòng)態(tài)加載的。這就是我們還得掛鉤加載新模塊的函數(shù)LdrLoadDll的原因。
               
                NTSTATUS LdrLoadDll(
                    PWSTR szcwPath,
                    PDWORD pdwLdrErr,     
                    PUNICODE_STRING pUniModuleName,
                    PHINSTANCE pResultInstance
                );

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

             

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

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

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

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

                #define STATUS_PARTIAL_COPY 0x8000000D

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

                為了使隱藏更完美,我們還要掛鉤NtQueryVirtualMemory。這個(gè)函數(shù)用來(lái)獲取虛擬內(nèi)存的信息。我們掛鉤它來(lá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ù)的類(lèi)別。我們對(duì)開(kāi)始的2種類(lèi)型感興趣。

                #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和它的類(lèi)型Type。空閑內(nèi)存的類(lèi)型是MEM_FREE。(區(qū)段對(duì)象就是文件映射對(duì)象,是可被映射到一個(gè)進(jìn)程的虛逆地址空間的對(duì)象)

                #define MEM_FREE 0x10000

                如果我們代碼之前一個(gè)區(qū)段的類(lèi)型是MEM_FREE我們就在它的RegionSize加上我們代碼的區(qū)段的大小。如果我們代碼之后的區(qū)段的類(lèi)型也是MEM_FREE那么就在之前區(qū)段的RegionSize上再加上之后的空閑區(qū)段的大小。
                如果我們代碼之前的區(qū)段是其它類(lèi)型,我們就對(duì)我們代碼的區(qū)段返回MEM_FREE。它的大小根據(jù)之后的區(qū)段來(lái)計(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. 句柄 ]=========================================

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


            =====[ 9.1 命名句柄并獲取類(lèi)型 ]===================================

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

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

                ObjectHandle是我們想要獲取有關(guān)信息的句柄,ObjectInformationClass是信息類(lèi)型,保存在以字節(jié)計(jì)算長(zhǎng)度為ObjectInformationLength的緩沖區(qū)ObjectInformation中。
                我們對(duì)OBJECT_INFORMATION_CLASS使用的類(lèi)是ObjectNameInformation和ObjectAllTypesInformation。ObjectNameInfromation類(lèi)在緩沖區(qū)中返回OBJECT_NAME_INFORMATION結(jié)構(gòu),而ObjectAllTypesInformation類(lèi)返回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決定類(lèi)型對(duì)象名字,類(lèi)型對(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)程中句柄的名字。這里有兩種命名的可能性。一是通過(guò)調(diào)用NtDuplicateObject把句柄拷貝到我們的進(jìn)程中然后命名它。這種方法對(duì)某些特殊類(lèi)型的句柄會(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. 端口 ]==========================================

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


                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è)套接字并開(kāi)始監(jiān)聽(tīng)時(shí),它就會(huì)有一個(gè)為它和打開(kāi)端口的打開(kāi)句柄。我們?cè)谙到y(tǒng)中枚舉所有的打開(kāi)句柄并通過(guò)NtDeviceIoControlFile把它們發(fā)送到一個(gè)特定的緩沖區(qū)中,來(lái)找出這個(gè)句柄是否是一個(gè)打開(kāi)端口的。這樣也能給我們有關(guān)端口的信息。因?yàn)榇蜷_(kāi)句柄太多了,所以我們只檢測(cè)類(lèi)型是File并且名字是\Device\Tcp或\Device\Udp的。打開(kāi)端口只有這種類(lèi)型和名字。

                如果你看一下iphlpapi.dll里函數(shù)的代碼,就會(huì)發(fā)現(xiàn)這些函數(shù)同樣調(diào)用NtDeviceIoControlFile并發(fā)送到一個(gè)特定緩沖區(qū)來(lái)獲得系統(tǒng)中所有打開(kāi)端口的列表。這意味著我們要想隱藏端口只需要掛鉤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ù),長(zhǎng)度為按字節(jié)計(jì)算的InputBufferLength,相似的還有OutputBuffer和OutputBufferLength。

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

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

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

            第一次調(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)在比較清晰地簡(jiǎn)要說(shuō)明一下:
               
                我們感興趣的調(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的大小分開(kāi)IoStatusBlock結(jié)構(gòu)里的Infotmation變量。隱藏其中一個(gè)ROW就會(huì)變的容易,只需要用之后的ROW改寫(xiě)他并刪掉最后一個(gè)ROW。不要忘了修改OutputBufferLength和IoStatusBlock。


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

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

                所以最先我們比較IoControlCode然后是類(lèi)型和句柄名字。如果這些都符合就接著比較輸入緩沖區(qū)長(zhǎng)度,它應(yīng)該和結(jié)構(gòu)TDI_CONNECTION_IN長(zhǎng)度一樣,為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è)打開(kāi)端口的方法請(qǐng)參考OpPorts的源代碼,在http://rookit.host.sk上可以找到。我們現(xiàn)在來(lái)隱藏指定端口。我們已經(jīng)比較過(guò)了InputBufferLength和IoControlCode,現(xiàn)在來(lái)比較RemoteAddressLength,對(duì)打開(kāi)端口來(lái)說(shuō)它總是3或4。最后要做的是比較OutputBufferBuffer里的ReceiveTsdus,用網(wǎng)絡(luò)上的端口和要隱藏的端口列表比較。區(qū)別TCP和UDP的做法是句柄的名字不一樣。在刪除了OutputBuffer、修改IoStatusBlock并返回STATUS_INVALID_ADDRESS后我們就已經(jīng)隱藏了這個(gè)端口了。

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

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

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

            后記:

                其實(shí)只要我們對(duì)Windows的內(nèi)核有一定程度的了解我們都知道單純靠掛鉤函數(shù)是不能真正做到隱藏的,這樣做只不過(guò)是欺騙操作系統(tǒng)的使用者,卻欺騙不了操作系統(tǒng)自己。線程要想被運(yùn)行就必須獲得時(shí)間片,將自己加入調(diào)度鏈表中,從而暴露自己。內(nèi)核維護(hù)一組被稱(chēng)為調(diào)度程序數(shù)據(jù)庫(kù)的數(shù)據(jù)結(jié)構(gòu)來(lái)做出線程調(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)的線程。可以很簡(jiǎn)單的枚舉這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 閱讀(1182) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): 轉(zhuǎn)的貼
            久久久久久久尹人综合网亚洲 | 久久久WWW免费人成精品| 久久精品国产亚洲一区二区| 热久久这里只有精品| 亚洲国产精品久久久久网站| 久久久国产精品| 久久香综合精品久久伊人| 香蕉久久av一区二区三区| 国产精品一久久香蕉国产线看| 精品视频久久久久| 久久经典免费视频| 精品国产福利久久久| 久久亚洲国产成人精品无码区| 亚洲AV日韩精品久久久久| 国产成人久久777777| 久久精品青青草原伊人| 久久亚洲中文字幕精品有坂深雪| 久久国产精品成人免费| 久久久无码精品亚洲日韩软件| 一本久久知道综合久久| 国内精品久久久久久麻豆| 亚洲AV无一区二区三区久久| 精品综合久久久久久88小说 | 久久久久亚洲av成人无码电影| 日韩欧美亚洲综合久久| 一本一道久久精品综合| 老男人久久青草av高清| 伊人久久大香线焦综合四虎| 亚洲国产精品无码久久一线| 久久久久亚洲精品无码网址| 2022年国产精品久久久久| 久久精品免费一区二区| 国产精品免费久久| 久久亚洲国产成人精品性色| 亚洲а∨天堂久久精品| 日韩亚洲欧美久久久www综合网 | 综合久久一区二区三区 | 亚洲av日韩精品久久久久久a| 久久精品无码专区免费| 国产成人精品久久一区二区三区 | 亚洲国产成人精品女人久久久|