ImageImportDescriptor结构体中的Name字段实际上是一个RVA(相对虚拟地址),指向DLL名称字符串在PE文件中的位置。要获取该字符串,需要进行以下步骤:
- 获取PE文件的DOS头和NT头。
- 遍历NT头中的数据目录表,找到导入表项(IMAGE_DIRECTORY_ENTRY_IMPORT)。
- 在导入表项中遍历每个导入描述符(IMAGE_IMPORT_DESCRIPTOR),找到Name字段对应的RVA。
- 使用该RVA计算出DLL名称字符串在PE文件中的偏移量,并读取该字符串。
下面是使用Golang代码实现这一过程的示例:
package main
import (
"encoding/binary"
"fmt"
"io/ioutil"
)
type IMAGE_DOS_HEADER struct {
e_magic uint16
e_cblp uint16
e_cp uint16
e_crlc uint16
e_cparhdr uint16
e_minalloc uint16
e_maxalloc uint16
e_ss uint16
e_sp uint16
e_csum uint16
e_ip uint16
e_cs uint16
e_lfarlc uint16
e_ovno uint16
}
type IMAGE_FILE_HEADER struct {
Machine uint16 // ...
}
type IMAGE_OPTIONAL_HEADER32 struct {
Magic uint16 // ...
MajorLinkerVersion byte // ...
MinorLinkerVersion byte // ...
SizeOfCode int32 // ...
SizeOfInitializedData int32 // ...
SizeOfUninitializedData int32 // ...
AddressOfEntryPoint int32 // ...
BaseOfCode int32 // ...
}
type IMAGE_DATA_DIRECTORY struct {
VirtualAddress uint32
Size uint32
}
type IMAGE_IMPORT_DESCRIPTOR struct {
OriginalFirstThunk uint32
TimeDateStamp uint32
ForwarderChain uint32
Name uint32
FirstThunk uint32
}
func main() {
fileBytes, err := ioutil.ReadFile("example.dll")
if err != nil {
panic(err)
}
dosHeader := IMAGE_DOS_HEADER{}
binary.Read(bytes.NewReader(fileBytes), binary.LittleEndian, &dosHeader)
ntHeaderOffset := dosHeader.e_lfarlc - uint16(binary.Size(dosHeader))
fileHeader := IMAGE_FILE_HEADER{}
optionalHeader := IMAGE_OPTIONAL_HEADER32{}
// 读取PE文件头和可选头信息,并确定导入表的位置和大小。
binary.Read(bytes.NewReader(fileBytes[ntHeaderOffset:]), binary.LittleEndian, &fileHeader)
if fileHeader.Machine == 0x014c { // x86 machine type
binary.Read(bytes.NewReader(fileBytes[ntHeaderOffset+int64(binary.Size(fileHeader)):]), binary.LittleEndian, &optionalHeader)
}
importDirectoryRva := optionalHeader.DataDirectory[1].VirtualAddress
importDirectorySize := optionalHeader.DataDirectory[1].Size
var importDescriptor IMAGE_IMPORT_DESCRIPTOR
for i := importDirectoryRva; i < importDirectoryRva+importDirectorySize; i += uint32(binary.Size(importDescriptor)) {
binary.Read(bytes.NewReader(fileBytes[i:]), binary.LittleEndian, &importDescriptor)
nameOffset := rvaToOffset(importDescriptor.Name, optionalHeader.BaseOfData, optionalHeader.SectionAlignment)
dllName := readCString(fileBytes[nameOffset:])
fmt.Printf("Importing from %s\n", dllName)
}
}
func rvaToOffset(rva, baseOfData, sectionAlignment uint32) int64 {
return int64(rva - baseOfData + sectionAlignment - 1) &^ (int64(sectionAlignment) - 1)
}
func readCString(data []byte) string {
nullByteIndex := -1
for i, b := range data {
if b == 0x00 {
nullByteIndex = i
break
}
}
if nullByteIndex < 0 {
return ""
}
return string(data[:nullByteIndex])
}
这个示例代码可以读取PE文件中的导入表,获取每个DLL名称字符串并打印出来。注意,这里假设PE文件是一个x86格式的可执行文件。如果要处理其他格式的PE文件,需要相应地修改代码。