LOADING

加载过慢请开启缓存 浏览器默认开启

XYCTF2025-crypto

Crypto

复复复数

task.py

class ComComplex:
    def __init__(self, value=[0,0,0,0]):
        self.value = value
    def __str__(self):
        s = str(self.value[0])
        for k,i in enumerate(self.value[1:]):
            if i >= 0:
                s += '+'
            s += str(i) +'ijk'[k]
        return s
    def __add__(self,x):
        return ComComplex([i+j for i,j in zip(self.value,x.value)])
    def __mul__(self,x):
        a = self.value[0]*x.value[0]-self.value[1]*x.value[1]-self.value[2]*x.value[2]-self.value[3]*x.value[3]
        b = self.value[0]*x.value[1]+self.value[1]*x.value[0]+self.value[2]*x.value[3]-self.value[3]*x.value[2]
        c = self.value[0]*x.value[2]-self.value[1]*x.value[3]+self.value[2]*x.value[0]+self.value[3]*x.value[1]
        d = self.value[0]*x.value[3]+self.value[1]*x.value[2]-self.value[2]*x.value[1]+self.value[3]*x.value[0]
        return ComComplex([a,b,c,d])
    def __mod__(self,x):
        return ComComplex([i % x for i in self.value])
    def __pow__(self, x, n=None):
        tmp = ComComplex(self.value)
        a = ComComplex([1,0,0,0])
        while x:
            if x & 1:
                a *= tmp
            tmp *= tmp
            if n:
                a %= n
                tmp %= n
            x >>= 1
        return a

from Crypto.Util.number import *
from secret import flag, hint

p = getPrime(256)
q = getPrime(256)
r = getPrime(256)
n = p * q * r

P = getPrime(512)
assert len(hint) == 20
hints = ComComplex([bytes_to_long(hint[i:i+5]) for i in range(0,20,5)])
keys = ComComplex([0, p, q, r])
print('hint =',hints)
print('gift =',hints*keys%P)
print('P =',P)

e = 65547
m = ComComplex([bytes_to_long(flag[i:i+len(flag)//4+1]) for i in range(0,len(flag),len(flag)//4+1)])
c = pow(m, e, n)
print('n =', n)
print('c =', c)

'''
hint = 375413371936+452903063925i+418564633198j+452841062207k
gift = 8123312244520119413231609191866976836916616973013918670932199631084038015924368317077919454611785179950870055560079987034735836668109705445946887481003729+20508867471664499348708768798854433383217801696267611753941328714877299161068885700412171i+22802458968832151777449744120185122420871929971817937643641589637402679927558503881707868j+40224499597522456323122179021760594618350780974297095023316834212332206526399536884102863k
P = 8123312244520119413231609191866976836916616973013918670932199631182724263362174895104545305364960781233690810077210539091362134310623408173268475389315109
n = 408713495380933615345467409596399184629824932933932227692519320046890365817329617301604051766392980053993030281090124694858194866782889226223493799859404283664530068697313752856923001112586828837146686963124061670340088332769524367
c = 212391106108596254648968182832931369624606731443797421732310126161911908195602305474921714075911012622738456373731638115041135121458776339519085497285769160263024788009541257401354037620169924991531279387552806754098200127027800103+24398526281840329222660628769015610312084745844610670698920371305353888694519135578269023873988641161449924124665731242993290561874625654977013162008430854786349580090169988458393820787665342793716311005178101342140536536153873825i+45426319565874516841189981758358042952736832934179778483602503215353130229731883231784466068253520728052302138781204883495827539943655851877172681021818282251414044916889460602783324944030929987991059211909160860125047647337380125j+96704582331728201332157222706704482771142627223521415975953255983058954606417974983056516338287792260492498273014507582247155218239742778886055575426154960475637748339582574453542182586573424942835640846567809581805953259331957385k
'''

用Grok可以一把梭出来p,q,r。

from Crypto.Util.number import *

# Given values
h0 = 375413371936
h1 = 452903063925
h2 = 418564633198
h3 = 452841062207
P = 8123312244520119413231609191866976836916616973013918670932199631182724263362174895104545305364960781233690810077210539091362134310623408173268475389315109
g0 = 8123312244520119413231609191866976836916616973013918670932199631084038015924368317077919454611785179950870055560079987034735836668109705445946887481003729
g1 = 20508867471664499348708768798854433383217801696267611753941328714877299161068885700412171
g2 = 22802458968832151777449744120185122420871929971817937643641589637402679927558503881707868
g3 = 40224499597522456323122179021760594618350780974297095023316834212332206526399536884102863

# Compute right-hand side
b0 = P - g0
b1 = g1
b2 = g2

# Compute determinant
s = h0**2 + h1**2 + h2**2 + h3**2
det_A = h3 * s

# Compute numerators using adj(A) * b
num_p = (h3 * h1 - h2 * h0) * b0 + (h2 * h1 + h3 * h0) * b1 + (h2**2 + h3**2) * b2
num_q = (h0 * h1 + h2 * h3) * b0 - (h1**2 + h3**2) * b1 + (h3 * h0 - h1 * h2) * b2
num_r = (h0**2 + h3**2) * b0 + (h2 * h3 - h1 * h0) * b1 - (h1 * h3 + h2 * h0) * b2

# Compute p, q, r
p = num_p // det_A
q = num_q // det_A
r = num_r // det_A

# Verify with fourth equation
assert -h2 * p + h1 * q + h0 * r == g3, "Solution inconsistent"
print(f"p = {p}")
print(f"q = {q}")
print(f"r = {r}")

# Verify n
n_computed = p * q * r
n_given = 408713495380933615345467409596399184629824932933932227692519320046890365817329617301604051766392980053993030281090124694858194866782889226223493799859404283664530068697313752856923001112586828837146686963124061670340088332769524367
assert n_computed == n_given, "n does not match"

然后就是四元复数的phi求解,这个比较难崩,之前没见过。

模n同余类的阶就是phi,这里的phi(p)=(p-1) p (p+1)

由于gcd(e,phi(p))=3,所以用phi//3作为模数算d_p

exp

from Crypto.Util.number import inverse, long_to_bytes
from Crypto.Util.number import GCD as gcd

n = 408713495380933615345467409596399184629824932933932227692519320046890365817329617301604051766392980053993030281090124694858194866782889226223493799859404283664530068697313752856923001112586828837146686963124061670340088332769524367
p = 63173373914948586508761871207488662566773264479285518327131522282352053209317
q = 80952808249768431401135151583780334402187954631449426293287427758105419709409
r = 79919542113632340528743451299804406313559069843835295267846968468567030982339
e = 65547
c = [
    212391106108596254648968182832931369624606731443797421732310126161911908195602305474921714075911012622738456373731638115041135121458776339519085497285769160263024788009541257401354037620169924991531279387552806754098200127027800103,
    24398526281840329222660628769015610312084745844610670698920371305353888694519135578269023873988641161449924124665731242993290561874625654977013162008430854786349580090169988458393820787665342793716311005178101342140536536153873825,
    45426319565874516841189981758358042952736832934179778483602503215353130229731883231784466068253520728052302138781204883495827539943655851877172681021818282251414044916889460602783324944030929987991059211909160860125047647337380125,
    96704582331728201332157222706704482771142627223521415975953255983058954606417974983056516338287792260492498273014507582247155218239742778886055575426154960475637748339582574453542182586573424942835640846567809581805953259331957385
]

c = ComComplex(c)

def crt(shares):
    """中国剩余定理合并"""
    res = []
    for i in range(4):
        a = [s.value[i] for s, _ in shares]
        m = [mod for _, mod in shares]
        res.append(int(crt_r(a, m)))
    return ComComplex(res)

def crt_r(a, m):
    """中国剩余定理单个分量"""
    M = 1
    for mi in m:
        M *= mi
    res = 0
    for ai, mi in zip(a, m):
        Mi = M // mi
        inv = inverse(Mi, mi)
        res = (res + ai * Mi * inv) % M
    return res

# 计算每个素数的d_s
def compute_d(s):
    phi_s = s * (s - 1) * (s**2 - 1)
    g_s = gcd(e, phi_s)
    phi_prime = phi_s // g_s
    d = inverse(e, phi_prime)
    return d

d_p = compute_d(p)
d_q = compute_d(q)
d_r = compute_d(r)

# 计算密文模每个素数后的值
c_p = c % p
c_q = c % q
c_r = c % r

# 解密每个素数对应的密文
m_p = pow(c_p, d_p, p)
m_q = pow(c_q, d_q, q)
m_r = pow(c_r, d_r, r)

# 应用中国剩余定理合并结果
m = crt([(m_p, p), (m_q, q), (m_r, r)])

# 转换四元数为字节
flag_parts = []
for component in m.value:
    flag_parts.append(long_to_bytes(component))

# 拼接各部分得到flag
flag = b''.join(flag_parts)
print(flag.decode())

看了下官方WP这是非预期解,在模p群中的这个m的阶没有3的因⼦,直接⽤phi=(p-1)p(p+1)//3

正解的话是gcd(e,phi)=9 先用e//9求逆得到m^9,再开根

reed

task.py

import string
import random
from secret import flag

assert flag.startswith('XYCTF{') and flag.endswith('}')
flag = flag.rstrip('}').lstrip('XYCTF{')

table = string.ascii_letters + string.digits
assert all(i in table for i in flag)
r = random.Random()

class PRNG:
    def __init__(self, seed):
        self.a = 1145140
        self.b = 19198100
        random.seed(seed)

    def next(self):
        x = random.randint(self.a, self.b)
        random.seed(x ** 2 + 1)
        return x

    def round(self, k):
        for _ in range(k):
            x = self.next()
        return x

def encrypt(msg, a, b):
    c = [(a * table.index(m) + b) % 19198111 for m in msg]
    return c

seed = int(input('give me seed: '))
prng = PRNG(seed)
a = prng.round(r.randrange(2**16))
b = prng.round(r.randrange(2**16))
enc = encrypt(flag, a, b)
print(enc)

这题应该也是非预期,爆破求解

exp

import string
from pwn import *
from gmpy2 import invert, gcd

# 字符表
table = string.ascii_letters + string.digits
r = remote("gz.imxbt.cn", 20141)
m = 19198111 # 注意检查此处模数是否应该与解密函数一致
res = []

def decrypt(enc):
    n = len(enc)
    pos = [(i, i+1) for i in range(n-1)]  # 相邻位置对
    for p1, p2 in pos:
        if p1 >= len(enc) or p2 >= len(enc):
            continue  # 避免越
        c0, c1 = enc[p1], enc[p2]
        for x0 in range(len(table)):
            for x1 in range(len(table)):
                if x0 == x1:
                    continue
                delta_x = x0 - x1
                try:
                    delta_x_inv = invert(delta_x, m)
                except ZeroDivisionError:
                    continue  # 不可逆时跳过

                a = ((c0 - c1) * delta_x_inv) % m
                # 检查a是否可逆
                if gcd(a, m) != 1:
                    continue

                b = (c0 - a * x0) % m
                # 计算a的逆
                try:
                    a_inv = invert(a, m)
                except ZeroDivisionError:
                    continue  # 确保不会出错

                plain = []
                valid = True
                for c in enc:
                    x = (c - b) * a_inv % m
                    if not (0 <= x < len(table)):
                        valid = False
                        break
                    plain.append(table[x])
                if valid:
                    res.append(''.join(plain))
    return res

r.recvuntil(b'give me seed: ')
r.sendline(b'0')
enc = eval(r.recvline().strip())
print("Received ciphertext:", enc)
flag= decrypt(enc)
print(set(flag))

结合一下AI,写的脚本

Division

task.py

# -*- encoding: utf-8 -*-
'''
@File    :   server.py
@Time    :   2025/03/20 12:25:03
@Author  :   LamentXU 
'''
import random 
print('----Welcome to my division calc----')
print('''
menu:
      [1]  Division calc
      [2]  Get flag
''')
while True:
    choose = input(': >>> ')
    if choose == '1':
        try:
            denominator = int(input('input the denominator: >>> '))
        except:
            print('INPUT NUMBERS')
            continue
        nominator = random.getrandbits(32)
        if denominator == '0':
            print('NO YOU DONT')
            continue
        else:
            print(f'{nominator}//{denominator} = {nominator//denominator}')
    elif choose == '2':
        try:
            ans = input('input the answer: >>> ')
            rand1 = random.getrandbits(11000)
            rand2 = random.getrandbits(10000)
            correct_ans = rand1 // rand2
            if correct_ans == int(ans):
                print('WOW')
                with open('flag', 'r') as f:
                    print(f'Here is your flag: {f.read()}')
            else:
                print(f'NOPE, the correct answer is {correct_ans}')
        except:
            print('INPUT NUMBERS')
    else:
        print('Invalid choice')

简单顶真一下,就是MT19937的基础款式,你需要向它输入624次,它每次就会生成个随机值与你输入的相除,于是我每次就输入1,这样就获得624个值了,如果有了624个值就可以预测后面所有的随机数

然后脚本直接OpenAI一把梭就行。

exp

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@File    :   solve_remote_socket.py
@Time    :   2025/04/09
@Author  :   LamentXU
@Desc    :   使用 socket 实现 telnet 交互,利用 MT19937 状态恢复进行漏洞利用
"""

import socket
import time
import re
import random

# ----------------------------
# Telnet-like 交互类(使用 socket 实现)
# ----------------------------
class TelnetLike:
    def __init__(self, host: str, port: int, timeout: float = 5.0):
        self.sock = socket.create_connection((host, port))
        self.sock.settimeout(timeout)

    def read_until(self, delimiter: bytes, timeout: float = 5.0) -> bytes:
        data = b''
        end_time = time.time() + timeout
        while not data.endswith(delimiter) and time.time() < end_time:
            try:
                chunk = self.sock.recv(4096)
                if not chunk:
                    break
                data += chunk
            except socket.timeout:
                break
        return data

    def send(self, msg: str):
        self.sock.sendall((msg + "\n").encode('utf-8'))

    def read_line(self) -> str:
        data = b""
        while not data.endswith(b"\n"):
            try:
                chunk = self.sock.recv(1)
                if not chunk:
                    break
                data += chunk
            except socket.timeout:
                break
        return data.decode('utf-8', errors='ignore')

    def read_all(self) -> str:
        try:
            return self.sock.recv(8192).decode('utf-8', errors='ignore')
        except socket.timeout:
            return ''

    def close(self):
        self.sock.close()

# ----------------------------
# MT19937 逆 tempering 实现
# ----------------------------
def unshift_right(y, shift):
    result = 0
    # 从高位到低位逐步恢复
    for i in range(32):
        # 每次处理最高未计算位
        part = y ^ (result >> shift)
        mask = 1 << (31 - i)
        result |= part & mask
    return result

def unshift_left(y, shift, mask):
    result = 0
    # 从低位到高位逐步恢复
    for i in range(32):
        part = y ^ ((result << shift) & mask)
        bit = (part >> i) & 1
        result |= bit << i
    return result

def untemper(y):
    # 恢复的顺序需要与 tempering 操作的逆序一致
    y = unshift_right(y, 18)           # 逆转 y ^= (y >> 18)
    y = unshift_left(y, 15, 0xEFC60000)  # 逆转 y ^= (y << 15) & 0xEFC60000
    y = unshift_left(y, 7, 0x9D2C5680)   # 逆转 y ^= (y << 7) & 0x9D2C5680
    y = unshift_right(y, 11)           # 逆转 y ^= (y >> 11)
    return y

# ----------------------------
# 主要利用流程
# ----------------------------
def main():
    # 修改为实际靶机 IP 和端口
    HOST = "gz.imxbt.cn"
    PORT = 20246

    print(f"[+] 连接到 {HOST}:{PORT}")
    tn = TelnetLike(HOST, PORT)

    # 读取欢迎信息及菜单
    banner = tn.read_until(b'menu:')
    print("[+] 收到信息:")
    print(banner.decode('utf-8', errors='ignore'))

    # ----------------------------
    # 1. 利用选项1采集624个随机输出
    # ----------------------------
    NUM_COLLECT = 624
    outputs = []
    print("[+] 开始采集 624 个随机数……")
    for i in range(NUM_COLLECT):
        # 发送选项 1
        tn.send("1")
        # 等待提示输入除数
        tn.read_until(b'input the denominator: >>> ')
        # 输入数字 1
        tn.send("1")
        # 读取返回行,格式示例: "123456789//1 = 123456789"
        line = tn.read_line().strip()
        # 尝试提取“//1 =”前面的数字作为随机数
        m = re.search(r'(\d+)\s*//\s*1\s*=', line)
        if m:
            num_str = m.group(1)
            try:
                num = int(num_str)
                outputs.append(num)
                print(f"[{i+1:03d}/{NUM_COLLECT}] 得到: {num}")
            except ValueError:
                print(f"[-] 解析错误: {line}")
                tn.close()
                return
        else:
            print(f"[-] 未能解析输出行: {line}")
            tn.close()
            return

    if len(outputs) != NUM_COLLECT:
        print("[-] 采集的随机数数量不足!")
        tn.close()
        return

    # ----------------------------
    # 2. 通过逆 tempering恢复 MT19937 内部状态
    # ----------------------------
    print("[+] 开始恢复内部状态……")
    state = [untemper(y) for y in outputs]
    # Python 内置随机数模块的状态格式为: (version, (MT[0], ..., MT[623], index), gaussian_state)
    # 其中 index 设置为 624 表示下次生成随机数时将进行 twist
    py_state = (3, tuple(state + [624]), None)
    random.setstate(py_state)
    print("[+] 内部状态恢复完毕,状态已同步。")

    # ----------------------------
    # 3. 预测选项2 中的随机输出,并计算正确答案
    # ----------------------------
    # 题目中选项2先调用 getrandbits(11000),再调用 getrandbits(10000)
    print("[+] 预测选项2 的输出……")
    predicted_rand1 = random.getrandbits(11000)
    predicted_rand2 = random.getrandbits(10000)
    correct_ans = predicted_rand1 // predicted_rand2
    print(f"[+] 预测答案为: {correct_ans}")

    # ----------------------------
    # 4. 提交选项2并获得 flag
    # ----------------------------
    # 读取可能的菜单提示(具体提示请根据服务实际情况调整)
    tn.read_until(b'menu:')
    tn.send("2")
    tn.read_until(b'input the answer: >>> ')
    tn.send(str(correct_ans))
    print("[+] 答案提交完毕,等待服务器响应 flag……")

    # 给服务一些响应时间
    time.sleep(1)
    response = tn.read_all()
    print("----- 服务器响应 -----")
    print(response)
    print("-----------------------")

    tn.close()

if __name__ == '__main__':
    main()

输出

[624/624] 得到: 2753415149
[+] 开始恢复内部状态……
[+] 内部状态恢复完毕,状态已同步。
[+] 预测选项2 的输出……
[+] 预测答案为: 2305887170558549180889868174723054828896373652953012476213566588974740311492554548136057564576668866652354201737517516721225135993798134252009965058404335449408858675937873421951145921114243695521952906175741615778154051588709475685883857588124422167642986077747009940931957286497406583096868103486903
[+] 答案提交完毕,等待服务器响应 flag……
----- 服务器响应 -----
WOW
Here is your flag: flag{I_do_not_want_any_CTFER_get_0_solve_in_Crypto_bad_bad_adwa}

这AI写的太啰嗦了,还是看我的

from pwn import *

# context.log_level = 'debug'
from tqdm import trange
import sys
sys.path.append('./MT19937-Symbolic-Execution-and-Solver-master/source')

from MT19937 import MT19937

# r = process(["python3", "server.py"])
r = remote("47.94.172.18", 36871)

data = []

for _ in trange(624):
    r.recvuntil(b'>>> ')
    r.sendline(b'1')
    r.recvuntil(b'>>> ')
    r.sendline(b'1')
    r.recvuntil(b'= ')
    data.append(int(r.recvline().strip()))

print(len(data))

rng = MT19937(state_from_data = (data, 32))

# for _ in range(624):
#     rng()
recover = [rng() for _ in range(624)]
assert recover == data, f"Recover failed: {recover} != {data}"

def getrandbits(n):
    num = 0
    for i in range(n//32):
        num = (rng() << (32 * i)) | num
    num = rng() >> (32 - (n % 32)) << n//32*32 | num
    return num

rand1 = getrandbits(11000)
rand2 = getrandbits(10000)

correct_ans = rand1 // rand2

r.recvuntil(b'>>> ')
r.sendline(b'2')
r.recvuntil(b'>>> ')
r.sendline(str(correct_ans).encode())
r.interactive()

Complex_signin

task.py

from Crypto.Util.number import *
from Crypto.Cipher import ChaCha20
import hashlib
from secret import flag


class Complex:
    def __init__(self, re, im):
        self.re = re
        self.im = im

    def __mul__(self, c):
        re_ = self.re * c.re - self.im * c.im
        im_ = self.re * c.im + self.im * c.re
        return Complex(re_, im_)

    def __eq__(self, c):
        return self.re == c.re and self.im == c.im

    def __rshift__(self, m):
        return Complex(self.re >> m, self.im >> m)

    def __lshift__(self, m):
        return Complex(self.re << m, self.im << m)

    def __str__(self):
        if self.im == 0:
            return str(self.re)
        elif self.re == 0:
            if abs(self.im) == 1:
                return f"{'-' if self.im < 0 else ''}i"
            else:
                return f"{self.im}i"
        else:
            return f"{self.re} {'+' if self.im > 0 else '-'} {abs(self.im)}i"

    def tolist(self):
        return [self.re, self.im]


def complex_pow(c, exp, n):
    result = Complex(1, 0)
    while exp > 0:
        if exp & 1:
            result = result * c
            result.re = result.re % n
            result.im = result.im % n
        c = c * c
        c.re = c.re % n
        c.im = c.im % n
        exp >>= 1
    return result

bits = 128
p = getPrime(1024)
q = getPrime(1024)
n = p * q
m = Complex(getRandomRange(1, n), getRandomRange(1, n))
e = 3
c = complex_pow(m, e, n)
print(f"n = {n}")
print(f"mh = {(m >> bits << bits).tolist()}")
print(f"C = {c.tolist()}")
print(f"enc = {ChaCha20.new(key=hashlib.sha256(str(m.re + m.im).encode()).digest(), nonce=b'Pr3d1ctmyxjj').encrypt(flag)}")

'''
n = 24240993137357567658677097076762157882987659874601064738608971893024559525024581362454897599976003248892339463673241756118600994494150721789525924054960470762499808771760690211841936903839232109208099640507210141111314563007924046946402216384360405445595854947145800754365717704762310092558089455516189533635318084532202438477871458797287721022389909953190113597425964395222426700352859740293834121123138183367554858896124509695602915312917886769066254219381427385100688110915129283949340133524365403188753735534290512113201932620106585043122707355381551006014647469884010069878477179147719913280272028376706421104753
mh = [3960604425233637243960750976884707892473356737965752732899783806146911898367312949419828751012380013933993271701949681295313483782313836179989146607655230162315784541236731368582965456428944524621026385297377746108440938677401125816586119588080150103855075450874206012903009942468340296995700270449643148025957527925452034647677446705198250167222150181312718642480834399766134519333316989347221448685711220842032010517045985044813674426104295710015607450682205211098779229647334749706043180512861889295899050427257721209370423421046811102682648967375219936664246584194224745761842962418864084904820764122207293014016, 15053801146135239412812153100772352976861411085516247673065559201085791622602365389885455357620354025972053252939439247746724492130435830816513505615952791448705492885525709421224584364037704802923497222819113629874137050874966691886390837364018702981146413066712287361010611405028353728676772998972695270707666289161746024725705731676511793934556785324668045957177856807914741189938780850108643929261692799397326838812262009873072175627051209104209229233754715491428364039564130435227582042666464866336424773552304555244949976525797616679252470574006820212465924134763386213550360175810288209936288398862565142167552]
C = [5300743174999795329371527870190100703154639960450575575101738225528814331152637733729613419201898994386548816504858409726318742419169717222702404409496156167283354163362729304279553214510160589336672463972767842604886866159600567533436626931810981418193227593758688610512556391129176234307448758534506432755113432411099690991453452199653214054901093242337700880661006486138424743085527911347931571730473582051987520447237586885119205422668971876488684708196255266536680083835972668749902212285032756286424244284136941767752754078598830317271949981378674176685159516777247305970365843616105513456452993199192823148760, 21112179095014976702043514329117175747825140730885731533311755299178008997398851800028751416090265195760178867626233456642594578588007570838933135396672730765007160135908314028300141127837769297682479678972455077606519053977383739500664851033908924293990399261838079993207621314584108891814038236135637105408310569002463379136544773406496600396931819980400197333039720344346032547489037834427091233045574086625061748398991041014394602237400713218611015436866842699640680804906008370869021545517947588322083793581852529192500912579560094015867120212711242523672548392160514345774299568940390940653232489808850407256752]
enc = b'\x9c\xc4n\x8dF\xd9\x9e\xf4\x05\x82!\xde\xfe\x012$\xd0\x8c\xaf\xfb\rEb(\x04)\xa1\xa6\xbaI2J\xd2\xb2\x898\x11\xe6x\xa9\x19\x00pn\xf6rs- \xd2\xd1\xbe\xc7\xf51.\xd4\xd2 \xe7\xc6\xca\xe5\x19\xbe'
'''

AI梭一下也能出思路就是二元copper。把泄露剩余部分解出来

from Crypto.Util.number import*
import itertools 
from Crypto.Cipher import ChaCha20
import hashlib
n = 24240993137357567658677097076762157882987659874601064738608971893024559525024581362454897599976003248892339463673241756118600994494150721789525924054960470762499808771760690211841936903839232109208099640507210141111314563007924046946402216384360405445595854947145800754365717704762310092558089455516189533635318084532202438477871458797287721022389909953190113597425964395222426700352859740293834121123138183367554858896124509695602915312917886769066254219381427385100688110915129283949340133524365403188753735534290512113201932620106585043122707355381551006014647469884010069878477179147719913280272028376706421104753
mh = [3960604425233637243960750976884707892473356737965752732899783806146911898367312949419828751012380013933993271701949681295313483782313836179989146607655230162315784541236731368582965456428944524621026385297377746108440938677401125816586119588080150103855075450874206012903009942468340296995700270449643148025957527925452034647677446705198250167222150181312718642480834399766134519333316989347221448685711220842032010517045985044813674426104295710015607450682205211098779229647334749706043180512861889295899050427257721209370423421046811102682648967375219936664246584194224745761842962418864084904820764122207293014016, 15053801146135239412812153100772352976861411085516247673065559201085791622602365389885455357620354025972053252939439247746724492130435830816513505615952791448705492885525709421224584364037704802923497222819113629874137050874966691886390837364018702981146413066712287361010611405028353728676772998972695270707666289161746024725705731676511793934556785324668045957177856807914741189938780850108643929261692799397326838812262009873072175627051209104209229233754715491428364039564130435227582042666464866336424773552304555244949976525797616679252470574006820212465924134763386213550360175810288209936288398862565142167552]
C = [5300743174999795329371527870190100703154639960450575575101738225528814331152637733729613419201898994386548816504858409726318742419169717222702404409496156167283354163362729304279553214510160589336672463972767842604886866159600567533436626931810981418193227593758688610512556391129176234307448758534506432755113432411099690991453452199653214054901093242337700880661006486138424743085527911347931571730473582051987520447237586885119205422668971876488684708196255266536680083835972668749902212285032756286424244284136941767752754078598830317271949981378674176685159516777247305970365843616105513456452993199192823148760, 21112179095014976702043514329117175747825140730885731533311755299178008997398851800028751416090265195760178867626233456642594578588007570838933135396672730765007160135908314028300141127837769297682479678972455077606519053977383739500664851033908924293990399261838079993207621314584108891814038236135637105408310569002463379136544773406496600396931819980400197333039720344346032547489037834427091233045574086625061748398991041014394602237400713218611015436866842699640680804906008370869021545517947588322083793581852529192500912579560094015867120212711242523672548392160514345774299568940390940653232489808850407256752]
enc = b'\x9c\xc4n\x8dF\xd9\x9e\xf4\x05\x82!\xde\xfe\x012$\xd0\x8c\xaf\xfb\rEb(\x04)\xa1\xa6\xbaI2J\xd2\xb2\x898\x11\xe6x\xa9\x19\x00pn\xf6rs- \xd2\xd1\xbe\xc7\xf51.\xd4\xd2 \xe7\xc6\xca\xe5\x19\xbe'


def small_roots(f, bounds, m=1, d=None):
    if not d:
        d = f.degree()

    R = f.base_ring()
    N = R.cardinality()

    f /= f.coefficients().pop(0)
    f = f.change_ring(ZZ)

    G = Sequence([], f.parent())
    for i in range(m + 1):
        base = N ^ (m - i) * f ^ i
        for shifts in itertools.product(range(d), repeat=f.nvariables()):
            g = base * prod(map(power, f.variables(), shifts))
            G.append(g)

    B, monomials = G.coefficient_matrix()
    monomials = vector(monomials)

    factors = [monomial(*bounds) for monomial in monomials]
    for i, factor in enumerate(factors):
        B.rescale_col(i, factor)

    B = B.dense_matrix().LLL()

    B = B.change_ring(QQ)
    for i, factor in enumerate(factors):
        B.rescale_col(i, 1 / factor)

    H = Sequence([], f.parent().change_ring(QQ))
    for h in filter(None, B * monomials):
        H.append(h)
        I = H.ideal()
        if I.dimension() == -1:
            H.pop()
        elif I.dimension() == 0:
            roots = []
            for root in I.variety(ring=ZZ):
                root = tuple(R(root[var]) for var in f.variables())
                roots.append(root)
            return roots

    return []
a=mh[0]
b=mh[1]
PR.<x,y> = PolynomialRing(Zmod(n))
f = (a+x)^3-3*(a+x)*(b+y)^2-C[0]
roots = small_roots(f, (2^128, 2^128))
#print(roots)
#[(200140573956551184845123803212115015633, 62109784561410747979732334460991877433)]
A=roots[0][0]+a
B=roots[0][1]+b
s = str(A + B).encode()  # 注意一定要使用字符串形式
key = hashlib.sha256(s).digest()

# 固定的 nonce 值(题目中给定)
nonce = b'Pr3d1ctmyxjj'

# 填入你的密文,格式可以是 bytes 类型或者十六进制字符串转 bytes
# 注意:示例中密文数据就是任务里给出的 enc 数据
enc = b'\x9c\xc4n\x8dF\xd9\x9e\xf4\x05\x82!\xde\xfe\x012$\xd0\x8c\xaf\xfb\rEb(\x04)\xa1\xa6\xbaI2J\xd2\xb2\x898\x11\xe6x\xa9\x19\x00pn\xf6rs- \xd2\xd1\xbe\xc7\xf51.\xd4\xd2 \xe7\xc6\xca\xe5\x19\xbe'

# 使用 ChaCha20 构造解密器
cipher = ChaCha20.new(key=key, nonce=nonce)
plaintext = cipher.decrypt(enc)

print("Decrypted flag:", plaintext.decode())

choice

choice.py

from Crypto.Util.number import bytes_to_long
from random import Random
from secret import flag

assert flag.startswith(b'XYCTF{') and flag.endswith(b'}')
flag = flag[6:-1]

msg = bytes_to_long(flag)
rand = Random()
test = bytes([i for i in range(255, -1, -1)])
open('output.py', 'w').write(f'enc = {msg ^ rand.getrandbits(msg.bit_length())}\nr = {[rand.choice(test) for _ in range(2496)]}')

后面就是ouput和random的源码,太长了就不放了。

这题其实和WKCTF的easy_random几乎一样的MT19937实战 | W’Blog

注意看random.py里面的这几个函数,首先我们肯定是搜索choice,然后发现它使用一个_randbelow 把len(test)传去

   def choice(self, seq):
        """Choose a random element from a non-empty sequence."""

        # As an accommodation for NumPy, we don't use "if not seq"
        # because bool(numpy.array()) raises a ValueError.
        if not len(seq):
            raise IndexError('Cannot choose from an empty sequence')
        return seq[self._randbelow(len(seq))]

    _randbelow = _randbelow_with_getrandbits

   def _randbelow_with_getrandbits(self, n):
        "Return a random int in the range [0,n).  Defined for n > 0."

        getrandbits = self.getrandbits
        k = n.bit_length() - 1
        r = getrandbits(k)  # 0 <= r < 2**k
        while r >= n:
            r = getrandbits(k)
        return r

其实就是把len(test).bit_length()-1,我们的test长度是256,比特位是9,减一就是8,于是又是经典的

getrandbits(8)

每一个 8bit 是对应 32bit 的前八位,如何得来?也就是先生成 一个 32bit 的,再右移 24bit,得到 8 bit,而且我们在生成随机数的时候,后生成的数在高位,先生成的在低位

现在这情况也就是给了高8位的MSB问题

import sys
# Use raw string for the path to avoid Unicode escape issues
sys.path.append(r'D:\10DASCTF\xyctf2025\choice\MT19937-Symbolic-Execution-and-Solver-master\source')
from Crypto.Util.number import *
from MT19937 import MT19937
from output import r, enc

r = [255-i for i in r]#注意这里要逆序,我们生成test时候是从255到0的,要逆序才能把生成的随机数找到对应的序号从而找到对应的值
rng = MT19937(state_from_data = (r, 8))

def getrandbits(n):
    num = 0
    for i in range(n//32):
        num = (rng() << (32 * i)) | num
    num = rng() >> (32 - (n % 32)) << n//32*32 | num
    return num

rng.reverse_states(enc.bit_length()//32+1)

randnum = getrandbits(175)  # 密文是 172 位,不是8的倍数,于是再去8位是176位,但是由于第一位是0,所以只需要175位
flag = enc ^ randnum
flag = long_to_bytes(flag)
print(flag)
#b'___0h_51mple_r@nd0m___'

勒索病毒

超实用简单的exe文件反编译成Python源码(可网页在线实现)!_反编译exe文件成源码-CSDN博客

这是一个python打包成exe的文件,需要解包一下

task.sage

# @author: Crypto0

import re
import base64
import os
import sys
from gmssl import sm4
from Crypto.Util.Padding import pad
import binascii
from random import shuffle, randrange

N = 49 
p = 3
q = 128  
d = 3
assert q > (6 * d + 1) * p
R.<x> = ZZ[]
def generate_T(d1, d2):
    assert N >= d1 + d2
    s = [1] * d1 + [-1] * d2 + [0] * (N - d1 - d2)
    shuffle(s)
    return R(s)

def invert_mod_prime(f, p):
    Rp = R.change_ring(Integers(p)).quotient(x^N - 1)
    return R(lift(1 / Rp(f)))

def convolution(f, g):
    return (f * g) % (x^N - 1)

def lift_mod(f, q):
    return R([((f[i] + q // 2) % q) - q // 2 for i in range(N)])

def poly_mod(f, q):
    return R([f[i] % q for i in range(N)])

def invert_mod_pow2(f, q):
    assert q.is_power_of(2)
    g = invert_mod_prime(f, 2)
    while True:
        r = lift_mod(convolution(g, f), q)
        if r == 1:
            return g
        g = lift_mod(convolution(g, 2 - r), q)

def generate_message():
    return R([randrange(p) - 1 for _ in range(N)])

def generate_key():
    while True:
        try:
            f = generate_T(d + 1, d)
            g = generate_T(d, d)
            Fp = poly_mod(invert_mod_prime(f, p), p)
            Fq = poly_mod(invert_mod_pow2(f, q), q)
            break
        except:
            continue
    h = poly_mod(convolution(Fq, g), q)
    return h, (f, g)

def encrypt_message(m, h):
    e = lift_mod(p * convolution(h, generate_T(d, d)) + m, q)
    return e

def save_ntru_keys():
    h, secret = generate_key()
    with open("pub_key.txt", "w") as f:
        f.write(str(h))
    m = generate_message()
    with open("priv_key.txt", "w") as f:
        f.write(str(m))
    e = encrypt_message(m, h)
    with open("enc.txt", "w") as f:
        f.write(str(e))

def terms(poly_str):
    terms = []
    pattern = r'([+-]?\s*x\^?\d*|[-+]?\s*\d+)'
    matches = re.finditer(pattern, poly_str.replace(' ', ''))

    for match in matches:
        term = match.group()
        if term == '+x' or term == 'x':
            terms.append(1)
        elif term == '-x':
            terms.append(-1)
        elif 'x^' in term:
            coeff_part = term.split('x^')[0]
            exponent = int(term.split('x^')[1])
            if not coeff_part or coeff_part == '+':
                coeff = 1
            elif coeff_part == '-':
                coeff = -1
            else:
                coeff = int(coeff_part)
            terms.append(coeff * exponent)
        elif 'x' in term:
            coeff_part = term.split('x')[0]
            if not coeff_part or coeff_part == '+':
                terms.append(1)
            elif coeff_part == '-':
                terms.append(-1)
            else:
                terms.append(int(coeff_part))
        else:
            if term == '+1' or term == '1':
                terms.append(0)
                terms.append(-0)
    return terms

def gen_key(poly_terms):
    binary = [0] * 128
    for term in poly_terms:
        exponent = abs(term)
        if term > 0 and exponent <= 127:  
            binary[127 - exponent] = 1
    binary_str = ''.join(map(str, binary))
    hex_key = hex(int(binary_str, 2))[2:].upper().zfill(32)
    return hex_key

def read_polynomial_from_file(filename):
    with open(filename, 'r') as file:
        return file.read().strip()


def sm4_encrypt(key, plaintext):
    assert len(key) == 16, "SM4 key must be 16 bytes"
    cipher = sm4.CryptSM4()
    cipher.set_key(key, sm4.SM4_ENCRYPT)
    padded_plaintext = pad(plaintext, 16)
    return cipher.crypt_ecb(padded_plaintext)

def sm4_encrypt_file(input_path, output_path, key):
    with open(input_path, 'rb') as f:
        plaintext = f.read()

    ciphertext = sm4_encrypt(key, plaintext)

    with open(output_path, 'wb') as f:
        f.write(ciphertext)

def resource_path(relative_path):
    if getattr(sys, 'frozen', False):
        base_path = sys._MEIPASS
    else:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

def encrypt_directory(directory, sm4_key, extensions=[".txt"]):
    if not os.path.exists(directory):
        print(f"Directory does not exist: {directory}")
        return

    for root, _, files in os.walk(directory):
        for file in files:
            if any(file.endswith(ext) for ext in extensions):
                input_path = os.path.join(root, file)
                output_path = input_path + ".enc"

                try:
                    sm4_encrypt_file(input_path, output_path, sm4_key)
                    os.remove(input_path)
                    print(f"Encrypted: {input_path} -> {output_path}")
                except Exception as e:
                    print(f"Error encrypting {input_path}: {str(e)}")

def main():
    try:
        save_ntru_keys()
        poly_str = read_polynomial_from_file("priv_key.txt")
        poly_terms = terms(poly_str)
        sm4_key = binascii.unhexlify(poly_terms)
        user_name = os.getlogin()
        target_dir = os.path.join("C:\Users", user_name, "Desktop", "test_files")

        if not os.path.exists(target_dir):
            os.makedirs(target_dir, exist_ok=True)
            print(f"Created directory: {target_dir}")
            return

        txt_files = [f for f in os.listdir(target_dir) 
                    if f.endswith('.txt') and os.path.isfile(os.path.join(target_dir, f))]

        if not txt_files:
            print("No .txt files found in directory")
            return

        for txt_file in txt_files:
            file_path = os.path.join(target_dir, txt_file)
            try:
                with open(file_path, 'rb') as f:
                    test_data = f.read()

                ciphertext = sm4_encrypt(sm4_key, test_data)
                encrypted_path = file_path + '.enc'

                with open(encrypted_path, 'wb') as f:
                    f.write(ciphertext)
            except Exception as e:
                print(f"Error processing {txt_file}: {str(e)}")

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

if __name__ == "__main__":
    main()