Grammar Lesson -- TU CTF 2017
Points: 150
Solves: ???
Description:
I do declare this to challenge to be at least mildly engaging, and certainly useful going forwards
flag format: TUCTF{…}
Given:
A file with a set of restrictions in Prolog that the keys have to satisfy in order to pass the check.
Solution
(by L0rdComm4nder)
TLDR
The prolog code encodes a series of constraints that are easily encoded in Z3
Description
Looking at the given files one notices the existence of two important functions tokenizer
and checkkey
.
The tokenizer
function basically transforms character codes into the corresponding characters 0
to 9
and a few others ( ) , - &
.
The checkkey
function checks the validity of the generated keys.
These constraints are easily encoded in Z3 and we know that a key should be of the form (XX,XX-XX,XX&XX,XX-XX,XX)
and satisfy some conditions.
#!/usr/bin/env python3
from z3 import *
from pwn import *
SERVER = "grammarlesson.tuctf.com"
PORT = 6661
s = Solver()
flag = [Int('flag%d' % i) for i in range(25)]
p = [Int('p%d' % i) for i in range(8)]
q = [Int('q%d' % i) for i in range(8)]
o = [Int('o%d' % i) for i in range(8)]
def is_digit(x):
s.add(x >= 0)
s.add(x <= 9)
s.add(flag[0] == ord('('))
is_digit(flag[1])
is_digit(flag[2])
s.add(flag[3] == ord(','))
is_digit(flag[4])
is_digit(flag[5])
s.add(flag[1]+flag[2] == p[0])
s.add(flag[4]+flag[5] == p[1])
s.add(p[0]*p[1] == q[0])
s.add(flag[6] == ord('-'))
is_digit(flag[7])
is_digit(flag[8])
s.add(flag[9] == ord(','))
is_digit(flag[10])
is_digit(flag[11])
s.add(flag[7]+flag[8] == p[2])
s.add(flag[10]+flag[11] == p[3])
s.add(p[2]*p[3] == q[1])
s.add(q[0] + q[1] == o[0])
s.add(flag[12] == ord('&'))
is_digit(flag[13])
is_digit(flag[14])
s.add(flag[15] == ord(','))
is_digit(flag[16])
is_digit(flag[17])
s.add(flag[13]+flag[14] == p[4])
s.add(flag[16]+flag[17] == p[5])
s.add(p[4]*p[5] == q[2])
s.add(flag[18] == ord('-'))
is_digit(flag[19])
is_digit(flag[20])
s.add(flag[21] == ord(','))
is_digit(flag[22])
is_digit(flag[23])
s.add(flag[19]+flag[20] == p[6])
s.add(flag[22]+flag[23] == p[7])
s.add(p[6]*p[7] == q[3])
s.add(q[2] + q[3] == o[1])
s.add(o[0] * o[1] == 82944)
s.add(flag[24] == ord(')'))
def hack(x):
if x>=0 and x<=9:
return chr(x +48)
else:
return chr(x)
chan = remote(SERVER, PORT, timeout=9999)
chan.recvuntil(': ')
rounds=0
while z3.sat == s.check():
rounds += 1
m = s.model()
plaint = "".join([ hack(int(repr(m[flag[i]]))) for i in range(len(flag)) ])
print plaint
chan.sendline(plaint)
if rounds == 10:
break
chan.recvuntil(': ')
block = []
for i in range(len(flag)):
block.append( flag[i] != m[flag[i]])
s.add(Or(block))
chan.interactive()
Running the above script, and submitting the 10 first retrieved flags one gets
(99,99-99,99&92,38-10,16)
(88,88-88,80&81,97-09,35)
(88,75-78,88&80,86-08,64)
(88,77-58,88&80,77-08,91)
(88,77-58,88&80,77-08,82)
(88,77-58,88&80,07-08,98)
(88,67-68,88&80,66-08,84)
(88,67-38,88&80,76-08,86)
(88,67-92,88&80,56-08,88)
(88,67-92,88&80,46-08,89)
TUCTF{Gr4mm3r_1s_v3ry_imp0rtAnT!_4ls0_Pr0log_iz_c00l!}