We got 1st place!! Thanks to all the admins for holding a good competition.
overall challenge files and scripts are here
[rev/misc/crypto] pyzzle1/2
The given file pyzzle
is an AST produced by libCST. @st98 found a way to recover it into python script.
from libcst import *
with open('pyzzle', 'r') as f:
program = f.read()
cst = eval(program)
with open('original.py', 'w') as f:
f.write(cst.code)
The logic of a recovered script file was quite simple. It did just XORing.
import binascii
plaintext = "REDACTED"
def exor(a, b):
temp = ""
for i in range(n):
if (a[i] == b[i]):
temp += "0"
else:
temp += "1"
return temp
def BinaryToDecimal(binary):
string = int(binary, 2)
return string
PT_Ascii = [ord(x) for x in plaintext]
PT_Bin = [format(y, '08b') for y in PT_Ascii]
PT_Bin = "".join(PT_Bin)
n = 26936
K1 = '...'
K2 = '...'
L1 = PT_Bin[0:n]
R1 = PT_Bin[n::]
f1 = exor(R1, K1)
R2 = exor(f1, L1)
L2 = R1
f2 = exor(R2, K2)
R3 = exor(f2, L2)
L3 = R2
R3 = '...'
L3 = '...'
cipher = L3+R3
So decryption was also simple.
from params import K1, K2, L3, R3
from Crypto.Util.number import *
def exor(a, b):
temp = ""
for i in range(n):
if (a[i] == b[i]):
temp += "0"
else:
temp += "1"
return temp
n = 26936
L1 = exor(exor(R3, K1), K2)
R1 = exor(exor(L3, K1), L1)
plaintext = long_to_bytes(int(L1 + R1, 2))
with open("plaintext", "wb") as f:
f.write(plaintext)
The first flag was in the comment section of plaintext: 3k{almost_done_shizzle_up_my_nizzle}
.
The plaintext was the STP format file. It had the edges and vertices with its coordinates. Let's draw them on the canvas.
from PIL import Image, ImageDraw
lines = open("plaintext2.stp").read().strip().split("\n")
points = {}
for line in lines:
if not line.startswith("DD"):
continue
_, idx, x, y = line.strip().split(" ")
points[int(idx)] = (int(x), int(y))
img = Image.new("RGB", (2200, 200))
draw = ImageDraw.Draw(img)
for line in lines:
if not line.startswith("E "):
continue
_, p1, p2, _ = line.strip().split(" ")
p1 = points[int(p1)]
p2 = points[int(p2)]
draw.line([p1, p2], fill=(255, 255, 255), width=1)
img.save("flag.png")
[rev] microscopic
There is a key checking logic in the sub_F7C
. It is straightforward, requiring that (len(input) ^ input[i]) + i == bss_0x202020[i]
.
xs = [0x14,0x4D,0x5E,0x4C,0x4A,0x4E,0x1D,0x51,0x56,0x5C,0x4C,0x5F,0x84,0x4F,0x5F,0x51,0x65,0x6F,0x62,0x62,0x56,0x6A,0x58,0x8F,0x5A,0x6A,0x5C,0x70,0x7A,0x70,0x6C,0x69,0x62,0x99,0x63,0x76,0x74,0x2B,0x80]
l = len(xs)
ys = []
for i, x in enumerate(xs):
ys.append(chr(x - i ^ l))
print("".join(ys))
[rev] Passage
The binary is based on the printbf. So I dumped brainf*ck instructions from memory and decode by the following script.
import collections
lines = open("instructions").read().strip().split("\n")
bf = ""
for line in lines:
inst = int(line[-2:], 16)
count = int(line[8:10], 16)
if inst == 18:
bf += "+" * count
elif inst == 15:
bf += "<" * count
elif inst == 14:
bf += ">" * count
elif inst == 13:
bf += "[-]"
elif inst == 12:
bf += "]"
elif inst == 11:
pass
elif inst == 10:
pass
elif inst == 9:
bf += "["
elif inst == 6:
pass
elif inst == 5:
bf += "."
print(bf)
Then I got a brainf*ck script. I wrote a simple interpreter because printbf's behaviour is a bit of different to usual interpreter. By emulating the script, I found some ascii-range values on the memory. So I dumped it then I got the flag.
The following is the my interpreter and solution script.
import string
table = set([ord(c) for c in string.ascii_letters + string.digits + ' -{}_'])
pc = 0
ptr = 0
mem = [0 for _ in range(65536)]
jumplist = {}
code = open("code.bf").read().strip()
while pc < len(code):
if code[pc] == '+':
mem[ptr] = (mem[ptr] + 1) % 256
elif code[pc] == '-':
mem[ptr] = (mem[ptr] - 1) % 256
elif code[pc] == '>':
ptr = (ptr + 1) % 65536
elif code[pc] == '<':
ptr = (ptr - 1) % 65536
elif code[pc] == '[':
cnt = 0
if pc not in jumplist:
pc2 = pc + 1
while True:
if code[pc2] == ']':
if cnt == 0:
jumplist[pc2] = pc-1
jumplist[pc] = pc2
break
else:
cnt -= 1
elif code[pc2] == '[':
cnt += 1
pc2 += 1
if mem[ptr] == 0:
pc = jumplist[pc]
print([chr(v) for v in mem if v in table])
elif code[pc] == ']':
pc = jumplist[pc]
elif code[pc] == '.':
pass
pc += 1
import code
code.interact(local=locals())
[crypto] once upon a time
The given encryption program is based on https://github.com/MatthewDarnell/SCSS. The difference were:
- delete decryption
- redact initialize key
So our task is found a redacted key and decrypt using the referenced repository. The key wasn't in the source code but in the compiled binary. Just reverse it and decrypt by ofb mode, then we could get the flag: 3k{my_hands_are_registered_as_lethal_weapons_that_means_we_get_into_a_fight_i_accidentally_kill_you_i_go_to_jail}
.
[crypto] You shall not get my cookies
Be careful that the banner's corner formed by pkcs7
(shit!). If you noticed that, just do the padding oracle encryption attack.
from ptrlib import padding_oracle_encrypt, Socket
from Crypto.Util.Padding import pad
from binascii import hexlify, unhexlify
from logging import getLogger, WARN
getLogger("ptrlib.pwn").setLevel(WARN + 1)
def decrypt(c):
sock = Socket("youshallnotgetmycookies.3k.ctf.to", 13337)
sock.sendlineafter("your cookie:", hexlify(c).upper())
result = sock.recvline().decode()
result = sock.recvline().decode()
sock.close()
if 'Nop' in result:
return True
elif "rude" in result:
raise Exception("RUDE!!!")
else:
return False
plain = pad(b'Maple Oatmeal Biscuits', 16)
print(padding_oracle_encrypt(decrypt, plain, bs=16))
By the way, my network issue and physical distance make my script running over 1 hour.
[crypto] RSA textbook
The challenge is pretty similar to De1CTF 2020 - Easy RSA. The difference is d's bit length is a bit of long and three of e are given this time. The paper records the lattice when the three e are given.
from sage.all import *
from Crypto.Util.number import long_to_bytes
import gmpy2
N = 12170190688004538444277411858659743698858881698522002560104158646993985233366286537140023919311475138247627293483294012428391264973961998717641256731743650816966630823200651379840685959607068295100050560483372204779059185916233606668058455492543750789590662887744463330804561435647720395428571207395720326720079784739378979190536116850099548129878814526898868252532680831715727722854511143304480680172052813141124420045611849828057723775363105949126828405455366200106404262310675794659470280643495090398585218024841176044368628727517113941678716688564185680961627362553111203039001198980515164358280669308454156335407
e1 = 3608925965574376523567945341556278002516414013521504911184984382205274952167864225371696017993914370497201512168776166664088733042670271646970202280312387281923684119807730941123751515160638485698825644259362763695807647636424870261387602647920252532625627698043754500477196359587280636895393200790995388403075695605752313259071472848947759665811971017441614507395695166977173983209874826569262567456954762013063380865150534725774724048932079149474773099227628235894219595993039786915437293792843467687437824528953466687440976745496029400371187996064849843219741352701174026683144995955926404403452468786948428940027
e2 = 2705742985834249419593731294742640629063082486092845443539270908653438835415907814640922583157596954837534793829979420920198220583127130141172579878212440120192417321752335684151522579530049628576946957043579231780623871429784888205786958275264941043957214384797910571377927751881376467152335343819159923905777819744174333126671522426796324838179520524018483919571215970759256674475827355541411323954244780923617745270386685895731319052600788632072228874066127098381849641952288244849313571762646260481179154523761739298818111645172710767353435965052896552293260158397774508443428719331456374349726714924857674679205
e3 = 3035365189985498586303047048784147960258852204370735950376858958412525602591256013408704341559429814208409740880540723564047453268194004007025515302654689487102249859432374152268384575758806644375008555596328108954751739613261097357911851867276625220692495609283168900124098676799207897772849526250849182230188000946516269730320060104809776594584791996622731993896557212016228453224349936398794285686351250712690093264997448606619172537355077652043467103166971300884698914802166607190765845078412359425449726466096602648615851255067426028168676632944926874766408639251537680297862420982004104687562994618386282503979
c = 8428440225372437159697731953040047552034760946696949785472254831497076019605441760668381296291759447497600405504562269104549915534101891117092326606340168399884372637000427767246446007973335837452013998684912761764510307152056346432612919127031634162077989869360717849451315550957073076040273291239841901635055739913150280146317663383345358421035311465389299247093408775682992619258004736599439564552486105106585061374815530581898589428950106020942797612406757368312750240904783591854877330620611395246954169095417676892061171148418509328414536101813085141351870212082214659362996288288403303642365539978759986883582
alpha2 = 815./2048
M1 = int(gmpy2.mpz(N)**(3./2))
M2 = int( gmpy2.mpz(N) )
M3 = int(gmpy2.mpz(N)**(3./2 + alpha2))
M4 = int( gmpy2.mpz(N)**(0.5) )
M5 = int( gmpy2.mpz(N)**(3./2 + alpha2) )
M6 = int( gmpy2.mpz(N)**(1.+alpha2) )
M7 = int( gmpy2.mpz(N)**(1.+alpha2) )
D = diagonal_matrix(ZZ, [M1, M2, M3, M4, M5, M6, M7, 1])
B = Matrix(ZZ, [ [1, -N, 0, N**2, 0, 0, 0, -N**3],
[0, e1, -e1, -e1*N, -e1, 0, e1*N, e1*N**2],
[0, 0, e2, -e2*N, 0, e2*N, 0, e2*N**2],
[0, 0, 0, e1*e2, 0, -e1*e2, -e1*e2, -e1*e2*N],
[0, 0, 0, 0, e3, -e3*N, -e3*N, e3*N**2],
[0, 0, 0, 0, 0, e1*e3, 0, -e1*e3*N],
[0, 0, 0, 0, 0, 0, e2*e3, -e2*e3*N],
[0, 0, 0, 0, 0, 0, 0, e1*e2*e3] ]) * D
L = B.LLL()
v = Matrix(ZZ, L[0])
x = v * B**(-1)
phi_ = (e1*x[0,1]/x[0,0]).floor()
print(phi_)
[crypto] A hundred friends
This is appearently Hastad's broadcast attack on padded message.
As the m
is large 3 ciphertexts are not enough to recover full plaintext, thus I used 5 ciphertexts.
"""
Sage implementation of Hastad's broadcast attack for small public exponent and multiple message/ciphertext pairs
"""
import binascii
import random
def linear_padding(ciphertexts, moduli, pad_array, const_array=(), e=3, eps=1/8):
'''
Initialization with placeholders
ciphertexts: ciphertext array
T_array: Chinese Remainder Theorem coefficients
moduli: Modulus array
pad_array: Linear coefficient of padding applied to the message during encryption
const_array: constant pad added to message during encryption (optional)
'''
n = len(ciphertexts)
T_array = [Integer(0)]*n
crt_array = [Integer(0)]*n
polynomial_array = []
for i in range(n):
crt_array = [0]*n
crt_array[i] = 1
T_array[i] = Integer(crt(crt_array,moduli))
G.<x> = PolynomialRing(Zmod(prod(moduli)))
for i in range(n):
polynomial_array += [T_array[i]*((pad_array[i]*x+const_array[i])^e - Integer(ciphertexts[i]))]
g = sum(polynomial_array).monic()
roots = g.small_roots(epsilon=eps)
return roots[0] if len(roots) >= 1 else -1
import json
from random import sample
with open("params.json", "r") as f:
params = json.load(f)
for i in range(1000):
ns = []
cs = []
alist = []
blist = []
es = []
e = 3
paramlist = sample(params, k=5)
for param in paramlist:
ns.append(param["n"])
cs.append(param["c"])
alist.append(1)
blist.append(param["pad"])
res = linear_padding(cs,ns,alist,blist,e=e)
if res == -1:
print("NOT FOUND ({})".format(i),es)
else:
print(res)
break
[misc] libcDB
jq was used to filter the result. and we could know that $maindb
was defined like that . as $maindb
from the error message. The query like this was to steal the username/password: .search fprintf 0x4b970 name=$maindb.users[].username
.
Then we could get the admin's username and password and could execute .secret
to get the flag: 3k{jq_is_r3ally_HelpFULL_3af4bcd97f5}
.