├── README.md └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # shellGo 2 | A Microsoft windows x86_64 Golang shellcode tester that includes example calc.exe shellcode. 3 | 4 | - go run main.go : runs calc.exe 5 | - go run main.go : loads the binary data from and attempts to call it as shellcode 6 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | MEM_COMMIT = 0x1000 12 | MEM_RESERVE = 0x2000 13 | PAGE_EXECUTE_READWRITE = 0x40 14 | ) 15 | 16 | var ( 17 | kernel32 = syscall.MustLoadDLL("kernel32.dll") 18 | ntdll = syscall.MustLoadDLL("ntdll.dll") 19 | VirtualAlloc = kernel32.MustFindProc("VirtualAlloc") 20 | RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory") 21 | shellcode_calc = []byte{ /* 22 | bits 64 23 | section .text 24 | global shellcode 25 | shellcode: 26 | 27 | ; x64 WinExec *requires* 16 byte stack alignment and four QWORDS of stack space, which may be overwritten. 28 | ; http://msdn.microsoft.com/en-us/library/ms235286.aspx 29 | 30 | push rax 31 | push rcx 32 | push rdx 33 | push rbx 34 | push rsi 35 | push rdi 36 | push rbp 37 | push 0x60 ; Stack is now 16 bit aligned 38 | pop rdx ; RDX = 0x60 39 | push 'calc' 40 | push rsp 41 | pop rcx ; RCX = &("calc") 42 | sub rsp, 0x28 ; Stack was 16 byte aligned already and there are >4 QWORDS on the stack. 43 | mov rsi, [gs:rdx] ; RSI = [TEB + 0x60] = &PEB 44 | mov rsi, [rsi + 0x18] ; RSI = [PEB + 0x18] = PEB_LDR_DATA 45 | mov rsi, [rsi + 0x10] ; RSI = [PEB_LDR_DATA + 0x10] = LDR_MODULE InLoadOrder[0] (process) 46 | lodsq ; RAX = InLoadOrder[1] (ntdll) 47 | mov rsi, [rax] ; RSI = InLoadOrder[2] (kernel32) 48 | mov rdi, [rsi + 0x30] ; RDI = [InLoadOrder[2] + 0x30] = kernel32 DllBase 49 | 50 | ; Found kernel32 base address (RDI) 51 | 52 | add edx, dword [rdi + 0x3c] ; RBX = 0x60 + [kernel32 + 0x3C] = offset(PE header) + 0x60 53 | 54 | ; PE header (RDI+RDX-0x60) = @0x00 0x04 byte signature 55 | ; @0x04 0x18 byte COFF header 56 | ; @0x18 PE32 optional header (= RDI + RDX - 0x60 + 0x18) 57 | 58 | mov ebx, dword [rdi + rdx - 0x60 + 0x18 + 0x70] ; RBX = [PE32+ optional header + offset(PE32+ export table offset)] = offset(export table) 59 | 60 | ; Export table (RDI+EBX) = @0x20 Name Pointer RVA 61 | 62 | mov esi, dword [rdi + rbx + 0x20] ; RSI = [kernel32 + offset(export table) + 0x20] = offset(names table) 63 | add rsi, rdi ; RSI = kernel32 + offset(names table) = &(names table) 64 | 65 | ; Found export names table (RSI) 66 | 67 | mov edx, dword [rdi + rbx + 0x24] ; EDX = [kernel32 + offset(export table) + 0x24] = offset(ordinals table) 68 | 69 | ; Found export ordinals table (RDX) 70 | 71 | find_winexec_x64: ; speculatively load ordinal (RBP) 72 | movzx ebp, word [rdi + rdx] ; RBP = [kernel32 + offset(ordinals table) + offset] = function ordinal 73 | lea edx, [rdx + 2] ; RDX = offset += 2 (will wrap if > 4Gb, but this should never happen) 74 | lodsd ; RAX = &(names table[function number]) = offset(function name) 75 | cmp dword [rdi + rax], 'WinE' ; *(DWORD*)(function name) == "WinE" ? 76 | jne find_winexec_x64 77 | 78 | mov esi, dword [rdi + rbx + 0x1c] ; RSI = [kernel32 + offset(export table) + 0x1C] = offset(address table) 79 | add rsi, rdi ; RSI = kernel32 + offset(address table) = &(address table) 80 | mov esi, [rsi + rbp * 4] ; RSI = &(address table)[WinExec ordinal] = offset(WinExec) 81 | add rdi, rsi ; RDI = kernel32 + offset(WinExec) = WinExec 82 | 83 | ; Found WinExec (RDI) 84 | 85 | cdq ; RDX = 0 (assuming EAX < 0x80000000, which should always be true) 86 | call rdi ; WinExec(&("calc"), 0); 87 | add rsp, 0x30 ; reset stack to where it was after pushing registers 88 | pop rbp ; pop all the items off the stack that we pushed on earlier 89 | pop rdi 90 | pop rsi 91 | pop rbx 92 | pop rdx 93 | pop rcx 94 | pop rax 95 | retn 96 | */ 97 | 98 | // nasm -DFUNC=TRUE -DCLEAN=TRUE -DSTACK_ALIGN=TRUE w64-exec-calc-shellcode.asm 99 | 100 | 0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 101 | 0x59, 0x48, 0x83, 0xEC, 0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76, 102 | 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 103 | 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 104 | 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 105 | 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 106 | 0x30, 0x5D, 0x5F, 0x5E, 0x5B, 0x5A, 0x59, 0x58, 0xC3, 107 | } 108 | ) 109 | 110 | func checkErr(err error) { 111 | if err != nil { 112 | if err.Error() != "The operation completed successfully." { 113 | println(err.Error()) 114 | os.Exit(1) 115 | } 116 | } 117 | } 118 | 119 | func main() { 120 | shellcode := shellcode_calc 121 | if len(os.Args) > 1 { 122 | shellcodeFileData, err := ioutil.ReadFile(os.Args[1]) 123 | checkErr(err) 124 | shellcode = shellcodeFileData 125 | } 126 | 127 | addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) 128 | if addr == 0 { 129 | checkErr(err) 130 | } 131 | _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode))) 132 | checkErr(err) 133 | syscall.Syscall(addr, 0, 0, 0, 0) 134 | } 135 | --------------------------------------------------------------------------------