-
Notifications
You must be signed in to change notification settings - Fork 1
Mitigations (ASLR, NX, etc.)
Back in the mid 90's, when Aleph One first published "Smashing the Stack for Fun and Profit", binary exploitation was pretty easy. (This is of course neglecting the fact that the knowledge of how to actually do it wasn't as widespread or refined as it is now). You could pretty much disassemble any executable, find a gets
or a strcpy
without a bounds check and get root via shellcode. But, as is always the case in this field of ours, defensive measures were eventually developed and implemented. Modern binary exploitation is comparatively more difficult. This page will go through some of the more common defense measures and talk about how to get around them.
The right way to check for defenses on a binary is to use checksec.sh, a nifty little shell script that tells you everything. checksec
now comes installed with pwntools
so you should be able to use it immediately by just typing checksec <binary>
.
[~]> checksec leakRop
[*] '/home/fortenforge/chals/rop/leakRop/leakRop'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[~]>
Note that checksec
will not inform you about ASLR, because that protection is a property of the machine and not the binary. To check if ASLR is turned on on a machine, run:
cat /proc/sys/kernel/randomize_va_space
If you see a 0, then ASLR is turned off. If you see a 1 or a 2, then ASLR is on. In a CTF, where you don't have access to the remote machine, organizers will often explicitly tell you whether ASLR is on or off in the challenge description. (Another sign is if they provide you with the shared libraries that the binary is linked with; this is a good indication that ASLR is on).
In a standard shellcode attack, you write assembly into a buffer on the stack and overwrite the return address with the address of the buffer so that the instruction pointer starts executing instructions off of the stack (remember, usually the instruction pointer is confined to the .text
region of memory).
An easy defense is to mark the stack as not executable (NX) so that if the instruction pointer is ever set to point to the stack, the processor will refuse to execute instructions and will just segfault. A less-discussed feature of NX is that it also allows you to set regions of memory as non-writable, so usually the stack is marked writable but not executable and the .text
section is marked as executable but not writable.
This is all implemented with some page-table tomfoolery. Note that NX is referred to as "Data Execution Prevention" (DEP) on Windows.
How do you get around NX? In a word, ROP. Note that if you can make a syscall using ROP, you can even call mprotect
and make the stack executable again.
Most compilers turn on NX by default. In order to compile a binary with it off pass the -z execstack
flag to gcc / clang.
Stack Canaries are random, integer values that the compiler will place in memory just before the return address on the stack for certain functions. Before the function returns, it will check if the canary's value has changed, and will immediately exit instead of returning, preventing any control over the return address.
Below is an example of a stack canary inserted by gcc: note that the canary's value is read from the fs
segment into [rbp-0x8]
and then checked at the end of the function.
00000000000006da <main>:
6da: 55 push rbp
6db: 48 89 e5 mov rbp,rsp
6de: 48 83 ec 20 sub rsp,0x20
6e2: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28
6e9: 00 00
6eb: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
6ef: 31 c0 xor eax,eax
<snip>
70e: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8]
712: 64 48 33 14 25 28 00 xor rdx,QWORD PTR fs:0x28
719: 00 00
71b: 74 05 je 722 <main+0x48>
71d: e8 7e fe ff ff call 5a0 <__stack_chk_fail@plt>
722: c9 leave
723: c3 ret
Stack canary values are generally random with a last byte of 0x00 to terminate strings early. To bypass stack canaries, you generally have to figure out how to leak their value. (In some rare cases, you may be able to exploit a vulnerability in the stack canary-checking assembly to cause a leak).
Most compilers turn on stack canaries by default. In order to compile a binary with them, pass the -fno-stack-canary
flag to gcc / clang.
ASLR stands for Address Space Layout Randomization. It does exactly what it says it does: it randomizes the layout of the process's address space. Generally, the last word and first byte of an address will remain the same (although this various from platform to platform). Also, not all parts of the address space will be randomized:
Section | ? |
---|---|
Stack | yes |
Heap | yes |
Text | no |
PLT / GOT | no |
data / bss | no |
static library | no |
shared library | yes |
With ASLR on (and without an information leak), you can only use ROP gadgets from the text section and any statically linked libraries. This makes ROP much harder. If you can bypass ASLR (usually by leaking a pointer from a shared library), you can then mine gadgets from all of libc
, and usually jump straight to one_gadget to win.
PIE stands for "position-independent executable". It was created after ASLR, and it essentially allows ASLR to be more effective by allowing more parts of the address space to be randomized. All shared libraries are position-independent by definition, since they need to work regardless of where the linker decides to place them in memory. PIE on a non-library allows the TEXT section to be randomized. PIE also randomizes the location of the GOT, making GOT overwrites / reads much trickier to pull off.
You can turn PIE on by passing the -fPIE
flag to gcc.
NX mitigates shellcode, ASLR and PIE mitigate ROP, and RELRO, which stands for "Relocation Read Only" mitigates the underrated GOT Overwrite attack. It comes in two flavors. Partial RELRO, marks the non-PLT GOT as read-only and does a couple other things, but is mostly useless. Full RELRO actually marks the GOT for the PLT as read-only (forcing the linker to fully populate the GOT before the program begins execution), and basically prevents a GOT overwrite.
You can read more about the PLT and GOT here.
You can turn on full RELRO with -Wl,-z,relro,-z,now
and partial RELRO with gcc -Wl,-z,relro
.