debug/pe - PE 文件格式
概述
debug/pe 包提供了对 PE(Portable Executable)文件格式的读取支持。
PE 是什么:
- 📋 Windows 标准格式:Windows 系统的可执行文件格式
- 🔧 多种文件类型:可执行文件(.exe)、动态库(.dll)、目标文件(.obj)、驱动程序(.sys)
- 📦 包含多个节:代码节、数据节、资源节、符号表等
- 🛠️ Windows 平台专用:用于 Windows NT 及后续版本
主要用途:
- 🔍 分析 Windows 可执行文件:读取节、符号、导入导出表
- 🛠️ 链接器开发:处理目标文件和符号解析
- 📊 二进制分析:提取程序结构信息
- 🔐 安全工具:检查二进制文件完整性、恶意软件分析
- 🐛 调试工具:配合调试器使用
重要说明:
- ⚠️ 只读访问:仅用于读取 PE 文件
- ⚠️ 底层格式:需要了解 PE 规范
- ⚠️ Windows 平台:主要用于 Windows 系统
- ✅ 标准库支持:Go 标准库提供完整支持
PE 文件结构
PE 文件布局
+------------------+
| DOS Header | <- DOS 文件头(64 字节)
+------------------+
| DOS Stub | <- DOS 存根程序(可选)
+------------------+
| PE Signature | <- PE 签名("PE\0\0")
+------------------+
| COFF File Header | <- COFF 文件头
+------------------+
| Optional Header | <- 可选头(PE32 或 PE32+)
+------------------+
| Section Table | <- 节表
+------------------+
| Section 1 | <- 各个节(.text、.data 等)
| Section 2 |
| ... |
+------------------+
| Symbol Table | <- 符号表(可选)
| String Table | <- 字符串表(可选)
+------------------+
DOS Header 结构
e_magic: 2 bytes - 魔术数字(0x5A4D = "MZ")
e_cblp: 2 bytes - 最后页大小
e_cp: 2 bytes - 页数
e_crlc: 2 bytes - 重定位数量
...
e_lfanew: 4 bytes - PE 头偏移量
PE Header 结构
Signature: 4 bytes - PE 签名(0x00004550 = "PE\0\0")
Machine: 2 bytes - 目标机器类型
NumberOfSections: 2 bytes - 节数量
TimeDateStamp: 4 bytes - 时间戳
PointerToSymbolTable: 4 bytes - 符号表偏移
NumberOfSymbols: 4 bytes - 符号数量
SizeOfOptionalHeader: 2 bytes - 可选头大小
Characteristics: 2 bytes - 文件标志
Optional Header 结构(PE32)
Magic: 2 bytes - 魔术数字(0x10b = PE32, 0x20b = PE32+)
...
AddressOfEntryPoint: 4 bytes - 入口点 RVA
ImageBase: 4 bytes - 首选加载地址
...
DataDirectories: 16 entries - 数据目录
核心类型
1. File - PE 文件
type File struct {
FileHeader
OptionalHeader interface{} // *OptionalHeader32 或 *OptionalHeader64
Sections []*Section
Symbols []Symbol
COFFSymbols []COFFSymbol
StringTable []byte
// 包含过滤或未导出的字段
}
功能:表示打开的 PE 文件。
字段说明:
FileHeader:COFF 文件头OptionalHeader:可选头(32 位或 64 位)Sections:节列表Symbols:符号列表COFFSymbols:COFF 符号列表StringTable:字符串表
主要方法:
// 打开文件
func Open(name string) (*File, error)
func NewFile(r io.ReaderAt) (*File, error)
// 关闭文件
func (f *File) Close() error
// 获取节
func (f *File) Section(name string) *Section
// 获取节(通过索引)
func (f *File) SectionByIndex(index int) (*Section, error)
// 获取符号
func (f *File) Symbols() ([]Symbol, error)
// 获取导入的库
func (f *File) ImportedLibraries() ([]string, error)
func (f *File) ImportedSymbols() ([]string, error)
// 获取 DWARF 数据
func (f *File) DWARF() (*dwarf.Data, error)
注意事项:
- ⚠️ PE 文件可能有多个节(最多 65535 个)
- ⚠️ 需要区分 PE32 和 PE32+ 格式
- ✅ 提供完整的符号表和导入导出表解析
2. FileHeader - 文件头
type FileHeader struct {
Machine uint16
NumberOfSections uint16
TimeDateStamp uint32
PointerToSymbolTable uint32
NumberOfSymbols uint32
SizeOfOptionalHeader uint16
Characteristics uint16
}
字段说明:
Machine:目标机器类型(如 x86、x64、ARM)NumberOfSections:节数量TimeDateStamp:编译时间戳(Unix 时间)PointerToSymbolTable:符号表文件偏移NumberOfSymbols:符号数量SizeOfOptionalHeader:可选头大小Characteristics:文件标志(可重定位、可执行等)
3. OptionalHeader32 - 可选头(32 位)
type OptionalHeader32 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
BaseOfData uint32
ImageBase uint32
SectionAlignment uint32
FileAlignment uint32
MajorOperatingSystemVersion uint16
MinorOperatingSystemVersion uint16
MajorImageVersion uint16
MinorImageVersion uint16
MajorSubsystemVersion uint16
MinorSubsystemVersion uint16
Win32VersionValue uint32
SizeOfImage uint32
SizeOfHeaders uint32
CheckSum uint32
Subsystem uint16
DllCharacteristics uint16
SizeOfStackReserve uint32
SizeOfStackCommit uint32
SizeOfHeapReserve uint32
SizeOfHeapCommit uint32
LoaderFlags uint32
NumberOfRvaAndSizes uint32
DataDirectory [16]DataDirectory
}
重要字段:
Magic:PE32(0x10b)或 PE32+(0x20b)AddressOfEntryPoint:程序入口点 RVAImageBase:首选加载地址SectionAlignment:节对齐大小FileAlignment:文件对齐大小Subsystem:子系统类型(GUI、Console 等)DataDirectory:数据目录(导入表、导出表等)
4. OptionalHeader64 - 可选头(64 位)
type OptionalHeader64 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
ImageBase uint64 // 64 位地址
SectionAlignment uint32
FileAlignment uint32
MajorOperatingSystemVersion uint16
MinorOperatingSystemVersion uint16
MajorImageVersion uint16
MinorImageVersion uint16
MajorSubsystemVersion uint16
MinorSubsystemVersion uint16
Win32VersionValue uint32
SizeOfImage uint32
SizeOfHeaders uint32
CheckSum uint32
Subsystem uint16
DllCharacteristics uint16
SizeOfStackReserve uint64 // 64 位大小
SizeOfStackCommit uint64
SizeOfHeapReserve uint64
SizeOfHeapCommit uint64
LoaderFlags uint32
NumberOfRvaAndSizes uint32
DataDirectory [16]DataDirectory
}
与 32 位的区别:
ImageBase:64 位地址SizeOfStackReserve/Commit:64 位大小SizeOfHeapReserve/Commit:64 位大小- 没有
BaseOfData字段
5. Section - 节
type Section struct {
SectionHeader
io.ReaderAt
}
功能:表示 PE 文件中的一个节。
字段说明:
SectionHeader:节头信息io.ReaderAt:用于读取节内容
主要方法:
// 读取节数据
func (s *Section) Data() ([]byte, error)
// 读取重定位
func (s *Section) Relocs() ([]Reloc, error)
// 读取符号
func (s *Section) Symbols() ([]Symbol, error)
6. SectionHeader - 节头
type SectionHeader struct {
Name [8]byte
VirtualSize uint32
VirtualAddress uint32
SizeOfRawData uint32
PointerToRawData uint32
PointerToRelocations uint32
PointerToLinenumbers uint32
NumberOfRelocations uint16
NumberOfLinenumbers uint16
Characteristics uint32
}
字段说明:
Name:节名称(8 字节,如.text、.data)VirtualSize:内存中的实际大小VirtualAddress:加载到内存时的 RVASizeOfRawData:文件中的大小PointerToRawData:文件偏移PointerToRelocations:重定位表偏移NumberOfRelocations:重定位数量Characteristics:节标志(可读、可写、可执行等)
7. Symbol - 符号
type Symbol struct {
Name string
Value uint32
SectionNumber int16
Type uint16
StorageClass uint8
NumAuxSymbols int
}
字段说明:
Name:符号名称Value:符号值(地址)SectionNumber:所在节索引Type:符号类型StorageClass:存储类别NumAuxSymbols:辅助符号数量
存储类别常量:
IMAGE_SYM_CLASS_END_OF_FUNCTION = 0xFF
IMAGE_SYM_CLASS_NULL = 0x00
IMAGE_SYM_CLASS_AUTOMATIC = 0x01
IMAGE_SYM_CLASS_EXTERNAL = 0x02
IMAGE_SYM_CLASS_STATIC = 0x03
IMAGE_SYM_CLASS_FUNCTION = 0x20
IMAGE_SYM_CLASS_FILE = 0x67
IMAGE_SYM_CLASS_SECTION = 0x68
IMAGE_SYM_CLASS_WEAK_EXTERNAL = 0x6F
8. COFFSymbol - COFF 符号
type COFFSymbol struct {
Name [8]byte
Value uint32
SectionNumber int16
Type uint16
StorageClass uint8
NumAuxSymbols int
}
功能:表示 COFF 格式的符号。
辅助方法:
// 获取符号名称(支持长名称)
func (s *COFFSymbol) FullName(StringTable []byte) (string, error)
9. DataDirectory - 数据目录
type DataDirectory struct {
VirtualAddress uint32
Size uint32
}
功能:表示数据目录项。
常见数据目录:
0: IMAGE_DIRECTORY_ENTRY_EXPORT // 导出表
1: IMAGE_DIRECTORY_ENTRY_IMPORT // 导入表
2: IMAGE_DIRECTORY_ENTRY_RESOURCE // 资源表
3: IMAGE_DIRECTORY_ENTRY_EXCEPTION // 异常表
4: IMAGE_DIRECTORY_ENTRY_SECURITY // 安全表
5: IMAGE_DIRECTORY_ENTRY_BASERELOC // 重定位表
6: IMAGE_DIRECTORY_ENTRY_DEBUG // 调试信息
7: IMAGE_DIRECTORY_ENTRY_ARCHITECTURE // 架构特定
8: IMAGE_DIRECTORY_ENTRY_GLOBALPTR // 全局指针
9: IMAGE_DIRECTORY_ENTRY_TLS // TLS 表
10: IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG // 加载配置
11: IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT // 绑定导入
12: IMAGE_DIRECTORY_ENTRY_IAT // 导入地址表
13: IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT // 延迟导入
14: IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR // COM 描述符
常量定义
Machine - 机器类型
const (
IMAGE_FILE_MACHINE_UNKNOWN uint16 = 0x0
IMAGE_FILE_MACHINE_I386 uint16 = 0x14c // x86
IMAGE_FILE_MACHINE_R3000 uint16 = 0x162 // MIPS
IMAGE_FILE_MACHINE_R4000 uint16 = 0x166 // MIPS
IMAGE_FILE_MACHINE_R10000 uint16 = 0x168 // MIPS
IMAGE_FILE_MACHINE_WCEMIPSV2 uint16 = 0x169 // MIPS
IMAGE_FILE_MACHINE_ALPHA uint16 = 0x184 // Alpha
IMAGE_FILE_MACHINE_SH3 uint16 = 0x1a2 // SuperH
IMAGE_FILE_MACHINE_SH3DSP uint16 = 0x1a3 // SuperH DSP
IMAGE_FILE_MACHINE_SH3E uint16 = 0x1a4 // SuperH 3E
IMAGE_FILE_MACHINE_SH4 uint16 = 0x1a6 // SuperH 4
IMAGE_FILE_MACHINE_SH5 uint16 = 0x1a8 // SuperH 5
IMAGE_FILE_MACHINE_ARM uint16 = 0x1c0 // ARM
IMAGE_FILE_MACHINE_THUMB uint16 = 0x1c2 // ARM Thumb
IMAGE_FILE_MACHINE_ARMNT uint16 = 0x1c4 // ARM Thumb-2
IMAGE_FILE_MACHINE_AM33 uint16 = 0x1d3 // AM33
IMAGE_FILE_MACHINE_POWERPC uint16 = 0x1f0 // PowerPC
IMAGE_FILE_MACHINE_POWERPCFP uint16 = 0x1f1 // PowerPC FP
IMAGE_FILE_MACHINE_IA64 uint16 = 0x200 // Itanium
IMAGE_FILE_MACHINE_MIPS16 uint16 = 0x266 // MIPS 16
IMAGE_FILE_MACHINE_ALPHA64 uint16 = 0x284 // Alpha 64
IMAGE_FILE_MACHINE_MIPSFPU uint16 = 0x366 // MIPS FPU
IMAGE_FILE_MACHINE_MIPSFPU16 uint16 = 0x466 // MIPS FPU 16
IMAGE_FILE_MACHINE_AXP64 uint16 = 0x284 // AXP 64
IMAGE_FILE_MACHINE_TRICORE uint16 = 0x520 // TriCore
IMAGE_FILE_MACHINE_CEF uint16 = 0xcef // CEF
IMAGE_FILE_MACHINE_EBC uint16 = 0xebc // EBC
IMAGE_FILE_MACHINE_AMD64 uint16 = 0x8664 // x86-64
IMAGE_FILE_MACHINE_M32R uint16 = 0x9041 // M32R
IMAGE_FILE_MACHINE_ARM64 uint16 = 0xaa64 // ARM 64
IMAGE_FILE_MACHINE_CEE uint16 = 0xc0ee // CEE
)
Characteristics - 文件标志
const (
IMAGE_FILE_RELOCS_STRIPPED uint16 = 0x0001
IMAGE_FILE_EXECUTABLE_IMAGE uint16 = 0x0002
IMAGE_FILE_LINE_NUMS_STRIPPED uint16 = 0x0004
IMAGE_FILE_LOCAL_SYMS_STRIPPED uint16 = 0x0008
IMAGE_FILE_AGGRESIVE_WS_TRIM uint16 = 0x0010
IMAGE_FILE_LARGE_ADDRESS_AWARE uint16 = 0x0020
IMAGE_FILE_BYTES_REVERSED_LO uint16 = 0x0080
IMAGE_FILE_32BIT_MACHINE uint16 = 0x0100
IMAGE_FILE_DEBUG_TRACED uint16 = 0x0200
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP uint16 = 0x0400
IMAGE_FILE_NET_RUN_FROM_SWAP uint16 = 0x0800
IMAGE_FILE_SYSTEM uint16 = 0x1000
IMAGE_FILE_DLL uint16 = 0x2000
IMAGE_FILE_UP_SYSTEM_ONLY uint16 = 0x4000
IMAGE_FILE_BYTES_REVERSED_HI uint16 = 0x8000
)
Section Characteristics - 节标志
const (
IMAGE_SCN_TYPE_NO_PAD uint32 = 0x00000008
IMAGE_SCN_CNT_CODE uint32 = 0x00000020
IMAGE_SCN_CNT_INITIALIZED_DATA uint32 = 0x00000040
IMAGE_SCN_CNT_UNINITIALIZED_DATA uint32 = 0x00000080
IMAGE_SCN_LNK_OTHER uint32 = 0x00000100
IMAGE_SCN_LNK_INFO uint32 = 0x00000200
IMAGE_SCN_LNK_REMOVE uint32 = 0x00000800
IMAGE_SCN_LNK_COMDAT uint32 = 0x00001000
IMAGE_SCN_NO_DEFER_SPEC_EXC uint32 = 0x00004000
IMAGE_SCN_GPREL uint32 = 0x00008000
IMAGE_SCN_MEM_FARDATA uint32 = 0x00008000
IMAGE_SCN_MEM_PURGEABLE uint32 = 0x00020000
IMAGE_SCN_MEM_16BIT uint32 = 0x00020000
IMAGE_SCN_MEM_LOCKED uint32 = 0x00040000
IMAGE_SCN_MEM_PRELOAD uint32 = 0x00080000
IMAGE_SCN_ALIGN_1BYTES uint32 = 0x00100000
IMAGE_SCN_ALIGN_2BYTES uint32 = 0x00200000
IMAGE_SCN_ALIGN_4BYTES uint32 = 0x00300000
IMAGE_SCN_ALIGN_8BYTES uint32 = 0x00400000
IMAGE_SCN_ALIGN_16BYTES uint32 = 0x00500000
IMAGE_SCN_ALIGN_32BYTES uint32 = 0x00600000
IMAGE_SCN_ALIGN_64BYTES uint32 = 0x00700000
IMAGE_SCN_ALIGN_128BYTES uint32 = 0x00800000
IMAGE_SCN_ALIGN_256BYTES uint32 = 0x00900000
IMAGE_SCN_ALIGN_512BYTES uint32 = 0x00A00000
IMAGE_SCN_ALIGN_1024BYTES uint32 = 0x00B00000
IMAGE_SCN_ALIGN_2048BYTES uint32 = 0x00C00000
IMAGE_SCN_ALIGN_4096BYTES uint32 = 0x00D00000
IMAGE_SCN_ALIGN_8192BYTES uint32 = 0x00E00000
IMAGE_SCN_ALIGN_MASK uint32 = 0x00F00000
IMAGE_SCN_LNK_NRELOC_OVFL uint32 = 0x01000000
IMAGE_SCN_MEM_DISCARDABLE uint32 = 0x02000000
IMAGE_SCN_MEM_NOT_CACHED uint32 = 0x04000000
IMAGE_SCN_MEM_NOT_PAGED uint32 = 0x08000000
IMAGE_SCN_MEM_SHARED uint32 = 0x10000000
IMAGE_SCN_MEM_EXECUTE uint32 = 0x20000000
IMAGE_SCN_MEM_READ uint32 = 0x40000000
IMAGE_SCN_MEM_WRITE uint32 = 0x80000000
)
Subsystem - 子系统类型
const (
IMAGE_SUBSYSTEM_UNKNOWN uint16 = 0
IMAGE_SUBSYSTEM_NATIVE uint16 = 1
IMAGE_SUBSYSTEM_WINDOWS_GUI uint16 = 2
IMAGE_SUBSYSTEM_WINDOWS_CUI uint16 = 3
IMAGE_SUBSYSTEM_OS2_CUI uint16 = 5
IMAGE_SUBSYSTEM_POSIX_CUI uint16 = 7
IMAGE_SUBSYSTEM_NATIVE_WINDOWS uint16 = 8
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI uint16 = 9
IMAGE_SUBSYSTEM_EFI_APPLICATION uint16 = 10
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER uint16 = 11
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER uint16 = 12
IMAGE_SUBSYSTEM_EFI_ROM uint16 = 13
IMAGE_SUBSYSTEM_XBOX uint16 = 14
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION uint16 = 16
)
Magic - 魔术数字
const (
IMAGE_NT_OPTIONAL_HDR32_MAGIC uint16 = 0x10b // PE32
IMAGE_NT_OPTIONAL_HDR64_MAGIC uint16 = 0x20b // PE32+
IMAGE_ROM_OPTIONAL_HDR_MAGIC uint16 = 0x107 // ROM
)
常见节名称
.text // 代码节
.data // 已初始化数据节
.bss // 未初始化数据节
.rdata // 只读数据节
.rsrc // 资源节
.reloc // 重定位节
.idata // 导入表节
.edata // 导出表节
.tls // TLS 节
.pdata // 异常处理信息
.debug // 调试信息
完整示例
示例 1:打开和读取 PE 文件
package main
import (
"debug/pe"
"fmt"
"log"
)
func main() {
// 1. 打开 PE 文件
f, err := pe.Open("myprogram.exe")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 2. 显示文件头信息
fmt.Printf("机器类型:0x%x\n", f.Machine)
fmt.Printf("节数量:%d\n", f.NumberOfSections)
fmt.Printf("时间戳:%d\n", f.TimeDateStamp)
fmt.Printf("符号表偏移:0x%x\n", f.PointerToSymbolTable)
fmt.Printf("符号数量:%d\n", f.NumberOfSymbols)
fmt.Printf("可选头大小:%d\n", f.SizeOfOptionalHeader)
fmt.Printf("特征:0x%x\n", f.Characteristics)
// 3. 显示可选头信息
switch oh := f.OptionalHeader.(type) {
case *pe.OptionalHeader32:
fmt.Printf("格式:PE32\n")
fmt.Printf("入口点:0x%x\n", oh.AddressOfEntryPoint)
fmt.Printf("镜像基址:0x%x\n", oh.ImageBase)
fmt.Printf("子系统:%d\n", oh.Subsystem)
case *pe.OptionalHeader64:
fmt.Printf("格式:PE32+\n")
fmt.Printf("入口点:0x%x\n", oh.AddressOfEntryPoint)
fmt.Printf("镜像基址:0x%x\n", oh.ImageBase)
fmt.Printf("子系统:%d\n", oh.Subsystem)
}
// 4. 显示节数量
fmt.Printf("节数量:%d\n", len(f.Sections))
}
示例 2:遍历所有节
package main
import (
"debug/pe"
"fmt"
"log"
"strings"
)
func main() {
f, err := pe.Open("myprogram.exe")
if err != nil {
log.Fatal(err)
}
defer f.Close()
fmt.Println("PE 节信息:")
fmt.Println("=" + "=" * 79)
for i, section := range f.Sections {
// 获取节名称(去掉填充的零字节)
name := strings.TrimRight(string(section.Name[:]), "\x00")
fmt.Printf("%2d. 名称:%s\n", i, name)
fmt.Printf(" 虚拟地址:0x%x\n", section.VirtualAddress)
fmt.Printf(" 虚拟大小:%d 字节\n", section.VirtualSize)
fmt.Printf(" 原始数据大小:%d 字节\n", section.SizeOfRawData)
fmt.Printf(" 原始数据偏移:0x%x\n", section.PointerToRawData)
fmt.Printf(" 重定位数量:%d\n", section.NumberOfRelocations)
fmt.Printf(" 特征:0x%x\n", section.Characteristics)
// 解析特征标志
fmt.Printf(" 标志:")
if section.Characteristics&0x20000000 != 0 {
fmt.Printf("可执行 ")
}
if section.Characteristics&0x40000000 != 0 {
fmt.Printf("可读 ")
}
if section.Characteristics&0x80000000 != 0 {
fmt.Printf("可写 ")
}
fmt.Println()
fmt.Println()
}
}
示例 3:读取特定节内容
package main
import (
"debug/pe"
"encoding/hex"
"fmt"
"log"
"strings"
)
func main() {
f, err := pe.Open("myprogram.exe")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 1. 读取 .text 节(代码)
textSection := f.Section(".text")
if textSection != nil {
data, err := textSection.Data()
if err != nil {
log.Fatal(err)
}
fmt.Printf(".text 节:\n")
fmt.Printf(" 大小:%d 字节\n", len(data))
fmt.Printf(" 前 64 字节:%x\n", data[:64])
}
// 2. 读取 .rdata 节(只读数据)
rdataSection := f.Section(".rdata")
if rdataSection != nil {
data, err := rdataSection.Data()
if err != nil {
log.Fatal(err)
}
fmt.Printf("\n.rdata 节:\n")
fmt.Printf(" 大小:%d 字节\n", len(data))
// 尝试提取字符串
strings := extractStrings(data)
fmt.Printf(" 提取的字符串:%d 个\n", len(strings))
// 显示前 20 个字符串
count := 0
for _, str := range strings {
if count >= 20 {
break
}
if len(str) >= 4 {
fmt.Printf(" \"%s\"\n", str)
count++
}
}
}
// 3. 读取 .data 节(已初始化数据)
dataSection := f.Section(".data")
if dataSection != nil {
data, err := dataSection.Data()
if err != nil {
log.Fatal(err)
}
fmt.Printf("\n.data 节:\n")
fmt.Printf(" 大小:%d 字节\n", len(data))
}
// 4. 读取 .rsrc 节(资源)
rsrcSection := f.Section(".rsrc")
if rsrcSection != nil {
data, err := rsrcSection.Data()
if err != nil {
log.Fatal(err)
}
fmt.Printf("\n.rsrc 节:\n")
fmt.Printf(" 大小:%d 字节\n", len(data))
}
}
// extractStrings 提取 ASCII 字符串
func extractStrings(data []byte) []string {
var strings []string
var current strings.Builder
for _, b := range data {
if b >= 0x20 && b <= 0x7e {
// 可打印 ASCII 字符
current.WriteByte(b)
} else {
if current.Len() >= 4 {
strings = append(strings, current.String())
}
current.Reset()
}
}
if current.Len() >= 4 {
strings = append(strings, current.String())
}
return strings
}
示例 4:读取符号表
package main
import (
"debug/pe"
"fmt"
"log"
)
func main() {
f, err := pe.Open("myprogram.exe")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 1. 读取符号表
fmt.Println("COFF 符号表:")
symbols := f.COFFSymbols
stringTable := f.StringTable
fmt.Printf("符号数量:%d\n", len(symbols))
fmt.Printf("字符串表大小:%d 字节\n", len(stringTable))
// 显示前 20 个符号
count := 0
for _, sym := range symbols {
if count >= 20 {
break
}
// 获取完整名称
name, err := sym.FullName(stringTable)
if err != nil {
name = fmt.Sprintf("<error: %v>", err)
}
fmt.Printf(" %s\n", name)
fmt.Printf(" 值:0x%x\n", sym.Value)
fmt.Printf(" 节:%d\n", sym.SectionNumber)
fmt.Printf(" 类型:0x%x\n", sym.Type)
fmt.Printf(" 存储类别:0x%x\n", sym.StorageClass)
fmt.Printf(" 辅助符号:%d\n", sym.NumAuxSymbols)
count++
}
// 2. 读取 Go 符号表(如果有)
fmt.Println("\nPE 符号:")
peSymbols, err := f.Symbols()
if err != nil {
log.Printf("读取 PE 符号失败:%v", err)
} else {
fmt.Printf("符号数量:%d\n", len(peSymbols))
for i, sym := range peSymbols {
if i >= 20 {
break
}
fmt.Printf(" %s (0x%x)\n", sym.Name, sym.Value)
}
}
}
示例 5:读取导入的库和符号
package main
import (
"debug/pe"
"fmt"
"log"
)
func main() {
f, err := pe.Open("myprogram.exe")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 1. 读取导入的库
fmt.Println("依赖的 DLL:")
libs, err := f.ImportedLibraries()
if err != nil {
log.Printf("读取库列表失败:%v", err)
} else {
for i, lib := range libs {
fmt.Printf(" %2d. %s\n", i+1, lib)
}
}
// 2. 读取导入的符号
fmt.Println("\n导入的符号:")
symbols, err := f.ImportedSymbols()
if err != nil {
log.Printf("读取导入符号失败:%v", err)
} else {
for i, sym := range symbols {
if i >= 30 {
break
}
fmt.Printf(" %s\n", sym)
}
}
// 3. 分析数据目录
fmt.Println("\n数据目录:")
switch oh := f.OptionalHeader.(type) {
case *pe.OptionalHeader32:
showDataDirectories(oh.DataDirectory[:])
case *pe.OptionalHeader64:
showDataDirectories(oh.DataDirectory[:])
}
}
// showDataDirectories 显示数据目录
func showDataDirectories(dirs []pe.DataDirectory) {
names := []string{
"导出表", "导入表", "资源表", "异常表",
"安全表", "重定位表", "调试信息", "架构特定",
"全局指针", "TLS 表", "加载配置", "绑定导入",
"导入地址表", "延迟导入", "COM 描述符",
}
for i, dir := range dirs {
if i >= len(names) {
break
}
if dir.Size > 0 {
fmt.Printf(" %-12s: RVA=0x%x, 大小=%d\n",
names[i], dir.VirtualAddress, dir.Size)
}
}
}
示例 6:检查 PE 文件特征
package main
import (
"debug/pe"
"fmt"
"log"
"time"
)
func main() {
f, err := pe.Open("myprogram.exe")
if err != nil {
log.Fatal(err)
}
defer f.Close()
fmt.Println("=== PE 文件特征分析 ===\n")
// 1. 机器类型
fmt.Printf("目标架构:")
switch f.Machine {
case pe.IMAGE_FILE_MACHINE_I386:
fmt.Printf("x86 (32 位)\n")
case pe.IMAGE_FILE_MACHINE_AMD64:
fmt.Printf("x86-64 (64 位)\n")
case pe.IMAGE_FILE_MACHINE_ARM:
fmt.Printf("ARM\n")
case pe.IMAGE_FILE_MACHINE_ARMNT:
fmt.Printf("ARM Thumb-2\n")
case pe.IMAGE_FILE_MACHINE_ARM64:
fmt.Printf("ARM 64 位\n")
case pe.IMAGE_FILE_MACHINE_IA64:
fmt.Printf("Intel Itanium\n")
default:
fmt.Printf("未知 (0x%x)\n", f.Machine)
}
// 2. 文件类型
fmt.Printf("文件类型:")
if f.Characteristics&pe.IMAGE_FILE_DLL != 0 {
fmt.Printf("动态库 (DLL)\n")
} else if f.Characteristics&pe.IMAGE_FILE_EXECUTABLE_IMAGE != 0 {
fmt.Printf("可执行文件 (EXE)\n")
} else {
fmt.Printf("目标文件 (OBJ)\n")
}
// 3. 编译时间
fmt.Printf("编译时间:%s\n",
time.Unix(int64(f.TimeDateStamp), 0).Format("2006-01-02 15:04:05"))
// 4. 特征标志
fmt.Printf("文件特征:\n")
flags := []struct {
mask uint16
name string
}{
{pe.IMAGE_FILE_RELOCS_STRIPPED, "重定位已剥离"},
{pe.IMAGE_FILE_EXECUTABLE_IMAGE, "可执行镜像"},
{pe.IMAGE_FILE_LINE_NUMS_STRIPPED, "行号已剥离"},
{pe.IMAGE_FILE_LOCAL_SYMS_STRIPPED, "本地符号已剥离"},
{pe.IMAGE_FILE_LARGE_ADDRESS_AWARE, "支持大地址"},
{pe.IMAGE_FILE_32BIT_MACHINE, "32 位机器"},
{pe.IMAGE_FILE_DEBUG_TRACED, "调试跟踪"},
{pe.IMAGE_FILE_SYSTEM, "系统文件"},
{pe.IMAGE_FILE_DLL, "DLL 文件"},
}
for _, flag := range flags {
if f.Characteristics&flag.mask != 0 {
fmt.Printf(" - %s\n", flag.name)
}
}
// 5. 子系统
fmt.Printf("\n子系统:")
var subsystem uint16
switch oh := f.OptionalHeader.(type) {
case *pe.OptionalHeader32:
subsystem = oh.Subsystem
case *pe.OptionalHeader64:
subsystem = oh.Subsystem
}
switch subsystem {
case pe.IMAGE_SUBSYSTEM_WINDOWS_GUI:
fmt.Printf("Windows GUI 程序\n")
case pe.IMAGE_SUBSYSTEM_WINDOWS_CUI:
fmt.Printf("Windows 控制台程序\n")
case pe.IMAGE_SUBSYSTEM_NATIVE:
fmt.Printf("原生程序(驱动程序)\n")
case pe.IMAGE_SUBSYSTEM_EFI_APPLICATION:
fmt.Printf("EFI 应用程序\n")
default:
fmt.Printf("未知 (%d)\n", subsystem)
}
// 6. 可选头信息
fmt.Printf("\n可选头信息:\n")
switch oh := f.OptionalHeader.(type) {
case *pe.OptionalHeader32:
fmt.Printf(" 格式:PE32 (32 位)\n")
fmt.Printf(" 入口点:0x%x\n", oh.AddressOfEntryPoint)
fmt.Printf(" 镜像基址:0x%x\n", oh.ImageBase)
fmt.Printf(" 栈保留:0x%x\n", oh.SizeOfStackReserve)
fmt.Printf(" 栈提交:0x%x\n", oh.SizeOfStackCommit)
fmt.Printf(" 堆保留:0x%x\n", oh.SizeOfHeapReserve)
fmt.Printf(" 堆提交:0x%x\n", oh.SizeOfHeapCommit)
case *pe.OptionalHeader64:
fmt.Printf(" 格式:PE32+ (64 位)\n")
fmt.Printf(" 入口点:0x%x\n", oh.AddressOfEntryPoint)
fmt.Printf(" 镜像基址:0x%x\n", oh.ImageBase)
fmt.Printf(" 栈保留:0x%x\n", oh.SizeOfStackReserve)
fmt.Printf(" 栈提交:0x%x\n", oh.SizeOfStackCommit)
fmt.Printf(" 堆保留:0x%x\n", oh.SizeOfHeapReserve)
fmt.Printf(" 堆提交:0x%x\n", oh.SizeOfHeapCommit)
}
}
示例 7:PE 文件分析工具
package main
import (
"debug/pe"
"encoding/hex"
"fmt"
"log"
"os"
"strings"
"time"
)
// PEAnalyzer PE 文件分析器
type PEAnalyzer struct {
file *pe.File
}
// NewPEAnalyzer 创建分析器
func NewPEAnalyzer(filename string) (*PEAnalyzer, error) {
f, err := pe.Open(filename)
if err != nil {
return nil, err
}
return &PEAnalyzer{file: f}, nil
}
// Close 关闭文件
func (a *PEAnalyzer) Close() error {
return a.file.Close()
}
// ShowHeader 显示文件头
func (a *PEAnalyzer) ShowHeader() {
f := a.file
fmt.Println("=== PE 文件头 ===")
fmt.Printf("机器类型:0x%x\n", f.Machine)
fmt.Printf("节数量:%d\n", f.NumberOfSections)
fmt.Printf("编译时间:%s\n",
time.Unix(int64(f.TimeDateStamp), 0).Format("2006-01-02 15:04:05"))
fmt.Printf("符号表偏移:0x%x\n", f.PointerToSymbolTable)
fmt.Printf("符号数量:%d\n", f.NumberOfSymbols)
fmt.Printf("可选头大小:%d\n", f.SizeOfOptionalHeader)
fmt.Printf("特征:0x%x\n", f.Characteristics)
fmt.Println()
}
// ShowSections 显示节信息
func (a *PEAnalyzer) ShowSections() {
fmt.Println("=== PE 节 ===")
for i, section := range a.file.Sections {
name := strings.TrimRight(string(section.Name[:]), "\x00")
fmt.Printf("%2d. %-10s RVA:0x%08x 大小:%6d 字节",
i, name, section.VirtualAddress, section.VirtualSize)
// 显示特征
var flags []string
if section.Characteristics&0x20000000 != 0 {
flags = append(flags, "X")
}
if section.Characteristics&0x40000000 != 0 {
flags = append(flags, "R")
}
if section.Characteristics&0x80000000 != 0 {
flags = append(flags, "W")
}
if len(flags) > 0 {
fmt.Printf(" [%s]", strings.Join(flags, ""))
}
fmt.Println()
}
fmt.Println()
}
// ShowSymbols 显示符号
func (a *PEAnalyzer) ShowSymbols() {
fmt.Println("=== 符号表 ===")
symbols := a.file.COFFSymbols
stringTable := a.file.StringTable
fmt.Printf("符号数量:%d\n\n", len(symbols))
// 按存储类别分组
external := make([]pe.COFFSymbol, 0)
static := make([]pe.COFFSymbol, 0)
file := make([]pe.COFFSymbol, 0)
for _, sym := range symbols {
switch sym.StorageClass {
case 0x02: // IMAGE_SYM_CLASS_EXTERNAL
external = append(external, sym)
case 0x03: // IMAGE_SYM_CLASS_STATIC
static = append(static, sym)
case 0x67: // IMAGE_SYM_CLASS_FILE
file = append(file, sym)
}
}
fmt.Printf("外部符号:%d\n", len(external))
fmt.Printf("静态符号:%d\n", len(static))
fmt.Printf("文件符号:%d\n\n", len(file))
// 显示前 10 个外部符号
if len(external) > 0 {
fmt.Println("外部符号示例:")
for i, sym := range external {
if i >= 10 {
break
}
name, _ := sym.FullName(stringTable)
fmt.Printf(" %-40s 0x%x\n", name, sym.Value)
}
fmt.Println()
}
}
// ShowLibraries 显示库依赖
func (a *PEAnalyzer) ShowLibraries() {
fmt.Println("=== 依赖库 ===")
libs, err := a.file.ImportedLibraries()
if err != nil {
fmt.Printf("读取库失败:%v\n", err)
return
}
for _, lib := range libs {
fmt.Printf(" %s\n", lib)
}
fmt.Println()
}
// ShowDataDirectories 显示数据目录
func (a *PEAnalyzer) ShowDataDirectories() {
fmt.Println("=== 数据目录 ===")
var dirs []pe.DataDirectory
switch oh := a.file.OptionalHeader.(type) {
case *pe.OptionalHeader32:
dirs = oh.DataDirectory[:]
case *pe.OptionalHeader64:
dirs = oh.DataDirectory[:]
}
names := []string{
"导出表", "导入表", "资源表", "异常表",
"安全表", "重定位表", "调试信息", "架构特定",
"全局指针", "TLS 表", "加载配置", "绑定导入",
"导入地址表", "延迟导入", "COM 描述符",
}
for i, dir := range dirs {
if i >= len(names) {
break
}
if dir.Size > 0 {
fmt.Printf("%2d. %-15s RVA:0x%08x 大小:%d\n",
i+1, names[i], dir.VirtualAddress, dir.Size)
}
}
fmt.Println()
}
// FindSymbol 查找符号
func (a *PEAnalyzer) FindSymbol(pattern string) error {
symbols := a.file.COFFSymbols
stringTable := a.file.StringTable
count := 0
for _, sym := range symbols {
name, err := sym.FullName(stringTable)
if err != nil {
continue
}
if strings.Contains(name, pattern) {
fmt.Printf("找到符号:%s\n", name)
fmt.Printf(" 值:0x%x\n", sym.Value)
fmt.Printf(" 节:%d\n", sym.SectionNumber)
fmt.Printf(" 类型:0x%x\n", sym.Type)
fmt.Printf(" 存储类别:0x%x\n", sym.StorageClass)
fmt.Println()
count++
if count >= 20 {
break
}
}
}
if count == 0 {
fmt.Printf("未找到匹配的符号\n")
} else {
fmt.Printf("找到 %d 个匹配符号\n", count)
}
return nil
}
// DumpSection 转储节内容
func (a *PEAnalyzer) DumpSection(name string, output string) error {
section := a.file.Section(name)
if section == nil {
return fmt.Errorf("未找到节:%s", name)
}
data, err := section.Data()
if err != nil {
return err
}
file, err := os.Create(output)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(data)
if err != nil {
return err
}
fmt.Printf("已将 %s 节保存到 %s (%d 字节)\n", name, output, len(data))
return nil
}
func main() {
if len(os.Args) < 2 {
log.Fatal("用法:pe-analyzer <pe-file> [command]")
}
filename := os.Args[1]
analyzer, err := NewPEAnalyzer(filename)
if err != nil {
log.Fatal(err)
}
defer analyzer.Close()
if len(os.Args) > 2 {
command := os.Args[2]
switch command {
case "header":
analyzer.ShowHeader()
case "sections":
analyzer.ShowSections()
case "symbols":
analyzer.ShowSymbols()
case "libs":
analyzer.ShowLibraries()
case "dirs":
analyzer.ShowDataDirectories()
case "find":
if len(os.Args) > 3 {
analyzer.FindSymbol(os.Args[3])
}
case "dump":
if len(os.Args) > 4 {
err := analyzer.DumpSection(os.Args[3], os.Args[4])
if err != nil {
log.Fatal(err)
}
}
default:
// 显示所有信息
analyzer.ShowHeader()
analyzer.ShowSections()
analyzer.ShowSymbols()
analyzer.ShowLibraries()
analyzer.ShowDataDirectories()
}
} else {
// 默认显示所有信息
analyzer.ShowHeader()
analyzer.ShowSections()
analyzer.ShowSymbols()
analyzer.ShowLibraries()
analyzer.ShowDataDirectories()
}
}
示例 8:读取 DWARF 调试信息
package main
import (
"debug/pe"
"debug/dwarf"
"fmt"
"io"
"log"
)
func main() {
f, err := pe.Open("myprogram.exe")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 读取 DWARF 数据
data, err := f.DWARF()
if err != nil {
log.Fatal("无 DWARF 信息:", err)
}
// 创建读取器
reader := data.Reader()
// 遍历 DWARF 条目
fmt.Println("DWARF 调试信息:")
for {
entry, err := reader.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
// 查找函数
if entry.Tag == dwarf.TagSubprogram {
name := entry.Val(dwarf.AttrName)
if name != nil {
fmt.Printf("函数:%v\n", name)
lowpc := entry.Val(dwarf.AttrLowpc)
highpc := entry.Val(dwarf.AttrHighpc)
if lowpc != nil && highpc != nil {
fmt.Printf(" 地址范围:0x%x - 0x%x\n", lowpc, highpc)
}
}
}
if entry.Children {
reader.SkipChildren()
}
}
}
示例 9:检查 PE 文件是否为有效的 Windows 可执行文件
package main
import (
"debug/pe"
"fmt"
"log"
"os"
)
// IsValidPE 检查文件是否为有效的 PE 文件
func IsValidPE(filename string) bool {
f, err := pe.Open(filename)
if err != nil {
return false
}
defer f.Close()
// 检查是否为可执行文件或 DLL
if f.Characteristics&pe.IMAGE_FILE_EXECUTABLE_IMAGE == 0 &&
f.Characteristics&pe.IMAGE_FILE_DLL == 0 {
return false
}
return true
}
// GetPEInfo 获取 PE 文件信息
func GetPEInfo(filename string) (map[string]interface{}, error) {
f, err := pe.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
info := make(map[string]interface{})
// 基本信息
info["machine"] = f.Machine
info["sections"] = f.NumberOfSections
info["timestamp"] = f.TimeDateStamp
info["is_dll"] = f.Characteristics&pe.IMAGE_FILE_DLL != 0
info["is_exe"] = f.Characteristics&pe.IMAGE_FILE_EXECUTABLE_IMAGE != 0
// 可选头信息
switch oh := f.OptionalHeader.(type) {
case *pe.OptionalHeader32:
info["format"] = "PE32"
info["entry_point"] = oh.AddressOfEntryPoint
info["image_base"] = oh.ImageBase
info["subsystem"] = oh.Subsystem
case *pe.OptionalHeader64:
info["format"] = "PE32+"
info["entry_point"] = oh.AddressOfEntryPoint
info["image_base"] = oh.ImageBase
info["subsystem"] = oh.Subsystem
}
// 导入库
libs, err := f.ImportedLibraries()
if err == nil {
info["imported_libraries"] = libs
}
return info, nil
}
func main() {
if len(os.Args) < 2 {
log.Fatal("用法:pe-check <pe-file>")
}
filename := os.Args[1]
if !IsValidPE(filename) {
fmt.Printf("%s 不是有效的 PE 文件\n", filename)
return
}
fmt.Printf("%s 是有效的 PE 文件\n", filename)
info, err := GetPEInfo(filename)
if err != nil {
log.Fatal(err)
}
fmt.Printf("机器类型:0x%x\n", info["machine"])
fmt.Printf("格式:%v\n", info["format"])
fmt.Printf("节数量:%v\n", info["sections"])
fmt.Printf("是否为 DLL: %v\n", info["is_dll"])
fmt.Printf("是否为 EXE: %v\n", info["is_exe"])
fmt.Printf("入口点:0x%x\n", info["entry_point"])
fmt.Printf("镜像基址:0x%x\n", info["image_base"])
fmt.Printf("子系统:%v\n", info["subsystem"])
if libs, ok := info["imported_libraries"].([]string); ok {
fmt.Printf("导入库数量:%d\n", len(libs))
}
}
安全最佳实践
✅ 推荐做法
-
始终检查错误
f, err := pe.Open("file") if err != nil { return err } defer f.Close() -
检查 nil 指针
section := f.Section(".text") if section != nil { data, _ := section.Data() } -
验证数据大小
data, err := section.Data() if err != nil { return err } if len(data) < expectedSize { return fmt.Errorf("数据太小") } -
检查文件格式
if f.Characteristics&pe.IMAGE_FILE_EXECUTABLE_IMAGE == 0 { return fmt.Errorf("不是可执行文件") }
❌ 不安全做法
-
不要忽略错误
// ❌ 错误 f, _ := pe.Open("file") // ✅ 正确 f, err := pe.Open("file") if err != nil { // 处理错误 } -
不要忘记关闭文件
// ❌ 错误 f, _ := pe.Open("file") // ✅ 正确 f, _ := pe.Open("file") defer f.Close() -
不要假设节一定存在
// ❌ 错误 data, _ := f.Section(".text").Data() // ✅ 正确 section := f.Section(".text") if section == nil { return error } data, err := section.Data()
总结
核心类型
File // PE 文件
FileHeader // COFF 文件头
OptionalHeader32 // 32 位可选头
OptionalHeader64 // 64 位可选头
Section // 节
SectionHeader // 节头
Symbol // 符号
COFFSymbol // COFF 符号
DataDirectory // 数据目录
使用场景
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 打开文件 | pe.Open() | 读取 PE 文件 |
| 读取节 | File.Section() | 获取特定节 |
| 读取符号 | File.Symbols() | 获取符号表 |
| 获取库依赖 | File.ImportedLibraries() | 获取依赖库 |
| 读取 DWARF | File.DWARF() | 获取调试信息 |
Machine 类型
| 类型 | 常量 | 说明 |
|---|---|---|
| x86 | IMAGE_FILE_MACHINE_I386 | 32 位 Intel |
| x86-64 | IMAGE_FILE_MACHINE_AMD64 | 64 位 Intel |
| ARM | IMAGE_FILE_MACHINE_ARM | ARM |
| ARM Thumb-2 | IMAGE_FILE_MACHINE_ARMNT | ARM Thumb-2 |
| ARM64 | IMAGE_FILE_MACHINE_ARM64 | ARM 64 位 |
| Itanium | IMAGE_FILE_MACHINE_IA64 | Intel Itanium |
文件类型
| 类型 | 标志 | 说明 |
|---|---|---|
| 可执行文件 | IMAGE_FILE_EXECUTABLE_IMAGE | .exe 文件 |
| 动态库 | IMAGE_FILE_DLL | .dll 文件 |
| 目标文件 | 无特殊标志 | .obj 文件 |
子系统类型
| 类型 | 常量 | 说明 |
|---|---|---|
| GUI 程序 | IMAGE_SUBSYSTEM_WINDOWS_GUI | Windows 图形界面 |
| 控制台程序 | IMAGE_SUBSYSTEM_WINDOWS_CUI | Windows 命令行 |
| 原生程序 | IMAGE_SUBSYSTEM_NATIVE | 驱动程序 |
| EFI 应用 | IMAGE_SUBSYSTEM_EFI_APPLICATION | EFI 应用程序 |
常见节
| 节名 | 用途 |
|---|---|
.text | 代码节 |
.data | 已初始化数据节 |
.rdata | 只读数据节 |
.bss | 未初始化数据节 |
.rsrc | 资源节 |
.reloc | 重定位节 |
.idata | 导入表节 |
.edata | 导出表节 |
PE32 vs PE32+
| 特性 | PE32 | PE32+ |
|---|---|---|
| 魔术数字 | 0x10b | 0x20b |
| 地址大小 | 32 位 | 64 位 |
| ImageBase | 32 位 | 64 位 |
| 栈/堆大小 | 32 位 | 64 位 |
| BaseOfData | 存在 | 不存在 |
参考资料
- Go debug/pe 包文档
- PE 文件格式规范
- PE 文件格式详解
- Microsoft PE and COFF Specification
- debug/dwarf 包文档
- debug/elf 包文档
- debug/macho 包文档
最后更新:2026-04-03
Go 版本:Go 1.23+