github.com/apernet/sing-tun@v0.2.6-0.20240323130332-b9f6511036ad/internal/wintun/memmod/memmod_windows.go (about) 1 /* SPDX-License-Identifier: MIT 2 * 3 * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. 4 */ 5 6 package memmod 7 8 import ( 9 "errors" 10 "fmt" 11 "strings" 12 "sync" 13 "syscall" 14 "unsafe" 15 16 "golang.org/x/sys/windows" 17 ) 18 19 type addressList struct { 20 next *addressList 21 address uintptr 22 } 23 24 func (head *addressList) free() { 25 for node := head; node != nil; node = node.next { 26 windows.VirtualFree(node.address, 0, windows.MEM_RELEASE) 27 } 28 } 29 30 type Module struct { 31 headers *IMAGE_NT_HEADERS 32 codeBase uintptr 33 modules []windows.Handle 34 initialized bool 35 isDLL bool 36 isRelocated bool 37 nameExports map[string]uint16 38 entry uintptr 39 blockedMemory *addressList 40 } 41 42 func (module *Module) BaseAddr() uintptr { 43 return module.codeBase 44 } 45 46 func (module *Module) headerDirectory(idx int) *IMAGE_DATA_DIRECTORY { 47 return &module.headers.OptionalHeader.DataDirectory[idx] 48 } 49 50 func (module *Module) copySections(address, size uintptr, oldHeaders *IMAGE_NT_HEADERS) error { 51 sections := module.headers.Sections() 52 for i := range sections { 53 if sections[i].SizeOfRawData == 0 { 54 // Section doesn't contain data in the dll itself, but may define uninitialized data. 55 sectionSize := oldHeaders.OptionalHeader.SectionAlignment 56 if sectionSize == 0 { 57 continue 58 } 59 dest, err := windows.VirtualAlloc(module.codeBase+uintptr(sections[i].VirtualAddress), 60 uintptr(sectionSize), 61 windows.MEM_COMMIT, 62 windows.PAGE_READWRITE) 63 if err != nil { 64 return fmt.Errorf("Error allocating section: %w", err) 65 } 66 67 // Always use position from file to support alignments smaller than page size (allocation above will align to page size). 68 dest = module.codeBase + uintptr(sections[i].VirtualAddress) 69 // NOTE: On 64bit systems we truncate to 32bit here but expand again later when "PhysicalAddress" is used. 70 sections[i].SetPhysicalAddress((uint32)(dest & 0xffffffff)) 71 dst := unsafe.Slice((*byte)(a2p(dest)), sectionSize) 72 for j := range dst { 73 dst[j] = 0 74 } 75 continue 76 } 77 78 if size < uintptr(sections[i].PointerToRawData+sections[i].SizeOfRawData) { 79 return errors.New("Incomplete section") 80 } 81 82 // Commit memory block and copy data from dll. 83 dest, err := windows.VirtualAlloc(module.codeBase+uintptr(sections[i].VirtualAddress), 84 uintptr(sections[i].SizeOfRawData), 85 windows.MEM_COMMIT, 86 windows.PAGE_READWRITE) 87 if err != nil { 88 return fmt.Errorf("Error allocating memory block: %w", err) 89 } 90 91 // Always use position from file to support alignments smaller than page size (allocation above will align to page size). 92 memcpy( 93 module.codeBase+uintptr(sections[i].VirtualAddress), 94 address+uintptr(sections[i].PointerToRawData), 95 uintptr(sections[i].SizeOfRawData)) 96 // NOTE: On 64bit systems we truncate to 32bit here but expand again later when "PhysicalAddress" is used. 97 sections[i].SetPhysicalAddress((uint32)(dest & 0xffffffff)) 98 } 99 100 return nil 101 } 102 103 func (module *Module) realSectionSize(section *IMAGE_SECTION_HEADER) uintptr { 104 size := section.SizeOfRawData 105 if size != 0 { 106 return uintptr(size) 107 } 108 if (section.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) != 0 { 109 return uintptr(module.headers.OptionalHeader.SizeOfInitializedData) 110 } 111 if (section.Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0 { 112 return uintptr(module.headers.OptionalHeader.SizeOfUninitializedData) 113 } 114 return 0 115 } 116 117 type sectionFinalizeData struct { 118 address uintptr 119 alignedAddress uintptr 120 size uintptr 121 characteristics uint32 122 last bool 123 } 124 125 func (module *Module) finalizeSection(sectionData *sectionFinalizeData) error { 126 if sectionData.size == 0 { 127 return nil 128 } 129 130 if (sectionData.characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0 { 131 // Section is not needed any more and can safely be freed. 132 if sectionData.address == sectionData.alignedAddress && 133 (sectionData.last || 134 (sectionData.size%uintptr(module.headers.OptionalHeader.SectionAlignment)) == 0) { 135 // Only allowed to decommit whole pages. 136 windows.VirtualFree(sectionData.address, sectionData.size, windows.MEM_DECOMMIT) 137 } 138 return nil 139 } 140 141 // determine protection flags based on characteristics 142 ProtectionFlags := [8]uint32{ 143 windows.PAGE_NOACCESS, // not writeable, not readable, not executable 144 windows.PAGE_EXECUTE, // not writeable, not readable, executable 145 windows.PAGE_READONLY, // not writeable, readable, not executable 146 windows.PAGE_EXECUTE_READ, // not writeable, readable, executable 147 windows.PAGE_WRITECOPY, // writeable, not readable, not executable 148 windows.PAGE_EXECUTE_WRITECOPY, // writeable, not readable, executable 149 windows.PAGE_READWRITE, // writeable, readable, not executable 150 windows.PAGE_EXECUTE_READWRITE, // writeable, readable, executable 151 } 152 protect := ProtectionFlags[sectionData.characteristics>>29] 153 if (sectionData.characteristics & IMAGE_SCN_MEM_NOT_CACHED) != 0 { 154 protect |= windows.PAGE_NOCACHE 155 } 156 157 // Change memory access flags. 158 var oldProtect uint32 159 err := windows.VirtualProtect(sectionData.address, sectionData.size, protect, &oldProtect) 160 if err != nil { 161 return fmt.Errorf("Error protecting memory page: %w", err) 162 } 163 164 return nil 165 } 166 167 func (module *Module) registerExceptionHandlers() { 168 directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXCEPTION) 169 if directory.Size == 0 || directory.VirtualAddress == 0 { 170 return 171 } 172 runtimeFuncs := (*windows.RUNTIME_FUNCTION)(unsafe.Pointer(module.codeBase + uintptr(directory.VirtualAddress))) 173 windows.RtlAddFunctionTable(runtimeFuncs, uint32(uintptr(directory.Size)/unsafe.Sizeof(*runtimeFuncs)), module.codeBase) 174 } 175 176 func (module *Module) finalizeSections() error { 177 sections := module.headers.Sections() 178 imageOffset := module.headers.OptionalHeader.imageOffset() 179 sectionData := sectionFinalizeData{} 180 sectionData.address = uintptr(sections[0].PhysicalAddress()) | imageOffset 181 sectionData.alignedAddress = alignDown(sectionData.address, uintptr(module.headers.OptionalHeader.SectionAlignment)) 182 sectionData.size = module.realSectionSize(§ions[0]) 183 sections[0].SetVirtualSize(uint32(sectionData.size)) 184 sectionData.characteristics = sections[0].Characteristics 185 186 // Loop through all sections and change access flags. 187 for i := uint16(1); i < module.headers.FileHeader.NumberOfSections; i++ { 188 sectionAddress := uintptr(sections[i].PhysicalAddress()) | imageOffset 189 alignedAddress := alignDown(sectionAddress, uintptr(module.headers.OptionalHeader.SectionAlignment)) 190 sectionSize := module.realSectionSize(§ions[i]) 191 sections[i].SetVirtualSize(uint32(sectionSize)) 192 // Combine access flags of all sections that share a page. 193 // TODO: We currently share flags of a trailing large section with the page of a first small section. This should be optimized. 194 if sectionData.alignedAddress == alignedAddress || sectionData.address+sectionData.size > alignedAddress { 195 // Section shares page with previous. 196 if (sections[i].Characteristics&IMAGE_SCN_MEM_DISCARDABLE) == 0 || (sectionData.characteristics&IMAGE_SCN_MEM_DISCARDABLE) == 0 { 197 sectionData.characteristics = (sectionData.characteristics | sections[i].Characteristics) &^ IMAGE_SCN_MEM_DISCARDABLE 198 } else { 199 sectionData.characteristics |= sections[i].Characteristics 200 } 201 sectionData.size = sectionAddress + sectionSize - sectionData.address 202 continue 203 } 204 205 err := module.finalizeSection(§ionData) 206 if err != nil { 207 return fmt.Errorf("Error finalizing section: %w", err) 208 } 209 sectionData.address = sectionAddress 210 sectionData.alignedAddress = alignedAddress 211 sectionData.size = sectionSize 212 sectionData.characteristics = sections[i].Characteristics 213 } 214 sectionData.last = true 215 err := module.finalizeSection(§ionData) 216 if err != nil { 217 return fmt.Errorf("Error finalizing section: %w", err) 218 } 219 return nil 220 } 221 222 func (module *Module) executeTLS() { 223 directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_TLS) 224 if directory.VirtualAddress == 0 { 225 return 226 } 227 228 tls := (*IMAGE_TLS_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) 229 callback := tls.AddressOfCallbacks 230 if callback != 0 { 231 for { 232 f := *(*uintptr)(a2p(callback)) 233 if f == 0 { 234 break 235 } 236 syscall.SyscallN(f, module.codeBase, DLL_PROCESS_ATTACH, 0) 237 callback += unsafe.Sizeof(f) 238 } 239 } 240 } 241 242 func (module *Module) performBaseRelocation(delta uintptr) (relocated bool, err error) { 243 directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_BASERELOC) 244 if directory.Size == 0 { 245 return delta == 0, nil 246 } 247 248 relocationHdr := (*IMAGE_BASE_RELOCATION)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) 249 for relocationHdr.VirtualAddress > 0 { 250 dest := module.codeBase + uintptr(relocationHdr.VirtualAddress) 251 252 relInfos := unsafe.Slice( 253 (*uint16)(a2p(uintptr(unsafe.Pointer(relocationHdr))+unsafe.Sizeof(*relocationHdr))), 254 (uintptr(relocationHdr.SizeOfBlock)-unsafe.Sizeof(*relocationHdr))/unsafe.Sizeof(uint16(0))) 255 for _, relInfo := range relInfos { 256 // The upper 4 bits define the type of relocation. 257 relType := relInfo >> 12 258 // The lower 12 bits define the offset. 259 relOffset := uintptr(relInfo & 0xfff) 260 261 switch relType { 262 case IMAGE_REL_BASED_ABSOLUTE: 263 // Skip relocation. 264 265 case IMAGE_REL_BASED_LOW: 266 *(*uint16)(a2p(dest + relOffset)) += uint16(delta & 0xffff) 267 break 268 269 case IMAGE_REL_BASED_HIGH: 270 *(*uint16)(a2p(dest + relOffset)) += uint16(uint32(delta) >> 16) 271 break 272 273 case IMAGE_REL_BASED_HIGHLOW: 274 *(*uint32)(a2p(dest + relOffset)) += uint32(delta) 275 276 case IMAGE_REL_BASED_DIR64: 277 *(*uint64)(a2p(dest + relOffset)) += uint64(delta) 278 279 case IMAGE_REL_BASED_THUMB_MOV32: 280 inst := *(*uint32)(a2p(dest + relOffset)) 281 imm16 := ((inst << 1) & 0x0800) + ((inst << 12) & 0xf000) + 282 ((inst >> 20) & 0x0700) + ((inst >> 16) & 0x00ff) 283 if (inst & 0x8000fbf0) != 0x0000f240 { 284 return false, fmt.Errorf("Wrong Thumb2 instruction %08x, expected MOVW", inst) 285 } 286 imm16 += uint32(delta) & 0xffff 287 hiDelta := (uint32(delta&0xffff0000) >> 16) + ((imm16 & 0xffff0000) >> 16) 288 *(*uint32)(a2p(dest + relOffset)) = (inst & 0x8f00fbf0) + ((imm16 >> 1) & 0x0400) + 289 ((imm16 >> 12) & 0x000f) + 290 ((imm16 << 20) & 0x70000000) + 291 ((imm16 << 16) & 0xff0000) 292 if hiDelta != 0 { 293 inst = *(*uint32)(a2p(dest + relOffset + 4)) 294 imm16 = ((inst << 1) & 0x0800) + ((inst << 12) & 0xf000) + 295 ((inst >> 20) & 0x0700) + ((inst >> 16) & 0x00ff) 296 if (inst & 0x8000fbf0) != 0x0000f2c0 { 297 return false, fmt.Errorf("Wrong Thumb2 instruction %08x, expected MOVT", inst) 298 } 299 imm16 += hiDelta 300 if imm16 > 0xffff { 301 return false, fmt.Errorf("Resulting immediate value won't fit: %08x", imm16) 302 } 303 *(*uint32)(a2p(dest + relOffset + 4)) = (inst & 0x8f00fbf0) + 304 ((imm16 >> 1) & 0x0400) + 305 ((imm16 >> 12) & 0x000f) + 306 ((imm16 << 20) & 0x70000000) + 307 ((imm16 << 16) & 0xff0000) 308 } 309 310 default: 311 return false, fmt.Errorf("Unsupported relocation: %v", relType) 312 } 313 } 314 315 // Advance to next relocation block. 316 relocationHdr = (*IMAGE_BASE_RELOCATION)(a2p(uintptr(unsafe.Pointer(relocationHdr)) + uintptr(relocationHdr.SizeOfBlock))) 317 } 318 return true, nil 319 } 320 321 func (module *Module) buildImportTable() error { 322 directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_IMPORT) 323 if directory.Size == 0 { 324 return nil 325 } 326 327 module.modules = make([]windows.Handle, 0, 16) 328 importDesc := (*IMAGE_IMPORT_DESCRIPTOR)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) 329 for importDesc.Name != 0 { 330 handle, err := windows.LoadLibraryEx(windows.BytePtrToString((*byte)(a2p(module.codeBase+uintptr(importDesc.Name)))), 0, windows.LOAD_LIBRARY_SEARCH_SYSTEM32) 331 if err != nil { 332 return fmt.Errorf("Error loading module: %w", err) 333 } 334 var thunkRef, funcRef *uintptr 335 if importDesc.OriginalFirstThunk() != 0 { 336 thunkRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.OriginalFirstThunk()))) 337 funcRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk))) 338 } else { 339 // No hint table. 340 thunkRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk))) 341 funcRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk))) 342 } 343 for *thunkRef != 0 { 344 if IMAGE_SNAP_BY_ORDINAL(*thunkRef) { 345 *funcRef, err = windows.GetProcAddressByOrdinal(handle, IMAGE_ORDINAL(*thunkRef)) 346 } else { 347 thunkData := (*IMAGE_IMPORT_BY_NAME)(a2p(module.codeBase + *thunkRef)) 348 *funcRef, err = windows.GetProcAddress(handle, windows.BytePtrToString(&thunkData.Name[0])) 349 } 350 if err != nil { 351 windows.FreeLibrary(handle) 352 return fmt.Errorf("Error getting function address: %w", err) 353 } 354 thunkRef = (*uintptr)(a2p(uintptr(unsafe.Pointer(thunkRef)) + unsafe.Sizeof(*thunkRef))) 355 funcRef = (*uintptr)(a2p(uintptr(unsafe.Pointer(funcRef)) + unsafe.Sizeof(*funcRef))) 356 } 357 module.modules = append(module.modules, handle) 358 importDesc = (*IMAGE_IMPORT_DESCRIPTOR)(a2p(uintptr(unsafe.Pointer(importDesc)) + unsafe.Sizeof(*importDesc))) 359 } 360 return nil 361 } 362 363 func (module *Module) buildNameExports() error { 364 directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT) 365 if directory.Size == 0 { 366 return errors.New("No export table found") 367 } 368 exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) 369 if exports.NumberOfNames == 0 || exports.NumberOfFunctions == 0 { 370 return errors.New("No functions exported") 371 } 372 if exports.NumberOfNames == 0 { 373 return errors.New("No functions exported by name") 374 } 375 nameRefs := unsafe.Slice((*uint32)(a2p(module.codeBase+uintptr(exports.AddressOfNames))), exports.NumberOfNames) 376 ordinals := unsafe.Slice((*uint16)(a2p(module.codeBase+uintptr(exports.AddressOfNameOrdinals))), exports.NumberOfNames) 377 module.nameExports = make(map[string]uint16) 378 for i := range nameRefs { 379 nameArray := windows.BytePtrToString((*byte)(a2p(module.codeBase + uintptr(nameRefs[i])))) 380 module.nameExports[nameArray] = ordinals[i] 381 } 382 return nil 383 } 384 385 type addressRange struct { 386 start uintptr 387 end uintptr 388 } 389 390 var ( 391 loadedAddressRanges []addressRange 392 loadedAddressRangesMu sync.RWMutex 393 haveHookedRtlPcToFileHeader sync.Once 394 hookRtlPcToFileHeaderResult error 395 ) 396 397 func hookRtlPcToFileHeader() error { 398 var kernelBase windows.Handle 399 err := windows.GetModuleHandleEx(windows.GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, windows.StringToUTF16Ptr("kernelbase.dll"), &kernelBase) 400 if err != nil { 401 return err 402 } 403 imageBase := unsafe.Pointer(kernelBase) 404 dosHeader := (*IMAGE_DOS_HEADER)(imageBase) 405 ntHeaders := (*IMAGE_NT_HEADERS)(unsafe.Add(imageBase, dosHeader.E_lfanew)) 406 importsDirectory := ntHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] 407 importDescriptor := (*IMAGE_IMPORT_DESCRIPTOR)(unsafe.Add(imageBase, importsDirectory.VirtualAddress)) 408 for ; importDescriptor.Name != 0; importDescriptor = (*IMAGE_IMPORT_DESCRIPTOR)(unsafe.Add(unsafe.Pointer(importDescriptor), unsafe.Sizeof(*importDescriptor))) { 409 libraryName := windows.BytePtrToString((*byte)(unsafe.Add(imageBase, importDescriptor.Name))) 410 if strings.EqualFold(libraryName, "ntdll.dll") { 411 break 412 } 413 } 414 if importDescriptor.Name == 0 { 415 return errors.New("ntdll.dll not found") 416 } 417 originalThunk := (*uintptr)(unsafe.Add(imageBase, importDescriptor.OriginalFirstThunk())) 418 thunk := (*uintptr)(unsafe.Add(imageBase, importDescriptor.FirstThunk)) 419 for ; *originalThunk != 0; originalThunk = (*uintptr)(unsafe.Add(unsafe.Pointer(originalThunk), unsafe.Sizeof(*originalThunk))) { 420 if *originalThunk&IMAGE_ORDINAL_FLAG == 0 { 421 function := (*IMAGE_IMPORT_BY_NAME)(unsafe.Add(imageBase, *originalThunk)) 422 name := windows.BytePtrToString(&function.Name[0]) 423 if name == "RtlPcToFileHeader" { 424 break 425 } 426 } 427 thunk = (*uintptr)(unsafe.Add(unsafe.Pointer(thunk), unsafe.Sizeof(*thunk))) 428 } 429 if *originalThunk == 0 { 430 return errors.New("RtlPcToFileHeader not found") 431 } 432 var oldProtect uint32 433 err = windows.VirtualProtect(uintptr(unsafe.Pointer(thunk)), unsafe.Sizeof(*thunk), windows.PAGE_READWRITE, &oldProtect) 434 if err != nil { 435 return err 436 } 437 originalRtlPcToFileHeader := *thunk 438 *thunk = windows.NewCallback(func(pcValue uintptr, baseOfImage *uintptr) uintptr { 439 loadedAddressRangesMu.RLock() 440 for i := range loadedAddressRanges { 441 if pcValue >= loadedAddressRanges[i].start && pcValue < loadedAddressRanges[i].end { 442 pcValue = *thunk 443 break 444 } 445 } 446 loadedAddressRangesMu.RUnlock() 447 ret, _, _ := syscall.SyscallN(originalRtlPcToFileHeader, pcValue, uintptr(unsafe.Pointer(baseOfImage))) 448 return ret 449 }) 450 err = windows.VirtualProtect(uintptr(unsafe.Pointer(thunk)), unsafe.Sizeof(*thunk), oldProtect, &oldProtect) 451 if err != nil { 452 return err 453 } 454 return nil 455 } 456 457 // LoadLibrary loads module image to memory. 458 func LoadLibrary(data []byte) (module *Module, err error) { 459 addr := uintptr(unsafe.Pointer(&data[0])) 460 size := uintptr(len(data)) 461 if size < unsafe.Sizeof(IMAGE_DOS_HEADER{}) { 462 return nil, errors.New("Incomplete IMAGE_DOS_HEADER") 463 } 464 dosHeader := (*IMAGE_DOS_HEADER)(a2p(addr)) 465 if dosHeader.E_magic != IMAGE_DOS_SIGNATURE { 466 return nil, fmt.Errorf("Not an MS-DOS binary (provided: %x, expected: %x)", dosHeader.E_magic, IMAGE_DOS_SIGNATURE) 467 } 468 if (size < uintptr(dosHeader.E_lfanew)+unsafe.Sizeof(IMAGE_NT_HEADERS{})) { 469 return nil, errors.New("Incomplete IMAGE_NT_HEADERS") 470 } 471 oldHeader := (*IMAGE_NT_HEADERS)(a2p(addr + uintptr(dosHeader.E_lfanew))) 472 if oldHeader.Signature != IMAGE_NT_SIGNATURE { 473 return nil, fmt.Errorf("Not an NT binary (provided: %x, expected: %x)", oldHeader.Signature, IMAGE_NT_SIGNATURE) 474 } 475 if oldHeader.FileHeader.Machine != imageFileProcess { 476 return nil, fmt.Errorf("Foreign platform (provided: %x, expected: %x)", oldHeader.FileHeader.Machine, imageFileProcess) 477 } 478 if (oldHeader.OptionalHeader.SectionAlignment & 1) != 0 { 479 return nil, errors.New("Unaligned section") 480 } 481 lastSectionEnd := uintptr(0) 482 sections := oldHeader.Sections() 483 optionalSectionSize := oldHeader.OptionalHeader.SectionAlignment 484 for i := range sections { 485 var endOfSection uintptr 486 if sections[i].SizeOfRawData == 0 { 487 // Section without data in the DLL 488 endOfSection = uintptr(sections[i].VirtualAddress) + uintptr(optionalSectionSize) 489 } else { 490 endOfSection = uintptr(sections[i].VirtualAddress) + uintptr(sections[i].SizeOfRawData) 491 } 492 if endOfSection > lastSectionEnd { 493 lastSectionEnd = endOfSection 494 } 495 } 496 alignedImageSize := alignUp(uintptr(oldHeader.OptionalHeader.SizeOfImage), uintptr(oldHeader.OptionalHeader.SectionAlignment)) 497 if alignedImageSize != alignUp(lastSectionEnd, uintptr(oldHeader.OptionalHeader.SectionAlignment)) { 498 return nil, errors.New("Section is not page-aligned") 499 } 500 501 module = &Module{isDLL: (oldHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0} 502 defer func() { 503 if err != nil { 504 module.Free() 505 module = nil 506 } 507 }() 508 509 // Reserve memory for image of library. 510 // TODO: Is it correct to commit the complete memory region at once? Calling DllEntry raises an exception if we don't. 511 module.codeBase, err = windows.VirtualAlloc(oldHeader.OptionalHeader.ImageBase, 512 alignedImageSize, 513 windows.MEM_RESERVE|windows.MEM_COMMIT, 514 windows.PAGE_READWRITE) 515 if err != nil { 516 // Try to allocate memory at arbitrary position. 517 module.codeBase, err = windows.VirtualAlloc(0, 518 alignedImageSize, 519 windows.MEM_RESERVE|windows.MEM_COMMIT, 520 windows.PAGE_READWRITE) 521 if err != nil { 522 err = fmt.Errorf("Error allocating code: %w", err) 523 return 524 } 525 } 526 err = module.check4GBBoundaries(alignedImageSize) 527 if err != nil { 528 err = fmt.Errorf("Error reallocating code: %w", err) 529 return 530 } 531 532 if size < uintptr(oldHeader.OptionalHeader.SizeOfHeaders) { 533 err = errors.New("Incomplete headers") 534 return 535 } 536 // Commit memory for headers. 537 headers, err := windows.VirtualAlloc(module.codeBase, 538 uintptr(oldHeader.OptionalHeader.SizeOfHeaders), 539 windows.MEM_COMMIT, 540 windows.PAGE_READWRITE) 541 if err != nil { 542 err = fmt.Errorf("Error allocating headers: %w", err) 543 return 544 } 545 // Copy PE header to code. 546 memcpy(headers, addr, uintptr(oldHeader.OptionalHeader.SizeOfHeaders)) 547 module.headers = (*IMAGE_NT_HEADERS)(a2p(headers + uintptr(dosHeader.E_lfanew))) 548 549 // Update position. 550 module.headers.OptionalHeader.ImageBase = module.codeBase 551 552 // Copy sections from DLL file block to new memory location. 553 err = module.copySections(addr, size, oldHeader) 554 if err != nil { 555 err = fmt.Errorf("Error copying sections: %w", err) 556 return 557 } 558 559 // Adjust base address of imported data. 560 locationDelta := module.headers.OptionalHeader.ImageBase - oldHeader.OptionalHeader.ImageBase 561 if locationDelta != 0 { 562 module.isRelocated, err = module.performBaseRelocation(locationDelta) 563 if err != nil { 564 err = fmt.Errorf("Error relocating module: %w", err) 565 return 566 } 567 } else { 568 module.isRelocated = true 569 } 570 571 // Load required dlls and adjust function table of imports. 572 err = module.buildImportTable() 573 if err != nil { 574 err = fmt.Errorf("Error building import table: %w", err) 575 return 576 } 577 578 // Mark memory pages depending on section headers and release sections that are marked as "discardable". 579 err = module.finalizeSections() 580 if err != nil { 581 err = fmt.Errorf("Error finalizing sections: %w", err) 582 return 583 } 584 585 // Register exception tables, if they exist. 586 module.registerExceptionHandlers() 587 588 // Register function PCs. 589 loadedAddressRangesMu.Lock() 590 loadedAddressRanges = append(loadedAddressRanges, addressRange{module.codeBase, module.codeBase + alignedImageSize}) 591 loadedAddressRangesMu.Unlock() 592 haveHookedRtlPcToFileHeader.Do(func() { 593 hookRtlPcToFileHeaderResult = hookRtlPcToFileHeader() 594 }) 595 err = hookRtlPcToFileHeaderResult 596 if err != nil { 597 return 598 } 599 600 // TLS callbacks are executed BEFORE the main loading. 601 module.executeTLS() 602 603 // Get entry point of loaded module. 604 if module.headers.OptionalHeader.AddressOfEntryPoint != 0 { 605 module.entry = module.codeBase + uintptr(module.headers.OptionalHeader.AddressOfEntryPoint) 606 if module.isDLL { 607 // Notify library about attaching to process. 608 r0, _, _ := syscall.SyscallN(module.entry, module.codeBase, DLL_PROCESS_ATTACH, 0) 609 successful := r0 != 0 610 if !successful { 611 err = windows.ERROR_DLL_INIT_FAILED 612 return 613 } 614 module.initialized = true 615 } 616 } 617 618 module.buildNameExports() 619 return 620 } 621 622 // Free releases module resources and unloads it. 623 func (module *Module) Free() { 624 if module.initialized { 625 // Notify library about detaching from process. 626 syscall.SyscallN(module.entry, module.codeBase, DLL_PROCESS_DETACH, 0) 627 module.initialized = false 628 } 629 if module.modules != nil { 630 // Free previously opened libraries. 631 for _, handle := range module.modules { 632 windows.FreeLibrary(handle) 633 } 634 module.modules = nil 635 } 636 if module.codeBase != 0 { 637 windows.VirtualFree(module.codeBase, 0, windows.MEM_RELEASE) 638 module.codeBase = 0 639 } 640 if module.blockedMemory != nil { 641 module.blockedMemory.free() 642 module.blockedMemory = nil 643 } 644 } 645 646 // ProcAddressByName returns function address by exported name. 647 func (module *Module) ProcAddressByName(name string) (uintptr, error) { 648 directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT) 649 if directory.Size == 0 { 650 return 0, errors.New("No export table found") 651 } 652 exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) 653 if module.nameExports == nil { 654 return 0, errors.New("No functions exported by name") 655 } 656 if idx, ok := module.nameExports[name]; ok { 657 if uint32(idx) > exports.NumberOfFunctions { 658 return 0, errors.New("Ordinal number too high") 659 } 660 // AddressOfFunctions contains the RVAs to the "real" functions. 661 return module.codeBase + uintptr(*(*uint32)(a2p(module.codeBase + uintptr(exports.AddressOfFunctions) + uintptr(idx)*4))), nil 662 } 663 return 0, errors.New("Function not found by name") 664 } 665 666 // ProcAddressByOrdinal returns function address by exported ordinal. 667 func (module *Module) ProcAddressByOrdinal(ordinal uint16) (uintptr, error) { 668 directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT) 669 if directory.Size == 0 { 670 return 0, errors.New("No export table found") 671 } 672 exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress))) 673 if uint32(ordinal) < exports.Base { 674 return 0, errors.New("Ordinal number too low") 675 } 676 idx := ordinal - uint16(exports.Base) 677 if uint32(idx) > exports.NumberOfFunctions { 678 return 0, errors.New("Ordinal number too high") 679 } 680 // AddressOfFunctions contains the RVAs to the "real" functions. 681 return module.codeBase + uintptr(*(*uint32)(a2p(module.codeBase + uintptr(exports.AddressOfFunctions) + uintptr(idx)*4))), nil 682 } 683 684 func alignDown(value, alignment uintptr) uintptr { 685 return value & ^(alignment - 1) 686 } 687 688 func alignUp(value, alignment uintptr) uintptr { 689 return (value + alignment - 1) & ^(alignment - 1) 690 } 691 692 func a2p(addr uintptr) unsafe.Pointer { 693 return unsafe.Pointer(addr) 694 } 695 696 func memcpy(dst, src, size uintptr) { 697 copy(unsafe.Slice((*byte)(a2p(dst)), size), unsafe.Slice((*byte)(a2p(src)), size)) 698 }