Last active
October 25, 2015 17:23
-
-
Save f0rki/ec1cb71cd35029b76063 to your computer and use it in GitHub Desktop.
python script to solve tumctf teaser challenge turbo (crypto 100)
This file contains 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
# 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) |
This file contains 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
$ ./attack_turbo.py | |
..... | |
[*] final flag should be hxp{1_r34LLy_L1k3_0r4cL3s__n0T_7h3_c0mp4nY} |
This file contains 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/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