craftwa.re

A walk outside the sandbox

Home Blog Cheat Sheets MacOS Tips Area 51 About

Test Code Execution On The Heap

|

Overview

In these four short posts we’ll test a few traditional anti-exploitation measures. The experiments below are inspired from the great book The Mac Hacker’s Handbook and are done on a macOS Sierra.

To check the other tests, see the links below:

Test Code Execution On The Heap

The following snippet creates a short shellcode and copies it into a heap allocated variable. It then casts that variable to a function and attempts to execute it:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Infinite loop shellcode
char shellcode[] = "\xeb\xfe";

typedef int (*funcPtr)();

int main(int argc, char *argv[]){
    int (*f)();             // Function pointer
    char *x = malloc(2);    // Heap variable
   
    // Copy shellcode on the heap
    memcpy(x, shellcode, sizeof(shellcode));
    
    // Cast to function pointer and execute
    f = (funcPtr)x;
    (*f)();
}

As before, let’s see what happens:

$ gcc execHeap.c -o execHeap
$ ./execHeap
zsh: bus error  ./execHeap

We see a similar issue as we’ve seen when trying to execue code from the heap. A EXC_BAD_ACCESS error is generated when the program attempts to jump to the shellcode on the heap and execute it:

$ lldb ./execHeap
(lldb) target create "./execHeap"
Current executable set to './execHeap' (x86_64).

(lldb) breakpoint set --name main
Breakpoint 1: where = execHeap`main, address = 0x0000000100000f20

(lldb) run
Process 680 launched: './execHeap' (x86_64)
Process 680 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000f20 execHeap`main
execHeap`main:
->  0x100000f20 <+0>: pushq  %rbp
    0x100000f21 <+1>: movq   %rsp, %rbp
    0x100000f24 <+4>: subq   $0x30, %rsp
    0x100000f28 <+8>: movl   $0x2, %eax
Target 0: (execHeap) stopped.

(lldb) continue
Process 680 resuming
Process 680 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x100200000)
    frame #0: 0x0000000100200000
->  0x100200000: jmp    0x100200000
    0x100200002: addb   %al, (%rax)

To address this, let’s change the permissions of the memory page containing the buffer and redo the test.

#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// Infinite loop shellcode
char shellcode[] = "\xeb\xfe";

typedef int (*funcPtr)();

int main(int argc, char *argv[]){
    int (*f)();             // Function pointer
    void *x = malloc(2);    // Heap variable
    
    int page_size;
    unsigned long page_start; 
    int ret;

    page_size = sysconf(_SC_PAGE_SIZE);    
    if (page_size == -1)
       perror("[-] sysconf failed");
    else
        printf("[*] page size: %d\n", page_size);
   
    printf("[*] size of pointer: %lu\n", sizeof(void*));
    printf("[*] size of int: %lu\n", sizeof(unsigned int));
    printf("[*] size of long: %lu\n", sizeof(unsigned long));
 
    page_start = ((unsigned long) x) & 0xfffffffffffff000;
    printf("[*] page start: 0x%016lx\n", page_start);
    printf("[*] buff start: 0x%016lx\n", (unsigned long) x);

    ret = mprotect((void *) page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
    if(ret<0){ 
        perror("[-] mprotect failed"); 
    }
    
    // Copy shellcode on the heap
    memcpy(x, shellcode, sizeof(shellcode));
    
    // Cast to function pointer and execute
    f = (funcPtr)x;
    (*f)();
}

The program is now in an infinite loop, executing the shellcode:

$ ./execHeap2
[*] page size: 4096
[*] size of pointer: 8
[*] size of int: 4
[*] size of long: 8
[*] page start: 0x00007fa622c02000
[*] buff start: 0x00007fa622c025d0

Conclusion

By default, code execution on the heap is prevented! It considerably more difficult to inject code via the heap and execute it, but not impossible. So this technique is adding another defense layer!