Finding offsets
this may be silly, but sometimes its easy to forget things like this!
symbol tables: objdump -T /usr/lib/x86_64-linux-gnu/libc.so.6 | grep vtable
function syms: readelf -s /usr/lib/x86_64-linux-gnu/libc.so.6 | grep puts
string offset: strings -at x /usr/lib/libc.so.6 | grep /bin/sh
objdump has many options that are extremely useful when debugging :)
Writing shellcode
first write your shellcode in intel (no at&t garbage plz)
the asm below is a simple call to exit() in 64 bit linux
.global _start
_start:
.intel_syntax noprefix
mov rax, 0x3c
syscall
to compile and extract the shellcode we can create a runner
#!/bin/bash
if [[ -z "$1" ]]; then
echo "no file specified"
exit
fi
compiled="${1}_compiled"
text="${1}_text"
gcc -nostdlib -static ${1} -o ${compiled}
objcopy --dump-section .text=${text} ${compiled}
echo "done"
this compiles and extracts the .text section of the binary, leaving us with the raw shellcode which we can use in our exploit payload
if we strace the compiled binary we see that exit was called
execve("./ex.s_compiled", ["./ex.s_compiled"], 0x7ffe08dd7d60 /* 18 vars */) = 0
exit(0) = ?
+++ exited with 0 +++
ELF in a nutshell
.text this is where the code lives
.plt library function stubs
.got pointers to function imports
.bss uninitialized global writable data such as global arrays with no init values
.data global writable data that is initialized with values
.rodata read-only data
stack local variables, temp storage, stack metadata
heap dynamic long-term data
Dynamic Allocators
ive (poorly) demonstrated how to exploit a tcache poisoning attack against modern libc versions in 2 recent CTF writeups:
these vulnerabilities are really fun to exploit :)
GOT Overwrites
for ease of explanation we will be targeting a binary with the following securities:
- no pie
- partial relro
there is some background to establish..
PLT
the procedure linkage table aka PLT. dynamically linked binaries will use the plt to resolve the addresses of functions in libc when the function is called. so for example, when a function like puts()
is called in a dynamically linked program the program is actually making a call to puts@plt
.
when this puts@plt
call is made the program will check the global offset table aka GOT to determine if there is already an entry stored for puts
. if there is, the function is not dynamically resolved and the address in the GOT entry is used. if there is not an entry for the function in the GOT then the address will be resolved in cached in the GOT for future use.
why is this useful? there are two reasons, in the case of partial relro we can actually overwrite the GOT so that when a specific function is called in the program we can control code execution. this can allow us to, depending on the circumstance, call arbitrary functions or perform a complex rop chain. the second reason is ret2plt, where we can abuse functions to print our resolved addresses, effectively giving us an ASLR bypass for libc function address resolution!
here is an example of a GOT
0x405020 <putchar@got.plt>: 0x00007b777961d280 0x0000000000401050
0x405030 <puts@got.plt>: 0x00007b777961b420 0x0000000000401070
0x405040 <write@got.plt>: 0x00007b77796a5280 0x0000000000401090
0x405050 <__stack_chk_fail@got.plt>: 0x00000000004010a0 0x00007b77795f8c90
0x405060 <geteuid@got.plt>: 0x00000000004010c0 0x00007b77796a5a20
0x405070 <read@got.plt>: 0x00000000004010e0 0x00000000004010f0
0x405080 <umask@got.plt>: 0x00007b77796a4d70 0x0000000000401110
lets say there is a win function at 0x401111
. if we can overwrite the got entry for a function that will be called in the program, say puts@got
0x405030 <puts@got.plt>: 0x0000000000401111 0x0000000000401070
next time that puts is called in the program it will call the win function instead