├── go.mod ├── go.sum ├── README.md ├── main.go ├── asm.s ├── func.go ├── unhook.go └── conf.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/timwhitez/JmpUnhook 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/Binject/debug v0.0.0-20211007083345-9605c99179ee 7 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Binject/debug v0.0.0-20211007083345-9605c99179ee h1:neBp9wDYVY4Uu1gGlrL+IL4JeZslz+hGEAjBXGAPWak= 2 | github.com/Binject/debug v0.0.0-20211007083345-9605c99179ee/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ= 3 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= 4 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JmpUnhook 2 | Ntdll Unhooking POC 3 | 4 | 又是受到别的项目启发 5 | 6 | https://github.com/trickster0/LdrLoadDll-Unhooking 7 | 8 | 这个项目采用了构造函数并跳转的方式进行脱钩 9 | 10 | 但是针对不同的函数又需要一系列定制的手段 11 | 12 | 本项目提供了一种思路,以通用方式构造中转函数 13 | 14 | 仅讨论实现思路,具体效果不在此讨论范围 15 | 16 | 具体实现思路: 17 | ``` 18 | 假设需要调用ntdll里A函数: 19 | 20 | 1.读取ntdll.dll文件里的函数A 21 | 2.读取内存ntdll里的函数A 22 | 3.判断这两个的函数前两个字节是否相等 23 | 4.若相等则判定为未被hook直接返回内存中的地址 24 | 5.若不相等遍历对比前几个字节直到第n个字节相等 25 | 6.构造中转函数,将前n个字节填充成ntdll文件内的内容,后面为jmp跳转指令跳转回内存中对应位置 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | func main(){ 11 | 12 | //构造函数(申请可执行内存空间) 13 | a := func()uintptr{return 0} 14 | ptr := syscall.NewCallback(a) 15 | 16 | //获取脱钩的函数地址 17 | p,raw,e := JmpUnhook(ptr,"NtWriteVirtualMemory") 18 | if e != nil{ 19 | panic(e) 20 | } 21 | 22 | //打印原函数地址 23 | fmt.Printf("Raw: 0x%x\n",raw) 24 | //打印新函数地址 25 | fmt.Printf("Addr: 0x%x\n",p) 26 | 27 | 28 | fmt.Print("Press 'Enter' to continue...") 29 | bufio.NewReader(os.Stdin).ReadBytes('\n') 30 | 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /asm.s: -------------------------------------------------------------------------------- 1 | 2 | //func getModuleLoadedOrder(i int) (start uintptr, size uintptr) 3 | TEXT ·getMLO(SB), $0-32 4 | //All operations push values into AX 5 | //PEB 6 | MOVQ 0x60(GS), AX 7 | BYTE $0x90 //NOP 8 | //PEB->LDR 9 | MOVQ 0x18(AX),AX 10 | BYTE $0x90 //NOP 11 | 12 | //LDR->InMemoryOrderModuleList 13 | MOVQ 0x20(AX),AX 14 | BYTE $0x90 //NOP 15 | 16 | //loop things 17 | XORQ R10,R10 18 | startloop: 19 | CMPQ R10,i+0(FP) 20 | BYTE $0x90 //NOP 21 | JE endloop 22 | BYTE $0x90 //NOP 23 | //Flink (get next element) 24 | MOVQ (AX),AX 25 | BYTE $0x90 //NOP 26 | INCQ R10 27 | JMP startloop 28 | endloop: 29 | //Flink - 0x10 -> _LDR_DATA_TABLE_ENTRY 30 | //_LDR_DATA_TABLE_ENTRY->DllBase (offset 0x30) 31 | 32 | MOVQ 0x30(AX),CX 33 | BYTE $0x90 //NOP 34 | MOVQ CX, size+16(FP) 35 | BYTE $0x90 //NOP 36 | 37 | 38 | MOVQ 0x20(AX),CX 39 | BYTE $0x90 //NOP 40 | MOVQ CX, start+8(FP) 41 | BYTE $0x90 //NOP 42 | 43 | 44 | MOVQ AX,CX 45 | BYTE $0x90 //NOP 46 | ADDQ $0x38,CX 47 | BYTE $0x90 //NOP 48 | MOVQ CX, modulepath+24(FP) 49 | //SYSCALL 50 | RET 51 | 52 | -------------------------------------------------------------------------------- /func.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func GetFunc(funcname string) uintptr { 8 | var phModule uintptr 9 | //Get dll module BaseAddr 10 | phModule, _ = inMemLoads("ntdll.dll") 11 | 12 | //get dll exports 13 | ex := GetExport(phModule) 14 | 15 | for _, exp := range ex { 16 | if exp.Name == funcname{ 17 | return uintptr(exp.VirtualAddress) 18 | } 19 | } 20 | return 0 21 | } 22 | 23 | 24 | 25 | 26 | //InMemLoads returns a map of loaded dll paths to current process offsets (aka images) in the current process. No syscalls are made. 27 | func inMemLoads(modulename string) (uintptr, uintptr) { 28 | s, si, p := gMLO(0) 29 | start := p 30 | i := 1 31 | if strings.Contains(strings.ToLower(p), strings.ToLower(modulename)) { 32 | return s, si 33 | } 34 | for { 35 | s, si, p = gMLO(i) 36 | if p != "" { 37 | if strings.Contains(strings.ToLower(p), strings.ToLower(modulename)) { 38 | return s, si 39 | } 40 | } 41 | if p == start { 42 | break 43 | } 44 | i++ 45 | } 46 | return 0, 0 47 | } 48 | 49 | //GetModuleLoadedOrder returns the start address of module located at i in the load order. This might be useful if there is a function you need that isn't in ntdll, or if some rude individual has loaded themselves before ntdll. 50 | func gMLO(i int) (start uintptr, size uintptr, modulepath string) { 51 | var badstring *sstring 52 | start, size, badstring = getMLO(i) 53 | modulepath = badstring.String() 54 | return 55 | } 56 | 57 | 58 | //getModuleLoadedOrder returns the start address of module located at i in the load order. This might be useful if there is a function you need that isn't in ntdll, or if some rude individual has loaded themselves before ntdll. 59 | func getMLO(i int) (start uintptr, size uintptr, modulepath *sstring) -------------------------------------------------------------------------------- /unhook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Binject/debug/pe" 6 | "golang.org/x/sys/windows" 7 | "unsafe" 8 | ) 9 | 10 | func JmpUnhook(ptr uintptr,funcN string)(uintptr,uintptr,error){ 11 | 12 | //构造填充 13 | jmpPre := []byte{0x49, 0xBB} 14 | //jmpAddr := []byte{0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF } 15 | jmpRet := []byte{0x41, 0xFF, 0xE3, 0xC3} 16 | 17 | 18 | //获取内存中函数地址 19 | memPtr := GetFunc(funcN) 20 | 21 | //获取文件内函数 22 | p, e := pe.Open("C:\\windows\\system32\\ntdll.dll") 23 | if e!= nil { 24 | return 0,memPtr,e 25 | } 26 | ex, e := p.Exports() 27 | if e != nil { 28 | return 0,memPtr,e 29 | } 30 | var buff []byte 31 | for _, exp := range ex { 32 | if exp.Name == funcN { 33 | dllOffset := uintptr(rvaToOffset(p, exp.VirtualAddress)) 34 | b, _ := p.Bytes() 35 | buff = b[dllOffset : dllOffset+21] 36 | } 37 | } 38 | if buff == nil{ 39 | return 0,memPtr,fmt.Errorf("not found in file") 40 | } 41 | 42 | //对比函数字节,判断是否被hook,若未被hook,返回原地址 43 | if buff[0] == *(*byte)(unsafe.Pointer(memPtr)) && buff[1] == *(*byte)(unsafe.Pointer(memPtr+1)){ 44 | return memPtr,memPtr,nil 45 | } 46 | 47 | //构造unhook函数 48 | for i := 0;i < len(buff)-2;i++{ 49 | if buff[i] == *(*byte)(unsafe.Pointer(memPtr+uintptr(i))) && buff[i+1] == *(*byte)(unsafe.Pointer(memPtr+uintptr(i+1))){ 50 | addr := memPtr+uintptr(i) 51 | jmpAddr := uintptrToBytes(&addr) 52 | 53 | windows.WriteProcessMemory(0xffffffffffffffff,ptr,&buff[0],uintptr(i),nil) 54 | windows.WriteProcessMemory(0xffffffffffffffff,ptr+uintptr(i),&jmpPre[0],uintptr(2),nil) 55 | windows.WriteProcessMemory(0xffffffffffffffff,ptr+uintptr(i)+2,&jmpAddr[0],uintptr(len(jmpAddr)),nil) 56 | windows.WriteProcessMemory(0xffffffffffffffff,ptr+uintptr(i)+2+8,&jmpRet[0],uintptr(4),nil) 57 | 58 | return ptr,memPtr,nil 59 | } 60 | } 61 | 62 | return 0,memPtr,fmt.Errorf("last err") 63 | 64 | } 65 | 66 | 67 | const sizeOfUintPtr = unsafe.Sizeof(uintptr(0)) 68 | 69 | func uintptrToBytes(u *uintptr) []byte { 70 | return (*[sizeOfUintPtr]byte)(unsafe.Pointer(u))[:] 71 | } 72 | 73 | 74 | //rvaToOffset converts an RVA value from a PE file into the file offset. When using binject/debug, this should work fine even with in-memory files. 75 | func rvaToOffset(pefile *pe.File, rva uint32) uint32 { 76 | for _, hdr := range pefile.Sections { 77 | baseoffset := uint64(rva) 78 | if baseoffset > uint64(hdr.VirtualAddress) && 79 | baseoffset < uint64(hdr.VirtualAddress+hdr.VirtualSize) { 80 | return rva - hdr.VirtualAddress + hdr.Offset 81 | } 82 | } 83 | return rva 84 | } 85 | -------------------------------------------------------------------------------- /conf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "golang.org/x/sys/windows" 5 | "unsafe" 6 | ) 7 | 8 | type ( 9 | DWORD uint32 10 | ULONGLONG uint64 11 | WORD uint16 12 | BYTE uint8 13 | LONG uint32 14 | ) 15 | 16 | const ( 17 | IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16 18 | ) 19 | 20 | type _IMAGE_FILE_HEADER struct { 21 | Machine WORD 22 | NumberOfSections WORD 23 | TimeDateStamp DWORD 24 | PointerToSymbolTable DWORD 25 | NumberOfSymbols DWORD 26 | SizeOfOptionalHeader WORD 27 | Characteristics WORD 28 | } 29 | 30 | type IMAGE_FILE_HEADER _IMAGE_FILE_HEADER 31 | 32 | type IMAGE_OPTIONAL_HEADER64 _IMAGE_OPTIONAL_HEADER64 33 | type IMAGE_OPTIONAL_HEADER IMAGE_OPTIONAL_HEADER64 34 | 35 | type _IMAGE_OPTIONAL_HEADER64 struct { 36 | Magic WORD 37 | MajorLinkerVersion BYTE 38 | MinorLinkerVersion BYTE 39 | SizeOfCode DWORD 40 | SizeOfInitializedData DWORD 41 | SizeOfUninitializedData DWORD 42 | AddressOfEntryPoint DWORD 43 | BaseOfCode DWORD 44 | ImageBase ULONGLONG 45 | SectionAlignment DWORD 46 | FileAlignment DWORD 47 | MajorOperatingSystemVersion WORD 48 | MinorOperatingSystemVersion WORD 49 | MajorImageVersion WORD 50 | MinorImageVersion WORD 51 | MajorSubsystemVersion WORD 52 | MinorSubsystemVersion WORD 53 | Win32VersionValue DWORD 54 | SizeOfImage DWORD 55 | SizeOfHeaders DWORD 56 | CheckSum DWORD 57 | Subsystem WORD 58 | DllCharacteristics WORD 59 | SizeOfStackReserve ULONGLONG 60 | SizeOfStackCommit ULONGLONG 61 | SizeOfHeapReserve ULONGLONG 62 | SizeOfHeapCommit ULONGLONG 63 | LoaderFlags DWORD 64 | NumberOfRvaAndSizes DWORD 65 | DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]IMAGE_DATA_DIRECTORY 66 | } 67 | type _IMAGE_DATA_DIRECTORY struct { 68 | VirtualAddress DWORD 69 | Size DWORD 70 | } 71 | type IMAGE_DATA_DIRECTORY _IMAGE_DATA_DIRECTORY 72 | 73 | type _IMAGE_NT_HEADERS64 struct { 74 | Signature DWORD 75 | FileHeader IMAGE_FILE_HEADER 76 | OptionalHeader IMAGE_OPTIONAL_HEADER 77 | } 78 | type IMAGE_NT_HEADERS64 _IMAGE_NT_HEADERS64 79 | type IMAGE_NT_HEADERS IMAGE_NT_HEADERS64 80 | type _IMAGE_DOS_HEADER struct { // DOS .EXE header 81 | E_magic WORD // Magic number 82 | E_cblp WORD // Bytes on last page of file 83 | E_cp WORD // Pages in file 84 | E_crlc WORD // Relocations 85 | E_cparhdr WORD // Size of header in paragraphs 86 | E_minalloc WORD // Minimum extra paragraphs needed 87 | E_maxalloc WORD // Maximum extra paragraphs needed 88 | E_ss WORD // Initial (relative) SS value 89 | E_sp WORD // Initial SP value 90 | E_csum WORD // Checksum 91 | E_ip WORD // Initial IP value 92 | E_cs WORD // Initial (relative) CS value 93 | E_lfarlc WORD // File address of relocation table 94 | E_ovno WORD // Overlay number 95 | E_res [4]WORD // Reserved words 96 | E_oemid WORD // OEM identifier (for E_oeminfo) 97 | E_oeminfo WORD // OEM information; E_oemid specific 98 | E_res2 [10]WORD // Reserved words 99 | E_lfanew LONG // File address of new exe header 100 | } 101 | 102 | type IMAGE_DOS_HEADER _IMAGE_DOS_HEADER 103 | 104 | 105 | func ntH(baseAddress uintptr) *IMAGE_NT_HEADERS { 106 | return (*IMAGE_NT_HEADERS)(unsafe.Pointer(baseAddress + uintptr((*IMAGE_DOS_HEADER)(unsafe.Pointer(baseAddress)).E_lfanew))) 107 | } 108 | 109 | 110 | // Export - describes a single export entry 111 | type Export struct { 112 | Name string 113 | VirtualAddress uintptr 114 | } 115 | type imageExportDir struct { 116 | _, _ uint32 117 | _, _ uint16 118 | Name uint32 119 | Base uint32 120 | NumberOfFunctions uint32 121 | NumberOfNames uint32 122 | AddressOfFunctions uint32 123 | AddressOfNames uint32 124 | AddressOfNameOrdinals uint32 125 | } 126 | 127 | func GetExport(pModuleBase uintptr) []Export { 128 | var exports []Export 129 | var pImageNtHeaders = ntH(pModuleBase) 130 | //IMAGE_NT_SIGNATURE 131 | if pImageNtHeaders.Signature != 0x00004550 { 132 | return nil 133 | } 134 | var pImageExportDirectory *imageExportDir 135 | 136 | pImageExportDirectory = ((*imageExportDir)(unsafe.Pointer(uintptr(pModuleBase + uintptr(pImageNtHeaders.OptionalHeader.DataDirectory[0].VirtualAddress))))) 137 | 138 | pdwAddressOfFunctions := pModuleBase + uintptr(pImageExportDirectory.AddressOfFunctions) 139 | pdwAddressOfNames := pModuleBase + uintptr(pImageExportDirectory.AddressOfNames) 140 | 141 | pwAddressOfNameOrdinales := pModuleBase + uintptr(pImageExportDirectory.AddressOfNameOrdinals) 142 | 143 | for cx := uintptr(0); cx < uintptr((pImageExportDirectory).NumberOfNames); cx++ { 144 | var export Export 145 | pczFunctionName := pModuleBase + uintptr(*(*uint32)(unsafe.Pointer(pdwAddressOfNames + cx*4))) 146 | pFunctionAddress := pModuleBase + uintptr(*(*uint32)(unsafe.Pointer(pdwAddressOfFunctions + uintptr(*(*uint16)(unsafe.Pointer(pwAddressOfNameOrdinales + cx*2)))*4))) 147 | export.Name = windows.BytePtrToString((*byte)(unsafe.Pointer(pczFunctionName))) 148 | export.VirtualAddress = uintptr(pFunctionAddress) 149 | exports = append(exports, export) 150 | } 151 | 152 | return exports 153 | } 154 | 155 | 156 | //sstring is the stupid internal windows definiton of a unicode string. I hate it. 157 | type sstring struct { 158 | Length uint16 159 | MaxLength uint16 160 | PWstr *uint16 161 | } 162 | 163 | func (s sstring) String() string { 164 | return windows.UTF16PtrToString(s.PWstr) 165 | } 166 | --------------------------------------------------------------------------------