I participated in PBCTF 2020 as a member of Super Guesser. We reached 2nd place :) 

Huge congratulations and respect to zer0pts, a team that clearly doesn't fit their team name.


I solved : QueenSarah2, Strong Cipher (with reversing help), LeaK, Special Gift, Special Gift Revenge, Find rbtree.


QueenSarah2


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from string import ascii_lowercase
from itertools import product
from random import SystemRandom
from math import ceil, log
from secretstuff import FLAG
 
random = SystemRandom()
ALPHABET = ascii_lowercase + "_"
assert all(char in ALPHABET for char in FLAG)
 
bigrams = [''.join(bigram) for bigram in product(ALPHABET, repeat=2)]
random.shuffle(bigrams)
 
S_box = {}
for i in range(len(ALPHABET)):
    for j in range(len(ALPHABET)):
        S_box[ALPHABET[i]+ALPHABET[j]] = bigrams[i*len(ALPHABET) + j]
 
assert len(set(S_box.keys())) == 27*27
 
def encrypt(message):
    if len(message) % 2:
        message += "_"
 
    message = list(message)
    rounds = int(2 * ceil(log(len(message), 2))) # The most secure amount of rounds
 
    for round in range(rounds):
        # Encrypt
        for i in range(0len(message), 2):
            message[i:i+2= S_box[''.join(message[i:i+2])]
 
        # Shuffle, but not in the final round
        if round < (rounds-1):
            message = [message[i] for i in range(len(message)) if i%2 == 0+ [message[i] for i in range(len(message)) if i%2 == 1]
 
    return ''.join(message)
 
# 123456 -> 135246
if __name__ == "__main__":
    print("This is a restricted service! Decrypt this password to proceed:")
    print({encrypt(FLAG)})
 
    for _ in range(1500):
        question = input("> ").strip()
        assert 0 < len(question) <= 10000
 
        if not question:
            print("Bye.")
            break
 
        elif question == FLAG:
            print(f"You got it. The flag is pbctf{{{FLAG}}}")
            break
 
        else:
            print("That's not quite right. Your password encrypts to this:")
            print(encrypt(question))
 
 
cs


We have a secret permutation of bigrams, which are used to encrypt the messages. By sending all $27^2$ bigrams, we can easily calculate the square of the permutation. This is because with messages of length 2, we go through 2 rounds of encryption, and the last shuffle algorithm doesn't actually shuffle anything. However, knowing the square of the permutation does not imply we cannot recover the permutation directly. This is only possible in certain cases - such as when all cycles of the squared permutation have different length. This happens with around 2% probability. Therefore, we may try to recover the original permutation with this idea, and recover the flag by reversing the encryption process. I got it with around 20 tries, so I got lucky here a bit. Looking at the flag, it looks like there should be a cooler solution.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def connect():
    r = remote('queensarah2.chal.perfect.blue'1)
    return r
 
def get_index(t):
    if ord('a'<= ord(t) <= ord('z'):
        return ord(t) - ord('a')
    return 26 
 
def attempt():
    dcyc = [0* (27 * 27)
    AL = ascii_lowercase + "_"
    r = connect()
    r.recvline()
    true_val = r.recvline()[2:-3].decode()
    for i in range(027):
        for j in range(027):
            s = AL[i] + AL[j]
            print(s)
            r.sendline(s)
            r.recvline()
            res = r.recvline()[0:2].decode()
            idx = 27 * get_index(res[0]) + get_index(res[1])
            dcyc[27 * i + j] = idx # square of permutation
    vis = [0* (27 * 27 + 5)
    sz = [0* (27 * 27 + 5)
    fk = [0* (27 * 27 + 5)
    # get cycles of the square permutation 
    for i in range(027 * 27):
        if vis[i] == 1:
            continue
        cur, t = i, 0
        L = []
        while vis[cur] == 0:
            L.append(cur)
            t += 1
            vis[cur] = 1
            cur = dcyc[cur]
        if sz[t] >= 1 or t % 2 == 0# all cycle's length different
            return
        sz[t] += 1
        for x in L:
            fk[x] = t
    # compute original permutation
    cyc = [0* (27 * 27)
    for i in range(027 * 27):
        it = (fk[i] + 1// 2
        u = i
        for j in range(0, it):
            u = dcyc[u]
        cyc[i] = u
    invcyc = [0* (27 * 27)
    for i in range(027 * 27):
        invcyc[cyc[i]] = i
    # decryption process
    rounds = int(2 * math.ceil(math.log(len(true_val), 2)))
    for i in range(0, rounds):
        if i != 0:
            msg = ''
            for j in range(0len(true_val) // 2):
                msg = msg + true_val[j]
                msg = msg + true_val[j + len(true_val) // 2]
            true_val = msg
        msg = ''
        for j in range(0len(true_val), 2):
            cc = 27 * get_index(true_val[j]) + get_index(true_val[j+1])
            cc = invcyc[cc]
            u = cc // 27
            v = cc % 27
            msg += AL[u]
            msg += AL[v]
        true_val = msg
    print(true_val)
    r.sendline(true_val)
    print(r.recvline())
 
NUM_ATTEMPT = 1000
for i in range(0, NUM_ATTEMPT):
    print("[+] Attempt ", i)
    attempt()
cs


Strong Cipher


After reversing, we found out that the encryption process was a simple vigenere cipher, with $GF(2^8)$ multiplications instead of addition. By brute forcing key length and selecting keys that gave maximum number of readable characters, we can recover the plaintext. I wasted a lot of time by incorrectly assuming that the plaintext is 100% readable. oof


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def gf2(src1, src2):
    ret = 0
    for i in range(08):
        if (src2 & (1 << i)) > 0:
            ret = ret ^ (src1 << i)
    for i in range(147-1):
        p = 0x11B << (i - 8)
        if (ret & (1 << i)) > 0:
            ret = ret ^ p
    assert 0 <= ret < 256
    return ret
 
= open("ciphertext""rb")
= f.read()
 
= len(f)
print(L)
 
cc = []
for i in range(1213): # should brute over 1 ~ 16
    for j in range(0, i):
        cmx, whi = 00
        for k in range(1256):
            cnt = 0
            for l in range(j, L, i):
                t = gf2(k, f[l]) 
                if 32 <= t <= 128:
                    cnt += 1
            if cmx < cnt:
                cmx = cnt
                whi = k
        cc.append(whi)
 
res = b""
 
for i in range(0, L):
    res += long_to_bytes(gf2(cc[i % 12], f[i]))
 
print(res)
cs


LeaK


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from ecdsa import SECP256k1
from ecdsa.ecdsa import Public_key, Private_key
from flag import flag
import hashlib
import random
 
= SECP256k1.generator
order = int(SECP256k1.order)
secret = random.randrange(2, order - 1)
pubkey = Public_key(g, g * secret)
privkey = Private_key(pubkey, secret)
 
arr = []
for i in range(30):
    h = random.randrange(2, order - 1)
    k = random.randrange(2, order - 1)
    sig = privkey.sign(h, k)
    
    lea_k = int("0x" + "{:064x}".format(k)[10:-10], 16)
 
    arr.append((h, lea_k, int(sig.r), int(sig.s)))
 
print(arr)
 
sha256 = hashlib.sha256()
sha256.update(str(secret).encode())
key = sha256.digest()
 
aes = AES.new(key, mode=AES.MODE_ECB)
print(aes.encrypt(pad(flag, 16)).hex())
cs


It's yet another hidden number problem. We have

$$s \equiv k^{-1}(h + r \cdot pvk) \pmod{n}$$ $$ (2^{216} \cdot small_1 + 2^{40} \cdot leak + small_2)s \equiv h + r \cdot pvk \pmod{n}$$ $$ r \cdot pvk \equiv 2^{216}s \cdot small_1 + s \cdot small_2 + 2^{40}s \cdot leak - h \pmod{n}$$

Now we can work with $r$, $2^{216}s$, $s$ and $n$ to make $2^{40}s \cdot leak - h$. Babai's algorithm can do this.


Small notes on the code below - I have used the following tricks to get the secret key.

  • The equation above is an equality, so we have to force it to be equal. 
  • This is done by scaling - i.e. multiplying a super large integer $n^2$ to the respective columns.
  • We have $pvk$ to be between $0$ and $n$, but $small_1, small_2$ between $0$ and $2^{40}$. 
  • We need the two to have a similar order for CVP algorithm to respect the bounds on $small_1, small_2$, so scale it by $n/2^{40}$. 
  • We have 30 signatures, but with the matrix below, using all of them takes too long. Using 10 is enough.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def Babai_closest_vector(M, G, target):
        small = target
        for _ in range(1):
            for i in reversed(range(M.nrows())):
                c = ((small * G[i]) / (G[i] * G[i])).round()
                small -=  M[i] * c
        return target - small 
 
NUM = 10
= 115792089237316195423570985008687907852837564279074904382605163141518161494337
SIG = [] ## signature
 
= Matrix(ZZ, 3 * NUM + 13 * NUM + 1)
for i in range(0, NUM):
    M[0, i] = SIG[i][2* n * n
M[0, NUM] = 1
 
for i in range(0, NUM):
    M[2 * i + 1, i] = (2 ** 216* SIG[i][3* n * n
    M[2 * i + 2, i] = SIG[i][3* n * n
    M[2 * i + 1, NUM + 2 * i + 1= n // (2 ** 40)
    M[2 * i + 2, NUM + 2 * i + 2= n // (2 ** 40)
for i in range(0, NUM):
    M[2 * NUM + 1 + i, i] = n * n * n
Target = [0* (3 * NUM + 1)
for i in range(0, NUM):
    Target[i] = ((SIG[i][1* (2 ** 40* SIG[i][3- SIG[i][0]) % n) * n * n
Target = vector(Target)
= M.LLL()
GG = M.gram_schmidt()[0]
TT = Babai_closest_vector(M, GG, Target)
print(TT - Target)
print(TT[NUM] % n) ## secret
cs


Special Gift


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from Crypto.Util.number import getStrongPrime, inverse, bytes_to_long, GCD as gcd
from Crypto.Random.random import randint
from flag import flag
 
= getStrongPrime(512)
= getStrongPrime(512)
= p * q
phi = (p - 1* (q - 1)
 
# Hehe, boi
while True:
    d = randint(int(N ** 0.399), int(N ** 0.4))
    if gcd(d, phi) == 1:
        break
 
= inverse(d, phi)
 
# Here's a special gift. Big.
gift = d >> 120
 
enc = pow(bytes_to_long(flag), e, N)
 
print("N =", N)
print("e =", e)
print("gift =", gift)
print("enc =", enc)
cs


My best achievement in this CTF is first solving this with unintended sol, forcing rbtree to write a revenge challenge.

This problem is also the reason I'm writing stuff up for this CTF in 2am ^__^

My solution resembles my alternate solution for crypto01 from SECCONCTF a little bit.


We start with $$ ed = k\phi(n) + 1 = k(n-p-q+1)+1 = kn - k(p+q-1) + 1$$

Since $e \approx n$ and $d \approx n^{0.4}$, we have $k \approx n^{0.4}$ as well. Also, we know that $$ 2\sqrt{n} \le p+q \le 3\sqrt{n/2}$$ so combining, we have something like $0 \le k(p+q-1) - 1 \le 3 \cdot n^{0.9}$. I calculated constants like $3$ very loosely, btw. 


Therefore, we have an interval of length $3 \cdot n^{0.9}$ that $ed \pmod{n}$ must lie inside. 

Writing $d = gift \cdot 2^{120} + c$ with $0 \le c < 2^{120}$, we have an interval on $ec \pmod{n}$. 


We have $2^{120}$ candidates for $c$, and we have a condition on $ec \pmod{n}$ that has, heuristically, a probability of $3 \cdot n^{-0.1}$ of satisfying. Therefore, we can expect around $2^{120} \cdot 3 \cdot n^{-0.1} \approx 3 \cdot 2^{18}$ "true" candidates for $c$. This is not an unreasonably large number, so if we can find those true candidates quickly, we can brute force every one of them to find the flag. 


How do we find the true candidates for $c$? This is equivalent to finding the smallest positive integer $x$ such that $$L \le Ax \pmod{M} \le R$$ for given $A, M, L, R$ quickly. Polynomial time solution of this problem was mentioned in the crypto01 problem writeup linked above. 

It takes around 30 minutes to compute the flag, and it iterates over around 700,000 candidates as expected.


The flag prints the link to a paper that contains the intended solution.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def ceil(n, m):
    return (n + m - 1// m
 
def optf(A, M, L, R):
    if L == 0:
        return 0
    if 2 * A > M:
        L, R = R, L
        A = M - A
        L = M - L
        R = M - R
    cc_1 = ceil(L, A)
    if A * cc_1 <= R:
        return cc_1
    cc_2 = optf(A - M % A, A, L % A, R % A)
    return ceil(L + M * cc_2, A)
 
= 124588792854585991543122421017579759242707321792822503200983206042530513248160179498235727796077646122690756838184806567078369714502863053151565317001149999657802192888347495811627518984421857644550440227092744651891241056244522365071057538408743656419815042273198915328775318113249292516318084091006804073157
= 109882604549059925698337132134274221192629463500162142191698591870337535769029028534472608748886487359428031919436640522967282998054300836913823872240009473529848093066417214204419524969532809574214972094458725753812433268395365056339836734440559680393774144424319015013231971239186514285386946953708656025167
gift = 870326170979229749948990285479428244545993216619118847039141213397137332130507928675398
enc = 67594553703442235599059635874603827578172490479401786646993398183588852399713973330711427103837471337354320292107030571309136139408387709045820388737058807570181494946004078391176620443144203444539824749021559446977491340748598503240780118417968040337516983519810680009697701876451548797213677765172108334420
 
CR = (-* (gift << 120)) % N
CL = (-* (gift << 120- 3 * (int)(N ** 0.9)) % N
 
lst = 0
 
while lst <= (2 ** 120):
    # find next solution by shifting
    NL = (CL - e * (lst + 1)) % N
    NR = (CR - e * (lst + 1)) % N
    if NL > NR:
        # 0 is a solution
        lst = lst + 1
    else:
        # find actual solution
        cc = optf(e, N, NL, NR)
        lst = lst + 1 + cc
    real_d = gift * (1 << 120+ lst
    s = long_to_bytes(pow(enc, real_d, N))
    if b"pbctf" in s:
        print(s)
cs


Special Gift Revenge


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from Crypto.Util.number import getStrongPrime, inverse, bytes_to_long, GCD as gcd
from Crypto.Random.random import randint
from flag import flag
import gmpy2
 
= getStrongPrime(512)
= getStrongPrime(512)
= p * q
phi = (p - 1* (q - 1)
 
# Hehe, boi
while True:
    d = randint(int(N ** gmpy2.mpfr(0.599)), int(N ** gmpy2.mpfr(0.6)))
    if gcd(d, phi) == 1:
        break
 
= inverse(d, phi)
 
# Here's a special gift. Big.
gift = d >> 120
 
enc = pow(bytes_to_long(flag), e, N)
 
print("N =", N)
print("e =", e)
print("gift =", gift)
print("enc =", enc)
cs


Now this problem blocks the unintended sol above. Therefore, we should implement the paper in the previous flag.

The implementation is not tough, but I surely learned a lot of lattice stuff from it. Thanks to rbtree :)


The flag is pbctf{thank_you_rkm0959,_for_finding_unintended_solution} which is pretty cool :P


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
## modified https://github.com/ubuntor/coppersmith-algorithm/blob/main/coppersmith.sage
 
debug = True
 
= 123463519828344660835965296108959625188149729700517379543746606603601816029557213728343115758280318474617032830851553509268562367217512005079977122560679743955588214135519642513042848616372204042776892196887455692479457740367547908255044784496969010537283159300508751036032559594474145098337531029291955103059
= 85803665824396212221464259773478155183477895540333642019501498374139506738444521180470104195883386495607712971252463223185914391456070458788554837326327618859712794129800329295751565279950274474800740076285111503780662397876663144946831503522281710586712396810593754749589799811545251575782431569881989690861
gift = 46710143823773072238724337855139753113453277386728402328859555407710009799097841900723288768522450009531777773692804519189753306306645410280934372812
enc = 106121451638162677594573310940827829041097305506084523508481527070289767121202640647932427882853090304492662258820333412210185673459181060321182621778215705296467924514370932937109363645133019461501960295399876223216991409548390823510949085131028770701612550221001043472702499511394058569487248345808385915190
 
delta = 0.6
gamma = 120 / 1022
lam = max(gamma, delta - 0.5)
d_0 = (gift << 120+ (1 << 119
k_0 = (e * d_0 - 1// N
= (int)(4 * (N ** lam))
= (int)(3 * ((N/2** 0.5))
= 5
= 3
 
P.<x,y> = PolynomialRing(ZZ)
pol = (1 + k_0 * N) % e + (k_0 % e) * y + (N % e) * x + x * y
 
while gcd(pol(0,0), X) != 1:
    X = next_prime(X, proof=False)
 
while gcd(pol(0,0), Y) != 1:
    Y = next_prime(Y, proof=False)
 
polynomials = []
for j in range(0, m+1):
    for i in range(0, m-j+t+1):
        polynomials.append(x^i * pol^j * e^(m-j))
for j in range(0, m+1):
    for i in range(1, m-j+1):
        polynomials.append(y^i * pol^j * e^(m-j))
 
monomials = []
for i in polynomials:
    for j in i.monomials():
        if j not in monomials:
            monomials.append(j)
monomials.sort()
 
= matrix(ZZ,len(monomials))
for i in range(len(monomials)):
    for j in range(len(monomials)):
        L[i,j] = polynomials[i](X*x,Y*y).monomial_coefficient(monomials[j])
 
= matrix(ZZ,sorted(L,reverse=True))
= L.LLL()
roots = []
 
for i in range(L.nrows()):
    for j in range(i+1, L.nrows()):
        pol1 = P(sum(map(mul, zip(L[i],monomials)))(x/X,y/Y))
        pol2 = P(sum(map(mul, zip(L[j],monomials)))(x/X,y/Y))
        r = pol1.resultant(pol2, y)
        if r.is_constant():
            continue
        for x0, _ in r.univariate_polynomial().roots():
            if x0 in [i[0for i in roots]:
                continue
            if debug:
                print("Potential x0:",x0)
            for y0, _ in pol1(x0,y).univariate_polynomial().roots():
                if debug:
                    print("Potential y0:",y0)
                if (x0,y0) not in roots:
                    roots.append((x0,y0))
print(roots)
 
cs


Find rbtree


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
prop = {
    "Eyewear": ["Glasses""Monocle""None"],
    "Eye color": ["Brown""Blue""Hazel"],
    "Hair": ["Straight""Curly""Bald"],
    "Outerwear": ["Coat""Hoodie""Poncho"],
    "T-shirt color": ["Red""Orange""Green"],
    "Trousers": ["Jeans""Leggings""Sweatpants"],
    "Socks color": ["Black""Gray""White"],
    "Shoes": ["Boots""Slippers""Sneakers"],
}
 
 
def stage(num_stage, num_people, num_ask):
    print("STAGE {} / 30".format(num_stage))
    print("Generating people... (and rbtree)")
 
    people = list(itertools.product(*prop.values()))
    random.shuffle(people)
    people = people[:num_people]
    rbtree = random.choice(people)
 
    print("=" * 29)
    for idx, person in enumerate(people):
        print(" ".join(" [PERSON {:4d}] ".format(idx + 1)))
        for prop_name, prop_val in zip(prop.keys(), person):
            print("{:14s}: {}".format(prop_name, prop_val))
        print("=" * 29)
 
    print("Now ask me!")
 
    for i in range(num_ask):
        prop_name = input("? > ")
        if prop_name == 'Solution':
            break
        if prop_name not in prop:
            return False
        
        prop_ask = input("! > ").strip().split(' ')
        for val in prop_ask:
            if val not in prop[prop_name]:
                return False
 
        if set(rbtree) & set(prop_ask):
            print("YES")
        else:
            print("NO")
 
    rbtree_guess = tuple(input("rbtree > ").strip().split(' '))
 
    if rbtree == rbtree_guess:
        return True
    else:
        return False
 
 
def main():
 
    cases = [(53), (73), (104), (154), (205), (255), (506), (757), (1008), (2509)]
    cases += [(40010)] * 5 + [(75011)] * 5 + [(100012)] * 5 + [(160012)] * 5
 
    for idx, (num_people, num_ask) in enumerate(cases):
        if not stage(idx + 1, num_people, num_ask):
            print("WRONG :(")
            return
        print("You found rbtree!")
              
    with open("flag.txt""r"as f:
        print(f.read())
 
 
if __name__ == "__main__":
    try:
        main()
    finally:
        print("BYEBYE!")
        exit(0)
cs


I tried the most naive way - finding the question that has "the best worst case". If I had used all my queries and I still had multiple candidates left, I chose one randomly and reported it as the answer. It looked it had decent chances of succeeding, so my teammates ran multiple instances :) I was worried about the server being adaptive like some challenges in competitive programming, but we got the flag.


UPD: So more of a backstory - I had realized that the number of candidates never decreased by more than half. This is strange unless the server is set up against you. This was the main fact that caused me to become worried about adversarial server. Turns out the server *was* adversarial, but wasn't perfectly so. The solution below is clearly unintended (smarter solutions should exist) but the server's antics can't really stop it from getting the flag. I guess they didn't block choosing one random answer while there are multiple candidates. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
prop_list = ["Eyewear""Eye color""Hair""Outerwear""T-shirt color""Trousers""Socks color""Shoes"]
prop = {
    "Eyewear": ["Glasses""Monocle""None"],
    "Eye color": ["Brown""Blue""Hazel"],
    "Hair": ["Straight""Curly""Bald"],
    "Outerwear": ["Coat""Hoodie""Poncho"],
    "T-shirt color": ["Red""Orange""Green"],
    "Trousers": ["Jeans""Leggings""Sweatpants"],
    "Socks color": ["Black""Gray""White"],
    "Shoes": ["Boots""Slippers""Sneakers"],
}
 
def checker(i, j, desc):
    for t in range(03):
        if (1 << t) & j and desc[i] == prop[prop_list[i]][t]:
            return True
    return False
 
def ask_query(whi_i, whi_j):
    r.sendline(prop_list[whi_i])
    s = ""
    for i in range(03):
        if whi_j & (1 << i):
            s += prop[prop_list[whi_i]][i]
            s += " "
    s = s[:-1]
    r.sendline(s)
    res = r.recvline()
    print(res)
    sx = False
    if b"YES" in res:
        sx = True
    return sx
 
def gogo(ppl_desc, num_query):
    print(len(ppl_desc))
    if len(ppl_desc) == 1:
        if num_query >= 1:
            r.sendline(b"Solution")
        s = ""
        for i in range(08):
            s += ppl_desc[0][i]
            if i != 7:
                s += " "
        r.sendline(s)
        r.recvline()
        return
    if num_query == 0:
        t = random.randrange(0len(ppl_desc))
        s = ""
        for i in range(08):
            s += ppl_desc[t][i]
            if i != 7:
                s += " "
        r.sendline(s)
        r.recvline()
        return
    T = len(ppl_desc)
    whi_i, whi_j, opt = -1-10
    for i in range(08):
        for j in range(14):
            cnt = 0
            for k in range(0, T):
                ex = checker(i, j, ppl_desc[k])
                if ex == True:
                    cnt += 1
            if min(cnt, T-cnt) > opt:
                opt = min(cnt, T-cnt)
                whi_i, whi_j = i, j
    sx = ask_query(whi_i, whi_j)
    nppl_desc = []
    for k in range(0, T):
        ex = checker(whi_i, whi_j, ppl_desc[k])
        if ex == sx:
            nppl_desc.append(ppl_desc[k])
    gogo(nppl_desc, num_query - 1)
    return
 
def solve(num_pp, num_ask):
    print(r.recvline())
    print(r.recvline())
    print(r.recvline())
    ppl_desc = []
    for i in range(0, num_pp):
        cur = []
        r.recvline()
        for j in range(08):
            s = r.recvline()
            s = s.split(b":")[1]
            s = s[1:-1].decode()
            cur.append(s)
        ppl_desc.append(cur)
        r.recvline()
    r.recvline()
    gogo(ppl_desc, num_ask)
 
 
= remote("find-rbtree.chal.perfect.blue"1)
 
for i in range(09):
    r.recvline()
 
cases = [(53), (73), (104), (154), (205), (255), (506), (757), (1008), (2509)]
cases += [(40010)] * 5 + [(75011)] * 5 + [(100012)] * 5 + [(160012)] * 5
 
for i in range(030):
    solve(cases[i][0], cases[i][1])
 
print(r.recvline())
cs


'CTF' 카테고리의 다른 글

0x41 CTF SPN Writeup  (0) 2021.01.31
TetCTF 2021 Crypto Writeups  (1) 2021.01.03
N1CTF 2020 Crypto Write-Ups  (0) 2020.10.18
SECCON 2020 OnlineCTF Crypto Write-Ups  (0) 2020.10.11
CryptoHack All Solve  (3) 2020.09.30