[WACON CTF 2023] WhiteArts

本来想把几个密码题都弄明白了,可实在后边太难了.先根据WP复现1个.

这个给了4个文件,看代码还插复杂,一点点理一下。

import math
import os
from Generator import Generator1, Generator2, Generator3, Generator4, Generator5

query_left = 266

def guess_mode(G, query_num):
    for _ in range(query_num):
        q = bytes.fromhex(input("q? > "))
        inverse = input("inverse(y/n)? > ") == 'y'
        assert len(q) == G.input_size
        print(G.calc(q, inverse).hex())
    
    # Time to guess
    assert input("mode? > ") == str(G.mode) # 0(gen) / 1(random)
        
def challenge_generator(challenge_name, Generator):
    global query_left
    print(f"#### Challenge = {challenge_name}")
    query_num = int(input(f"How many queries are required to solve {challenge_name}? > "))
    query_left -= query_num
    for _ in range(40):
        G = Generator()
        guess_mode(G, query_num)


challenge_generator("Generator1", Generator1)  #输入 '1'*16 + '0'*16 返回 startswith('0'*16) mode=0
challenge_generator("Generator2", Generator2)  #2次,输入 b'\x00'*7+b'\x01'*9, b'\x00'*16 前7字符相同

if query_left < 0:
    print("You passed all challenges for EASY but query limit exceeded. Try harder :(")
    exit(-1)

print("(Only for a junior division) Good job! flag_baby =", open("flag_baby.txt").read())

challenge_generator("Generator3", Generator3)

if query_left < 0:
    print("You passed all challenges for EASY but query limit exceeded. Try harder :(")
    exit(-1)

print("Good job! flag_easy =", open("flag_easy.txt").read())

challenge_generator("Generator4", Generator4)
challenge_generator("Generator5", Generator5)

if query_left < 0:
    print("You passed all challenges for HARD but query limit exceeded. Try harder :(")
    exit(-1)

print("(Only for general/global divisions) Good job! flag_hard =", open("flag_hard.txt").read())

这是主程序部分一共分5步,每一块都是先生成一对随机数对象,根据输入输出函数值。然后要求判断是用的哪个随机数函数,前两关通过得到flag_baby,第3关得到flag_easy,4、5关得到flag_hard。

随机函数基本上分两类,一个是函数一个是随机。但都有缓存让相同的值会得到相同的结果。

第1段:

class Generator1:
    def __init__(self):
        self.mode = os.urandom(1)[0] & 1
        self.n = 8
        self.input_size = 2 * self.n
        self.RF_gen = RandomFunction(self.n)
        self.RF_random = RandomFunction(2 * self.n)
    
    def func_gen(self, q):
        L, R = q[:self.n], q[self.n:]
        L, R = R, xor(L, self.RF_gen.query(R))
        return L+R
    def func_random(self, q):
        return self.RF_random.query(q)
    def calc(self, q, inverse):
        assert inverse == False, "inverse query is not allowed for Generator1"
        ret_gen = self.func_gen(q)
        ret_random = self.func_random(q)
        print(self.mode, ret_gen, ret_random)
        if self.mode == 0:
            return ret_gen
        else:
            return ret_random

模式0的加密方法

L R
加密后 R L^T(R)

显然R部的明文直接暴露,只要判断右侧即可。

from pwn import *

p = remote('175.118.127.63', 2821)

def p_query(msg, reverse=b'n'):
    p.sendlineafter(b"? > ", msg)
    p.sendlineafter(b"inverse(y/n)? > ", reverse)
    return p.recvline().strip()

#1
p.sendlineafter(b"? > ", b'1')
for _ in range(40):
    res = p_query(b'0'*32, b'n')
    if b'0'*16 == res[:16]:
        p.sendlineafter(b"mode? > ", b'0')
    else:
        p.sendlineafter(b"mode? > ", b'1')

第2段:

class Generator2:
    def __init__(self):
        self.mode = 0 #os.urandom(1)[0] & 1
        self.n = 8
        self.input_size = 2 * self.n
        self.RF_gen = RandomFunction(self.n)
        self.RF_random = RandomFunction(2 * self.n)
    
    def func_gen(self, q):
        L, R = q[:self.n], q[self.n:]
        L, R = R, xor(L, self.RF_gen.query(R))
        L, R = R, xor(L, self.RF_gen.query(R))
        return L+R
    def func_random(self, q):
        return self.RF_random.query(q)
    def calc(self, q, inverse):
        assert inverse == False, "inverse query is not allowed for Generator2"
        ret_gen = self.func_gen(q)
        ret_random = self.func_random(q)
        print(self.mode, ret_gen, ret_random)
        if self.mode == 0:
            return ret_gen
        else:
            return ret_random

第2段,加密作了两次

L R L:0 R:0 L:1 R:0
1 R L^T(R) 0 T(0) 0 1^T(0)
2 L^T(R) R^T(L^T(R)) T(0) T(T(0)) 1^T(0) T(1^T(0))

当两次使用右半都是相同左侧不同时显然两次的结果左半只相差左侧的值(L^T(R))

#2
p.sendlineafter(b"? > ", b'2')
for _ in range(40):
    res1 = p_query(b'0'*32, b'n')
    res2 = p_query(b'1'+b'0'*31, b'n')

    if res1[2:16] == res2[2:16]:
        p.sendlineafter(b"mode? > ", b'0')
    else:
        p.sendlineafter(b"mode? > ", b'1')

print(p.recvline())
#(Only for a junior division) Good job! flag_baby = WACON2023{8c3deab3b7a89f9bc7941a201}

第3段:

class Generator3:
    def __init__(self):
        self.mode = 0 #os.urandom(1)[0] & 1
        self.n = 8
        self.input_size = 2 * self.n
        self.RF_gen = RandomFunction(self.n)
        self.RF_random = RandomPermutation(2 * self.n)
    
    def func_gen(self, q, inverse):
        if not inverse:
            L, R = q[:self.n], q[self.n:]
            L, R = R, xor(L, self.RF_gen.query(R))
            L, R = R, xor(L, self.RF_gen.query(R))
            L, R = R, xor(L, self.RF_gen.query(R))
        else:
            L, R = q[:self.n], q[self.n:]
            L, R = xor(R, self.RF_gen.query(L)), L
            L, R = xor(R, self.RF_gen.query(L)), L
            L, R = xor(R, self.RF_gen.query(L)), L
        return L+R

    def func_random(self, q, inverse):
        return self.RF_random.query(q, inverse)

    def calc(self, q, inverse):        
        ret_gen = self.func_gen(q, inverse)
        ret_random = self.func_random(q, inverse)
        if self.mode == 0:
            return ret_gen
        else:
            return ret_random

第3段可以选择逆向,可以作一个正向一个逆向,我想的方法比较复杂了需要4次,然而这里有个坑。一共只能处理266次,3、4都作4次的话前边一共11次后边只剩下255次,第5段作不了。

第1,2次 L:0 R:1 L:1 R:1
1 T(1) 1 1^T(1)
T(1) 1^T(T(1)) 1^T(1) 1^T(1^T(1))
1^1^T(T(1)) T(1)^T(1^T(T(1))) 1^T(1^T(1)) 1^T(1)^T(1^T(1^T(1)))
第3,4次 1^1^T(T(1)) 1^T(1)^T(1^T(T(1))) 1^T(1^T(1)) 0^T(1)^T(1^T(1^T(1)))
1^T(1) 1^T(T(1)) T(1) 1^T(1^T(1))
1^T(T(1))^T(1^T(1)) 1^T(1) T(T(1))^1^T(1^T(1)) T(1)
1^T(1)^T(1^T(T(1))^T(1^T(1))) 1^T(T(1))^T(1^T(1)) T(1)^T(1^T(T(1))^T(1^T(1))) 1^T(T(1))^T(1^T(1))

这样第3、4次得到的右部相同。后来看WP可以只用两次,显然他只是左右两部分加密后相反。

第1次正向 第2次反向
0 0 0 0
0 T(0) T(0) 0
T(0) T(T(0)) T(T(0)) T(0)
T(T(0)) T(0)^T(T(T(0))) T(0)^T(T(T(0))) T(T(0))
p.sendlineafter(b"? > ", b'2')
for _ in range(40):
    res1 = p_query(b'0'*32, b'n')
    res2 = p_query(b'0'*32, b'y')
    if res1[16:32] == res2[:16]:
        p.sendlineafter(b"mode? > ", b'0')
    else:
        p.sendlineafter(b"mode? > ", b'1')

print(p.recvline())
#Good job! flag_easy = WACon2023{930db8b4dedb8cb86f309521011a1039}

第4段:

class Generator4:
    def __init__(self):
        self.mode = os.urandom(1)[0] & 1
        self.n = 8
        self.input_size = 2 * self.n
        self.RF_gen = RandomPermutation(self.n)
        self.RF_random = RandomPermutation(2 * self.n)
    
    def func_gen(self, q, inverse):
        X, T = q[:self.n], q[self.n:]
        X = xor(X, T)
        X = self.RF_gen.query(X, inverse)
        X = xor(X, T)
        X = self.RF_gen.query(X, inverse)
        X = xor(X, T)
                               
        return X

    def func_random(self, q, inverse):
        return self.RF_random.query(q, inverse)[:self.n]

    def calc(self, q, inverse):        
        ret_gen = self.func_gen(q, inverse)
        ret_random = self.func_random(q, inverse)
        if self.mode == 0:
            return ret_gen
        else:
            return ret_random

第4段只对左半加密,且间隔与右半部分异或。这个也想复杂了。

第1,2次 0,1 1,0
T(T(1)^1)^1 T(T(1))
第3,4次 T(T(1)^1),0 T(T(1))^1,1
T'(T(1)^1) T'((T(1)^1)^1

通过两正两逆,得到的结果只差异或1

WP的方法只用两次,第2次发送第1次的结果,右为0可以解加原值0

发送 0,0 T(T(0)),0
结果 T(T(0)) 0
p.sendlineafter(b"? > ", b'2')
for _ in range(40):
    res1 = p_query(b'0'*32, b'n')
    res1 = p_query(res1+b'0'*16, b'y')
    
    if res1 == b'0'*16:
        p.sendlineafter(b"mode? > ", b'0')
    else:
        p.sendlineafter(b"mode? > ", b'1')

第5段:

class Generator5:
    def __init__(self):
        self.mode = os.urandom(1)[0] & 1
        self.n = 1
        self.input_size = self.n
        self.RF_gen = [RandomPermutation(self.n) for _ in range(4)]
        self.RF_random = RandomFunction(self.n)
    
    def func_gen(self, q):
        ret = bytes(1)
        for i in range(4):
            ret = xor(ret, self.RF_gen[i].query(q))
        return ret

    def func_random(self, q):
        return self.RF_random.query(q)

    def calc(self, q, inverse):
        assert inverse == False, "inverse query is not allowed for Generator2"
        ret_gen = self.func_gen(q)
        ret_random = self.func_random(q)
        if self.mode == 0:
            return ret_gen
        else:
            return ret_random

第5段说难也不难,只用1位,可以处理256次而返回结果用的随机数也有个限制就是结果也不相同,如果256次的话返回值就是1-255,那么这些值异或结果就是0。只是坑在前边,必需给这里保留256次。也就是3,4两步不能都用4次,少一次就行。

#5
p.sendlineafter(b"? > ", b'256')
for _ in range(40):
    v =0
    for i in range(256):
        v ^= bytes.fromhex(p_query(bytes([i]).hex().encode(),b'n').decode())[0]
    if v == 0:
        p.sendlineafter(b"mode? > ", b'0')
    else:
        p.sendlineafter(b"mode? > ", b'1')

print(p.recvline())
#(Only for general/global divisions) Good job! flag_hard = WACon2023{c7a47ff1646698d275602dce1355645684f743f1}
p.interactive()

你可能感兴趣的:(CTF,python)