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

            S.l.e!ep.¢%

            像打了激速一樣,以四倍的速度運轉,開心的工作
            簡單、開放、平等的公司文化;尊重個性、自由與個人價值;
            posts - 1098, comments - 335, trackbacks - 0, articles - 1
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            ? 文件過濾系統驅動開發 Filemon 學習筆記

            ? ??????????????????????????

            ?

            WINDOWS 文件過濾系統驅動開發,可用于硬盤還原,防病毒,文件安全防護,文件加密等諸多領域。而掌握核心層的理論及實踐,對于成為一名優秀的開發人員不可或缺。

            ?

            WINDOWS 文件過濾系統驅動開發的兩個經典例子, Filemon SFilter ,初學者在經過一定的理論積累后,對此兩個例子代碼的研究分析,會是步入驅動開發殿堂的重要一步,相信一定的理論積累以及貫穿剖析理解此兩個例程后,就有能力開始進行文件過濾系統驅動開發的實際工作了。

            對于 SFilter 例子的講解,楚狂人的教程已經比較流行,而 Filemon 例子也許因框架結構相對明晰,易于剖析理解,無人貼出教程,本人在剖析 Filemon 的過程中積累的一些筆記資料,陸續貼出希望對初學者有所幫助,并通過和大家的交流而互相提高。

            ?

            ???????????????????? Filemon 學習筆記 第一篇:

            ?

            ?

            Filemon 的大致架構為,在此驅動程序中,創建了兩類設備對象。

            一類設備對象用于和 Filemon 對應的 exe 程序通信,以接收用戶輸入信息,比如掛接或監控哪個分區,是否要掛接,是否要監控,監控何種操作等。此設備對象只創建了一個,在驅動程序的入口函數 DriverEntry 中。此類設備對象一般稱為控制設備對象,并有名字,以方便應用層與其通信操作。

            第二類設備對象用于掛接到所須監控的分區,比如 c :, d :或 e :, f :,以便攔截到引應用層對該分區所執行的讀,寫等操作。此類設備對象為安全起見,一般不予命名,可根據須監控多少分區而創建一個或多個。

            ?

            驅動入口函數大致如下

            ?

            NTSTATUS

            DriverEntry(

            ??? IN PDRIVER_OBJECT DriverObject,

            ??? IN PUNICODE_STRING RegistryPath

            ??? )

            {

            ??? NTSTATUS??????????????? ntStatus;

            ??? PDEVICE_OBJECT????????? guiDevice;

            ??? WCHAR?????????????????? deviceNameBuffer[]? = L"\\Device\\Filemon";

            ??? UNICODE_STRING????????? deviceNameUnicodeString;

            ??? WCHAR?????????????????? deviceLinkBuffer[]? = L"\\DosDevices\\Filemon";

            ??? UNICODE_STRING????????? deviceLinkUnicodeString;

            ??? ULONG?????????????????? i;

            ?

            ??? DbgPrint (("Filemon.SYS: entering DriverEntry\n"));

            ??? FilemonDriver = DriverObject;

            ?

            ??? //???

            ??? // Setup the device name

            ??? //???

            ??? RtlInitUnicodeString (&deviceNameUnicodeString,

            ????????????????????????? deviceNameBuffer );

            ?

            ??? //

            ??? // Create the device used for GUI communications

            ??? // 此設備對象用來和用戶交互信息

            ??? ntStatus = IoCreateDevice ( DriverObject,

            ??????????????????????????????? sizeof(HOOK_EXTENSION),

            ??????????????????????????????? &deviceNameUnicodeString,

            ??????????????????????????????? FILE_DEVICE_FILEMON,

            ??????????????????????????????? 0,

            ?????????????????? ?????????????TRUE,

            ??????????????????????????????? &guiDevice );

            ?

            ??? //

            ??? // If successful, make a symbolic link that allows for the device

            ??? // object's access from Win32 programs

            ??? //

            ??? if(NT_SUCCESS(ntStatus)) {

            ?

            ??????? //

            ??????? // Mark this as our GUI device

            ??????? //

            ??????? ((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE;

            ?

            ??????? //

            ??????? // Create a symbolic link that the GUI can specify to gain access

            ??????? // to this driver/device

            ??????? //

            ??????? RtlInitUnicodeString (&deviceLinkUnicodeString,

            ????????????????????????????? deviceLinkBuffer );

            ??????? ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,

            ???????????????????????????????????????? &deviceNameUnicodeString );

            ??????? if(!NT_SUCCESS(ntStatus)) {

            ?

            ??????????? DbgPrint (("Filemon.SYS: IoCreateSymbolicLink failed\n"));

            ??????????? IoDeleteDevice( guiDevice );

            ??????????? return ntStatus;???????????

            ??????? }

            ?

            ??????? //

            ??????? // Create dispatch points for all routines that must be handled.

            ?? ?????// All entry points are registered since we might filter a

            ??????? // file system that processes all of them.

            ??????? //

            ??????? for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) {

            ?

            ??????????? DriverObject->MajorFunction[i] = FilemonDispatch;

            ??????? }

            #if DBG???????

            ??????? //

            ??????? // Driver unload is only set if we are debugging Filemon. This is

            ??????? // because unloading a filter is not really safe - threads could

            ??????? // be in our fastio routines (or about to enter them), for example,

            ?? ?????// and there is no way to tell. When debugging, we can risk the

            ??????? // occasional unload crash as a trade-off for not having to

            ??????? // reboot as often.

            ??????? //

            ??????? // DriverObject->DriverUnload = FilemonUnload;

            #endif // DBG

            ?

            ??????? //

            ??????? // Set up the Fast I/O dispatch table

            ??????? //

            ??????? DriverObject->FastIoDispatch = &FastIOHook;

            ?

            ??? } else {

            ?

            ??????? //

            ??????? // If something went wrong, cleanup the device object and don't load

            ??????? //

            ??????? DbgPrint(("Filemon: Failed to create our device!\n"));

            ??????? return ntStatus;

            ??? }

            ?

            ??? //

            ??? // Initialize the name hash table

            ??? //

            ??? for(i = 0; i < NUMHASH; i++ ) HashTable[i] = NULL;

            ?

            ??? //

            ??? // Find the process name offset

            ??? //

            ??? ProcessNameOffset = FilemonGetProcessNameOffset();// 為了得到當前進程名字

            ?

            ??? //

            ??? // Initialize the synchronization objects

            ??? //

            #if DBG

            ??? KeInitializeSpinLock( &CountMutex );

            #endif

            ??? ExInitializeFastMutex( &LogMutex );

            ??? ExInitializeResourceLite( &FilterResource );

            ??? ExInitializeResourceLite( &HashResource );

            ?

            ??? //

            ??? // Initialize a lookaside for file names

            ??? //

            ??? ExInitializeNPagedLookasideList( &FullPathLookaside, NULL, NULL,

            ??????????????????????????? ???? 0, MAXPATHLEN, 'mliF', 256 );

            ?

            ??? //

            ??? // Allocate the first output buffer

            ??? //

            ? ??CurrentLog = ExAllocatePool( NonPagedPool, sizeof(*CurrentLog) );

            ??? if( !CurrentLog ) {

            ?

            ??????? //

            ??????? // Oops - we can't do anything without at least one buffer

            ??????? //

            ??????? IoDeleteSymbolicLink( &deviceLinkUnicodeString );

            ??????? IoDeleteDevice( guiDevice );

            ??????? return STATUS_INSUFFICIENT_RESOURCES;

            ??? }

            ?

            ??? //

            ??? // Set the buffer pointer to the start of the buffer just allocated

            ??? //

            ??? CurrentLog->Len? = 0;

            ??? CurrentLog->Next = NULL;

            ??? NumLog = 1;

            ?

            ??? return STATUS_SUCCESS;

            }

            ?

            在此驅動入口點函數中,主要做了生成新的設備對象,此設備對象用來和應用層信息交互,比如應用層向驅動傳遞需要掛接或者監控的分區盤符,或者是否掛接盤符,是否監控操作等。

            上面創建設備對象的代碼為:

            ntStatus = IoCreateDevice ( DriverObject,

            ??????????????????????????????? sizeof(HOOK_EXTENSION),

            ??????????????????????????????? &deviceNameUnicodeString,

            ??? ????????????????????????????FILE_DEVICE_FILEMON,

            ??????????????????????????????? 0,

            ??????????????????????????????? TRUE,

            ??????????????????????????????? &guiDevice );

            ?

            ??? //

            ??? // If successful, make a symbolic link that allows for the device

            ??? // object's access from Win32 programs

            ??? //

            ??? if(NT_SUCCESS(ntStatus)) {

            ?

            ??????? //

            ??????? // Mark this as our GUI device

            ??????? //

            ??????? ((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE;

            ?

            ??????? //

            ??????? // Create a symbolic link that the GUI can specify to gain access

            ??????? // to this driver/device

            ??????? //

            ??????? RtlInitUnicodeString (&deviceLinkUnicodeString,

            ????????????????????????????? deviceLinkBuffer );

            ??????? ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,

            ???????????????????????????????????????? &deviceNameUnicodeString );

            ??????? if(!NT_SUCCESS(ntStatus)) {

            ?

            ??????????? DbgPrint (("Filemon.SYS: IoCreateSymbolicLink failed\n"));

            ??????????? IoDeleteDevice( guiDevice );

            ??????????? return ntStatus;???????? ???

            ??????? }

            上面代碼完成的功能為創建了用于與應用層交互的控制設備對象,名字在參數 &deviceNameUnicodeString, 中。設備對象創建成功后又調用 IoCreateSymbolicLink 創建了一個符號連接,以便于應用層交互。

            ?

            在入口點函數 DriverEntry 代碼中,還有一處代碼:

            ProcessNameOffset = FilemonGetProcessNameOffset();// 為了得到當前進程名字。

            此函數體如下:

            ULONG

            FilemonGetProcessNameOffset(

            ??? VOID

            ??? )

            {

            ??? PEPROCESS?????? curproc;

            ??? int???????????? i;

            ?

            ??? curproc = PsGetCurrentProcess();// 調用 PsGetCurrentProcess 取得 KPEB 基址

            ?

            ??? // 然后搜索 KPEB ,得到 ProcessName 相對 KPEB 的偏移量

            ??? // Scan for 12KB, hoping the KPEB never grows that big!

            ??? //

            ? ??for( i = 0; i < 3*PAGE_SIZE; i++ ) {

            ????

            ??????? if( !strncmp( SYSNAME, (PCHAR) curproc + i, strlen(SYSNAME) )) {

            ?

            ??????????? return i;

            ??????? }

            ??? }

            ?

            ??? //

            ??? // Name not found - oh, well

            ??? //

            ??? return 0;

            ?

            這個函數通過查找 KPEB (Kernel?Process?Environment?Block) ,取得進程名, GetProcessNameOffset 主要是調用 PsGetCurrentProcess 取得 KPEB 基址,然后搜索 KPEB ,得到 ProcessName 相對 KPEB 的偏移量,存放在全局變量 ProcessNameOffset 中,得到此偏移量的作用是:無論當前進程為哪個,其名字在 KPEB 中的偏移量不變,所以都可以通過此偏移量得到。而在入口點函數 DriverEntry 執行時,當前進程必為系統進程,所以在此函數中方便地根據系統進程名 SYSNAME #define SYSNAME? ??"System" )得到此偏移量。

            ?

            分發函數剖析:

            在入口點函數中,通過代碼:

            for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) {

            ?

            ??????????? DriverObject->MajorFunction[i] = FilemonDispatch;

            ??????? }

            簡單地把各個分發例程設置成了 FilemonDispatch; 然后我們追蹤其函數體:

            ?

            NTSTATUS

            FilemonDispatch(

            ??? IN PDEVICE_OBJECT DeviceObject,

            ??? IN PIRP Irp

            ??? )

            {

            ??? //

            ??? // Determine if its a request from the GUI to us, or one that is

            ??? // directed at a file system driver that we've hooked

            ??? //

            ??? if( ((PHOOK_EXTENSION) DeviceObject->DeviceExtension)->Type == GUIINTERFACE ) {

            ?

            ??????? return FilemonDeviceRoutine( DeviceObject, Irp );

            ?

            ??? } else {

            ?

            ??????? return FilemonHookRoutine( DeviceObject, Irp );

            ??? }

            }

            ?

            函數體先判斷需要處理 IRP 包的設備對象的類型,看是屬于控制設備對象,還是屬于用于掛接并監控文件讀寫操作的過濾設備對象。如果是屬于后者 則進入: FilemonHookRoutine( DeviceObject, Irp )

            此函數是攔截文件操作的中心,在其中獲得了被操作的文件名字,并且根據操作類型,在

            switch( currentIrpStack->MajorFunction ) {

            }

            中針對不同的 MajorFunction ,打印出相關操作信息。

            因此函數體太長 不再全部列出。

            其函數體總體框架為:得到被操作的文件名字,打印相關操作信息,然后下發 IRP 到底層驅動。

            在下發 IRP 到底層驅動處理前,本層驅動必須負責設置下層 IO 堆棧的內容。這樣下一層驅動調用 IoGetCurrentIrpStackLocation() 時能得到相應的數據。

            設置下層 IO 堆棧的內容,一般用兩個函數來實現:

            IoCopyCurrentIrpStackLocationToNext( Irp )

            此函數一般用在本驅動設置了完成例程時調用,把本層 IO _STACK_LOCATION 中的參數 copy 到下層,但與完成例程相關的參數信息例外。因為本驅動設置的完成例程只對本層驅動有效。

            IoSkipCurrentIrpStackLocationToNext(Irp)

            此函數的作用是:直接把本層驅動 IO 堆棧的內容設置為下層驅動 IO 堆棧指針的指向。因兩層驅動 IO 堆棧的內容完全一致,省卻 copy 過程。

            ?

            而在 Filemon 的處理中,它用了一個特別的辦法,沒有調用此兩個函數, FilemonHookRoutine 函數體里面有三句代碼:

            ?

            ??? PIO_STACK_LOCATION? currentIrpStack = IoGetCurrentIrpStackLocation(Irp);

            PIO_STACK_LOCATION? nextIrpStack??? = IoGetNextIrpStackLocation(Irp);

            ?

            *nextIrpStack = *currentIrpStack;// 此步設置了下層驅動的 IO_STACK_LOCATION

            直接設置了下層驅動 IO 堆棧的值。

            ?

            FilemonHookRoutine 函數里,用一個宏實現了復雜的獲得攔截到的被操作文件的名字:

            ?

            if( FilterOn && hookExt->Hooked ) {

            ?

            ? ??????GETPATHNAME( createPath );

            }

            ?

            GETPATHNAME( createPath ) 宏展開為:

            ?

            #define GETPATHNAME(_IsCreate)????????????????????????????????????????????????? \

            ??????? fullPathName = ExAllocateFromNPagedLookasideList( &FullPathLookaside ); \

            ??????? if( fullPathName ) {??????????????????????????????????????????????????? \

            ??????????? FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName ); \

            ??????? } else {??????????????????????????????????????????????????????????????? \

            ??????????? fullPathName = InsufficientResources;?????????????????????????????? \

            ??????? }?????

            ?

            在函數: FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName ) 中實現了獲得被操作的文件名字,此函數代碼較多,判斷條件復雜,理解起來比較麻煩,下面重點講解。

            對函數 FilemonGetFullPath 的理解關鍵在于理順結構,

            此函數的功能就是獲得文件名字,獲得文件名字一般在三種狀態下:

            一:在打開文件請求中,但在打開文件前。

            二:在打開文件請求中,但在打開文件后,通過在本層驅動中設置完成例程。在完成例程中獲得。

            三:在過濾到讀寫等操作時。

            而在此函數中,它包含了第一種和第三種方法,通過一些煩瑣的條件判斷,先判斷出目前是處于什么狀態中,然后根據不同狀態采取不同方法。

            先分析當在第一種條件下,此函數的處理方法,可以精煉為如下:

            ?

            ?

            VOID

            FilemonGetFullPath(

            ??? BOOLEAN createPath,

            ??? PFILE_OBJECT fileObject,

            ??? PHOOK_EXTENSION hookExt,

            ??? PCHAR fullPathName

            ??? )

            {

            ?????? ULONG????? ?????????pathLen, prefixLen, slashes;

            ??? PCHAR?????????????? pathOffset, ptr;

            ??? BOOLEAN???????????? gotPath;

            ??? PFILE_OBJECT??????? relatedFileObject;

            ???

            ??? ANSI_STRING???????? fileName;

            ??? ANSI_STRING???????? relatedName;

            ???

            ??? UNICODE_STRING? ????fullUniName;

            ?

            ?

            ?????? prefixLen = 2; // "C:"

            ?

            ?????? if( !fileObject ) {

            ?

            ??????? sprintf( fullPathName, "%C:", hookExt->LogicalDrive );

            ??????? return;

            ??? }

            ?

            ??????

            ??? //

            ??? // Initialize variables

            ??? //

            ??? fileName.Buffer = NULL;

            ??? relatedName.Buffer = NULL;

            ??? gotPath = FALSE;

            ?

            ?????? if( !fileObject->FileName.Buffer)

            ?????? {

            ????????????? sprintf( fullPathName, "%C:", hookExt->LogicalDrive);

            ????????????? return;

            ?????? }else

            ????????????? DbgPrint("fileOjec->FileName:%s",fileObject->FileName);

            ?

            ?????? ?if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fileObject->FileName, TRUE ))) {

            ?

            ??????????? sprintf( fullPathName, "%C: <Out of Memory>", hookExt->LogicalDrive );

            ??????????? return;

            ??????? }

            ?

            ?????? ?

            ??????? pathLen = fileName.Length + prefixLen;

            ??????? relatedFileObject = fileObject->RelatedFileObject;

            ?

            ????????????? ? //

            ??????? // Only look at related file object if this is a relative name

            ??????? //

            ??????? if( fileObject->FileName.Buffer[0] != L'\\' &&

            ??????????? relatedFileObject && relatedFileObject->FileName.Length ) {

            ???????????????????? DbgPrint("relatedFileObject filename : %s",relatedFileObject->FileName);

            ?????? ???????

            ???????????????????? if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &relatedName, &relatedFileObject->FileName, TRUE ))) {

            ?

            ??????????????

            ??????????????? sprintf( fullPathName, "%C: <Out of Memory>", hookExt->LogicalDrive );

            ??????????????? RtlFreeAnsiString( &fileName );

            ??????????????? return;

            ??????????? }

            ??????????? pathLen += relatedName.Length+1;

            ??????? }

            ?

            ????????????? if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) {

            ?

            ??????????? sprintf( fullPathName, "%C:", hookExt->LogicalDrive );

            ??????? }

            ?

            ????????????? ?if( pathLen >= MAXPATHLEN ) {

            ???????????

            ??????????? strcat( fullPathName, " <Name Too Long>" );

            ?

            ??????? } else {

            ???

            ??????????? //

            ??????????? // Now we can build the path name

            ??????????? //

            ?????? ?????fullPathName[ pathLen ] = 0;

            ???????????

            ??????????? pathOffset = fullPathName + pathLen - fileName.Length;

            ??????????? memcpy( pathOffset, fileName.Buffer, fileName.Length + 1 );

            ???

            ??????????? if( fileObject->FileName.Buffer[0] != L'\\' &&

            ???? ???????????relatedFileObject && relatedFileObject->FileName.Length ) {

            ?

            ??????????????? //

            ??????????????? // Copy the component, adding a slash separator

            ??????????????? //

            ??????????????? *(pathOffset - 1) = '\\';

            ??????????????? pathOffset -= relatedName.Length + 1;

            ???????????????????

            ??????????????? memcpy( pathOffset, relatedName.Buffer, relatedName.Length );

            ?

            ??????????????? //

            ??????????????? // If we've got to slashes at the front zap one

            ??????????????? //

            ??????????????? if( pathLen > 3 && fullPathName[2] == '\\' && fullPathName[3] == '\\' )? {

            ???????????????????

            ??????????????????? strcpy( fullPathName + 2, fullPathName + 3 );

            ??????????????? }

            ??????????? }

            ??????? }?

            ??????

            ?

            }

            上面的精簡后的函數代碼為只考慮目前處于第一種情況,即打開文件請求中,但文件尚未打開時。

            在此時,文件的名字信息存儲在文件對象 fileObject->FileName, fileObject->RelatedFileObject->FileName, FileObject->FileName RelatedObject 的相對路徑, 通過對兩者的解析組合出文件名字。

            ?

            ?

            而在 FilemonGetFullPath 函數體中的另一段代碼:

            ?

            ?

            FilemonGetFullPath

            {

            …………………..

            …………………..

            …………………..

            if( !gotPath && !createPath ) {

            ?

            ???????

            ??????? fileNameInfo = (PFILE_NAME_INFORMATION) ExAllocatePool( NonPagedPool,

            ??????????????????????????????????????????????????????????????? MAXPATHLEN*sizeof(WCHAR) );

            ?

            ??????? if( fileNameInfo &&

            ??????????? FilemonQueryFile(hookExt->FileSystem, fileObject, FileNameInformation,

            ???????????????????????????? fileNameInfo, (MAXPATHLEN - prefixLen - 1)*sizeof(WCHAR) )) {

            ?

            ??????????? fullUniName.Length = (SHORT) fileNameInfo->FileNameLength;

            ????? ??????fullUniName.Buffer = fileNameInfo->FileName;

            ??????????? if( NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fullUniName, TRUE ))) {

            ?

            ??????????????? fullPathName[ fileName.Length + prefixLen ] = 0;

            ?

            ??????????????? if( hookExt->Type == NPFS ) {

            ???????????????????

            ??????????????????? strcpy( fullPathName, NAMED_PIPE_PREFIX );

            ?

            ??????????????? } else if( hookExt->Type == MSFS ) {

            ?

            ??????????????????? strcpy( fullPathName, MAIL_SLOT_PREFIX );

            ?

            ??????????????? } else if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) {

            ?

            ??????????????????? sprintf( fullPathName, "%C:", hookExt->LogicalDrive );

            ?

            ??????????????? } else {

            ???????????????

            ??????????????????? //

            ??????????????????? // No prefix for network devices

            ???????? ???????????//

            ??????????????? }

            ?

            ??????????????? memcpy( &fullPathName[prefixLen], fileName.Buffer, fileName.Length );

            ??????????????? gotPath = TRUE;

            ??????????????? RtlFreeAnsiString( &fileName );

            ??????????????? fileName.Buffer = NULL;

            ??????????? }

            ?? ?????}

            ??????? if( fileNameInfo ) ExFreePool( fileNameInfo );

            }

            ………………….

            …………………..

            ………………………

            }

            ?

            上面這段代碼是處理另外一種情況,即是在其他讀寫請求中,自己根據攔截到的 fileObject 構建 IRP ,下發到底層,以此來查詢文件名信息。整個過程還是易于理解的。

            ?

            在理清這兩種脈絡后,再剖析此整個函數,就很容易理解整個函數代碼了。

            代碼中對 MajorFunction == IRP_MJ_CREATE_NAMED_PIPE

            ??????? ?MajorFunction == IRP_MJ_CREATE_MAILSLOT ? 的判斷是為了辨別對攔截到的進程間的兩種通信方式:命名管道與郵槽的處理。

            久久亚洲国产欧洲精品一| 精品久久8x国产免费观看| 久久亚洲国产成人精品无码区| 久久人人爽人人爽人人片AV东京热| 亚洲午夜久久久| 国产亚洲色婷婷久久99精品| 色综合久久综合网观看| 性做久久久久久久久久久| 久久久免费精品re6| 国产精品免费久久久久电影网| 欧美一级久久久久久久大片| 日韩AV无码久久一区二区 | 久久精品国产99国产精品导航| 性欧美大战久久久久久久久| 国产午夜精品理论片久久| 无码伊人66久久大杳蕉网站谷歌| 久久久久18| 久久婷婷综合中文字幕| 国产成人精品久久| 久久青青草原精品国产软件| 国产精品一区二区久久精品| 久久妇女高潮几次MBA| 欧美久久亚洲精品| 国产99精品久久| 国产精品一久久香蕉国产线看| 伊人久久大香线蕉综合5g| 久久久久亚洲AV成人网人人软件| 9久久9久久精品| 狠狠色丁香久久婷婷综合五月 | 久久精品国产99国产电影网| 久久久久人妻一区二区三区 | 欧美日韩精品久久久免费观看| 久久国产精品99久久久久久老狼 | 久久人妻少妇嫩草AV蜜桃| 国产精品美女久久久久av爽 | 久久精品国产精品亚洲艾草网美妙| 国内精品久久久久影院优| 无码精品久久久天天影视| 一级做a爰片久久毛片毛片| 国产免费久久精品99re丫y| 亚洲人成无码久久电影网站|