Test Code Execution On The Stack
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 Stack Smashing Protection
- Test ASLR (Address space layout randomization)
- Test Code Execution On The Heap
Test Code Execution On The Stack
The following snippet creates a short shellcode and copies it into a stack 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[4]; // Stack variable
// Copy shellcode on the stack
memcpy(x, shellcode, sizeof(shellcode));
// Cast to function pointer and execute
f = (funcPtr)x;
(*f)();
}
Let’s see what happens:
$ gcc execStack.c -o execStack
$ ./execStack
zsh: segmentation fault ./execStack
LLDB
can be used to investigate the point of the crash. A EXC_BAD_ACCESS
error is generated when the program attempts to jump to the shellcode on the stack and execute it:
$ lldb ./execStack
(lldb) target create "./execStack"
Current executable set to './execStack' (x86_64).
(lldb) breakpoint set --name main
Breakpoint 1: where = execStack`main, address = 0x0000000100000f70
(lldb) run
Process 615 launched: './execStack' (x86_64)
Process 615 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000f70 execStack`main
execStack`main:
-> 0x100000f70 <+0>: pushq %rbp
0x100000f71 <+1>: movq %rsp, %rbp
0x100000f74 <+4>: subq $0x20, %rsp
0x100000f78 <+8>: leaq -0x1c(%rbp), %rax
Target 0: (execStack) stopped.
(lldb) continue
Process 615 resuming
Process 615 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7fff5fbffa14)
frame #0: 0x00007fff5fbffa14
-> 0x7fff5fbffa14: jmp 0x7fff5fbffa14
0x7fff5fbffa16: addb %al, (%rax)
0x7fff5fbffa18: adcb $-0x6, %al
0x7fff5fbffa1a: movl $0x7fff5f, %edi ; imm = 0x7FFF5F
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
char x[4]; // Stack variable
unsigned long page_start;
int ret;
int page_size;
page_size = sysconf(_SC_PAGE_SIZE);
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 stack
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:
$ ./execStack2
[*] page start: 0x00007fff556d9000
[*] buff start: 0x00007fff556d99f4
Conclusion
By default, code execution on the stack is prevented! This is a good thing since there are very few legitimate usages of this technique, outside of the exploitation realm.