In maple ctf 2022 (more write up at here). There is a quesiton require bruteforcing canary
To be honest, this is the first time I got practice on brute-forcing canary.
0x1 What is Canary
so, before talking about brute force canary. What is canary
Basically, canary is a mitigation that prevent buffer overflow. Since buffer over utilize the overflow the variable on the stack to overwrite rbp and rip. This could help attacker to execute arbitrary code by constructing a rop chain or something. The canary can help prevent simple buffer overflow by put a cookie just above the RBP.
Lets take look at what should stack looks like after canary is enbled.
1 2 3 4 5 6 7 8
RSP - - local variables....... - .... - RBP-8 - Canary is here !!!!
RBP - some rbp address RIP - some rip address
We can see canary place a cookie just above RBP, so if an attacker overwrites the stack, for example - overwrite rip, canary is also overwrited.
Before return to rip, bianry will check if canary is same as before, if canary is different, it means there is a buffer overflow happening.
0x2 How to bypass canary
so, if canary is open, how can we bypass it.
First method is just find way to overwrite rip without overwrite the canary. For example, use printf. Or use some bug in the system that may allow you to write data to RIP.
Second method is brute force the canary.
0x3 Lets brute force a canary
Lets use birbs as an example.
in the find_exit(), it first fork the program, than runs guess_exit()
fork() is very important here. If the program use fork, the canary in the subroutine will always be the same. Which means the canary of which guess_exit() will always be the same.
This give us a chance of brute focre the canary of guess_exit and overwrite the data
The guess_exit also provide us an opportunity to overwrite the whole stack.
The opinion of brute force canary is very simple. Since a canary is 8 byte. We just test one byte by byte.
If the program crash, it means it is not the correct byte, if program not crash, we save the byte and move to next byte. Until all 8 byte of canary leak.
# Set up pwntools for the correct architecture exe = context.binary = ELF(BinaryInfo.exe) exe_rop = ROP(exe) if BinaryInfo.libc != "": libc = ELF(BinaryInfo.libc) libc_rop = ROP(libc) else: libc = None libc_rop = None
# Many built-in settings can be controlled on the command-line and show up # in "args". For example, to dump all data sent/received, and disable ASLR # for all created processes... # ./exploit.py DEBUG NOASLR # ./exploit.py GDB HOST=example.com PORT=4141 host = args.HOST or BinaryInfo.host port = int(args.PORT or BinaryInfo.port)
def start_remote(argv=[], *a, **kw): '''Connect to the process on the remote host''' io = connect(host, port) if args.GDB: gdb.attach(io, gdbscript=gdbscript) return io
def start(argv=[], *a, **kw): '''Start the exploit against the target.''' if args.LOCAL: return start_local(argv, *a, **kw) else: return start_remote(argv, *a, **kw)
# =========================================================== # EXPLOIT GOES HERE # =========================================================== # Arch: amd64-64-little # RELRO: Partial RELRO # Stack: Canary found # NX: NX enabled # PIE: No PIE (0x400000)
# Specify your GDB script here for debugging # GDB will be launched if the exploit is run via e.g. # ./exploit.py GDB gdbscript = ''' tbreak main continue '''.format(**locals())