原文链接地址:http://www.cnblogs.com/shadow-lei/p/3554670.html

PE文件定义

PE 文件(”Portable executable”, 可移植的可执行文件)文件格式,是微软Windows NT, 中Win32、Win32s中的可执行的二进制的文件格式。 包括:.exe, .dll, .sys, .com, .ocs. PE文件最重要的两个因素:

1.磁盘上的可执行文件和它被映射到windows内存之后的格式非常相像。

2.对于Win32 来讲, 模块中多使用的所有代码、数据、资源、导入表、和其他需要的模块数据结构都在一个连续的内存块中。因此,只需要知道PE Loader把可执行文件映射到了内存的什么地方(基址),通过作为映像的一部分指针,就可以找到这个模块的所有不同的块。

PE文件总览:

放另一张图:

再放一张:

1. DOS Header: (size:64byte)

_IMAGE_DOS_HEADER结构体:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

结构体中有两个重要的数据成员。第一个为e_magic,这个必须为MZ,即0x5A4D。另一个重要的数据成员是最后一个成员e_lfanew,这个成员的值为IMAGE_NT_HEADERS的偏移。其中,*e_lfanew这个字段的值:   PE Header 在磁盘文件中相对于文件开始的偏移地址.

实例截图:

2.     PE Header: (size: 248bytes)

IMAGE_NT_HEADERS 紧接在DOS Stub之后,位置有e_lfanew所指

typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //4 bytes PE文件头标志:(e_lfanew)->‘PE’
IMAGE_FILE_HEADER FileHeader;//20 bytes PE文件物理分布的信息
IMAGE_OPTIONAL_HEADER32 OptionalHeader;//224bytes PE文件逻辑分布的信息
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

PE Header 总览

IMAGE_NT_HEADERS结构体成员解析:

2.1.Signature: (4 bytes)

2.2.IMAGE_FILE_HEADER(20 bytes)

typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运行平台
WORD NumberOfSections; //文件区块数目
DWORD TimeDateStamp; //文件创建日期和时间
DWORD PointerToSymbolTable; //指向符号表(主要用于调试)
DWORD NumberOfSymbols; //符号表中符号个数
WORD SizeOfOptionalHeader; //IMAGE_OPTIONAL_HEADER32 结构大小
WORD Characteristics; //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

IMAGE_FILE_HEADER结构体成员解析:

1). Machine 代表了CPU的类型  //运行平台
#define IMAGE_FILE_MACHINE_UNKNOWN           0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
………………….
#define IMAGE_FILE_MACHINE_CEE 0xC0EE
2)       NumberOfSections: 代表区块的数目,区块表紧跟在IMAGE_NT_HEADERS后面, 区块表大概是一个链表结构,链表长度由NumberOfSection的数值决定.
3)       TimeDataStamp: 表明文件的创建时间
4)       SizeOfOptionalHeader: 是IMAGE_NT_HEADERS的另一个子结构IMAGE_OPTIONAL_HEADER的大小
5)       Characteristics: 代表文件的属性EXE文件一般是0100h DLL文件一般是210Eh,多种属性可以用或运算同时拥有
#define IMAGE_FILE_RELOCS_STRIPPED   0x0001 // 重定位信息被移除
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // 文件可执行
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // 行号被移除
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // 符号被移除
……..
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32位机器
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // .dbg文件的调试信息被移除
………………….
#define IMAGE_FILE_SYSTEM       0x1000 // 系统文件
#define IMAGE_FILE_DLL         0x2000 // 文件是一个dll
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // 文件只能运行在单处理器上

实例截图:

2.3. IMAGE_OPTIONAL_HEADER(224 bytes)

紧接IMAGE_FILE_HEADER之后,IMAGE_OPTIONAL_HEADER的大小由IMAGE_FILE_HEADER中倒数第二个成员(SizeOfOptionalHeader)指定. IMAGE_OPTIONAL_HEADER结构体如下:

typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //映像文件的状态
BYTE MajorLinkerVersion; //连接器的主版本号
BYTE MinorLinkerVersion; //连接器的次版本号
DWORD SizeOfCode; //代码段的大小,如果有多个代码段则为总和
DWORD SizeOfInitializedData; //初始化数据段大小.如果多个则为总和
DWORD SizeOfUninitializedData;//未初始化数据段大小,.如果多个则为总和.bbs
DWORD AddressOfEntryPoint; //PE文件入口地址的RAV:OEP = ImageBase + RAV
DWORD BaseOfCode; //代码块起始地址的RVA
DWORD BaseOfData;//数据块的起始地址的RVA
//
// NT additional fields.
//
DWORD ImageBase; //可执行文件的基址ImageBase
DWORD SectionAlignment; //每一个块必须保证始于这个值的整数倍
DWORD FileAlignment; //对齐映射文件部分原始数据 2 or 512 or 64: 默认为512
WORD MajorOperatingSystemVersion;//要求的操作系统的主版本号
WORD MinorOperatingSystemVersion;//要求的操作系统的次版本号
WORD MajorImageVersion;//映像的主版本号
WORD MinorImageVersion;//映像的次版本号
WORD MajorSubsystemVersion;//子系统的主版本号
WORD MinorSubsystemVersion;//子系统的次版本号
DWORD Win32VersionValue;//保留值.必须为0
DWORD SizeOfImage;//映像文件的大小
DWORD SizeOfHeaders;
DWORD CheckSum;//映像文件的校验和
WORD Subsystem;//运行此映像的字系统
WORD DllCharacteristics;//映像文件的DLL特征
DWORD SizeOfStackReserve;//堆栈保留字节. 0x100000
DWORD SizeOfStackCommit;//线程开始提交的初始堆栈大小
DWORD SizeOfHeapReserve;//为初始进程保留的虚拟内存总数
DWORD SizeOfHeapCommit;//进程开始提交初始虚拟内存的大小
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //0x10
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
//指向第一个IMAGE_DATA_DIRECTORY
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

_IMAGE_OPTIONAL_HEADER结构体实例截图以及成员解析:

1) Magic:32位可执行文件来:0x010B

   64位可执行文件来:0x020B

   0x107

2) SizeOfCode: 代码段的大小,如果有多个代码段则为总和

RAW:经过文件对齐处理后大小(PE文件中的大小)

3) SizeOfInitializedData: 初始化数据段大小.如果多个则为总和

4) ImageBase: 建议的装载地址

PE建议装载地址:

实际装载地址:

5) AddressOfEntryPoint: 程序执行的入口RVA地址

OEP = ImageBase + (AddressOfEntryPoint)RVA

6) SectionAlignment:为内存中节的对齐大小,一般为0×00001000

7) FileAlignment:为PE文件中节的对齐大小

8)  SizeofImage:映像文件的大小

9) DataDirectory为数据目录表数组,比较重要:共有16个表项

Size = sizeof(_IMAGE_DATA_DIRECTORY) * 16

sizeof(_IMAGE_DATA_DIRECTORY) = 8 bytes

_IMAGE_DATA_DIRECTORY结构体以及成员定义:

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
// Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor

Import Table RVA & Size截图:

3. Section Header:

Section Header 数量:_IMAGE_FILE_HEADER结构体中NumberOfSections成员。

Section Header 定位:紧跟在IMAGE_NT_HEADERS后面

结构体大小:40 bytes

_IMAGE_SECTION_HEADER 结构体以及重要变量的定义:

#define IMAGE_SIZEOF_SHORT_NAME   8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; //内存中偏移地址
DWORD SizeOfRawData; //PE文件中对其之后的大小
DWORD PointerToRawData;//为PE块区在PE文件中偏移
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //块区的属性:可读、可写..
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER 40

重要数据成员

1)      Name[IMAGE_SIZEOF_SHORT_NAME]:

8字节大小的NAME, 如果节区名称小于8个字节,则多余的用0填充,否则全部填充节名,末尾不保证有1个0,同样会被名字填充

2)      PointerToRawData:

为节区在PE文件中的偏移

3)      Characteristics: 为节区的属性,如可读、可写、可执行等

#define IMAGE_SCN_CNT_CODE       0x00000020  // Section contains code.
#define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 // Section contains extended relocations.
#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // Section can be discarded.
#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // Section is not cachable.
#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 // Section is not pageable.
#define IMAGE_SCN_MEM_SHARED 0x10000000 // Section is shareable.
#define IMAGE_SCN_MEM_EXECUTE 0x20000000 // Section is executable.
#define IMAGE_SCN_MEM_READ 0x40000000 // Section is readable.

Section Header 实例截图:

4.  导入表

_IMAGE_IMPORT_DESCRIPTOR 数据结构:(20 bytes)

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA 指向INT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp;
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; //dll 名称
DWORD FirstThunk; //指向引入函数真实地址单元处的RVA IAT
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

其中OriginalFirstThunk和FirstThunk非常类似,指向两个本质上相同的数组IMAGE_THUNK_DATA。

1) 定位查找IMAGE_IMPORT_DESCRIPTO结构

插入另一图

A 获取引入表的RVA.也就是

data directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress 所指的值

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory

查看 IMAGE_DIRECTORY_ENTRY_IMPORT的值

&data directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = 0x0002D51C

&data directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualSize= 0xA0

B. 查找导入表所在的区段:

if(RVA>=SECTION.VirtualAddress && RVA<SECTION.Misc.VirtualSize)

{

     //处于该节

}

else

{

     //不在该节中

}

C. 找到引入表所在的节后就可以用该节的VirtualAddress和PointerToRawData两个域确定引入表在文件中的偏移量:RVA –△k

VirtualAddress = 0x00026000

PointerToRawData = 0x00025000

△k = VirtualAddress – PointerToRawData = 0x1000H

Address = 0x0002D51C - △k = 0x0002C51C

然后定位到文件偏移处:

阴影部分是IID的内容, IID的大小为20h, 阴影部分存在链各个IID,最后一个为0000000, 说明此PE文件只有8个IID, 对应8个dll。

0xA0  = 160 bytes = 7 * 20bytes +20bytes(空白)

截图

第四个变量的地址RVA:0002DB14, 需要转换成对应的文件偏移

(0x0002DB14 –△k) = 0x0002cb14,定位到文件偏移为0x0002cb14的地方

查看内容,里面记录的是IMAGE_IMPORT_DESCRIPTOR的第四个成员变量多对应的dll的名字,mfc90u.dll,到此,我们已经找到了这个输入的dll

2) 获取dll调用的所有函数

IMAGE_IMPORT_DESCRIPTOR中的第一个参数和最后一个参数,original_first_thunk 和first_thunk分别指向了INT(输入名称表)IAT(输入地址表)这两个表里面分别记录了指向调用函数名的地址,和此函数在dll中的序号(序号用来快速索引dll中的函数)

0x0002D85C和0x000262A0是INT 和IAT数组的首地址,下面我们跳到该地址(由于△K=0x1000,故RVA=文件偏移0x0002C85C和0x000252A0)

_IMAGE_THUNK_DATA32数据结构:

typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal; //数据
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

a.当一个函数以序号导入.MAGE_THUNK_DATA结构中的AddressOfData最高位被设成1.用16进制表示为(0x80000000),例入一个IMAGE_THUNK_DATA的AddressOfData的值为0x 800010E4在mfc90u.dll数组中.就表示IMAGE_THUNK_DATA将引入mfc90u.dll

中的第10E4号函数

INT数组:

IAT数组:

b.如果一个函数以名称导入.IMAGE_THUNK_DATA结构中的Ordinal域就包含一个RVA.这个RVA指向一个IMAGE_IMPORT_BY_NAME 结构.该结构保存了一个引入函数的相关信息:例如MSVCR90.dll

typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //序列号
BYTE Name[1];//函数名称
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

有第四个变量Name定位dll名字

定位INT和IAT:

INT:

IAT

数组里面的值为指向IMAGE_IMPORT_BY_NAME的地址,IMAGE_IMPORT_BY_NAME里面存放的是所调用函数的名字的地址,下来我们选取一个数组里面的值,跳转到相应的IMAGE_IMPORT_BY_NAME

可以看到,上面的那个图片中显示了所调用的函数的名字,名字前面的两个字节代表的是函数在dll中的序号,方便以后快速索引到

3. 需要注意的地方

INT 和IAT数组在一开始的时候,里面存放的地址都是一样的,他们都是指向所调用函数的名字的字符串。而在加载到内存的时候,IAT的值会发生变换,它的值存放的是dll中函数实际被调用的地址,在加载到内存后,就只需要保存IAT就可以了,利用它来调用函数

方另一张图

PE文件结构部分解析以及输入的定位的更多相关文章

  1. PE文件加节感染之Win32.Loader.bx.V病毒分析

    一.病毒名称:Win32.Loader.bx.V 二.分析工具:IDA 5.5.OllyDebug.StudPE 三.PE文件加节感染病毒简介 PE病毒感染的方式比较多,也比较复杂也比较难分析,下面就 ...

  2. PE格式文件的解析代码

    #include "Global.h" ; //标志,用于表示是否为pe32+文件 ; //标志,用于表示读入的模式,若为0代表是内存读入,不为0,代表是文件打开,此时mode是文 ...

  3. C++PE文件格式解析类(轻松制作自己的PE文件解析器)

    PE是Portable Executable File Format(可移植的运行体)简写,它是眼下Windows平台上的主流可运行文件格式. PE文件里包括的内容非常多,详细我就不在这解释了,有兴趣 ...

  4. 解析PE文件

    最近在自学解析PE文件,根据小辣椒(CFF Explorer)以及各论坛上大佬的帖子,做了个黑屏打印PE文件的,历时7天完成,在此想跟有相关需要的同学们分享下思路,有不足之处也希望大家不吝赐教,指点出 ...

  5. PE文件解析器的编写(二)——PE文件头的解析

    之前在学习PE文件格式的时候,是通过自己查看各个结构,自己一步步计算各个成员在结构中的偏移,然后在计算出其在文件中的偏移,从而找到各个结构的值,但是在使用C语言编写这个工具的时候,就比这个方便的多,只 ...

  6. 解析PE文件的附加数据

    解析程序自己的附加数据,将附加数据写入文件里. 主要是解析PE文件头.定位到overlay的地方.写入文件. 常应用的场景是在crackme中,crackme自身有一段加密过的附加数据.在crackm ...

  7. PE文件 03 重定位表

    0x01  重定位表结构   重定位表是由数据目录表中的第六个成员指出的: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; D ...

  8. PE文件解析 基础篇

    PE文件解析 基础篇 来源 https://bbs.pediy.com/thread-247114.htm 前言 之前学习了PE格式,为了更好的理解,决定写一个类似LoadPE的小工具. 编译器是VS ...

  9. PE文件中的输入表

    前言 PE文件中的输入表含有三个重要结构IID,IDT,IAT.PE文件为需要加载的DLL文件创建一个IID结构,一个DLL与一个IID对应.IDT是输入名称表,IAT输入地址表,在没有绑定输入的情况 ...

随机推荐

  1. IOS开发UI基础之UIButton

    什么是按钮?

  2. 用cryptico.js实现RSA加密(应对cryptico不支持PEM)

    问题: 随手分享一下好了,这个问题困扰了很久. cryptico.js这个加密算法库很全,很适合在前端用到各种加密解密算法的需求.但是美中不足的是,它的RSA加密不支持PEM格式,所以如果你后端用ja ...

  3. Qt Style Sheet实践(三):QCheckBox和QRadioButton

    导读 单选按钮(QRadioButton)和复选框(QCheckBox)是界面设计中的重要元素.单选按钮只允许用户在一组选项中选择一个,且当其中一个被选中的时候,按钮组中的其他单选按钮自动取消.复选框 ...

  4. Ubuntu系统操作快捷键

    Ubuntu操作基本快捷键* 打开主菜单 = Alt + F1* 运行 = Alt + F2* 显示桌面 = Ctrl + Alt + d* 最小化当前窗口 = Alt + F9* 最大化当前窗口 = ...

  5. asp.net的code-Behind技术

    新建一个VS.NET下的项目..看到ASPX,RESX和CS三个后缀的文件了吗??这个就是代码分离.实现了HTML代码和服务器代码分离.方便代码编写和整理. code-Behind:asp.net中的 ...

  6. Python入门笔记(23):模块

    一.模块基础 1.模块 自我包含,且有组织的代码片段就是模块 模块是Pyhon最高级别的程序组织单元,它将程序代码和数据封装起来以便重用.实际的角度,模块往往对应Python程序文件. 每个文件都是一 ...

  7. 批量插入数据 C# SqlBulkCopy使用

    转自:http://blog.csdn.net/wangzh300/article/details/7382506 private static void DataTableToSQLServer( ...

  8. 具有timeout 功能的函数调用

    做项目的时候有时经常会需要一个带有timeout功能的函数调用. 比如从后台读数据并期望在给定时间内返回.借此机会包装了一个简单的C# class, 直接上代码吧. public class Time ...

  9. 重新想象 Windows 8 Store Apps (40) - 剪切板: 复制/粘贴文本, html, 图片, 文件

    [源码下载] 重新想象 Windows 8 Store Apps (40) - 剪切板: 复制/粘贴文本, html, 图片, 文件 作者:webabcd 介绍重新想象 Windows 8 Store ...

  10. javascript类的理解和使用

    距离上次写博客已经过去好几个月了,现在手里的项目正好都结束了,闲暇之后开始理一下开发中一些问题,这次说一下javascript当中的类,可能很多人对于写惯了前台页面效果的coder来说,对于javas ...