首页
社区
课程
招聘
[原创]PE学习笔记
发表于: 2020-5-20 10:58 7044

[原创]PE学习笔记

2020-5-20 10:58
7044

最近对PE文件学习了下,整理下来加深影响。文中的ASLR,在另个帖子有总结:这里

PE文件总体框架.

PE文件执行顺序. ( 摘自网络 )

分别介绍3个结构体

PE文件中的code(代码), data(数据), resource(资源)等按照属性分类储存在不同的节区, (1)这样分类便于统一和查看 (2)这样可以在一定程度上保护程序的安全性, 因为如果把所有的代码数据放在一起的话, 当我们向数据区写数据时, 若输入超过缓冲区的大小, 那么就有可能会将其下的code(指令)覆盖掉, 造成应用程序崩溃. PE文件就可以把相似属性的的数据保存在一个被称为"节区"的地方, 然后为每个节区设置不同的特性,访问权限等.

​ 摘自 逆向工程核心原理

节区头是由IMAGE_SECTION_HEADER结构体组成的数组, 每个结构体对应一个节区

下图展示OD程序的各个节, 并将(.txt)节中的各成员值在上面依次标出

由于每个节区都有内存地址到文件偏移间的映射(RAW-RVA). 我们可以通过节区的VirtualAddress与PointerToRawData来从RVA->RAW.

注: 由于VirtualAddress是未对齐的大小,而SizeofRawData是对齐后的大小, 那么 VirtualAddress一般比SizeofRawData小. 但是也有例外, 就是当含有未初始化数据的节(如.bss), 在磁盘中未初始化数据是不占空间的, 但是到了内存, 未初始化的数据是要赋值占空间.

一般dll文件才有,DataDirectory[0]记录了RVA及Size.

用来描述模块(dll)中的导出函数的结构,如果一个模块导出了函数,那么这个函数会被记录在导出表中,从 库向其他PE文件提供服务

从导出表中获得函数地址的API为: GetProcAddress()函数. 该API用来引用EAT来获取指定的API的地址.

注: (1) 导出函数也可能没有名称的, 这时只能通过序数导出 (2) 序数是指定某个输出函数的独一无二的16位数字(2个字节)

两种导出函数的方法:

一:按函数名字

(1)通过AddressOfNames找到函数名称数组. 使用strcmp()函数, 在(RVA)指针数组从索引值0开 始依次与我们要找的函数名称对比,从而找到索引值 index_name

(2)通过AddressOfNameOrdinals找到存放函数序号的数组, 使用步骤(1)获得的index_name为 索引值找到函数地址的序号(index_address)

(3)通过AddressOfFunctions找到函数地址数组(EAT), 在EAT中使用步骤(2)获得的index_address 为索引值找到指定函数的RVA

二:按函数序号

(1)使用我们函数的序号减去 _IMAGE_EXPORT_DIRECTORY.Base 的值得到函数地址索引值 index_address.

(2)通过AddressOfFunctions找到函数地址数组(EAT), 在EAT中使用步骤(1)获得的index_address为 索引值找到指定函数的RVA

下面依旧用OD程序来看导出表, 并将每个值标在上面每个成员下面, 通过上面IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]介绍, 已经标出导出表的RVA: 0010F000 Size: 000012FA. 再通过CFF Explorer 工具查看每个节的地址可以计算出输出表的 RAW : 00CE200

1.查看输出表名称(RVA : 0010F780 -> RAW: 000CE980)



2.查找函数名称.

(1)AddressOfNames. (RVA: 0010F318 -> RAW: 000CE518)



由(RVA:0010F78C -> RAW: 000CE98C):


现在已经找到了函数的名称, 下面模拟查看一个指定名称函数的RVA. 假设我们找的是Addsorteddata.(即第一个函数), (1)通过strcmp(). 得到它的索引值是0, 记为 index_name. (2)通过AddressOfNameOrdinals使用index_name找到函数的序数, 通过下图得到序数0, 记为index_address.

AddressOfNameOrdinals. (RVA: 0010F608 -> RAW: 000CE808):


(3)通过AddressOfFunctions函数地址数组(EAT), 使用index_address为索引值得到我们指定函数的RVA.

AddressOfFunctions(RVA: 0010F028 -> RAW: 000CE228):

到此, 得到我们指定输出函数Addsorteddata.的RVA: 00054EFC. 最后通过 OD载入OD看一下.

从这里也说明了, .exe文件也是可能有输出表的

记录PE文件要导入那些库文件 DataDirectory[1]记录了RVA及Size.

首先, 执行一个程序会有很多的函数是公用的,在动态链接库里(动态链接库, .dll文件总是附加在一个要执行的程序中, .dll文件中有说明库EAT的输出表), 如下图, 一个程序加载的部分 .dll文件.

我们的输入表记录了需要用到的函数名称, 通过在加载的动态链接库中搜索该函数得到实际的RVA, 再记录到输入表中, 供程序使用. 另外执行一个普通的程序一般需要多个库, 那导入多少库, 就会有多少个输入表结构体. 这就构成了结构体数组且结构体数组最后以 NULL 结束 (即每个导入的 DLL 都会成为数组中的一项).

注: 上面所讲的 OriginalFirstThunk 成员(指针数组)的值是不能改写的, 通过它寻找函数的名称. 而 FirstThunk 成员(指针数组)的值在PE文件在被PE装载器时, PE装载器会通过 OriginalFirstThunk 得到函数的名称或者序数, 然后通过函数名称在加载的.dll文件的输出表中找到函数的实际地址, 然后替换到FirstThunk的一个值. 装载完成后, FirstThunk 数组就指向向了函数实际的地址. 另外上面的 TimeDateStamp 成员可以用来确定输入表是否绑定从而是否需要重定位, 如果它的值是0, 那么输入列表没有被绑定, 加载器总是要修复输入表. 否则输入被绑定, 但该时间戳的值必须和.dll文件头中 TimeDateStamp 的一样, 如果不一样, 仍会修正输入表, 就会进行下面的步骤.

导入函数输入到 IAT 的顺序 (摘自 逆向工程核心原理)

1.读取 _IMAGE_IMPORT_DESCRIPTOR中的name成员, 获取库名称字符串. 如(user32.dll)

2.装载相应的库. LoadLibrary("user32.dll")

3.读取_IMAGE_IMPORT_DESCRIPTOR中的 OriginalFirstThunk 成员, 得到 INT地址.

4.逐一读取 INT中数组的值, 获取相应的 IMAGE_IMPORT_BY_NAME地址(RVA)

5.使用 IMAGE_IMPORT_BY_NAME的Hint (ordinak/序数)或name项, 获取相应函数的起始地址.

GetProcAddress("函数名称")

6.读取 IAT 成员, 获得IAT地址.

7.将上面获得的函数地址输入相应的IAT数组值.

8.重复 步骤 4 -7, 直到INT结束.

图示一下, INT 与 IAT 关系 (技术太差了.png).

下面实例查看OD程序的输入表.

1.首先从PE文件可选头的 DataDirectory[1].VirtualAdress 得到输入表的RVA: 10D000h 及size: 1c87h

2.RVA: 10D000h -> RAW: (10D000-10D000+CC400) = CC400h

3.找到输入表. 记录下对应成员的RVA.

4.查看该输入表名称: ADVAPI32,DLL, RVA: 10D9C8 -> RAW: (10D9C8-10D000+CC400) = CCDC8

5.查看 OriginalFirstThunk( INT ) RVA:10D0C8 -> RAW: (10D0C8-10D000+CC400) = CC4C8

6.可以看到第一成员的最高位是 0, 则该值是IMAGE_IMPORT_BY_NAME的RVA.(RVA: 10DA33 -> RAW: CCE33)


7.查看 FirstThunk( IAT ) RVA: 10D0E4 -> RAW: (10D0E4-10D000+CC400) = CC4E4

8.从步骤7可以看到, PE装载器装载PE文件之前, INT与IAT各元素同时指向相同的地址.

9.再看 TimeDateStamp 成员的值为 0, 那就是输入表被绑定, 如果与该对应 .dll PE文件的文件头的 TimeDateStamp的值相同, 那这个输入表是不需要修正的.

10.从上面知道 IAT 的RVA: 10D0E4. 库名称: ADVAPI32,DLL 另外使用一个OD载入这个OD程序看看. 可以看到加载该.dll文件文件的RVA是从 FC0000开始的, 而查看未被PE装载器装载前的状态, IAT的RVA是 10D0E4,所以显然这是需要PE装载器装载时对输入表修正的, 那也可推出他们的 TimeDateStamp 的值是不同的

输入表与输出表联系还是比较大, 结合以来看看清楚很多.

PE

 
 
 
 
typedef struct _IMAGE_NT_HEADERS
{
    DOWORD Signature; //PE头的标志 50450000
    IMAGE_FILE_HEADER FileHeader; //文件头  size: 0xF8  记载文件的大部分属性
    IMAGE_OPTIONAL_HEADER32 OptionalHeader; //可选头  very important
}IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_NT_HEADERS
{
    DOWORD Signature; //PE头的标志 50450000
    IMAGE_FILE_HEADER FileHeader; //文件头  size: 0xF8  记载文件的大部分属性
    IMAGE_OPTIONAL_HEADER32 OptionalHeader; //可选头  very important
}IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_FILE_HEADER
{                                           
    WORD    Machine;         //指出该PE文件运行的平台,每个CPU都有唯一的标识码,一般0x14c(x86)
    014C
 
    WORD    NumberOfSections;  //指出文件中存在的节区数量 注:这里的定义一定要等于实际
    0008                       //的大小, 不然程序会运行失败
 
    DWORD   TimeDateStamp; //PE文件的创建时间,一般有连接器填写 UTC(世界标准时)进
    40B10868               //行存储 从19701100:00:00算起的秒数值 我们可以用C
                           //语言的localtime()函数(时区也会转换)计算.               
 
    DWORD   PointerToSymbolTable; //指向符号表COFF的指针, 用于调试信息. 发现每次看都是0
    00000000
 
    DWORD   NumberOfSymbols; //符号表数量. 发现每次看都是0
    00000000
 
    WORD    SizeOfOptionalHeader; //指出PE的IMAGE_OPTIONAL_HEADER32结构体或
    00E0                          //PE+格式文件的IMAGE_OPTIONAL_HEADER64结构体的长度
                                  //这两个结构体尺寸是不相同的,所以需要在
                                  //SizeOfOptionalHeader中指明大小, 32位通常位: E0
                                  //64位通常为:F0(不是绝对的)它们只是最小值,可能有更大的值                                                                      
 
    WORD    Characteristics; //标识文件的属性, 文件是否可运行, 是否为DLL文件等.
    010E                     //二进制中每一位代表不同属性, 以 bit oR形式结合起来
                             //2个需要记住的值. 0002h:.exe文件  2000h: .dll文件
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
 
下图为OD程序的文件头. 在上面每个成员下面依次标出.
typedef struct _IMAGE_FILE_HEADER
{                                           
    WORD    Machine;         //指出该PE文件运行的平台,每个CPU都有唯一的标识码,一般0x14c(x86)
    014C
 
    WORD    NumberOfSections;  //指出文件中存在的节区数量 注:这里的定义一定要等于实际
    0008                       //的大小, 不然程序会运行失败
 
    DWORD   TimeDateStamp; //PE文件的创建时间,一般有连接器填写 UTC(世界标准时)进
    40B10868               //行存储 从19701100:00:00算起的秒数值 我们可以用C
                           //语言的localtime()函数(时区也会转换)计算.               
 
    DWORD   PointerToSymbolTable; //指向符号表COFF的指针, 用于调试信息. 发现每次看都是0
    00000000
 
    DWORD   NumberOfSymbols; //符号表数量. 发现每次看都是0
    00000000
 
    WORD    SizeOfOptionalHeader; //指出PE的IMAGE_OPTIONAL_HEADER32结构体或
    00E0                          //PE+格式文件的IMAGE_OPTIONAL_HEADER64结构体的长度
                                  //这两个结构体尺寸是不相同的,所以需要在
                                  //SizeOfOptionalHeader中指明大小, 32位通常位: E0
                                  //64位通常为:F0(不是绝对的)它们只是最小值,可能有更大的值                                                                      
 
    WORD    Characteristics; //标识文件的属性, 文件是否可运行, 是否为DLL文件等.
    010E                     //二进制中每一位代表不同属性, 以 bit oR形式结合起来
                             //2个需要记住的值. 0002h:.exe文件  2000h: .dll文件
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
 
下图为OD程序的文件头. 在上面每个成员下面依次标出.
typedef struct _DATA_DIRECTORY //定义了DataDirectory的结构体
{
    DOWORD VirtualAddress; //该结构体的RVA
    DOWORD Size;            //该结构体的大小
}IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
 
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
 
typedef struct _IMAGE_OPTIONAL_HEADER  
{
    WORD    Magic; //这个可选头的类型  PE: 10Bh   PE+: 20Bh  可以依次区分是32位还是64
    010B
 
    BYTE    MajorLinkerVersion; //链接器的版本号(不重要)
    05
 
    BYTE    MinorLinkerVersion; //链接器的小版本号(不重要)
    00
 
    DWORD   SizeOfCode; //代码段的长度
    000AF000
 
    DWORD   SizeOfInitializedData; //初始化的数据长度
    0008EC00
 
    DWORD   SizeOfUninitializedData; //未初始化的数据长度
    00000000
 
    DWORD   AddressOfEntryPoint; //程序EP的RVA, 指出程序最先执行代码的起始地址 (很重要)
    00000100
 
    DWORD   BaseOfCode;//代码段起始地址的RVA
    00000100
 
    DWORD   BaseOfData;//数据段起始地址的RVA
    000B0000
 
    DWORD   ImageBase; //VA: 0~FFFFFFFF(32位系统).PE文件加载到虚拟内存时, 指出文件优先装入地址
    00400000           //EXE, DLL文件被装载到0~7FFFFFFF
                       //SYS文件载入内核内存的 80000000~FFFFFFFF
                       //执行PE文件时,PE装载器会把EIP设置为: ImageBase+AddressOfEntrypoint
 
    DWORD   SectionAlignment; //节在内存中的最小单位 (对齐单位) 一般为: 1000h
    00001000
 
    DWORD   FileAlignment; //节在磁盘文件中的最小单位 (对齐单位) 一般为: 200h
    00000200               //一般SectionAlignment <= FileAlignment,节省储存空间.
 
    WORD    MajorOperatingSystemVersion; //操作系统主版本号(不重要)
    0004
 
    WORD    MinorOperatingSystemVersion; //操作系统小版本号(不重要)
    0000
 
    WORD    MajorImageVersion; //映象文件主版本号, 这个是开发者自己指定的,由连接器填写(不重要)
    0000
 
    WORD    MinorImageVersion; //映象文件小版本号(不重要)
    0000
 
    WORD    MajorSubsystemVersion; //子系统版本号
    0004
 
    WORD    MinorSubsystemVersion; //子系统小版本号
    0000
 
    DWORD   Win32VersionValue; //Win32版本值 目前看过的文件都是 0
    00000000
 
    DWORD   SizeOfImage;//指定PE image在虚拟内存中所占空间的大小 SectionAlignment的倍数
    00180000
 
    DWORD   SizeOfHeaders; //指出整个PE头的大小(FileAlignment整数倍)
    00000600               //它也是从文件的开头到第一节的原始数据的偏移量, 可以找到第一节区
 
    DWORD   CheckSum; //映象文件的校验和 目的是为了防止载入无论如何都会冲突的、已损坏的二进制文件
    00000000
 
    WORD    Subsystem; //说明映像文件应运行于什么样的NT子系统之上
    0002               //该值用来区分系统驱动文件(*.sys)与普通的可执行文件(*.exe, *.dll)
                       //value: 1   含义: Driver文件  tips: 系统驱动(如: ntfs.sys)
                       //value: 2   含义: GUI文件      tips: 窗口应用程序(如: notepad.exe)
                       //value: 3   含义: CUI文件     tips: 控制台应用程序(如: cmd.exe)
 
    WORD    DllCharacteristics; //DLL的文件属性 如果是DLL文件,何时调用DLL文件的入口点
    0000                        //一般的exe文件有以下2个属性:
                                //IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE(表示支持终端服务器)8000h  IMAGE_DLLCHARACTERISTICS_NX_COMPAT
                                //(表示程序采用了)/NXCOMPAT编译100h  (bit or 81000)
                                //但是开启了ASLR的程序会多一个
                                //IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE(DLL can move)
                                //40h的属性  (bit or 后为8140),那可以修改这里关闭ASLR
 
    DWORD   SizeOfStackReserve; //保留栈的大小 默认是1MB
    00100000
 
    DWORD   SizeOfStackCommit;  //初始时指定栈大小 默认是4KB
    00020000
 
    DWORD   SizeOfHeapReserve;  //保留堆的大小 默认是1MB
    01000000
 
    DWORD   SizeOfHeapCommit;   //指定堆大小 默认是4K
    00001000
 
    DWORD   LoaderFlags; //看到的资料都是保留 value 为 0
    00000000
 
    DWORD   NumberOfRvaAndSizes; //数据目录的项数, 即指出了我们下面一个成员数组的个数
    00000010                     //虽然宏定义了#defineIMAGE_NUMBEROF_DIRECTORY_ENTRIES16
                                 //但是PE装载器会通过此值来识别数组大小,说明数组大小也可能非16
 
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //很重要
                                                    //数据目录, 重点:  EXPORT
                                                    //IMPORT, RESOURCE, TLS Direction
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _DATA_DIRECTORY //定义了DataDirectory的结构体
{
    DOWORD VirtualAddress; //该结构体的RVA
    DOWORD Size;            //该结构体的大小
}IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
 
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
 
typedef struct _IMAGE_OPTIONAL_HEADER  
{
    WORD    Magic; //这个可选头的类型  PE: 10Bh   PE+: 20Bh  可以依次区分是32位还是64
    010B
 
    BYTE    MajorLinkerVersion; //链接器的版本号(不重要)
    05
 
    BYTE    MinorLinkerVersion; //链接器的小版本号(不重要)
    00
 
    DWORD   SizeOfCode; //代码段的长度
    000AF000
 
    DWORD   SizeOfInitializedData; //初始化的数据长度
    0008EC00
 
    DWORD   SizeOfUninitializedData; //未初始化的数据长度
    00000000
 
    DWORD   AddressOfEntryPoint; //程序EP的RVA, 指出程序最先执行代码的起始地址 (很重要)
    00000100
 
    DWORD   BaseOfCode;//代码段起始地址的RVA
    00000100
 
    DWORD   BaseOfData;//数据段起始地址的RVA
    000B0000
 
    DWORD   ImageBase; //VA: 0~FFFFFFFF(32位系统).PE文件加载到虚拟内存时, 指出文件优先装入地址
    00400000           //EXE, DLL文件被装载到0~7FFFFFFF
                       //SYS文件载入内核内存的 80000000~FFFFFFFF
                       //执行PE文件时,PE装载器会把EIP设置为: ImageBase+AddressOfEntrypoint
 
    DWORD   SectionAlignment; //节在内存中的最小单位 (对齐单位) 一般为: 1000h
    00001000
 
    DWORD   FileAlignment; //节在磁盘文件中的最小单位 (对齐单位) 一般为: 200h
    00000200               //一般SectionAlignment <= FileAlignment,节省储存空间.
 
    WORD    MajorOperatingSystemVersion; //操作系统主版本号(不重要)
    0004
 
    WORD    MinorOperatingSystemVersion; //操作系统小版本号(不重要)
    0000
 
    WORD    MajorImageVersion; //映象文件主版本号, 这个是开发者自己指定的,由连接器填写(不重要)
    0000
 
    WORD    MinorImageVersion; //映象文件小版本号(不重要)
    0000
 
    WORD    MajorSubsystemVersion; //子系统版本号
    0004
 
    WORD    MinorSubsystemVersion; //子系统小版本号
    0000
 
    DWORD   Win32VersionValue; //Win32版本值 目前看过的文件都是 0
    00000000
 
    DWORD   SizeOfImage;//指定PE image在虚拟内存中所占空间的大小 SectionAlignment的倍数
    00180000
 
    DWORD   SizeOfHeaders; //指出整个PE头的大小(FileAlignment整数倍)
    00000600               //它也是从文件的开头到第一节的原始数据的偏移量, 可以找到第一节区
 
    DWORD   CheckSum; //映象文件的校验和 目的是为了防止载入无论如何都会冲突的、已损坏的二进制文件
    00000000
 
    WORD    Subsystem; //说明映像文件应运行于什么样的NT子系统之上
    0002               //该值用来区分系统驱动文件(*.sys)与普通的可执行文件(*.exe, *.dll)
                       //value: 1   含义: Driver文件  tips: 系统驱动(如: ntfs.sys)
                       //value: 2   含义: GUI文件      tips: 窗口应用程序(如: notepad.exe)
                       //value: 3   含义: CUI文件     tips: 控制台应用程序(如: cmd.exe)
 
    WORD    DllCharacteristics; //DLL的文件属性 如果是DLL文件,何时调用DLL文件的入口点
    0000                        //一般的exe文件有以下2个属性:
                                //IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE(表示支持终端服务器)8000h  IMAGE_DLLCHARACTERISTICS_NX_COMPAT
                                //(表示程序采用了)/NXCOMPAT编译100h  (bit or 81000)
                                //但是开启了ASLR的程序会多一个
                                //IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE(DLL can move)
                                //40h的属性  (bit or 后为8140),那可以修改这里关闭ASLR
 
    DWORD   SizeOfStackReserve; //保留栈的大小 默认是1MB
    00100000
 
    DWORD   SizeOfStackCommit;  //初始时指定栈大小 默认是4KB
    00020000
 
    DWORD   SizeOfHeapReserve;  //保留堆的大小 默认是1MB
    01000000
 
    DWORD   SizeOfHeapCommit;   //指定堆大小 默认是4K
    00001000
 
    DWORD   LoaderFlags; //看到的资料都是保留 value 为 0
    00000000

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-9-17 23:07 被BIX编辑 ,原因:
上传的附件:
收藏
免费 6
支持
分享
最新回复 (8)
雪    币: 26588
活跃值: (63242)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
2
感谢分享哦
2020-5-20 11:17
0
雪    币: 43
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享!
2020-5-21 16:31
0
雪    币: 2
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享!
2020-6-5 08:41
0
雪    币: 122
活跃值: (360)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
写的不错
2020-6-25 16:44
0
雪    币: 259
活跃值: (283)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
666
2020-6-25 21:57
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
不错666
2020-6-26 16:04
0
雪    币: 118
活跃值: (304)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
我来复习下。谢谢分享
2020-6-27 18:02
0
雪    币: 300
活跃值: (2447)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
赞一下
2020-6-27 18:14
0
游客
登录 | 注册 方可回帖
返回
//