Idea: We can get whole data from exploiting P.random_element(6) function. Evidently, first base starts at 1, others in [0,x) with x < 50 . If we analysic data and calculate the probability, we'll get :
Value a > 40 : 30%
The average value of other bases : 15-29
From that data, we can bruteforce all the bases:
for a in range(1,47):
for b in range(0,47):
for c in range(0,47):
for d in range(0,47):
for e in range(0,47):
for f in range(0,47):
if (a+b+c+d+e+f+1)%47 == 20 and (17*a+32*b+16*c+8*d+4e+2*f +1)%47 ==35 and (24*a+8*b +34*c+27*d+9*e+3*f+1) % 47 ==33 and (7*a+37*b+21*c+17*d+16*e+4*f +1)%47 == 42 and (21*a+23*b +14*c+31*d+25*e+5*f+1)%47 == 14 and (32*a+21*b+27*c+28*d+36*e+6*f+1)%47 == 41:
print(a,b,c,d,e,f)
break;
print(a)
# 41 15 40 9 28 27
After bruteforcing we get a = 41 :)))). Analysicing base d will faster . Full_solve
#DUCTF{go0d_0l'_l4gr4ng3}
#Break Me!
#!/usr/bin/python3
import sys
import os
from Crypto.Cipher import AES
from base64 import b64encode
bs = 16 # blocksize
flag = open('flag.txt', 'rb').read().strip()
key = open('key.txt', 'r').read().strip().encode() # my usual password
def enc(pt):
cipher = AES.new(key, AES.MODE_ECB)
ct = cipher.encrypt(pad(pt+key))
res = b64encode(ct).decode('utf-8')
return res
def pad(pt):
while len(pt) % bs:
pt += b'0'
return (pt)
def main():
print('AES-128')
while(1):
msg = input('Enter plaintext:\n').strip()
pt = flag + str.encode(msg)
ct = enc(pt)
print(ct)
if __name__ == '__main__':
main()
Comment:
This is block cipher ECB, each block holds 16 characters
flag + input + key =>if we don't input, we'll get flag+key
base64 of flag is constant => len(flag) = 32, len(key) = 16
flag is in block1 and block2, we input from block 3
(flag + input + key) then padding by '0'
Idea:
We input 1 character which is bruceforced + '0'*16, block4 will be '0' + key misses the last character , block5 will be the last character of key + '0'*15
Compare block 3 and block 5, if they are equal, we can get the last key's character, do that continually until the key's complete. Having key and cipher => get flag
from pwn import *
from Crypto.Util.number import *
import codecs
import base64
import string
p= remote("pwn-2021.duc.tf", 31914)
char = string.printable
key = ""
k = 0
for k in range(0,16):
for i in char:
p.recvuntil("Enter plaintext:\n")
p.sendline(i + key + 16*'0')
a = p.recvuntil('\n',drop = True)
b = base64.b64decode(a)
if b[32:48] == b[64:80]:
c = i
key = c + key
print(key)
break
print(key)
#!_SECRETSOURCE_!
# treasure
#!/usr/bin/python3
import re
from Crypto.Util.number import long_to_bytes
from Crypto.Random import random
from secret import REAL_COORDS, FLAG_MSG
FAKE_COORDS = 5754622710042474278449745314387128858128432138153608237186776198754180710586599008803960884
p = 13318541149847924181059947781626944578116183244453569385428199356433634355570023190293317369383937332224209312035684840187128538690152423242800697049469987
def create_shares(secret):
r1 = random.randint(1, p - 1)
r2 = random.randint(1, p - 1)
s1 = zr1*r2*secret % p
s2 = r1*r1*r2*secret % p
s3 = r1*r2*r2*secret % p
return [s1, s2, s3]
def reveal_secret(shares):
s1, s2, s3 = shares
secret = pow(s1, 3, p) * pow(s2*s3, -1, p) % p
return secret
def run_combiner(shares):
try:
your_share = int(input('Enter your share: '))
return reveal_secret([your_share, shares[1], shares[2]])
except:
print('Invalid share')
exit()
def is_coords(s):
try:
return re.match(r'-?\d+\.\d+?, -?\d+\.\d+', long_to_bytes(s).decode())
except:
return False
def main():
shares = create_shares(REAL_COORDS)
print(f'Your share is: {shares[0]}')
print(f'Your two friends input their shares into the combiner and excitedly wait for you to do the same...')
secret_coords = run_combiner(shares)
print(f'The secret is revealed: {secret_coords}')
if not is_coords(secret_coords):
print('"Hey those don\'t look like coordinates!"')
print('Your friends grow a bit suspicious, but you manage to convince them that you just entered a digit wrong. You decide to try again...')
else:
print('"Let\'s go get the treasure!!"')
print('Your friends run off to the revealed location to look for the treasure...')
exit()
secret_coords = run_combiner(shares)
if not is_coords(secret_coords):
print('"This is way too sus!!"')
exit()
if secret_coords == FAKE_COORDS:
print('You\'ve successfully deceived your friends!')
try:
real_coords = int(input('Now enter the real coords: '))
if real_coords == REAL_COORDS:
print(FLAG_MSG)
else:
print('Incorrect!')
except:
print('Incorrect!')
else:
print('You are a terrible trickster!')
if __name__ == '__main__':
main()
Comment
When we input shares[0], the server responses 'secret', contemporary it calls to exit() function , 'secret 'is constant
We can bypass the first Function run_combiner(shares) by inputing random intergers
So we've to pow(the input, 3) equal to (r1*r2)^3 * (*secret^2) * FAKE_COORDS
# treasure
def cuberoot(a, p):
if p == 2:
return a
if p == 3:
return a
if (p%3) == 2:
return pow(a,(2*p - 1)//3, p)
if (p%9) == 4:
root = pow(a,(2*p + 1)//9, p)
if pow(root,3,p) == a%p:
return root
else:
return None
if (p%9) == 7:
root = pow(a,(p + 2)//9, p)
if pow(root,3,p) == a%p:
return root
else:
return None
else:
print( "Not implemented yet. See the second paper")
# r1 = random.randint(1, p - 1)
# r2 = random.randint(1, p - 1)
# s1 = r1*r2*secret % p
# s2 = r1*r1*r2*secret % p
# s3 = r1*r2*r2*secret % p
secret = 5756627544102572649201219381096443309301530404084814366157678459246004007288774904822314549
FAKE_COORDS = 5754622710042474278449745314387128858128432138153608237186776198754180710586599008803960884
p = 13318541149847924181059947781626944578116183244453569385428199356433634355570023190293317369383937332224209312035684840187128538690152423242800697049469987
s0 = 10746762628325636011924185441153274926904641696665889398587793249399628791439639198296232637211075901379214232485250556903699116729999543184000129920606492
r12= (s0 * pow(secret,-1,p))%p
a = (r12**3)*(secret**2)*FAKE_COORDS
print(cuberoot(a,p))