Skip to content

Instantly share code, notes, and snippets.

@f0rki
Last active October 25, 2015 17:23
Show Gist options
  • Save f0rki/ec1cb71cd35029b76063 to your computer and use it in GitHub Desktop.
Save f0rki/ec1cb71cd35029b76063 to your computer and use it in GitHub Desktop.
python script to solve tumctf teaser challenge turbo (crypto 100)
# compression "oracle"
import sys
from pwn import remote, log, context, process
import string
import zlib
#context.log_level = 'debug'
BLKSIZE = 16
doremote = True
#doremote = False
def get_length(inp):
rem = None
inp = inp + " a"
if doremote:
rem = remote("1.ctf.link", 1033)
rem.sendline(inp)
rem.shutdown("write")
ret = rem.recvline().strip()
rem.shutdown("read")
else:
rem = process("./turbo.py")
out, err = rem.communicate(inp + "\n")
ret = out.strip()
log.info("\n".join(ret.split("\n")[:-1]))
ret = ret.split("\n")[-1].strip()
if err:
log.info(err)
rem.close()
del rem
if not ret:
raise Exception("Received empty response!")
return len(ret) / 2 / BLKSIZE
printables = string.digits + "_" + string.ascii_letters
printables = printables[::-1]
# the password will be the guessed flag.
# "hxp{" + guess
# We can find out how long the flag is by gradually increasing the length of
# the password we supply. If we get another block of encrypted data, we know we
# filled up the padding. Therefore we know how much padding there is given an
# empty password. We can then calculate the length of the flag by subtracting
# the length of the padding and the other known plaintext. The only thing
# that's left must be the length of the flag. At least that's the idea. For
# some reason (I guess gzip) this isn't working so well...
flaglen = 0
minlen = get_length("")
datalen = len(zlib.compress('Today\'s special: .\nWrong password: .\n', 9))
padlen = 0
pl = minlen
for i in range(1, BLKSIZE):
log.info("trying {} chars".format(i))
l = get_length(printables[:i])
log.info("got length {}".format(l))
if l != pl:
padlen = i + 1
break
log.info("deduced padding length {}".format(padlen))
flaglen = ((minlen - 1) * 16) - datalen - padlen
flaglen -= 2 # to account for padding bug
log.info("flag must be {} bytes long".format(flaglen))
#sys.exit(0)
#### some testing
# add some data, which will fill up padding space, but not create a new block
log.info("Trying hxp{ flag prefix")
s = printables[:(padlen - 2)]
log.info(s)
pl = get_length(s)
assert pl == minlen
# adding "hxp{" to the input will not increase the length of the received data, except for
# apparently two bytes, that are need to reference the first occurance of the hxp{ string...
s = printables[:(padlen - 4)] + ""
x = s + "hxp{"
log.info(x)
l = get_length(x)
assert l == minlen
# for local testing, to make sure everything is working as it's supposed to...
if not doremote:
testflag = open("./flag.txt").read().strip()
# these strings will be compressed and replaced by a ref to previous occurance
for teststr in ("hxp{s", "hxp{some_flag", testflag):
x = s + teststr
log.info(x)
l = get_length(x)
assert l == minlen, "teststr not compressed: " + repr(teststr)
x = s + "hxp{X"
log.info(x)
l = get_length(x)
assert l > minlen
#### The actual flag bruteforcing,
# byte by byte using the compression oracle
# block boundary, # bytes that can be added before next block
# the padding length from above - the space need for the reference or something?
BB = padlen - 4
# the guess results
previous = []
pl = 0
for i in range(len(previous), flaglen):
log.info("guessing character number {}".format(i))
# get the length of the flag prefix we have so far. I guess this can be optimized out I guess?
test = "hxp{" + "".join(previous)
test = printables[:BB] + test
log.info("trying prefix: " + test)
pl = get_length(test)
log.info("got length = {}".format(pl))
foundit = False
# loop throug all possible candidates of the last character of the flag
for c in printables + "}":
test = "hxp{" + "".join(previous)
test = printables[:BB] + test + c
log.info("trying prefix: " + test)
l = get_length(test)
log.info("l = {}, pl = {}".format(l, pl))
if l == pl:
# character was found that doesn't increase # blocks --> add to flag prefix
log.info("found next char: " + c)
previous.append(c)
foundit = True
break
else:
log.info("guess was wrong")
if foundit:
log.info("Current flag prefix guess is hxp{{{}"
.format("".join(previous)))
else:
log.error("Couldn't guess char {}".format(i))
if previous[-1] == "}":
break
flag = "hxp{" + "".join(previous)
if flag[-1] != "}":
flag += "}"
log.info("final flag should be " + flag)
with open("./real_flag.txt", "w") as f:
f.write(flag)
$ ./attack_turbo.py
.....
[*] final flag should be hxp{1_r34LLy_L1k3_0r4cL3s__n0T_7h3_c0mp4nY}
#!/usr/bin/env python3
# tumctf teaser 2015 - crypto 100 turbo
from Crypto.Cipher import AES
import binascii, gzip, subprocess
try:
pw, cmd = input().split(' ', 1)
except ValueError:
exit(1)
goodpw = open('password.txt', 'r').read().strip()
assert len(goodpw) >= 32
key = open('key.bin', 'rb').read()
assert len(key) in AES.key_size
iv = open('/dev/urandom', 'rb').read(AES.block_size)
assert len(iv) == AES.block_size
aes = AES.new(key, AES.MODE_CBC, iv)
flag = open('flag.txt', 'r').read().strip()
r = b''
r += 'Today\'s special: {}.\n'.format(flag).encode()
if pw == goodpw:
r += subprocess.check_output(cmd, shell = True)
else:
r += 'Wrong password: {}.\n'.format(pw).encode()
padded = gzip.compress(r)
import sys
l = len(padded)
print("unpadded len =", len(padded), file=sys.stderr)
padded += bytes([AES.block_size - len(padded) % AES.block_size or AES.block_size])
print("adding zero padding of size", AES.block_size - len(padded) % AES.block_size, file=sys.stderr)
padded += b'\0' * (AES.block_size - len(padded) % AES.block_size)
print("padding = ", repr(padded[l:]), file=sys.stderr)
print("padded len =", len(padded), file=sys.stderr)
print(binascii.hexlify(iv + aes.encrypt(padded)).decode())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment