Contents

ApoorvCTF 2025

赛时就做了两题,跟着佬的wp复现一下

task

 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
from sympy import primerange
import random
from collections import deque

def generate(size):
    grid = [[random.randint(0, 9) for col in range(size)] for row in range(size)]
    grid[0][0] = 0
    return grid

def encrypt(n, a, b, mod=101):
    return (a * n + b) % mod

def build_encrypted_grid(grid, a, b, mod=101):
    size = 10
    encry_grid = []
    for y in range(size):
        row = []
        for x in range(size):
            enc_val = encrypt(grid[y][x], a, b, mod)
            row.append(str(enc_val).zfill(2))
        encry_grid.append(row)
    return encry_grid

def optimize(grid):
    #hidden
    pass

grid = generate(10)
a = random.choice(list(primerange(2, 12)))
b = random.choice(range(101))
encry_grid = build_encrypted_grid(grid, a, b, mod=101)

#nc chals1.apoorvctf.xyz 4002

generate是正常一个10*10的迷宫,每个数字0-9,起点是0 encrypt是正常的线性同余加密 optimize没啥用 build_encrypted_grid是生成一个没个数加密后的矩阵,并且输出为字符串,比如5->05

a=[2,3,5,7,11]

 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
//nc情况
  ____                 _         _
 / ___|  ___  _ __    (_) _   _ | |_  ___  _   _
| |  _  / _ \| '_ \   | || | | || __|/ __|| | | |
| |_| ||  __/| | | |  | || |_| || |_ \__ \| |_| |
 \____| \___||_| |_| _/ | \__,_| \__||___/ \__,_|
                    |__/

Welcome to Genjutsu Labyrinth!
Your goal is to navigate from the top-left to the bottom-right successfully
Note: Your current position is denoted by Pa. The first cell has a value 0
-------------------------------------------------

Pa 49 42 42 00 00 21 00 21 00
49 00 42 21 28 56 07 00 42 56
21 28 42 42 14 21 21 21 00 14
07 49 07 35 07 07 42 56 35 07
63 28 28 07 00 49 00 56 21 28
07 56 07 35 14 42 21 35 35 00
14 21 00 07 21 35 49 07 14 28
21 35 07 00 49 14 21 00 42 42
42 21 56 28 49 56 07 14 49 28
63 07 49 35 07 07 07 28 63 00

Use S/D to move down or right. Type 'exit' to quit.
Enter move (S/D):

哎,其实我们要找的是xor为0,即从 enc_val 中穷举 a, b 的值。然后根据 a, b 的值求出 grid 的值,进行穷举路径,找到 XOR 值为 0 的路径。

exp

 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
from pwn import *
import itertools
from Crypto.Util.number import inverse

p = remote('chals1.apoorvctf.xyz', 4002)

for _ in range(12):
    data = p.recvuntil(b'\n').strip().decode()
    print(data)

# 读取加密网格,每行接收后替换'Pa'为'00',并转换成数字列表
encry_grid = []
for _ in range(10):
    data = p.recvuntil(b'\n').strip().decode()
    print(data)
    data = data.replace('Pa', '00')
    row = list(map(int, data.split()))
    encry_grid.append(row)

encs = []
for i in range(10):
    for j in range(10):
        if encry_grid[i][j] not in encs:
            encs.append(encry_grid[i][j])

found = False
for a in [2, 3, 5, 7, 11]:
    for b in range(101):
        success = True
        for i in range(10):
            c = (a * i + b) % 101
            if c not in encs:
                success = False
                break
        if success:
            found = True
            break
    if found:
        break

grid = []
for i in range(10):
    row = []
    for j in range(10):
        r = (encry_grid[i][j] - b) * inverse(a, 101) % 101
        row.append(r)
    grid.append(row)

# 构造移动指令:总共需要18步,选取9步向下(S)移动,其余为向右(D)移动,
# 利用排列枚举所有可能的路径,选择 XOR 结果为 0 的路径
seq = list(range(18))
for perm in itertools.permutations(seq, 9):
    com = ''
    val = 0
    x = 0
    y = 0
    for i in range(18):
        if i in perm:
            com += 'S'
            x += 1
            val ^= grid[x][y]
        else:
            com += 'D'
            y += 1
            val ^= grid[x][y]
    if val == 0:
        break

for i in range(18):
    data = p.recvuntil(b': ')
    print(data.decode() + com[i])
    p.sendline(com[i].encode())
    data = p.recvuntil(b'\n').strip().decode()
    print(data)

data = p.recvuntil(b'exit!\n').strip().decode()
print(data)
for _ in range(2):
    data = p.recvuntil(b'\n').strip().decode()
    print(data)

p.interactive()

两个照片什么都没有,有点抽象 尝试了很多方法后,将 RGB 的值相加后除以 256 的余数,中央出现了旗帜。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from PIL import Image

img1 = Image.open('part1.png').convert('RGB')
img2 = Image.open('part2.png').convert('RGB')

w, h = img1.size

output_img = Image.new('RGB', (w, h), (255, 255, 255))

for y in range(h):
    for x in range(w):
        r1, g1, b1 = img1.getpixel((x, y))
        r2, g2, b2 = img2.getpixel((x, y))
        r = (r1 + r2) % 256
        g = (g1 + g2) % 256
        b = (b1 + b2) % 256
        output_img.putpixel((x, y), (r, g, b))

output_img.save('flag.png')

task

 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
import hashlib

def check_hex_data(hex1, hex2, start_string):
    if hex1 == hex2:
        return "Error: Even a Saiyan warrior knows that true strength lies in difference! The two inputs must not be identical."

    try:
        data1 = bytes.fromhex(hex1)
        data2 = bytes.fromhex(hex2)
    except ValueError:
        return "Error: Looks like you misfired a Ki blast! Invalid hex input detected."

    start_bytes = start_string.encode()

    if not (data1.startswith(start_bytes) and data2.startswith(start_bytes)):
        return "Error: These aren't true warriors! Both inputs must start with the legendary sign of 'GOKU' to proceed."

    def md5_hash(data):
        hasher = hashlib.md5()
        hasher.update(data)
        return hasher.hexdigest()

    hash1 = md5_hash(data1)
    hash2 = md5_hash(data2)

    if hash1 != hash2:
        return "Error: These warriors are impostors! They wear the same armor but their Ki signatures (MD5 hashes) don't match."

    try:
        with open("flag.txt", "r") as flag_file:
            flag = flag_file.read().strip()
        return f"🔥 You have found the real Goku! Your flag is: {flag}"
    except FileNotFoundError:
        return "Error: The Dragon Balls couldn't summon the flag! 'flag.txt' is missing."

if __name__ == "__main__":
    start_string = "GOKU"
    hex1 = input("Enter first hex data: ")
    hex2 = input("Enter second hex data: ")
    print(check_hex_data(hex1, hex2, start_string))

总而言之言而总之想让md5相等,直接开爆

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

p = remote('chals1.apoorvctf.xyz', 5002)

with open('md5_data1', 'rb') as f:
    data1 = f.read().hex()

with open('md5_data2', 'rb') as f:
    data2 = f.read().hex()

data = p.recvuntil(b': ')
print(data.decode() + data1)
p.sendline(data1.encode())
data = p.recvuntil(b': ')
print(data.decode() + data2)
p.sendline(data2.encode())

data = p.recvuntil(b'\n').rstrip()
print(data.decode())

p.interactive()

task

 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
import sys
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from random import randbytes

def main():
    key = randbytes(16)
    cipher = AES.new(key, AES.MODE_ECB)
    flag = b'apoorvctf{fake_flag_123}'

    print("Welcome to the ECB Oracle challenge!")
    print("Enter your input in hex format.")

    try:
        while True:
            print("Enter your input: ", end="", flush=True)
            userinput = sys.stdin.readline().strip()

            if not userinput:
                break

            try:
                userinput = bytes.fromhex(userinput)
                ciphertext = cipher.encrypt(pad(userinput + flag + userinput, 16))
                print("Ciphertext:", ciphertext.hex())

            except Exception as e:
                print(f"Error: {str(e)}")

    except KeyboardInterrupt:
        print("Server shutting down.")

if __name__ == "__main__":
    main()

# nc chals1.apoorvctf.xyz 4001

很有意思,加密格式变成x+flag+x,我们ECB的特点就是相同的明文块会生成相同的密文块,padding怎么办呢?消除影响就好了

exp

 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 pwn import *

p = remote('chals1.apoorvctf.xyz', 4001)

for _ in range(2):
    data = p.recvuntil(b'\n').strip().decode()
    print(data)

flag = ''
for i in range(32):
    for code in range(33, 127):
        inp = 'X' * (31 - i) + flag + chr(code) + 'X' * (31 - i)
        print('[+] input:', inp)
        inp_hex = inp.encode().hex()
        prompt = p.recvuntil(b': ').decode()
        print(prompt + inp_hex)
        p.sendline(inp_hex.encode())
        data = p.recvuntil(b'\n').strip().decode()
        print(data)
        ct = data.split(' ')[-1]
        if ct[32:64] == ct[96:128]:
            flag += chr(code)
            break

print('[*] flag:', flag)
p.interactive()