craftwa.re

A walk outside the sandbox

Home Blog Cheat Sheets MacOS Tips Area 51 About

Fun With Shellcode On MacOS x86_64

|

Overview and historic info

Before diving into building a test 64-bit shellcode on macOS Sierra, some historic information will help to understand the context:

  • The stack of applications is marked as non-executable by default to prevent code injection and stack-based buffer overflows.
  • The heap is not executable by default, although it is considerably harder (although not impossible) to inject code via the heap.
  • On previoud macOS versions, both these settings could be changed system-wide using sysctl (8) command and setting the vm.allow_stack_exec and vm.allow_heap_exec variables to 1. This is no longer possible in Sierra:
$ sysctl -a | grep exec
security.mac.qtn.user_approved_exec: 1

$ sysctl -w vm.allow_stack_exec = 1
sysctl: unknown oid 'vm.allow_stack_exec'

$ sysctl -w vm.allow_heap_exec = 1
sysctl: unknown oid 'vm.allow_heap_exec'
  • For iOS, by default neither heap nor stack are executable.

Building shellcode

To start with, we need a simple x86-64 assembly source code. The one from here looks good:

section .data
hello_world     db "Hello World!", 0x0a

section .text
global start

start:
mov rax, 0x2000004   ; System call write = 4
mov rdi, 1           ; Write to standard out = 1
mov rsi, hello_world ; The address of hello_world string
mov rdx, 14          ; The size to write
syscall              ; Invoke the kernel
mov rax, 0x2000001   ; System call number for exit = 1
mov rdi, 0           ; Exit success = 0
syscall              ; Invoke the kernel

Next, compile the assembly, link the object to a binary and test it. A newer version of nasm is needed since the default one in Sierra doesn’t suport macho64 objects:

$ nasm -v
NASM version 2.13.03 compiled on Feb  8 2018

$ brew install nasm
$ ln -s /usr/local/Cellar/nasm/2.13.03/bin/nasm myNasm

$ ./myNasm -v
NASM version 2.13.03 compiled on Feb  8 2018

$ ./myNasm -f macho64 hello-simple.s
$ ld hello-simple.o -o hello-simple
$ ./hello-simple
Hello World!

OK, it works. Next, to obtain a shellcode from the binary, extract the code bytes of the text section:

$ objdump -d hello-simple

hello-simple:	file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
__text:
    1fd9:	b8 04 00 00 02 	movl	$33554436, %eax
    1fde:	bf 01 00 00 00 	movl	$1, %edi
    1fe3:	48 be 00 20 00 00 00 00 00 00 	movabsq	$8192, %rsi
    1fed:	ba 0e 00 00 00 	movl	$14, %edx
    1ff2:	0f 05 	syscall
    1ff4:	b8 01 00 00 02 	movl	$33554433, %eax
    1ff9:	bf 00 00 00 00 	movl	$0, %edi
    1ffe:	0f 05 	syscall

start:
    1fd9:	b8 04 00 00 02 	movl	$33554436, %eax
    1fde:	bf 01 00 00 00 	movl	$1, %edi
    1fe3:	48 be 00 20 00 00 00 00 00 00 	movabsq	$8192, %rsi
    1fed:	ba 0e 00 00 00 	movl	$14, %edx
    1ff2:	0f 05 	syscall
    1ff4:	b8 01 00 00 02 	movl	$33554433, %eax
    1ff9:	bf 00 00 00 00 	movl	$0, %edi
    1ffe:	0f 05 	syscall

$ otool -t hello-simple
hello-simple:
Contents of (__TEXT,__text) section
0000000000001fd9	b8 04 00 00 02 bf 01 00 00 00 48 be 00 20 00 00
0000000000001fe9	00 00 00 00 ba 0e 00 00 00 0f 05 b8 01 00 00 02
0000000000001ff9	bf 00 00 00 00 0f 05

Next, we need to plug this shellcode into a template c code that will execute it. We need to make sure that the shellcode will be in an executable memory section. By default, a string we define would reside in the .data section. To be safe, we’ll move it to the .text section, which contains code and is executable:

const char sc[] __attribute__((section("__TEXT,__text"))) = "\xb8\x04\x00\x00\x02\xbf\x01\x00\x00\x00\x48\xbe\x00\x20\x00\x00\x00\x00\x00\x00\xba\x0e\x00\x00\x00\x0f\x05\xb8\x01\x00\x00\x02\xbf\x00\x00\x00\x00\x0f\x05";

typedef int (*funcPtr)();
int main(int argc, char **argv)
{
    funcPtr func = (funcPtr) sc;
    (*func)();

    return 0;
}

Let’s test:

$ clang  hello.c -o hello2
$ ./hello2

No message. Apparently nothign happens. Time to bring up lldb. As a side-note, if you’re not familiar with lldb there is a nice cheatsheet mapping GDB to LLDB commands. Fire up lldb and start analysing:

$ lldb ./hello2
(lldb) target create "./hello2"
Current executable set to './hello2' (x86_64).
(lldb) breakpoint set --name main
Breakpoint 1: where = hello2`main, address = 0x0000000100000f50
(lldb) r
Process 4650 launched: './hello2' (x86_64)
Process 4650 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000f50 hello2`main
hello2`main:
->  0x100000f50 <+0>: pushq  %rbp
    0x100000f51 <+1>: movq   %rsp, %rbp
    0x100000f54 <+4>: subq   $0x20, %rsp
    0x100000f58 <+8>: leaq   0x31(%rip), %rax          ; sc
[..]

Step into the call running the shellcode and notice the point where the message string gets moved into rsi:

->  0x100000f9a <+10>: movabsq $0x2000, %rsi             ; imm = 0x2000
    0x100000fa4 <+20>: movl   $0xe, %edx
    0x100000fa9 <+25>: syscall
    0x100000fab <+27>: movl   $0x2000001, %eax          ; imm = 0x2000001
Target 0: (hello2) stopped.
(lldb) x/s 0x2000
error: failed to read memory from 0x2000.

The problem is that code needs to be position independent, and in this case clearly it’s not since the initial binary was reading the string from the .data section. This is a well-known issue, not specific to OSX or 64-bit so I won’t insist on it. The solution is also well-known:

section .data
; Not relevant; just to avoid 'dyld: no writable segment' error
hello   db  "empty!"

section .text

global start

start:
    jmp trick

continue:
    pop rsi              ; Pop string ddress into rsi
    mov rax, 0x2000004   ; System call write = 4
    mov rdi, 1           ; Write to standard out = 1
    mov rdx, 14          ; The size to write
    syscall              ; Invoke the kernel
    mov rax, 0x2000001   ; System call number for exit = 1
    mov rdi, 0           ; Exit success = 0
    syscall              ; Invoke the kernel

trick:
    call continue
    db "Hello World!", 0x0d, 0x0a

Let’s see if it works now:

$ ./myNasm -f macho64 hello.s
$ ld hello.o -o hello

$ ./hello
Hello World!

$ otool -t hello
hello:
Contents of (__TEXT,__text) section
0000000000001fcd	eb 1e 5e b8 04 00 00 02 bf 01 00 00 00 ba 0e 00
0000000000001fdd	00 00 0f 05 b8 01 00 00 02 bf 00 00 00 00 0f 05
0000000000001fed	e8 dd ff ff ff 48 65 6c 6c 6f 20 57 6f 72 6c 64
0000000000001ffd	21 0d 0a

$ clang  hello.c -o hello3

$ ./hello3
Hello World!

There are still more steps to do, like removing null-bytes for example, but it’s a good start!