PE文件结构部分解析以及输入的定位
原文链接地址: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文件结构部分解析以及输入的定位的更多相关文章
- PE文件加节感染之Win32.Loader.bx.V病毒分析
一.病毒名称:Win32.Loader.bx.V 二.分析工具:IDA 5.5.OllyDebug.StudPE 三.PE文件加节感染病毒简介 PE病毒感染的方式比较多,也比较复杂也比较难分析,下面就 ...
- PE格式文件的解析代码
#include "Global.h" ; //标志,用于表示是否为pe32+文件 ; //标志,用于表示读入的模式,若为0代表是内存读入,不为0,代表是文件打开,此时mode是文 ...
- C++PE文件格式解析类(轻松制作自己的PE文件解析器)
PE是Portable Executable File Format(可移植的运行体)简写,它是眼下Windows平台上的主流可运行文件格式. PE文件里包括的内容非常多,详细我就不在这解释了,有兴趣 ...
- 解析PE文件
最近在自学解析PE文件,根据小辣椒(CFF Explorer)以及各论坛上大佬的帖子,做了个黑屏打印PE文件的,历时7天完成,在此想跟有相关需要的同学们分享下思路,有不足之处也希望大家不吝赐教,指点出 ...
- PE文件解析器的编写(二)——PE文件头的解析
之前在学习PE文件格式的时候,是通过自己查看各个结构,自己一步步计算各个成员在结构中的偏移,然后在计算出其在文件中的偏移,从而找到各个结构的值,但是在使用C语言编写这个工具的时候,就比这个方便的多,只 ...
- 解析PE文件的附加数据
解析程序自己的附加数据,将附加数据写入文件里. 主要是解析PE文件头.定位到overlay的地方.写入文件. 常应用的场景是在crackme中,crackme自身有一段加密过的附加数据.在crackm ...
- PE文件 03 重定位表
0x01 重定位表结构 重定位表是由数据目录表中的第六个成员指出的: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; D ...
- PE文件解析 基础篇
PE文件解析 基础篇 来源 https://bbs.pediy.com/thread-247114.htm 前言 之前学习了PE格式,为了更好的理解,决定写一个类似LoadPE的小工具. 编译器是VS ...
- PE文件中的输入表
前言 PE文件中的输入表含有三个重要结构IID,IDT,IAT.PE文件为需要加载的DLL文件创建一个IID结构,一个DLL与一个IID对应.IDT是输入名称表,IAT输入地址表,在没有绑定输入的情况 ...
随机推荐
- Spring基础—— SpEL
一.SpEL:Spring 表达式语言,在使用的时候类似于 EL 表达式,但是需要注意的是,SpEL 使用在 Spring Config 文件中. 二.格式:使用 #{} 作为界定符,所有在大括号中的 ...
- Use the PDFs below or the HTML contents to the left to install and configure P6 EPPM and its additional components.
Welcome to Your Documentation Use the PDFs below or the HTML contents to the left to install and c ...
- C#设计模式——组合模式(Composite Pattern)
一.概述 在软件开发中,我们往往会遇上类似树形结构的对象体系.即某一对象既可能在树形结构中作为叶节点存在,也可能作为分支节点存在.比如在文件系统中,文件是作为叶节点存在,而文件夹就是分支节点.在设计这 ...
- js定时器调用参数的方法
var userName="Tony"; //根据用户名显示欢迎信息 function ss(_name){ alert("ss,"+_name); } 使用字 ...
- 【jQuery基础学习】09 jQuery与前端(这章很水)
这章主要是将如何将jQuery应用到网站中,或者说其实就是一些前端知识,对于我这种后端程序来说其实还是蛮有用的. 关于网站结构 文件结构 前端文件分三个文件夹放 images文件夹用来存放将要用到的图 ...
- Studio for WPF:使用 C1TileView 创建图片库
C1TileView 提供了数据交互浏览的功能.允许我们设置最大化和最小化浏览模板,我们可以通过最小化模板快速定位详细浏览选项. 下面我们分步分享实现方法: 1.添加 C1TileView 到窗体,并 ...
- Verilog学习笔记基本语法篇(十)········ 常用系统函数
$display 和 $write 任务 格式: $display (p1,p2,...,pn); $write (p1,p2,..,pn); 这两个函数和系统的任务作用是用来输出信息,即将参数p2到 ...
- 六个创建模式之工厂方法模式(Factory Method Pattern)
问题: 在使用简单工厂模式的时候,如果添加新的产品类,则必需修改工厂类,违反了开闭原则. 定义: 定义一个用于创建对象的接口,让子类决定具体实例化哪个产品类.此时工厂和产品都具有相同的继承结构,抽象产 ...
- Selenium&EmguCV实现爬虫图片识别
概述 爬虫需要抓取网站价格,与一般抓取网页区别的是抓取内容是通过AJAX加载,并且价格是通过CSS背景图片显示的. 每一个数字对应一个样式,如'p_h57_5' .p_h57_5 { backgrou ...
- .NET破解之PDFdo转换器
无意中看到一个PDF转换器,叫PDFdo,看起了功能挺多的,于是想把它破了. 下载 官网:http://www.pdfdo.com/ 安装 安装后,只有一个exe应用程序,如果是.NET 程序应该有很 ...