分享
 
 
 

《Undocumented Windows 2000 Secrets》翻译 --- 第二章(2)

王朝system·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

第二章 The Windows 2000 Native API

翻译:Kendiv

更新:Friday, February 04, 2005

Windows 2000运行时库

Nt*()和Zw*()函数构成了Native API的基本部分,但并不是主要部分,还有一部分代码位于ntdll.dll中。该DLL至少导出了1179个符号。其中的249和248个分别属于Nt*()和Zw*()函数集,剩余的682个函数并不通过INT 2eh中断进行调用。显然,这一大组函数并不依赖Windows 2000内核。那提供它们的目的何在呢?让我们继续往下看。

C运行时库

如果你研究过位于ntdll.dll导出节(export section)的符号,你会发现很多在C程序员看来很熟悉的小写的函数名称。这些都是众所周知的名子,如memcpy()、sprintf()和qsort(),这些C运行时库中的函数都合并到了ntdll.dll中。对于ntoskrnl.exe也是如此,它同样提供了一组与C运行时函数十分相像的函数,虽然这两组函数并不相同。附录B的表B-3列出了这两组函数,并指出了每个函数分别属于哪个模块。

你可以简单的将ntdll.lib(来自Windows 2000 DDK)添加到导入库列表(链接器在解析符号期间将扫描该列表)中,就可以链接到这些函数。如果你更喜欢对话框,你可以选择Visual C/C++的工程菜单中的Settings子菜单,然后单击Linke页,选择Category General,然后将ntdll.dll添加到Object/Library模块列表中。还有一种方法:在源文件中,添加如下的内容:

#pragma comment(linker,”/defaultlib:ntdll.lib”)

这同样有效,好处是,其他开发人员可以使用Visual C/C++的默认设置来rebuild你的工程。

反编译这些与C运行时函数类似的函数(来自ntdll.dll和ntoskrnl.exe),会发现ntdll.dll并不依赖于ntoskrnl.exe,这和ndll.dll中的Native API不一样。事实上,这两个模块分别实现了这些函数。本节出现的其他函数也是如此。注意,表B-3中的一些函数并不使用其导出的名称。例如,如果在内核模式的驱动程序中针对一个64位的LARGE_INTEGER使用移位操作符<<和>>,编译器和链接器会自动导入ntoskrnl.exe的_allshr()和_allshl()。

扩展的运行时函数

随同标准的C运行时函数,Windows 2000还提供了一组扩展的运行时函数。在次强调,ntdll.dll和ntoskrnl.exe分别实现了它们。并且其中有些函数是重叠的。这些扩展函数的名字都有一个共同的前缀Rtl(for Runtime Library)。附录B的表B-4列出了所有这些扩展函数。Windows 2000提供的这些运行时函数还包含用于普通任务的助手函数(helper function),这些任务都超过了C运行时函数的能力范围。例如,其中的某些用于管理安全性,另一些用于操作Windows 2000特有的数据结构,还有一些对内存管理提供支持。很难理解为什么微软仅在Windows 2000 DDK中提供了其中115个函数的文档,而扔掉了其余406个非常有用的函数。

浮点模拟器(The Floating-Point Emulator)

让我用ntdll.dll提供的另一组函数集合来结束这次API函数汇展。表2-1列出了这些函数的名称,这些名称可能对于汇编程序员有些眼熟。去了名称前的__e前缀,你就会得到i386系列CPU中的FPU(Floating-Point Unit)汇编助记符。事实上,从表2-1中列出的函数来看,ntdll.dll包含了一个完整的浮点模拟器。这再次证明了这个DLL是一个庞大的代码仓库,这吸引了众多的System Spelunker去反编译它。

表2-1. ntdll.dll的浮点模拟器接口

函数名称

_eCommonExceptions

_eFIST32

_eFLD64

_eFSTP32

_eEnulatorInit

_eFISTP16

_eFLD80

_eFSTp64

_eF2XM1

_eFISTP32

_eFLDCW

_eFSTP80

_eFABS

_eFISTP64

_eFLDENV

_eFSTSW

_eFADD32

_eFISUB16

_eFLDL2E

_eFSUB32

_eFADD64

_eFISUB32

_eFLDLN2

_eFSUB64

_eFADDPreg

_eFISUBR16

_eFLDPI

_eFSUBPreg

_eFADDreg

_eFISUBR32

_eFLDZ

_eFSUBR32

_eFADDtop

_eFLDI

_eFMUL32

_eFSUBR64

_eFCHS

_eFIDIVR16

_eFMUL64

_eFSUBreg

_eFCOM

_eFIDIVR32

_eFMULPreg

_eFSUBRPreg

_eFCOM32

_eFILD16

_eFMULreg

_eFSUBRreg

_eCOM64

_eFILD32

_eFMULtop

_eFSUBRtop

_eFCOMP

_eFILD64

_eFPATAN

_eFSUBtop

_eFCOMP32

_eFIMUL16

_eFPREm

_eFTST

_eFCOMP64

_eFIMUL32

_eFPREM1

_eFUCOM

_eFCOMPP

_eFINCSTP

_eFPTAN

_eFUCOMP

_eFCOS

_eFINIT

_eFRNDINT

_eFUCOMPP

_eFDECSTP

_eFIST16

_eFRSTOR

_eFXAM

_eFIDIVR16

_eFIST32

_eFSAVE

_eFXCH

_eFIDIVR32

_eFISTP16

_eFSCALE

_eFXTRACT

_eFILD16

_eFISTP32

_eFSIN

_eFYL2X

_eFILD32

_eFISTP64

_eFSQRT

_eFYL2XP1

_eFILD64

_eFISUB16

_eFST

_eGetStatusWord

_eFIMUL16

_eFISUB32

_eFST32

NPXEMULATORTABLE

_eFIMUL32

_eFISUBR16

_eFST64

RestoreEm87Context

_eFINCSTP

_eFISUBR32

_eFSTCW

SaveEm87Context

_eFINIT

_eFLD16

_eFSTENV

_eFIST16

_eFLD32

_eFSTP

有关浮点指令集的更多信息,请参考Intel 80386 CPU的原始文档。可以从Intel官方网站:http://developer.intel.com/design/pentium/manuals/来下载PDF格式的Pentium手册。讲解这些机器码指令集的手册是:Intel Architecture SoftWare Developer’s Manual. Volume 2:Instruction Set Reference(Intel 1999b)。

其它的API函数

除附录B和表2-1列出的函数外,ntdll.dll和ntoskrnl.exe还为多个内核组件导出了为数众多的函数。为了避免更长的表格,我这里仅列出可用函数的名称前缀及其所属类别(表2-2)。

表2-2 函数名前缀及其所属分类

前缀

ntdll.dll

ntoskrnl.exe

分类

_e

N/A

浮点模拟器

Cc

N/A

Cache管理器

Csr

Client-Server运行时库

Dbg

N/A

调试支持

Ex

N/A

执行支持(Executive Support)

FsRtl

N/A

文件系统运行时库

Hal

N/A

硬件抽象层调度器

Inbv

N/A

系统初始化/VGA启动驱动(bootvid.dll)

Init

N/A

系统初始化

Interlocked

N/A

处理线程安全的变量

Io

N/A

I/O管理器

Kd

N/A

内核调试支持

Ke

N/A

内核例程

Ki

内核中断例程

Ldr

映像加载器

Lpc

N/A

本地过程调用(LPC)设备

Lsa

N/A

本地安全授权

Mm

N/A

内存管理器

Nls

National Language Support (NLS)

Nt

NT Native API

Ob

N/A

对象管理器

Pfx

前缀处理

Po

N/A

电源管理器

Ps

N/A

进程支持

READ_REGISTER_

N/A

从寄存器地址中读取

Rtl

Windows 2000运行时库

Se

N/A

安全处理

WRITE_REGISTER_

N/A

向寄存器地址中写入

Zw

另一组Native API

<other>

帮助函数和C运行时库

很多内核函数都使用统一的命名规则----PrefixOperationObject()。例如,NtQueryInformationFile()函数属于Native API,这是因为其Nt前缀,而且该函数显然针对一个文件对象执行了QueryInformation操作。但并不是所有函数都遵循这一规则,不过绝大多数都是如此。因此,可以很容易的通过函数的名称猜测其功能。

经常使用的数据类型

当编写与Windows 2000内核有关的软件时---不管是和用户模式的ntdll.dll还是和内核模式的ntoskrnl.exe,你都必须处理几个基本的数据类型,而这些数据类型在Win32世界里非常少见。它们中的多数都会在本书中反复出现。下面的章节将介绍使用频率最高的数据类型。

整型

一般说来,整数类型有多个不同的变体。Win32 SDK的头文件和SDK文档使用了其专有的术语,这些术语很容易和C/C++的基本类型以及一些派生类型相混淆。表2-3列出了这些整数类型,以及它们之间的等价关系。在“MASM”列中,给出了微软宏汇编语言(MASM)使用的类型名称。Win32 SDK为C/C++的基本数据类型定义了对应的BYTE、WORD、DWORD别名。“别名1”和“别名2”两列包含其经常使用的别名。例如,WCHAR代表基础的Unicode字符类型。最后一列“有符号的”,列出了对应的有符号类型的常见别名。一定要记住ANSI字符类型CHAR是有符号的,而Unicode类型WCHAR是无符号的。当编译器将表达式或计算中的这些类型转换为整数类型时,这种不一致性将导致意外的错误。

表2-3最后一行的MASM的TBYTE类型(读做“10-byte”)是一个80位的浮点数,用于高精度的浮点运算操作。Microsoft Visual C/C++没有为Win32程序员提供对应的数据类型。需要注意的是,MASM的TBYTE和Win32的TBYTE(读做“text byte”)没有任何关系,后者只是一个用于转换的宏,根据源文件中是否有#define UNICODE而分别对应CHAR或WCHAR。

表2-3. 等价的整数类型

位数

MASM

基本类型

别名1

别名2

有符号的

8

BTYE

unsigned char

UCHAR

CHAR

16

WORD

unsigned short

USHORT

WCHAR

SHORT

32

DWORD

unsigned long

ULONG

LONG

32

DWORD

unsigned int

UINT

INT

64

QWORD

unsigned __int64

ULONGLONG

DWORDLONG

LONGLONG

80

TBYTE

N/A

由于在32位编程环境中较难处理64位整数,Windows 2000通常不提供64位的基本类型,如__int64或其派生类型。替代的,DDK头文件ntdef.h中定义了一个精巧的union结构,可以将一个64位数解释为一对32位数或一个完整的64位数,参见列表2-3给出了LARGE_INTEGER和ULARGE_INTEGER类型定义。该类型可分别表示有符号和无符号的整数。通过使用LONGLONG/ULONGLONG(针对64位的QuadPart成员)或者LONG/ULONG(针对32位的HighPart成员)来控制有无符号。

typedef union _LARGE_INTEGER

{

struct

{

ULONG LowPart;

LONG HighPart;

}

LONGLONG QuadPart;

} LARGE_INTEGER,*PLARGE_INTEGER;

typedef union _ULARGE_INTEGER

{

struct

{

ULONG LowPart;

ULONG HighPat;

}

ULONGLONG QuadPat;

} ULARGE_INTEGER,*PULARGE_INTEGER;

列表2-3. LARGE_INTEGER和ULARGE_INTEGER

字符串

在Win32程序设计中,常使用PSTR和PWSTR来分别代替ANSI和Unicode字符串。PSTR被定义为CHAR*,PWSTR则定义为WCHAR*(参见表2-3)。通过源文件中是否出现#define UNICODE指示符,附加的PTSTR类型分别对应PSTR或PWSTR,这样就可通过单一的源文件来维护应用程序的ANSI和Unicode版本。基本上,这些字符串都是简单的指向以零结尾的CHAR或WCHAR类型的数组。如果你常和Windows 2000内核打交道,你将必须处理一种很不同的字符串表示法。最常见的类型是UNICODE_STRING,这是一个第三方类型,列表2-4给出了它的定义。

typedef struct _UNICODE_STRING

{

USHORT Length;

USHORT MaximumLength;

PWSTR Buffer;

} UNICODE_STRING,*PUNICODE_STRING;

typedef struct _STRING

{

USHORT Length;

USHORT MaximumLength;

PCHAR Buffer;

} STRING, *PSTRING;

typedef STRING ANSI_STRING, *PANSI_STRING;

typedef STRING OEM_STRING, *POEM_STRING;

列表2-4. 字符串类型

Length成员给出了当前字符串的字节数(注意,不是字符个数),MaximumLength成员指出Buffer所指向内存块的大小,实际的字符串数据将保存在该内存块中。注意,MaximumLength也是字节数。由于Unicode字符宽度为16位,所有其长度总是字符个数的两倍。通常,Buffer指向的字符串都是以零结尾的。然而,有些内核模块可能仅依赖字符串的长度值,而不考虑结尾的0字符,这种情况下要小心处理。

Windows 2000的ANSI字符串叫做STRING,如列表2-4中所示。为了方便,nedef.h分别定义了ANSI_STRING和OEM_STRING来代表使用不同代码页的8位字符串(ANSI默认代码页为1252;OEM默认代码页为437)。不过,Windows 2000内核使用的主要字符串类型还是UNICODE_STRING。你可能偶尔会碰到8位字符串。

在图2-3中,我给出了两个典型的UNICODE_STRING示例。左面的那个包含两个独立的内存块:一个UNICODE_STRING结构和一个16位PWCHAR类型的Unicode字符数组。这或许是在Windows 2000数据类型中最常见的字符串类型。右边的是一种频繁出现的特殊类型,在此种类型中,UNICODE_STRING和PWCHAR数组位于同一个内存块中。有些内核函数,包括Native API内部使用的一些函数,都在连续的内存块中保存其返回的结构化的系统信息。如果数据中包含字符串,它们通常都存储在嵌入式的UNICODE_STRING中,如图2-3右面所示。例如,NtQuerySystemInformation()函数就频繁使用了这种特殊的字符串类型。

这些字符串结构不许要手工维护,ntdll.dll和ntoskrnl.exe导出了一组丰富的运行时API函数,如RtlCreateUnicodeString()、RtlInitUnicodeString()、RtlCopyUnicodeString()等。通常,STRING和ANSI_STRING也有对应的等价函数。这些函数中的大多数在DDK中都有文档记录,但其中有些没有。不过,很容易猜出这些未文档化的字符串函数的功能及其需要的参数。使用UNICODE_STRING、STRING的好处是,可以隐示的指定Buffer可容纳的字符串的大小。如果你给一个函数传递了一个UNICODE_STRING类型的字符串,而该函数需要适当改变该字符串的值,而这可能会增加该字符串的长度,那这个函数只需要简单的检查MaximumLength成员就可确定是否有足够的空间来存放结果。

结构体

个别的几个内核API函数期望其处理的对象有一个合适的OBJECT_ATTRIBUTES结构,列表2-5给出了该结构的定义。例如,NtOpenFile()函数没有PWSTR或PUNICODE_STRING参数用来指定要打开的文件的路径。替代的,OBJECT_ATTRIBUTES结构中的ObjectName成员给出了该路径。通常,设置该结构很容易。除ObjectName外,还需要设置Length和Attributes成员。Length必须设置为:sizeof(OBJECT_ATTRIBUTES),Attributes是一组来自ntdef.h的OBJ_*常量。例如,如果你对象名称不区分大小写的话,Attributes应设置为OBJ_CASE_INSENSITIVE。当然,ObjectName成员是一个UNICODE_STRING指针,并不是通常的PWSTR。剩余的成员只要不使用,都可设置为NULL。

typedef struct _OBJECT_ATTRIBUTES

{

ULONG Length;

HANDLE RootDirectory;

PUNICODE_STRING ObjectName;

ULONG Attributes;

PVOID SecurityDescriptor;

PVOID SecurityQualityOfService;

} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

列表2-5. OBJECT_ATTRIBUTES结构

OBJECT_ATTRIBUTES结构仅描述函数使用的数据的细节,列表2-6给出的IO_STATUS_BLOCK结构则用于记录对用户所提交的操作的处理结果。该结构很简单---Staus成员存放一个NTSTATUS类型的代码,其值可能是STATUS_SUCCESS或定义于ntstatus.h中的所有可能的错误代码。Information成员在操作成功的情况下,提供与操作相关的附加数据。比如,如果函数返回一个数据块,该成员将被设置为该数据块的大小。

typedef struct _IO_STRATUS_BLOCK

{

NTSTATUS Status;

ULONG Information;

} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

列表2-6. IO_STATUS_BLOCK结构

另一个常见的Windows 2000数据类型是LIST_ENTRY结构,列表2-7给出了该结构的定义。内核使用该结构将所有对象维护在一个双向链表中。一个对象分属多个链表是很常见的,Flink成员是一个向前链接,指向下一个LIST_ENTRY结构,Blink成员则是一个向后链接,指向前一个LIST_ENTRY结构。通常情况下,这些链表都成环形,也就是说,最后一个Flink指向链表中的第一个LIST_ENTRY结构,而第一个Blink指向最后一个。这样就很容易双向遍历该链表。如果一个程序要遍历整个链表,它需要保存第一个LIST_ENTRY结构的地址,以判断是否已遍历了整个链表。如果链表仅包含一个LIST_ENTRY结构,那么该LIST_ENTRY结构必须引用其自身,也就是说,Flink和Blink都指向其自己。

typedef struct _LIST_ENTRY

{

struct _LIST_ENTRY *Flink;

struct _LIST_ENTRY *Blink;

} LIST_ENTRY, *PLIST_ENTRY;

列表2-7. LIST_ENTRY结构

图2-4展示了对象链表各成员间的关系。对象A1、A2、A3属于同一链表。注意,A3的Flink指向A1,A1的Blink指向A3。最右边的对象B1仅有一个成员,因此,其Flink和Blink都指向相同的地址---即对象B1的地址。典型的双向链表的例子是进程和线程链表。内部变量PsActiveProcessHead就是一个LIST_ENTRY结构,位于ntoskrnl.exe的.data节中。该变量指向系统进程列表的首部(通过其Blink指针)。你可以在内核调试器中使用dd PsActiveProcessHead来获取该链表的首部,然后通过其Flink和Blink指针遍历整个链表(仍使用dd命令)。当然,这种探测Windows进程的方法非常繁琐,但这可使你深入的观察基本的系统结构。Windows 2000 Native API提供了更便利的方法来枚举进程,如NtQuerySystemInformation()函数。

typedef struct _CLIENT_ID

{

HANDLE UniqueProcess;

HANDLE UniqueThread;

} CLIENT_ID, *PCLIENT_ID;

列表2-8. CLIENT_ID结构

处理进程和线程的API函数,如:NtOpenProcess()和NtOpenThread(),使用列表2-8给出的CLIENT_ID结构来和特定的进程、线程相关联。尽管其类型为HANDLE,实际上,从严格的意义上来讲UniqueProcess和UniqueThread成员并不是句柄(Handle),它们都是整数型的进程ID和线程ID。即标准Win32函数GetCurrentProcessId()和GetCurrentThreadId()返回的DWORD类型的数值。

Windows 2000执行体(Executive)还使用CLIENT_ID结构在全局范围内标识唯一的线程。例如,如果你使用内核调试器的!thread命令来显示当前线程参数,就会在输出的第一行看到类似“Cid ppp.ttt”的显示,其中“ppp”就是CLIENT_ID的UniqueProcess成员,而“ttt”则代表UniqueThread,如下所示。注意,我用黑体标出的地方。

kd> !thread

THREAD 83a51ba8 Cid 0a5c.0e64 Teb: 7ffdd000 Win32Thread: e14f4eb0 RUNNING on processor 0

Not impersonating

DeviceMap e20fb208

Owning Process 83a14708

Wait Start TickCount 906512 Elapsed Ticks: 68570

Context Switch Count 266 LargeStack

UserTime 00:00:00.0312

KernelTime 00:00:00.0015

。。。。。。。。。。。。。。。。。。。

Native API的接口

对于内核模式的驱动程序,使用Native API的接口非常平常,就像在用户模式下的程序中调用Win32 API一样。Windows 2000 DDK提供的头文件和库包含了所有在调用ntoskrnl.exe导出的Native API时所需的信息。而另一方面,Win32 SDK几乎不支持在程序中调用ntdll.dll导出的Native API。我说“几乎不”是因为Win32 SDK实际上提供了一个重要的东西:导入库ntdll.lib,该文件位于\Program Files\Microsoft Platfrom SDK\Lib目录中。如果没有这个库,将很难调用ntdll.dll导出的函数。

译注:

你需要安装Windows 2000 DDK才能获得ntdll.lib

可以到 http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ 下载最新的SDK

将NTDLL.DLL导入库添加到工程中

在你能成功的编译和链接在用户模式下使用ntdll.dll导出函数的代码之前,你必须考虑如下的四个重点:

1. SDK的头文件中,没有包含这些函数的原型。

2. SDK文件中缺少这些函数使用的几个基本的数据类型。

3. SDK和DDK头文件并不兼容,你不能将#include <ntddk.h>加入你的Win32 C源代码文件中。

4. ntdll.lib并没有加入Visual C/C++默认的导入库列表中

最后一个问题很容易解决,只需要编辑工程的设置属性,或者将如下内容加入你的源代码中,#pragma comment(linker,”defaultlib:ntdll.lib”),像在前面的Windows 2000运行时库一节解释的那样,这会在编译时,将ntdll.dll加入链接器的/defaultlib设置中。解决缺失的定义比较困难。因为不可能将SDK和DDK头文件整合到C程序中,最简易的解决方法是写一格自定义的头文件,在该头文件中包含所有调用ntdll.dll导出函数必须的定义。幸运的是,你不需要开始这项工作了,在本书光盘的\src\common\include目录下的w2k_def.h文件包含了你所需要的所有基本信息。该头文件将在第六、七两章中扮演重要角色。因为它被设计为可同时兼容用户模式和内核模式的工程,在用户模式代码中,你必须在#include <w2k_def.h>之前插入#define _USER_MODE_,以加入仅出现在DDK中的一些定义。

有关Native API编程的很多详细信息都已经出版,目前看来,针对Windows 2000平台的好书是Gary Nebbett’s的《Windows NT/2000 Native API Reference》。该书提供的示例程序较少,但它覆盖了Windows NT/2000平台上的所有Native API,还包括这些函数需要的数据结构定义以及其他必须的一些结构定义。

将在第六章介绍的w2k_call.dll示例库,演示了w2k_def.h的典型用法。第六章还将讨论另一种在用户模式进入Windows 2000内核的方法,此种方法不受限于Native API。事实上,这种技巧也可用于ntoskrnl.exe,对于所有加载到内核空间的模块,只要它们导出了函数或者可以和.dbg或.pdb符号文件相匹配都可以使用此方法。如你所见,在本书剩余章节中还有很多有趣的信息。但是,在我们到达那儿之前,我们会继续讨论一些基本的概念和技术。

< 本章完 >

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有