2026-04-06 00:20:51 -05:00

224 lines
5.5 KiB
Go

package main
import (
"bytes"
"debug/pe"
"encoding/binary"
"fmt"
"os"
"path/filepath"
"strings"
"unsafe"
)
func CreateDefinitionFile(content []byte, tempDir string) (string, error) {
reader := bytes.NewReader(content)
file, err := pe.NewFile(reader)
if err != nil {
return "", fmt.Errorf("Failed to open DLL: %v", err)
}
defer func(file *pe.File) {
_ = file.Close()
}(file)
exports, dllName, err := getExports(file)
if err != nil {
return "", fmt.Errorf("Failed to read exports: %v", err)
}
if len(exports) == 0 {
return "", fmt.Errorf("No exports found in DLL")
}
baseName := strings.TrimSuffix(dllName, filepath.Ext(dllName))
defFileName := tempDir + "/" + baseName + ".def"
defFile, err := os.Create(defFileName)
if err != nil {
return "", fmt.Errorf("Failed to create def file: %v", err)
}
defer func(defFile *os.File) {
_ = defFile.Close()
}(defFile)
_, _ = fmt.Fprintf(defFile, "EXPORTS\n")
for i, exportName := range exports {
_, _ = fmt.Fprintf(defFile, "%s=%s.%s @%v\n", exportName, baseName, exportName, i+1)
}
return defFileName, nil
}
type ExportDirectory struct {
ExportFlags uint32
TimeDateStamp uint32
MajorVersion uint16
MinorVersion uint16
NameRVA uint32
OrdinalBase uint32
NumberOfFunctions uint32
NumberOfNames uint32
AddressTableRVA uint32
NamePointerRVA uint32
OrdinalTableRVA uint32
}
func getDLLName(file *pe.File, exportDir *ExportDirectory) (string, error) {
nameSection := findSectionByRVA(file, exportDir.NameRVA)
if nameSection == nil {
return "", fmt.Errorf("dll name section not found")
}
data, err := nameSection.Data()
if err != nil {
return "", fmt.Errorf("failed to read dll name section: %v", err)
}
offset := exportDir.NameRVA - nameSection.VirtualAddress
if offset >= uint32(len(data)) {
return "", fmt.Errorf("dll name offset out of bounds")
}
nameBytes := data[offset:]
for i, b := range nameBytes {
if b == 0 {
return string(nameBytes[:i]), nil
}
}
return "", fmt.Errorf("dll name not null-terminated")
}
func getExports(file *pe.File) ([]string, string, error) {
var exports []string
// Get the export directory from the data directories
if file.OptionalHeader == nil {
return nil, "", fmt.Errorf("no optional header found")
}
var exportDirRVA, exportDirSize uint32
switch oh := file.OptionalHeader.(type) {
case *pe.OptionalHeader32:
if len(oh.DataDirectory) > 0 {
exportDirRVA = oh.DataDirectory[0].VirtualAddress
exportDirSize = oh.DataDirectory[0].Size
}
case *pe.OptionalHeader64:
if len(oh.DataDirectory) > 0 {
exportDirRVA = oh.DataDirectory[0].VirtualAddress
exportDirSize = oh.DataDirectory[0].Size
}
default:
return nil, "", fmt.Errorf("unsupported optional header type")
}
if exportDirRVA == 0 || exportDirSize == 0 {
return nil, "", fmt.Errorf("no export directory found")
}
// Find the section containing the export directory
var section *pe.Section
for _, s := range file.Sections {
if exportDirRVA >= s.VirtualAddress && exportDirRVA < s.VirtualAddress+s.VirtualSize {
section = s
break
}
}
if section == nil {
return nil, "", fmt.Errorf("export directory not found in any section")
}
// Read section data
data, err := section.Data()
if err != nil {
return nil, "", fmt.Errorf("failed to read section data: %v", err)
}
// Calculate offset within section
offset := exportDirRVA - section.VirtualAddress
if offset >= uint32(len(data)) {
return nil, "", fmt.Errorf("export directory offset out of bounds")
}
// Parse export directory
if len(data[offset:]) < int(unsafe.Sizeof(ExportDirectory{})) {
return nil, "", fmt.Errorf("insufficient data for export directory")
}
exportDir := (*ExportDirectory)(unsafe.Pointer(&data[offset]))
dllName, err := getDLLName(file, exportDir)
if err != nil {
return nil, dllName, fmt.Errorf("DLL name not found")
}
if exportDir.NumberOfNames == 0 {
return nil, dllName, fmt.Errorf("no named exports found")
}
// Read name pointer table
nameTableRVA := exportDir.NamePointerRVA
nameTableSection := findSectionByRVA(file, nameTableRVA)
if nameTableSection == nil {
return nil, dllName, fmt.Errorf("name table section not found")
}
nameTableData, err := nameTableSection.Data()
if err != nil {
return nil, dllName, fmt.Errorf("failed to read name table section: %v", err)
}
nameTableOffset := nameTableRVA - nameTableSection.VirtualAddress
// Read each name RVA and then the actual name
for i := uint32(0); i < exportDir.NumberOfNames; i++ {
nameRVAOffset := nameTableOffset + i*4
if nameRVAOffset+4 > uint32(len(nameTableData)) {
break
}
nameRVA := binary.LittleEndian.Uint32(nameTableData[nameRVAOffset:])
// Find section containing the name
nameSection := findSectionByRVA(file, nameRVA)
if nameSection == nil {
continue
}
nameSectionData, err := nameSection.Data()
if err != nil {
continue
}
nameOffset := nameRVA - nameSection.VirtualAddress
if nameOffset >= uint32(len(nameSectionData)) {
continue
}
// Read null-terminated string
nameBytes := nameSectionData[nameOffset:]
var name string
for j, b := range nameBytes {
if b == 0 {
name = string(nameBytes[:j])
break
}
}
if name != "" {
exports = append(exports, name)
}
}
return exports, dllName, nil
}
func findSectionByRVA(file *pe.File, rva uint32) *pe.Section {
for _, section := range file.Sections {
if rva >= section.VirtualAddress && rva < section.VirtualAddress+section.VirtualSize {
return section
}
}
return nil
}