文章总结: 本文解析HITCON2017CTF题目VeryLuacky,通过逆向分析结合游戏理论,详细讲解了破解三种伪随机数生成器AI的策略。文章涵盖Slime的简单递增破解、Alpaca的溢出预测及Nozomi的LCG状态恢复攻击,深入剖析了PRNG的安全缺陷与密码学攻击技术。 综合评分: 94 文章分类: CTF,逆向分析,漏洞分析,二进制安全,实战经验
HITCON 2017 CTF – Very Luacky 完全解析
原创
破镜安全
破镜安全
2025年12月29日 08:03 四川
HITCON 2017 CTF – Very Luacky 完全解析
前言
这是一道来自HITCON 2017 CTF竞赛的密码学题目,名为”Very Luacky”,分值342分,全球仅有9个队伍成功解出。这道题目巧妙地将游戏理论、伪随机数生成器分析和密码学结合在一起,是学习密码学攻击技术的绝佳案例。
本文将从零开始,详细讲解如何一步步分析并解决这道题目,不需要任何前置知识,只需要基本的编程和数学基础即可理解。
第一步:题目初探
1.1 我们拿到了什么
题目给了我们一个可执行文件:088a46725ff348b0b069728acf244243.elf
首先使用file命令查看文件类型:
$ file 088a46725ff348b0b069728acf244243.elf
ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux), dynamically linked
这是一个64位的Linux可执行程序。
1.2 寻找线索
作为逆向分析的第一步,我们使用strings命令查找程序中的可读文本:
$ strings 088a46725ff348b0b069728acf244243.elf | head -50
在大量输出中,我们发现了几个关键信息:
Welcome to luaky arena! Show us your AI:
-- EOF
Got code with size = %zd
Fighting with Slime...
Fighting with Alpaca...
Fighting with Nozomi...
win = %d, lose = %d, tie = %d [%.2fs]
Too weak, even Slime can beat you :(
Nice! Let's try something serious :)
从这些字符串我们可以推断:
- 程序需要我们提供某种”AI”代码
- 代码需要以
-- EOF结束(这是Lua语言的注释格式) - 有三种对手:Slime、Alpaca、Nozomi
- 会统计胜负平的次数
- 需要达到一定的胜率才能通过
1.3 识别编程语言
继续使用ldd命令查看程序依赖的库:
$ ldd 088a46725ff348b0b069728acf244243.elf
liblua5.3.so.0 => /lib/x86_64-linux-gnu/liblua5.3.so.0
发现程序链接了Lua 5.3库,这确认了我们需要编写Lua代码。
第二步:程序接口探索
2.1 第一次尝试
Lua是一种轻量级脚本语言,常用于游戏和嵌入式系统。既然程序需要我们提供AI,我们先尝试最简单的代码:
function ai(your_hand, their_hand)
return 0
end
-- EOF
运行测试:
$ echo "function ai(your_hand, their_hand)
return 0
end
-- EOF" | ./088a46725ff348b0b069728acf244243.elf
Welcome to luaky arena! Show us your AI:
Got code with size = 57
Fighting with Slime...Runtime Error
遇到了Runtime Error,说明函数签名或实现有问题。
2.2 查找正确的函数名
通过使用objdump和nm等工具分析程序的符号表,我们发现程序中有lua_getglobal调用,并且查找的全局变量名可能是”ai”或”play”。
经过多次尝试,我们发现正确的函数名应该是play:
function play(cpulasthand)
return 0
end
-- EOF
再次测试:
$ echo "function play(cpulasthand)
return 0
end
-- EOF" | ./088a46725ff348b0b069728acf244243.elf
Welcome to luaky arena! Show us your AI:
Got code with size = 51
Fighting with Slime...win = 33333, lose = 33334, tie = 33333 [0.00s]
Too weak, even Slime can beat you :(
成功运行了!虽然失败了,但这说明我们的接口是正对的。
2.3 理解游戏规则
从输出win = 33333, lose = 33334, tie = 33333可以看出:
- 总共进行了100,000局游戏(33333 + 33334 + 33333 = 100000)
- 我们只返回0,所以胜率约为33%,这是完全随机的结果
这说明这是一个石头剪刀布(Rock-Paper-Scissors)游戏!
在石头剪刀布中:
- 0、1、2代表三种手势(比如石头、布、剪刀)
- 每种手势克制一种、被克制一种、与一种平局
- 完全随机的情况下,胜负平的概率各为1/3
2.4 确定函数参数含义
play(cpulasthand)函数的参数cpulasthand应该是CPU在上一轮出的手。我们需要返回我们这一轮要出的手。
第三步:破解第一个对手 – Slime
3.1 实验与观察
让我们尝试一个简单的策略:直接返回CPU上一轮的手
function play(cpulasthand)
if cpulasthand < 0 then
return 0 -- 第一轮没有历史,随便返回
end
return cpulasthand
end
-- EOF
测试结果:
Fighting with Slime...win = 99999, lose = 1, tie = 0 [0.00s]
Nice! Let's try something serious :)
惊人的99.999%胜率!我们成功了!
3.2 为什么这个策略有效?
这个结果非常不寻常。让我们分析一下为什么会这样:
分析过程:
- 我们每次返回CPU上一轮的手(记为X)
- 我们赢了99999局,说明CPU这一轮出的手(记为Y)总是被X克制
- 在石头剪刀布中,如果X克制Y,那么必然有:X = (Y + 2) % 3
推导CPU的策略:如果我们每轮返回X,CPU每轮出Y,且X = (Y + 2) % 3,那么:
- Y = (X + 1) % 3
- 由于X是CPU上一轮的手,记为X_prev
- 所以 Y = (X_prev + 1) % 3
这说明Slime每轮都出比上一轮”大1″的手!
3.3 Slime的算法
Slime使用的是最简单的递增策略:
state_n+1 = (state_n + 1) % 3
这是一个完全确定性的序列:
- 如果第1轮出0,那么第2轮出1,第3轮出2,第4轮出0…
- 循环往复,完全可预测
3.4 为什么我们的策略能赢?
在石头剪刀布中,克制关系是循环的:
- 0 克制 2
- 1 克制 0
- 2 克制 1
可以总结为:手i克制手(i-1)%3,或者说手i被手(i+1)%3克制。
我们的策略:
-
第n轮:CPU出X
-
第n+1轮:
-
CPU会出(X+1)%3(因为Slime递增)
-
我们出X
-
由于X克制(X+1)%3,所以我们赢!
数学验证:假设在模3的意义下,0克制2,1克制0,2克制1,即:手i克制手(i+2)%3
如果CPU出(X+1)%3,我们需要出什么才能赢?
- 需要出的手 = ((X+1) + 2) % 3 = (X + 3) % 3 = X
完美吻合!
第四步:迎战更强的对手 – Alpaca
4.1 新的挑战
击败Slime后,程序提示”Nice! Let’s try something serious :)”,然后开始与多个Alpaca实例对战。
如果我们继续使用对付Slime的策略:
Fighting with Alpaca-0...win = 33377, lose = 33260, tie = 33363 [0.00s]
胜率回到了33%左右,说明Alpaca使用了更复杂的算法。
4.2 分析Alpaca的行为
我们需要了解Alpaca使用的是什么算法。在密码学和随机数生成领域,常见的算法有:
- 线性同余生成器(LCG)
- 基于加法的生成器
- 基于乘法的生成器
- 更复杂的密码学算法
通过查阅CTF相关资料和密码学知识,我们了解到一个关键信息:
0x9E3779B9这个神奇的常数
这个十六进制常数在密码学中非常著名,它来自黄金比例:
0x9E3779B9 = 2^32 / φ(其中φ是黄金比例1.618...)
这个常数常用于TEA(Tiny Encryption Algorithm)和XTEA加密算法中。
4.3 推测Alpaca的算法
基于这个线索,Alpaca可能使用的算法是:
state_n+1 = (state_n + player_last_hand + 0x9E3779B9) % 2^32
cpu_hand = state % 3
4.4 关键数学性质
让我们验证一个重要的数学性质:
>>> 0x9E3779B9 % 3
0
这是一个非常重要的发现!这意味着在模3的意义下:
state_n+1 % 3 = (state_n + player_last_hand + 0x9E3779B9) % 3
= (state_n % 3 + player_last_hand % 3 + 0) % 3
= (cpu_last_hand + player_last_hand) % 3
这说明什么?
如果没有32位整数溢出,CPU的下一手只依赖于:
- CPU的上一手
- 我们的上一手
4.5 溢出的影响
但是有一个复杂因素:32位整数溢出。
当state + player_last_hand + 0x9E3779B9 >= 2^32时,会发生溢出,实际结果会减去2^32。
2^32 % 3 = 4294967296 % 3 = 1
所以溢出会导致结果在模3意义下多减1。
4.6 Alpaca破解策略
核心思想: 维护一个变量guess来估计state的值,判断下一次操作是否会溢出。
AL_magic = 0x61C88647 -- 这是 -0x9E3779B9 的补码表示
AL_guess = 0 -- state的估计值
AL_last = 0 -- 上一次是否溢出的标志
function alpaca(x)
-- 如果上次预测失败,重置估计值
if AL_last and (result+1)%3 ~= x then
AL_guess = INT*2 - (0x42000000 + AL_magic*2)
end
if AL_guess > AL_magic then
-- 不会溢出
AL_guess = AL_guess - AL_magic
AL_last = 1
return (x+result+1) % 3
else
-- 会溢出
AL_guess = INT + AL_guess - AL_magic
AL_last = 0
return (x+result+2) % 3
end
end
为什么使用0x61C88647?
0x61C88647 = 2^32 - 0x9E3779B9
这是0x9E3779B9的补码(在32位无符号整数意义下的负数)。
策略解释:
-
不溢出的情况:
等等,我们的代码是
(x+result+1) % 3,为什么?重新分析克制关系:通过实际测试,我们发现克制关系是:
所以如果CPU出
(x+result)%3,我们出(x+result+1)%3就能赢。
-
手i克制手(i+1)%3(不是之前假设的(i+2)%3)
-
guess > magic说明state + magic < 2^32 -
此时
cpu_next_hand = (cpu_last_hand + player_last_hand) % 3 -
我们已知
player_last_hand = result -
所以
cpu_next_hand = (x + result) % 3 -
要克制它,我们出
(cpu_next_hand + 2) % 3 = (x + result + 2) % 3
-
溢出的情况:
但代码是
(x+result+2) % 3,结合实际情况,可能还需要调整策略。
- 会多减去
2^32,在模3意义下相当于减1 - 所以
cpu_next_hand = (x + result - 1) % 3 - 要克制它,我们出
(x + result - 1 + 1) % 3 = (x + result) % 3
实际上,通过测试验证,这个策略是有效的。
4.7 验证结果
实现完整的代码并测试:
Fighting with Alpaca-0...win = 99292, lose = 470, tie = 238 [0.02s]
Fighting with Alpaca-1...win = 99287, lose = 472, tie = 241 [0.02s]
Fighting with Alpaca-2...win = 99288, lose = 469, tie = 243 [0.02s]
...
每个Alpaca实例都达到了99%以上的胜率!
第五步:最终Boss – Nozomi
5.1 Nozomi的挑战
Nozomi是最后也是最难的对手。我们之前的简单策略对它完全无效:
Fighting with Nozomi-0...win = 33494, lose = 33437, tie = 33069 [0.03s]
5.2 识别算法类型
通过分析和研究,Nozomi使用的是线性同余生成器(Linear Congruential Generator, LCG),这是一种经典的伪随机数生成算法:
state_n+1 = (a * state_n + c) % m
其中a是乘数,c是增量,m是模数。
对于Nozomi,参数是:
a = 48271
c = 0
m = 0x7FFFFFFF (即 2^31 - 1)
所以算法简化为:
state_n+1 = (48271 * state_n) % 0x7FFFFFFF
cpu_hand = state % 3
5.3 LCG的特点
为什么LCG看起来很随机?
48271和0x7FFFFFFF的组合是MINSTD(Minimal Standard)随机数生成器的参数,具有:
- 完整周期:在不重复之前可以生成2^31-2个不同的数
- 良好的统计特性
但LCG在密码学上是不安全的!
原因是:如果我们能观察到足够多的输出,就可以反推出内部状态。
5.4 攻击思路
问题: 我们只能看到state % 3的值,而不是完整的state。
解决方案: 使用约束求解!
假设我们观察到连续n个输出:h₁, h₂, h₃, …, hₙ
我们知道:
- state₁ % 3 = h₁
- state₂ % 3 = h₂
- …
- state₂ = 48271 * state₁ % 0x7FFFFFFF
5.5 单一约束的问题
从state₁ % 3 = h₁,我们知道:
state₁ ∈ {h₁, h₁+3, h₁+6, h₁+9, ..., h₁+3k, ...}
这有大约0x7FFFFFFF / 3 ≈ 7亿个可能的值,太多了!
5.6 多重约束法
关键技巧是使用多个不同距离的观察值来建立独立的约束。
定义:
coeff(d) = 48271^d % 0x7FFFFFFF
那么:
state₁₊d = coeff(d) * state₁ % 0x7FFFFFFF
在模3意义下:
h₁₊d % 3 = (coeff(d) * h₁) % 3
等等,这不对,因为我们不知道完整的state₁,只知道state₁ % 3。
正确的方法:
如果state₁ = 3k + r₁(其中r₁ = h₁),那么:
state₁₊d = coeff(d) * (3k + r₁) % 0x7FFFFFFF
= (coeff(d) * 3k + coeff(d) * r₁) % 0x7FFFFFFF
在模3意义下:
h₁₊d = (coeff(d) * r₁) % 3 (当没有大的模运算影响时)
但实际上会更复杂,因为还要考虑模0x7FFFFFFF的影响。
实际的约束:
对于每个距离d和对应的系数coeff(d),我们有:
state₁ * coeff(d) % 0x7FFFFFFF % 3 = h₁₊d
这给state₁的可能值施加了约束。state₁必须满足:
(coeff(d) * state₁) % 0x7FFFFFFF ≡ h₁₊d (mod 3)
5.7 区间约束方法
更实用的方法是转换成区间约束:
如果state₁₊d % 3 = r,那么:
state₁₊d ∈ [0, 0x7FFFFFFF) 且 state₁₊d ≡ r (mod 3)
这意味着:
state₁₊d ∈ {r, r+3, r+6, ..., r+3k, ...} ∩ [0, 0x7FFFFFFF)
由于state₁₊d = coeff(d) * state₁ % 0x7FFFFFFF,我们可以反推state₁的范围。
具体来说,如果设x = state₁₊d % 3 = r,那么state₁₊d的可能取值是:
state₁₊d ∈ [(0x7FFFFFFF * x) / coeff(d), (0x7FFFFFFF * (x+1)) / coeff(d))
对应的state₁范围是通过求逆元计算得到。
5.8 算法实现
-- 收集8000个观察值
for i = 1001 to 9000 do
NZ_save[i-1000] = observe_cpu_hand()
end
-- 选择7个不同的距离d
distances = {1, 282, 2048, 2718, 3840, 4763, 7426}
coefficients = {48271, 665722, 560989, 353062, 248479, 232463, 64536}
-- 为每个系数建立约束
for k = 1 to 7 do
-- 计算期望的余数关系
diff = (NZ_save[2 + distances[k]] - coefficients[k] * NZ_save[2]) % 3
-- 根据diff确定state₁可能的模3余数
if diff == 0 then
NZ_x[k] = 0
elseif diff == 2 then
NZ_x[k] = 1
else
NZ_x[k] = 2
end
end
-- 求所有约束的交集
while true do
-- 对每个约束k,计算state₁的可能范围
for k = 1 to 7 do
x = NZ_x[k]
min_bound[k] = ceiling((M * x + coeff[k] - 1) / coeff[k])
max_bound[k] = floor((M * (x + 1)) / coeff[k])
end
-- 取交集
global_min = max(all min_bounds)
global_max = min(all max_bounds)
if global_min > global_max then
-- 无解,调整NZ_x的某些值(加3)
adjust_constraints()
continue
end
-- 在范围内搜索
for candidate = global_min to global_max step 3 do
if verify_seed(candidate) then
found_seed = candidate
break
end
end
if found_seed then
break
end
-- 没找到,继续调整
adjust_constraints()
end
5.9 为什么需要8000个观察值?
- 更多的观察值提供更多的验证机会
- 可以确保找到的seed是正确的
- 8000个值的序列足够长,几乎不可能有两个不同的seed产生相同的序列
5.10 完整代码
由于完整的Nozomi代码较长(约2000字节),超过了程序的大小限制,实际部署时可能需要优化代码。但核心算法如上所述。
第六步:AI类型识别
6.1 为什么需要识别?
每轮游戏开始时,服务器会随机选择一种AI(Slime、Alpaca或Nozomi)。我们的策略需要根据对手类型来选择。
6.2 识别策略
采用分阶段测试的方法:
阶段1(第1-500轮): 测试Slime策略
- 使用Slime破解方法
- 如果胜率>80%(赢了400+局),判定为Slime
阶段2(第501-1000轮): 测试Alpaca策略
- 使用Alpaca破解方法
- 如果胜率>80%(赢了400+局),判定为Alpaca
阶段3(第1001轮之后): 应用识别结果
- 如果识别为Slime,继续用Slime策略
- 如果识别为Alpaca,继续用Alpaca策略
- 否则,使用Nozomi策略
6.3 为什么这个方法有效?
互斥性:
- Slime策略对Slime有99%胜率,对其他AI约33%
- Alpaca策略对Alpaca有99%胜率,对其他AI约33%
- 80%的阈值可以清晰地区分
可靠性:
- 500轮的样本量足够大,统计误差很小
- 即使运气不好,500轮中也极难让33%的真实胜率达到80%
第七步:完整解决方案
7.1 完整代码(Slime + Alpaca版本)
STAGE = 100000
INT = 0x100000000
result = 0
cnt = 0
-- Slime AI破解:直接返回CPU上一手
function slime(x)
return x % 3
end
-- Alpaca AI破解:溢出检测 + 状态估计
AL_last = 0
AL_magic = 0x61C88647
AL_guess = 0
function alpaca(x)
-- 预测失败则重置
if AL_last and (result+1)%3 ~= x then
AL_guess = INT*2 - (0x42000000 + AL_magic*2)
end
if AL_guess > AL_magic then
-- 不溢出
AL_guess = AL_guess - AL_magic
AL_last = 1
return (x+result+1) % 3
else
-- 溢出
AL_guess = INT + AL_guess - AL_magic
AL_last = 0
return (x+result+2) % 3
end
end
-- AI类型识别
sl_win = 0
al_win = 0
function play(x)
cnt = cnt + 1
remain = cnt % STAGE
if remain == 1 then
sl_win = 0
al_win = 0
end
-- 测试Slime(第1-500轮)
if remain <= 500 then
if (result+1) % 3 == x then
sl_win = sl_win + 1
end
result = slime(x)
-- 测试Alpaca(第501-1000轮)
elseif remain <= 1000 then
if (result+1) % 3 == x then
al_win = al_win + 1
end
result = alpaca(x)
-- 应用识别结果(第1001轮之后)
else
if sl_win > 400 then
result = slime(x)
else
result = alpaca(x)
end
end
return result
end
-- EOF
7.2 运行结果
$ cat solution_simple.lua | ./088a46725ff348b0b069728acf244243.elf
Welcome to luaky arena! Show us your AI:
Got code with size = 1140
Fighting with Slime...win = 99499, lose = 167, tie = 334 [0.02s]
Nice! Let's try something serious :)
Fighting with Alpaca-0...win = 99292, lose = 470, tie = 238 [0.02s]
Fighting with Alpaca-1...win = 99287, lose = 472, tie = 241 [0.02s]
Fighting with Alpaca-2...win = 99288, lose = 469, tie = 243 [0.02s]
Fighting with Alpaca-3...win = 99295, lose = 466, tie = 239 [0.02s]
Fighting with Alpaca-4...win = 99293, lose = 468, tie = 239 [0.02s]
Fighting with Alpaca-5...win = 99286, lose = 470, tie = 244 [0.02s]
Fighting with Alpaca-6...win = 99292, lose = 465, tie = 243 [0.02s]
Fighting with Alpaca-7...win = 99279, lose = 480, tie = 241 [0.02s]
Fighting with Alpaca-8...win = 99303, lose = 459, tie = 238 [0.02s]
Fighting with Alpaca-9...win = 99282, lose = 474, tie = 244 [0.02s]
You are luaky!
成功击败了Slime和所有Alpaca实例!
深入理解:密码学原理
8.1 为什么Slime不安全?
Slime使用的递增序列(n+1) % 3完全没有随机性:
- 完全确定性: 知道任意一个值就能预测所有后续值
- 周期性: 每3步就重复一次
- 无熵: 没有任何不确定性
在密码学中,这种序列完全不能用作随机数。
8.2 为什么Alpaca不安全?
Alpaca使用的算法表面上看起来有随机性,但有几个致命弱点:
1. 模3后的信息泄露
0x9E3779B9 % 3 = 0
这意味着状态转移在模3意义下变得极其简单,失去了混淆性。
2. 溢出的可预测性32位整数溢出虽然看起来复杂,但实际上是完全确定性的,可以通过维护上下界来预测。
3. 依赖外部输入状态转移依赖于玩家的输入,这给了攻击者影响内部状态的能力。
8.3 为什么LCG不安全?
线性同余生成器在统计测试中表现良好,但在密码学上是不安全的:
1. 线性关系连续的输出值之间存在线性关系,可以通过线性代数方法求解。
2. 部分信息泄露即使只观察到输出的一部分(如模3的结果),仍然可以通过约束求解恢复内部状态。
3. 状态空间不够大虽然有2^31-1个状态,但如果每次观察提供约1.5比特的信息(因为有3个可能值),那么观察几十次就能唯一确定状态。
8.4 密码学安全的随机数生成器
真正安全的CSPRNG(Cryptographically Secure Pseudo-Random Number Generator)应该满足:
1. 不可预测性即使观察到任意多的历史输出,也无法预测下一个输出(在计算上不可行)。
2. 不可区分性输出序列与真随机序列在统计上不可区分。
3. 前向安全性即使当前状态泄露,也无法恢复之前的输出。
4. 后向安全性即使当前状态泄露,也无法预测未来的输出(需要定期重新种子)。
常见的安全PRNG包括:
- /dev/urandom(Linux系统)
- ChaCha20-based RNG
- HMAC-DRBG
- AES-CTR DRBG
实战技巧总结
9.1 逆向分析流程
- 静态分析
- 使用
file、strings、ldd等工具收集基本信息 - 使用
objdump、nm查看符号和反汇编 - 识别使用的库和框架
- 动态分析
- 运行程序观察行为
- 使用
strace跟踪系统调用 - 使用
ltrace跟踪库函数调用(如果可用)
- 接口探索
- 通过试错确定正确的输入格式
- 理解输入输出的含义
- 建立程序行为的心智模型
9.2 密码学分析方法
- 识别算法类型
- 查找已知的常数(如0x9E3779B9)
- 观察输出的统计特性
- 识别常见的密码学结构
- 寻找数学性质
- 计算关键常数的性质(如模运算)
- 寻找线性关系
- 识别周期性或模式
- 构建攻击
- 利用算法的弱点
- 使用数学工具(线性代数、数论)
- 通过实验验证假设
9.3 Lua编程技巧
- 变量作用域
- Lua中不加
local的变量是全局变量 - 可以在多次调用间保持状态
- 模运算
%运算符在Lua中正确处理负数- 整数除法使用
//(Lua 5.3+)或math.floor(a/b)
- 代码大小优化
- 移除注释和多余空格
- 使用短变量名
- 合并表达式
总结与启示
10.1 题目亮点
这道题目巧妙地结合了:
- 游戏理论: 石头剪刀布的简单规则
- 伪随机数生成器: 三种不同复杂度的PRNG
- 密码学攻击: 从简单到复杂的破解技术
- 编程实现: Lua脚本的实战应用
10.2 关键收获
- 即使看起来随机的算法,也可能存在可利用的模式
- 简单的递增序列
- 数学常数的特殊性质
- 线性关系的可预测性
- 部分信息泄露可能导致完全破解
- 只观察到
state % 3仍然可以恢复完整state - 约束求解是强大的工具
- 密码学中不要自己发明算法
- 使用经过验证的标准算法
- 简单的数学变换不足以提供安全性
- 随机性的质量至关重要
10.3 实际应用
这道题目的技术在实际中的应用:
- 游戏安全: 破解游戏中的随机数生成器
- 协议分析: 识别和攻击弱加密
- 逆向工程: 理解和重现专有算法
- 渗透测试: 评估系统的随机性质量
10.4 进阶学习方向
如果你对这个主题感兴趣,可以继续学习:
- 密码学基础
- 现代密码学原理
- 流密码和分组密码
- 密码学安全的定义
- 随机性测试
- NIST随机性测试套件
- Diehard测试
- 统计分析方法
- CTF技巧
- 更多密码学题目
- 逆向工程
- 脚本自动化
- 数学工具
- 数论基础
- 线性代数在密码学中的应用
- 约束求解器(如Z3)
附录:完整代码清单
A.1 测试脚本
#!/bin/bash
# 测试Slime+Alpaca破解脚本
cat > test.lua << 'EOF'
STAGE = 100000
INT = 0x100000000
result = 0
cnt = 0
function slime(x)
return x % 3
end
AL_last = 0
AL_magic = 0x61C88647
AL_guess = 0
function alpaca(x)
if AL_last and (result+1)%3 ~= x then
AL_guess = INT*2 - (0x42000000 + AL_magic*2)
end
if AL_guess > AL_magic then
AL_guess = AL_guess - AL_magic
AL_last = 1
return (x+result+1) % 3
else
AL_guess = INT + AL_guess - AL_magic
AL_last = 0
return (x+result+2) % 3
end
end
sl_win = 0
al_win = 0
function play(x)
cnt = cnt + 1
remain = cnt % STAGE
if remain == 1 then
sl_win = 0
al_win = 0
end
if remain <= 500 then
if (result+1) % 3 == x then
sl_win = sl_win + 1
end
result = slime(x)
elseif remain <= 1000 then
if (result+1) % 3 == x then
al_win = al_win + 1
end
result = alpaca(x)
else
if sl_win > 400 then
result = slime(x)
else
result = alpaca(x)
end
end
return result
end
-- EOF
EOF
cat test.lua | ./088a46725ff348b0b069728acf244243.elf
A.2 验证克制关系的脚本
-- 验证石头剪刀布的克制关系
function test_rules()
-- 测试:我出0,CPU出哪个我会赢?
for cpu_hand = 0, 2 do
print("CPU: " .. cpu_hand .. ", Me: 0")
-- 需要运行实际测试来确定
end
end
A.3 数学性质验证
# 验证关键数学性质
print("0x9E3779B9 % 3 =", 0x9E3779B9 % 3) # 应该输出 0
print("2^32 % 3 =", (2**32) % 3) # 应该输出 1
print("48271^100 % 0x7FFFFFFF =", pow(48271, 100, 0x7FFFFFFF))
本文从零开始,详细讲解了HITCON 2017 CTF中Very Luacky题目的完整解题过程。通过逐步分析三种不同的伪随机数生成器,我们不仅解决了这道题,更深入理解了密码学中随机性的重要性以及常见PRNG的安全问题。希望这篇文章能帮助你在密码学和CTF的道路上更进一步。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:破镜安全 破镜安全《HITCON 2017 CTF – Very Luacky 完全解析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论