Skip to main content

Compilation

The best way to avoid vulnerabilities is to not create them during development. However, software development is hard, and mistakes can happen. That is why operating systems and compilers have evolved throughout time to mitigate classic vulnerabilities.

Stack Canary

To prevent a buffer overflow, one simple idea would be to implement a canary, a value that is stored on the stack, right after a buffer, and checked at runtime. If that value is overwritten, it means that the buffer was overflowed.

This can be enabled by using the compilation flag -fstack-protector, which will add a few assembly instructions to check a randomly generated canary value.

This can be used to mitigate buffer overflow exploits.

However, it can be bypassed with 2 methods :

  • either leaking the canary value, which is stored on the stack (using a format string exploit for example)
  • bruteforcing the canary value, under certain conditions

ASLR

ASLR for Address Space Layout Randomization is a system-wide setting that will tell the OS to randomize the address space of a process for every execution.

So everytime a binary is executed, the stack, the heap and the shared libraries (amongst other things), are loaded at a different address.

That is why you were asked to disable ASLR during the shellcode section: the buffer in which we injected our shellcode would have changed everytime we executed the binary, which would have made it very hard to load it without leaking its address.

However, if a format string vulnerability is present, the address of the buffer used to host the shellcode can be leaked, which can then be used to bypass ASLR.

No eXecute

The NX bit (No eXecute) marks certain areas of a process as not executable, such as the stack or the heap, which should not be expected to store instructions. This can effectively block exploits using shellcodes.

By default, compiling with gcc will enable the NX bit. This can be disabled with the flag -zexecstack.

The presence of the NX bit can be verified using checksec.

└─$ pwn checksec main
[*] '/tmp/test/main'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

You can try with one of the shellcode exercise, re-compile the binary with gcc -o main -m32 main.c, and the NX bit will be enabled. Your exploit should no longer be working.

However, NX can also be bypassed using a known attack called ret2libc (not seen in this course), where the attacker modifies the return address of a vulnerable function with a buffer overflow, and returns to a libc function, usually system().

PIE

PIE, for Position Independent Executable, is a compilation flag that randomizes the code instructions addresses for each execution.

By default, gcc will compile the binary to be PIE. This can be disabled with the flag -no-pie.

For example, in the ret2win exercises, the win() function's address would be random for each execution, which would make the ret2win impossible.

However, this can be bypassed by leaking an address (using a format string exploit for example).

Conclusion

We have seen a few possible mitigations, that if enabled, could "protect" the binary from vulnerabilities created by insecure code. However, these mitigations can be bypassed, it only makes exploiting harder for an attacker, but not impossible.