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

            君子性非異也,善假于物也。

            如有恒,何須三更起,半夜眠;最怕莫,三天打魚(yú)兩天曬網(wǎng),竹籃打水一場(chǎng)空!
            posts - 31, comments - 23, trackbacks - 0, articles - 30
              C++博客 :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理
            “鼠標(biāo)屏幕取詞”技術(shù)是在電子字典中得到廣泛地應(yīng)用的,如四通利方和金山詞霸等軟件,這個(gè)技術(shù)看似簡(jiǎn)單,其實(shí)在WINDOWS系統(tǒng)中實(shí)現(xiàn)卻是非常復(fù)雜的,總的來(lái)說(shuō)有兩種實(shí)現(xiàn)方式:
            ??? 第一種:采用截獲對(duì)部分GDI的API調(diào)用來(lái)實(shí)現(xiàn),如TextOut,TextOutA等。
            ??? 第二種:對(duì)每個(gè)設(shè)備上下文(DC)做一分Copy,并跟蹤所有修改上下文(DC)的操作。?????
            ??? 第二種方法更強(qiáng)大,但兼容性不好,而第一種方法使用的截獲WindowsAPI的調(diào)用,這項(xiàng)技術(shù)的強(qiáng)大可能遠(yuǎn)遠(yuǎn)超出了您的想象,毫不夸張的說(shuō),利用WindowsAPI攔截技術(shù),你可以改造整個(gè)操作系統(tǒng),事實(shí)上很多外掛式Windows中文平臺(tái)就是這么實(shí)現(xiàn)的!而這項(xiàng)技術(shù)也正是這篇文章的主題。
            ??? 截WindowsAPI的調(diào)用,具體的說(shuō)來(lái)也可以分為兩種方法:
            ??? 第一種方法通過(guò)直接改寫WinAPI 在內(nèi)存中的映像,嵌入?yún)R編代碼,使之被調(diào)用時(shí)跳轉(zhuǎn)到指定的地址運(yùn)行來(lái)截獲;第二種方法則改寫IAT(Import Address Table 輸入地址表),重定向WinAPI函數(shù)的調(diào)用來(lái)實(shí)現(xiàn)對(duì)WinAPI的截獲。
            ??? 第一種方法的實(shí)現(xiàn)較為繁瑣,而且在Win95、98下面更有難度,這是因?yàn)殡m然微軟說(shuō)WIN16的API只是為了兼容性才保留下來(lái),程序員應(yīng)該盡可能地調(diào)用32位的API,實(shí)際上根本就不是這樣!WIN 9X內(nèi)部的大部分32位API經(jīng)過(guò)變換調(diào)用了同名的16位API,也就是說(shuō)我們需要在攔截的函數(shù)中嵌入16位匯編代碼!
            ??? 我們將要介紹的是第二種攔截方法,這種方法在Win95、98和NT下面運(yùn)行都比較穩(wěn)定,兼容性較好。由于需要用到關(guān)于Windows虛擬內(nèi)存的管理、打破進(jìn)程邊界墻、向應(yīng)用程序的進(jìn)程空間中注入代碼、PE(Portable Executable)文件格式和IAT(輸入地址表)等較底層的知識(shí),所以我們先對(duì)涉及到的這些知識(shí)大概地做一個(gè)介紹,最后會(huì)給出攔截部分的關(guān)鍵代碼。
            ????? 先說(shuō)Windows虛擬內(nèi)存的管理。Windows9X給每一個(gè)進(jìn)程分配了4GB的地址空間,對(duì)于NT來(lái)說(shuō),這個(gè)數(shù)字是2GB,系統(tǒng)保留了2GB到 4GB之間的地址空間禁止進(jìn)程訪問(wèn),而在Win9X中,2GB到4GB這部分虛擬地址空間實(shí)際上是由所有的WIN32進(jìn)程所共享的,這部分地址空間加載了共享Win32 DLL、內(nèi)存映射文件和VXD、內(nèi)存管理器和文件系統(tǒng)碼,Win9X中這部分對(duì)于每一個(gè)進(jìn)程都是可見(jiàn)的,這也是Win9X操作系統(tǒng)不夠健壯的原因。Win9X中為16位操作系統(tǒng)保留了0到4MB的地址空間,而在4MB到2GB之間也就是Win32進(jìn)程私有的地址空間,由于 每個(gè)進(jìn)程的地址空間都是相對(duì)獨(dú)立的,也就是說(shuō),如果程序想截獲其它進(jìn)程中的API調(diào)用,就必須打破進(jìn)程邊界墻,向其它的進(jìn)程中注入截獲API調(diào)用的代碼,這項(xiàng)工作我們交給鉤子函數(shù)(SetWindowsHookEx)來(lái)完成,關(guān)于如何創(chuàng)建一個(gè)包含系統(tǒng)鉤子的動(dòng)態(tài)鏈接庫(kù),《電腦高手雜志》在第?期已經(jīng)有過(guò)專題介紹了,這里就不贅述了。所有系統(tǒng)鉤子的函數(shù)必須要在動(dòng)態(tài)庫(kù)里,這樣的話,當(dāng)進(jìn)程隱式或顯式調(diào)用一個(gè)動(dòng)態(tài)庫(kù)里的函數(shù)時(shí),系統(tǒng)會(huì)把這個(gè)動(dòng)態(tài)庫(kù)映射到這個(gè)進(jìn)程的虛擬地址空間里,這使得DLL成為進(jìn)程的一部分,以這個(gè)進(jìn)程的身份執(zhí)行,使用這個(gè)進(jìn)程的堆棧,也就是說(shuō)動(dòng)態(tài)鏈接庫(kù)中的代碼被鉤子函數(shù)注入了其它GUI進(jìn)程的地址空間(非GUI進(jìn)程,鉤子函數(shù)就無(wú)能為力了),
            當(dāng)包含鉤子的DLL注入其它進(jìn)程后,就可以取得映射到這個(gè)進(jìn)程虛擬內(nèi)存里的各個(gè)模塊(EXE和DLL)的基地址,如:
            HMODULE hmodule=GetModuleHandle(“Mypro.exe”);
            在MFC程序中,我們可以用AfxGetInstanceHandle()函數(shù)來(lái)得到模塊的基地址。EXE和DLL被映射到虛擬內(nèi)存空間的什么地方是由它們的基地址決定的。它們的基地址是在鏈接時(shí)由鏈接器決定的。當(dāng)你新建一個(gè)Win32工程時(shí),VC++鏈接器使用缺省的基地址0x00400000??梢酝ㄟ^(guò)鏈接器的BASE選項(xiàng)改變模塊的基地址。EXE通常被映射到虛擬內(nèi)存的0x00400000處,DLL也隨之有不同的基地址,通常被映射到不同進(jìn)程
            的相同的虛擬地址空間處。
            系統(tǒng)將EXE和DLL原封不動(dòng)映射到虛擬內(nèi)存空間中,它們?cè)趦?nèi)存中的結(jié)構(gòu)與磁盤上的靜態(tài)文件結(jié)構(gòu)是一樣的。即PE (Portable Executable) 文件格式。我們得到了進(jìn)程模塊的基地址以后,就可以根據(jù)PE文件的格式窮舉這個(gè)模塊的IMAGE_IMPORT_DESCRIPTOR數(shù)組,看看進(jìn)程空間中是否引入了我們需要截獲的函數(shù)所在的動(dòng)態(tài)鏈接庫(kù),比如需要截獲“TextOutA”,就必須檢查“Gdi32.dll”是否被引入了。說(shuō)到這里,我們有必要介紹一下PE文件的格式,如右圖,這是PE文件格式的大致框圖,最前面是文件頭,我們不必理會(huì),從PE File Optional Header后面開(kāi)始,就是文件中各個(gè)段的說(shuō)明,說(shuō)明后面才是真正的段數(shù)據(jù),而實(shí)際上我們關(guān)心的只有一個(gè)段,那就是“.idata”段,這個(gè)段中包含了所有的引入函數(shù)信息,還有IAT(Import Address Table)的RVA(Relative Virtual Address)地址。
            說(shuō)到這里,截獲WindowsAPI的整個(gè)原理就要真相大白了。實(shí)際上所有進(jìn)程對(duì)給定的API函數(shù)的調(diào)用總是通過(guò)PE文件的一個(gè)地方來(lái)轉(zhuǎn)移的,這就是一個(gè)該模塊(可以是EXE或DLL)的“.idata”段中的IAT輸入地址表(Import Address Table)。在那里有所有本模塊調(diào)用的其它DLL的函數(shù)名及地址。對(duì)其它DLL的函數(shù)調(diào)用實(shí)際上只是跳轉(zhuǎn)到輸入地址表,由輸入地址表再跳轉(zhuǎn)到DLL真正的函數(shù)入口。

            具體來(lái)說(shuō),我們將通過(guò)IMAGE_IMPORT_DESCRIPTOR數(shù)組來(lái)訪問(wèn)“.idata”段中引入的DLL的信息,然后通過(guò)IMAGE_THUNK_DATA數(shù)組來(lái)針對(duì)一個(gè)被引入的DLL訪問(wèn)該DLL中被引入的每個(gè)函數(shù)的信息,找到我們需要截獲的函數(shù)的跳轉(zhuǎn)地址,然后改成我們自己的函數(shù)的地址……具體的做法在后面的關(guān)鍵代碼中會(huì)有詳細(xì)的講解。
            ?? 講了這么多原理,現(xiàn)在讓我們回到“鼠標(biāo)屏幕取詞”的專題上來(lái)。除了API函數(shù)的截獲,要實(shí)現(xiàn)“鼠標(biāo)屏幕取詞”,還需要做一些其它的工作,簡(jiǎn)單的說(shuō)來(lái),可以把一個(gè)完整的取詞過(guò)程歸納成以下幾個(gè)步驟:
            1. 安裝鼠標(biāo)鉤子,通過(guò)鉤子函數(shù)獲得鼠標(biāo)消息。
            使用到的API函數(shù):SetWindowsHookEx
            2. 得到鼠標(biāo)的當(dāng)前位置,向鼠標(biāo)下的窗口發(fā)重畫(huà)消息,讓它調(diào)用系統(tǒng)函數(shù)重畫(huà)窗口。
            ???? 使用到的API函數(shù):WindowFromPoint,ScreenToClient,InvalidateRect
            3. 截獲對(duì)系統(tǒng)函數(shù)的調(diào)用,取得參數(shù),也就是我們要取的詞。
            對(duì)于大多數(shù)的Windows應(yīng)用程序來(lái)說(shuō),如果要取詞,我們需要截獲的是“Gdi32.dll”中的“TextOutA”函數(shù)。
            我們先仿照TextOutA函數(shù)寫一個(gè)自己的MyTextOutA函數(shù),如:
            BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,int cbString)
            {
            ?????? // 這里進(jìn)行輸出lpszString的處理
            ?????????? // 然后調(diào)用正版的TextOutA函數(shù)
            }
            把這個(gè)函數(shù)放在安裝了鉤子的動(dòng)態(tài)連接庫(kù)中,然后調(diào)用我們最后給出的HookImportFunction函數(shù)來(lái)截獲進(jìn)程
            對(duì)TextOutA函數(shù)的調(diào)用,跳轉(zhuǎn)到我們的MyTextOutA函數(shù),完成對(duì)輸出字符串的捕捉。HookImportFunction的
            用法:
            ?HOOKFUNCDESC hd;
            ?PROC???????? pOrigFuns;
            ?hd.szFunc=TextOutA;
            ?hd.pProc=(PROC)MyTextOutA;
            ?HookImportFunction (AfxGetInstanceHandle(),gdi32.dll,&hd,pOrigFuns);
            下面給出了HookImportFunction的源代碼,相信詳盡的注釋一定不會(huì)讓您覺(jué)得理解截獲到底是怎么實(shí)現(xiàn)的
            很難,Ok,Let’s Go:

            ///////////////////////////////////////////// Begin ///////////////////////////////////////////////////////////////
            #include <crtdbg.h>

            // 這里定義了一個(gè)產(chǎn)生指針的宏
            #define MakePtr(cast, ptr, AddValue) (cast)((DWORD)(ptr)+(DWORD)(AddValue))

            // 定義了HOOKFUNCDESC結(jié)構(gòu),我們用這個(gè)結(jié)構(gòu)作為參數(shù)傳給HookImportFunction函數(shù)
            typedef struct tag_HOOKFUNCDESC
            {
            ? LPCSTR szFunc; // The name of the function to hook.
            ? PROC pProc;??? // The procedure to blast in.
            } HOOKFUNCDESC , * LPHOOKFUNCDESC;

            // 這個(gè)函數(shù)監(jiān)測(cè)當(dāng)前系統(tǒng)是否是WindowNT
            BOOL IsNT();

            // 這個(gè)函數(shù)得到hModule -- 即我們需要截獲的函數(shù)所在的DLL模塊的引入描述符(import descriptor)
            PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule);

            // 我們的主函數(shù)
            BOOL HookImportFunction(HMODULE hModule, LPCSTR szImportModule,
            ???????????????????????? LPHOOKFUNCDESC paHookFunc, PROC* paOrigFuncs)
            {
            /////////////////////// 下面的代碼檢測(cè)參數(shù)的有效性 ////////////////////////////
            ?_ASSERT(szImportModule);
            ?_ASSERT(!IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC)));
            #ifdef _DEBUG
            ?if (paOrigFuncs) _ASSERT(!IsBadWritePtr(paOrigFuncs, sizeof(PROC)));
            ?_ASSERT(paHookFunc.szFunc);
            ?_ASSERT(*paHookFunc.szFunc != \0);
            ??????? _ASSERT(!IsBadCodePtr(paHookFunc.pProc));
            #endif
            ?if ((szImportModule == NULL) || (IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC))))
            ?{
            ??_ASSERT(FALSE);
            ??SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
            ??return FALSE;
            ?}
            //////////////////////////////////////////////////////////////////////////////

            ?// 監(jiān)測(cè)當(dāng)前模塊是否是在2GB虛擬內(nèi)存空間之上
            ?// 這部分的地址內(nèi)存是屬于Win32進(jìn)程共享的
            ?if (!IsNT() && ((DWORD)hModule >= 0x80000000))
            ?{
            ??_ASSERT(FALSE);
            ??SetLastErrorEx(ERROR_INVALID_HANDLE, SLE_ERROR);
            ??return FALSE;
            ?}
            ??? ?// 清零
            ?if (paOrigFuncs) memset(paOrigFuncs, NULL, sizeof(PROC));

            ?// 調(diào)用GetNamedImportDescriptor()函數(shù),來(lái)得到hModule -- 即我們需要
            ?// 截獲的函數(shù)所在的DLL模塊的引入描述符(import descriptor)
            ?PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hModule, szImportModule);
            ?if (pImportDesc == NULL)
            ?return FALSE; // 若為空,則模塊未被當(dāng)前進(jìn)程所引入

            ?//? 從DLL模塊中得到原始的THUNK信息,因?yàn)閜ImportDesc->FirstThunk數(shù)組中的原始信息已經(jīng)
            ?//? 在應(yīng)用程序引入該DLL時(shí)覆蓋上了所有的引入信息,所以我們需要通過(guò)取得pImportDesc->OriginalFirstThunk
            ?//? 指針來(lái)訪問(wèn)引入函數(shù)名等信息
            ?PIMAGE_THUNK_DATA pOrigThunk = MakePtr(PIMAGE_THUNK_DATA, hModule,
            ?????????????????????????????????????????????? pImportDesc->OriginalFirstThunk);

            ?//? 從pImportDesc->FirstThunk得到IMAGE_THUNK_DATA數(shù)組的指針,由于這里在DLL被引入時(shí)已經(jīng)填充了
            ?//? 所有的引入信息,所以真正的截獲實(shí)際上正是在這里進(jìn)行的
            ?PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hModule, pImportDesc->FirstThunk);

            ?//? 窮舉IMAGE_THUNK_DATA數(shù)組,尋找我們需要截獲的函數(shù),這是最關(guān)鍵的部分!
            ?while (pOrigThunk->u1.Function)
            ?{
            ??// 只尋找那些按函數(shù)名而不是序號(hào)引入的函數(shù)
            ??if (IMAGE_ORDINAL_FLAG != (pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG))
            ??{
            ???// 得到引入函數(shù)的函數(shù)名
            ???PIMAGE_IMPORT_BY_NAME pByName = MakePtr(PIMAGE_IMPORT_BY_NAME, hModule,
            ?????????????? pOrigThunk->u1.AddressOfData);

            ???// 如果函數(shù)名以NULL開(kāi)始,跳過(guò),繼續(xù)下一個(gè)函數(shù)??
            ???if (\0 == pByName->Name[0])
            ????continue;

            ???// bDoHook用來(lái)檢查是否截獲成功
            ???BOOL bDoHook = FALSE;

            ???// 檢查是否當(dāng)前函數(shù)是我們需要截獲的函數(shù)
            ???if ((paHookFunc.szFunc[0] == pByName->Name[0]) &&
            ????(strcmpi(paHookFunc.szFunc, (char*)pByName->Name) == 0))
            ???{
            ????// 找到了!
            ????if (paHookFunc.pProc)
            ????bDoHook = TRUE;
            ???}
            ???if (bDoHook)
            ???{
            ????// 我們已經(jīng)找到了所要截獲的函數(shù),那么就開(kāi)始動(dòng)手吧
            ????// 首先要做的是改變這一塊虛擬內(nèi)存的內(nèi)存保護(hù)狀態(tài),讓我們可以自由存取
            ????MEMORY_BASIC_INFORMATION mbi_thunk;
            ????VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
            ????_ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,
            ??????????????????????? PAGE_READWRITE, &mbi_thunk.Protect));

            ????// 保存我們所要截獲的函數(shù)的正確跳轉(zhuǎn)地址
            ????if (paOrigFuncs)
            ????? paOrigFuncs = (PROC)pRealThunk->u1.Function;

            ????// 將IMAGE_THUNK_DATA數(shù)組中的函數(shù)跳轉(zhuǎn)地址改寫為我們自己的函數(shù)地址!
            ????// 以后所有進(jìn)程對(duì)這個(gè)系統(tǒng)函數(shù)的所有調(diào)用都將成為對(duì)我們自己編寫的函數(shù)的調(diào)用
            ????pRealThunk->u1.Function = (PDWORD)paHookFunc.pProc;

            ????// 操作完畢!將這一塊虛擬內(nèi)存改回原來(lái)的保護(hù)狀態(tài)
            ????DWORD dwOldProtect;
            ????_ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,
            ??????????????????????? mbi_thunk.Protect, &dwOldProtect));
            ????SetLastError(ERROR_SUCCESS);
            ????return TRUE;
            ???}
            ??}
            ??// 訪問(wèn)IMAGE_THUNK_DATA數(shù)組中的下一個(gè)元素
            ??pOrigThunk++;
            ??pRealThunk++;
            ?}
            ?return TRUE;
            }

            // GetNamedImportDescriptor函數(shù)的實(shí)現(xiàn)
            PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule)
            {
            ?// 檢測(cè)參數(shù)
            ?_ASSERT(szImportModule);
            ?_ASSERT(hModule);
            ?if ((szImportModule == NULL) || (hModule == NULL))
            ?{
            ??_ASSERT(FALSE);
            ??SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
            ??return NULL;
            ?}

            ?// 得到Dos文件頭
            ?PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;

            ?// 檢測(cè)是否MZ文件頭
            ?if (IsBadReadPtr(pDOSHeader, sizeof(IMAGE_DOS_HEADER)) ||
            ??(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE))
            ?{
            ??_ASSERT(FALSE);
            ??SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
            ??return NULL;
            ?}

            ?// 取得PE文件頭
            ?PIMAGE_NT_HEADERS pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDOSHeader, pDOSHeader->e_lfanew);

            ?// 檢測(cè)是否PE映像文件
            ?if (IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) ||
            ?? (pNTHeader->Signature != IMAGE_NT_SIGNATURE))
            ?{
            ??_ASSERT(FALSE);
            ??SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
            ??return NULL;
            ?}

            ?// 檢查PE文件的引入段(即 .idata section)
            ?if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)
            ??return NULL;

            ?// 得到引入段(即 .idata section)的指針
            ?PIMAGE_IMPORT_DESCRIPTOR pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDOSHeader,
            ??pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

            ?// 窮舉PIMAGE_IMPORT_DESCRIPTOR數(shù)組尋找我們需要截獲的函數(shù)所在的模塊
            ?while (pImportDesc->Name)
            ?{
            ??PSTR szCurrMod = MakePtr(PSTR, pDOSHeader, pImportDesc->Name);
            ??if (stricmp(szCurrMod, szImportModule) == 0)
            ????? break; // 找到!中斷循環(huán)
            ??// 下一個(gè)元素
            ??pImportDesc++;
            ?}

            ?// 如果沒(méi)有找到,說(shuō)明我們尋找的模塊沒(méi)有被當(dāng)前的進(jìn)程所引入!
            ?if (pImportDesc->Name == NULL)
            ??return NULL;

            ?// 返回函數(shù)所找到的模塊描述符(import descriptor)
            ?return pImportDesc;
            }

            // IsNT()函數(shù)的實(shí)現(xiàn)
            BOOL IsNT()
            {
            ?OSVERSIONINFO stOSVI;
            ?memset(&stOSVI, NULL, sizeof(OSVERSIONINFO));
            ?stOSVI.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
            ?BOOL bRet = GetVersionEx(&stOSVI);
            ?_ASSERT(TRUE == bRet);
            ?if (FALSE == bRet) return FALSE;
            ?return (VER_PLATFORM_WIN32_NT == stOSVI.dwPlatformId);
            }
            /////////////////////////////////////////////// End //////////////////////////////////////////////////////////////////////

            ?? 不知道在這篇文章問(wèn)世之前,有多少朋友嘗試過(guò)去實(shí)現(xiàn)“鼠標(biāo)屏幕取詞”這項(xiàng)充滿了挑戰(zhàn)的技術(shù),也只有嘗試過(guò)的朋友才能體會(huì)到其間的不易,尤其在探索API函數(shù)的截獲時(shí),手頭的幾篇資料沒(méi)有一篇是涉及到關(guān)鍵代碼的,重要的地方都是一筆代過(guò),MSDN更是顯得蒼白而無(wú)力,也不知道除了IMAGE_IMPORT_DESCRIPTOR和IMAGE_THUNK_DATA,微軟還隱藏了多少秘密,好在硬著頭皮還是把它給攻克了,希望這篇文章對(duì)大家能有所幫助。

            国产午夜精品理论片久久| 97香蕉久久夜色精品国产 | 日韩精品久久久久久免费| 久久午夜综合久久| 久久精品国产亚洲AV无码娇色 | 久久久SS麻豆欧美国产日韩| 国产美女久久久| 日韩电影久久久被窝网| 久久久www免费人成精品| 国内精品久久久久久久97牛牛| 国产精品久久久久影视不卡| 久久综合给久久狠狠97色| 好久久免费视频高清| 久久免费视频一区| 伊人丁香狠狠色综合久久| 欧美与黑人午夜性猛交久久久| 97久久婷婷五月综合色d啪蜜芽 | 精品久久人人爽天天玩人人妻| 99久久er这里只有精品18| 久久精品国产一区二区三区不卡| 久久99精品国产麻豆宅宅| 亚洲国产精品久久久久久| 一本久久a久久精品vr综合| 一本久久a久久精品综合夜夜| 伊人久久亚洲综合影院| 久久综合中文字幕| 久久精品国产精品亚洲毛片| 国产三级观看久久| AAA级久久久精品无码区| 午夜欧美精品久久久久久久| 99久久成人国产精品免费| 中文字幕亚洲综合久久菠萝蜜| 国产精品久久网| 久久国产精品久久久| 久久Av无码精品人妻系列| 亚洲国产婷婷香蕉久久久久久| 亚洲国产成人久久精品动漫| 色婷婷久久综合中文久久一本| 一级做a爱片久久毛片| 久久本道伊人久久| 国产亚洲精午夜久久久久久|