BioTerra CTF 2016: Schinken
The service encrypts a string with a password, into a pdf file (or the tex). You can notice that the letters of Lerem-ipsum have different fonts.
Actually, this fonts contain bits about the xor-value of the plaintext and the password.
You can find this by trying pairs like: password is AAAA
and text is AAAA
, or password is AAA
and text is ABCD
.
Especially, the only letters ABCDEFGHIJKLMNOPQRSTUVWXYZ{}_
are allowed, and the integers to xor is the indices in the letters (not ascii-code).
To decrypt it, pdf2ps
helped me.
#!/usr/bin/env python2
from pwn import * # https://pypi.python.org/pypi/pwntools
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
subparser = subparsers.add_parser('query')
subparser.add_argument('--name', required=True)
subparser.add_argument('--address', required=True)
subparser.add_argument('--text', required=True)
subparser.add_argument('--password', required=True)
subparser.add_argument('--do', required=True, choices=[ 'letter', 'source' ])
subparser = subparsers.add_parser('crack')
subparser.add_argument('file')
subparser.add_argument('--key', default='Geheim')
parser.add_argument('--host', default='pwn.bioterra.xyz')
parser.add_argument('--port', default=6969, type=int)
args = parser.parse_args()
context.log_level = 'debug'
def query(name, address, text, password, do):
import base64
p = remote(args.host, args.port)
p.recvuntil('Enter your name:')
p.sendline(name)
p.recvuntil('Enter your address:')
p.sendline(address)
p.recvuntil('Enter your text to encrypt:')
p.sendline(text)
p.recvuntil('Enter a password:')
p.sendline(password)
p.recvuntil('Choice:')
p.sendline(do[0])
p.recvuntil('-----BEGIN MESSAGE----')
s = p.recvuntil('-----END MESSAGE-----')
s = base64.b64decode(s)
p.recvall()
p.close()
return s
def crack(pdfstr):
import re
import subprocess
p = subprocess.Popen(['pdf2ps', '-', '-'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
psstr, _ = p.communicate(pdfstr)
msg = ''
bit = []
state = None
for line in psstr.split('\n'):
if re.search(r'/R6 \S+ Tf', line):
state = 0
if re.search(r'/R12 \S+ Tf', line):
state = 1
for s in re.findall(r'\(([^)]+)\)', line):
for c in s.strip('()'):
msg += c
bit += [state]
i = msg.index('Lorem')
j = msg.index('Mitfreundlichen')
msg = msg[i : j]
bit = bit[i : j]
log.info('msg: %s', repr(msg))
log.info('bit: %s', repr(bit))
log.info('len: %d', len(bit))
assert len(bit) % 5 == 0
return bit
if args.command == 'query':
s = query(args.name, args.address, args.text, args.password, args.do)
if args.do == 'letter':
import tempfile
import subprocess
import time
with tempfile.NamedTemporaryFile(suffix='.pdf') as fh:
fh.write(s)
fh.flush()
subprocess.call(['xdg-open', fh.name])
time.sleep(2)
elif args.do == 'source':
import itertools
import re
log.info(s)
for line in s.split('\n'):
if len(re.findall(r'\\fontfamily', line)) > 3:
for i in itertools.count():
m = re.search(r'^\\fontfamily{(\w\w\w)}\\selectfont (\S) ?', line)
if not m:
break
log.info('%d %s: %d', i, m.group(2), { 'ppl': 0, 'phv': 1 }[m.group(1)])
if i % 5 == 4:
log.info('')
line = line[m.end(): ]
elif args.command == 'crack':
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ{}_'
with open(args.file) as fh:
s = crack(fh.read())
flag = ''
for i in range(len(s) // 5):
a = int(''.join(map(str, s[i*5 : i*5+5])), 2)
b = alphabet.index(args.key[i % len(args.key)].upper())
c = alphabet[a ^ b]
flag += c
log.info(flag)