[Writeup] TJCTF - Bad_Python

- 8 mins

Description

My friend wrote a cool program to encode text data! His code is sometimes hard to understand, and only he knows how it works. I ran the program twice, but forgot the input I used for the first time. I didn’t save the key I used either, but I know it was 15 characters long. Can you figure out what text I encoded the first time?

Output 1 =  b'\x02\x19\x01\x16Q\r\x07\nS\x02)\x1a1=EE2\x0e=G/D\nRY)\nV\x1bJ'
Input 2 =  "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
Output 2 = b':\x1c\x10\x07ZV\x1a\x12\x11B\x1bS\x06\r[\x19\x01B\x11^\x02S\x03\x0fR\x02_B\x01X\x18\x00\x07\x01C\x13\x07\x17\x10\x17\x17\x17\x0b\x12^\x05\x10\x0b\x0cPV\x16\x0e\x0bC'

here’s the program:

import random
A=bool
N=ord
y=sorted
Y=list
h=bin
T=int
e=range
F=len
L=open
O=repr
R=random.shuffle
import itertools
import functools
D=functools.reduce
S=A
P=lambda a,b:(N(a)^N(b)).to_bytes(1,'big')
B='[redacted - 15 chars]'
E=((0,3),(1,4),(0,1),(3,4),(2,3),(1,2))
B=y(Y(B))
x=lambda a,b:h((N(a)-N(b))^(T('1'*10,2)))[0]!='-'
def u(li):
 Q=[li[i::3]for i in e(3)]
 for i in Q:
  while not W(i):
   pass
 return Q
def W(i):
 R(i)
 a=[S(T(h(N('e'))[2:][-1]))]
 return[n(a,x(i[E[j][0]],i[E[j][1]]))for j in e(F(E))][-1]
def n(g,k):
 g[0]=g[0]and k
 return g[0]
f=u(B)
a=L('input.txt','r').read()
m='output.txt'
L(m,'w').write(O(b''.join([D(P,[(((N(a[i])&(~N(f[j][i%5])))|((~N(a[i]))&(N(f[j][i%5])))).to_bytes(1,"big"))for j in e(F(f))])for i in e(F(a))])))

#   if c%100000==0:
#     print(i)
#     print(c)


Solution

so i tried to make it more readable and removed the unused/dead code, here’s the clean version of the challenge:

import functools
def u(arr):
	Q=[arr[i::3]for i in range(3)]
	return Q 
	#returns [[key[0], key[3], key[6], key[9], key[12]], [key[1], key[4], key[7], key[10], key[13]], [key[2], key[5], key[8], key[11], key[14]]]
xor_two_bytes=lambda a,b:(ord(a)^ord(b)).to_bytes(1,'big')
key='[redacted - 15 chars]'
key = u(key)
flag=open('input.txt','r').read()
enc_flag = [functools.reduce(xor_two_bytes,[(((ord(flag[i])&(~ord(key[j][i%5])))|((~ord(flag[i]))&(ord(key[j][i%5])))).to_bytes(1,"big")) for j in range(len(key))] ) for i in range(len(flag))]

enc_flag=repr(b''.join(enc_flag))
output_file='output.txt'
open(output_file,'w').write(enc_flag)
	

so here’s a nice reference to understand how functools’ reduce function works[and how the encoder works in general?]: https://www.python-course.eu/python3_lambda.php

now it’s clear that we need to use Input2 and Output2 to get the key and then use the key to get the flag so i made this script to get the key

from z3 import * 
import functools

Input2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
output2= b':\x1c\x10\x07ZV\x1a\x12\x11B\x1bS\x06\r[\x19\x01B\x11^\x02S\x03\x0fR\x02_B\x01X\x18\x00\x07\x01C\x13\x07\x17\x10\x17\x17\x17\x0b\x12^\x05\x10\x0b\x0cPV\x16\x0e\x0bC'
xor_two_bytes=lambda a,b:(a^b)
s=Solver()
key = [BitVec("key{}".format(i),8) for i in range(15)]
key2=[key[i::3] for i in range(3)]

constraints= [functools.reduce(xor_two_bytes,[((ord(Input2[i])&(~key2[j][i%5]))|((~ord(Input2[i]))&(key2[j][i%5]))) for j in range(len(key2))] ) for i in range(len(Input2))]

for i in range(len(constraints)):
	s.add(constraints[i]==output2[i])

if(s.check()==sat):
	key_=[]
	solution = s.model()
	for c in key:
		key_.append(s.model()[c].as_long())
# key_ = [27, 253, 144, 60, 191, 240, 60, 191, 225, 10, 31, 119, 58, 191, 178]

now that we have the key here’s another z3 script to get the flag

from z3 import * 
import functools

enc_flag = b'\x02\x19\x01\x16Q\r\x07\nS\x02)\x1a1=EE2\x0e=G/D\nRY)\nV\x1bJ'
key = [27, 253, 144, 60, 191, 240, 60, 191, 225, 10, 31, 119, 58, 191, 178]

xor_two_bytes=lambda a,b:(a^b)

s=Solver()
key = [key[i::3] for i in range(3)]
flag = [BitVec("flag{}".format(i),8) for i in range(30)] #because the enc flag is 30 bytes long

constraints = [functools.reduce(xor_two_bytes,[(((flag[i])&(~key[j][i%5]))|((~(flag[i]))&(key[j][i%5]))) for j in range(len(key))] ) for i in range(len(flag))]
for i in range(len(enc_flag)):
	s.add( constraints[i] == enc_flag[i])
if (s.check()==sat):
	f=""
	solution = s.model()
	for e in flag:
		f+= chr(solution[e].as_long())
	print(f)

Flag

tjctf{th15_iS_r3Al_pY7h0n_y4y}

coreflood

coreflood

Just another reverse engineer.

rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora