2020 0xGame WriteUp
前言
主办方:南京邮电大学
Misc
flip(波形图 + 摩斯密码 + BinWalk + 倒序)

打开压缩包后得到一个 ZIP 和 MP3

ZIP 需要密码,使用 Audacity 打开 MP3

根据波形长短猜测是摩斯密码
1 | ....- ----- ----- ---.. ..... -.... -.... ----. ----. ...-- ----- ----- ... .. -.. .-. --- .-- ... ... .- .--. |
解密得到
1 | 400856699300sidrowssap |
翻译了一下题目题目名称有翻转的意思,把摩斯密码倒序发现:
1 | passwordis003996658004 |
003996658004 即为压缩包密码
解压后还是压缩包

使用 Kali 的 BinWalk 分离

查看 password.txt 文件发现
1 | ?gnorw eb ot smees gnihtemos tub ,yranib ni elif etirw ot tnaw I |
明显的倒序排列
将所有内容倒序后得到
1 | 01011001 |
二进制转十六进制再转 ASCII 码得到
1 | You are so clever that this problem is just a piece of cake for you, the password is A_pi3ce_0f_C4ke |
使用 A_pi3ce_0f_C4ke 密码提取出压缩包中的 galf_si_siht.png
提示我们打不开文件

使用 010editor 打开该文件
发现文件头不是 PNG 格式

拉到最底下发现还是倒序 黄豆流汗脸.jpg

将整个文件的十六进制倒序后保存为 PNG 图片打开发现二维码,使用二维码扫描工具得到
1 | http://am473ur.com/0xgame/flip/bd055250d3906d1f791d8e83b4396893.php |
访问拿到 flag(估计不在了)
pacp(SQL 盲注流量 + PNG 宽高)

打开流量包发现是盲注流量

过滤 POST 请求发现有上传一个文件
1 | http.request.method == POST |

追踪 HTTP 流复制原始数据

用 WinHex 复制成新文件解压缩文件是一张图片,打开没内容,检查 CRC 发现宽高被更改过

使用随波逐流梭哈出原图

differentPic(图像合成)

解压缩文件是两个一模一样的图片

使用 StegSolve 打开选择 Image Combiner 模式(图像合成)

发现可疑内容

保存后再打开切换通道发现是二维码

扫码拿到 flag

extract(BinWalk + Stegpy)

打开压缩包后发现图片

使用 BinWalk 检查发现有隐藏文件

提取出二维码扫描给出提示工具 Stegpy

使用 Stegpy 成功拿到 flag

easyMisc(拨号+ Stegpy)

解压缩出三个文件

打开音频文件听着像是拨号声音,疑似拨号隐写

把频率设置为低频 697、高频 1633


得到下图

对照拨号隐写表格解密出:2821876761

使用 2821876761 解压得到 flag 图片
再次使用 Stegpy 得到 flag

lowerBase64(Base64 解码)

Base64字符串中的字母被随机转换了大小写
正常的Base64解码器要求严格的大小写,所以我们需要找到正确的大小写组合
Base64 编码原理
1 | # Base64 编码过程: |
由于我们不知道哪些字母应该是大写、哪些应该是小写,所以需要暴力枚举所有可能的大小写组合
1 | for i in range(0, len(c), 4): # 以 4 个字符为一组处理 |
举例说明:
如果当前 chunk 是 "AbC1",那么:
'A'→['a', 'A']'b'→['b', 'B']'C'→['c', 'C']'1'→['1']
通过 product(*pos) 会生成:"abc1", "abC1", "aBc1", "aBC1", "Abc1", "AbC1", "ABc1", "ABC1" 共 8 种组合
最后验证结果
1 | for case in cases: |

threeThousand(ZIP 爆破)

第一次爆出密码是 33

打开发现还有压缩包,且都是加密

写脚本爆破
1 | import zipfile |

Crypto
Calendar(日期网格坐标密码)

题目给出了一张图片

以及一段字符
1 | SAT1,THU1,MON3,MON2,WED3,SUN2,THU1,SUN4,FRI3,THU1,MON4,MON4,FRI4,THU3,SUN4,SUN2,TUE4,THU1,FRI1,MON3,MON2 |
观察最后的数字只有 1~4,推测这就是行号,而字符就是对应的列
将得到的数字按照字母表的顺序转换最后得到
1 | 0xGame{calendarpasswordtable} |
easyXor(异或加密)

连续两次异或同一个数结果不变
1 | cipher = [72, 63, 38, 12, 8, 30, 30, 6, 82, 4, 84, 88, 92, 7, 79, 29, 8, 90, 85, 26, 25, 87, 80, 10, 20, 20, 9, 4, 80, 73, 31, 5, 82, 0, 1, 92, 0, 0, 94, 81, 4, 85, 27, 35] |
supperAffine(仿射加密变种)

解题技巧先放前面:
- 识别密码体制: 代码中出现了
affine(x, a, b)和GCD(MOD, ...),立即锁定这是仿射密码及其变种 - 识别模数: 立即进行模数分解,MOD = 62 = 2 * 31
- **代数化简:**看到
f3(f2(f1(x)))这种结构,立即意识到这是可以合并的
给出源代码
1 | from Crypto.Util.number import * |
equationSet(提公因数 p)

解题技巧先放前面:
- 符号化:所有给出的数值用数学符号表示出来,并建立它们之间的关系
- 寻找公因数:如果给出的两个大数 A 和 B 都是由相同的素数因子构成的,那么 GCD(A, B) 往往是解密的关键
- **确定解题路径:**一旦 p 被分解出来,接下来就要想方设法得到 q 和 r 的信息
题目给出代码
1 | from Crypto.Util.number import * |
转换成数学公式
$$
\begin{aligned}
&n\ =\ p\ *\ q\ *\ r\
&t\ =\ p\ *\ q\ +\ p\ *\ r\ =\ p(q\ +\ r)\
&ϕ(n)\ =\ (p\ -\ 1)(q\ -\ 1)(r\ -\ 1)
\end{aligned}
$$
我们可以清楚地看到,p 是 n 和 t 的公共因子
那 GCD(n, t) 会正好是 p 吗?会不会包含其他因子?
$$
\begin{aligned}
&GCD(pqr,p(q\ +\ r))\ =\ p\ *\ GCD(qr,q\ +\ r)
\end{aligned}
$$
由于 q 和 r 都是生成的 512 位随机大素数,它们互质,且它们的和 q+r 极大概率与它们的积 qr 互质
因此 GCD(qr, q+r) = 1
结论: 直接计算 GCD(n, t) 就能无痛提取出素数 p
对于 n=pqr,欧拉函数定义为:
$$
\begin{aligned}
&\phi(n)\ =\ (p-1)(q-1)(r-1)\\
&\phi(n)\ =\ (p-1)\ *\ [(q-1)(r-1)]\\
&n\ -\ t\ =\ pqr\ -\ pq\ - \ pr\
&n\ -\ t\ =\ p(qr\ -\ q\ - \ r)\
&(n\ -\ t)\ //\ p\ =\ (qr\ -\ q\ - \ r)\
&(n\ -\ t)\ //\ p\ +\ 1\ =\ (qr\ -\ q\ - \ r)\ +\ 1\\
&qr\ -\ q\ - \ r\ +\ 1\ =\ q(r\ -\ 1)\ -\ 1(r\ -\ 1)\
&q(r\ -\ 1)\ -\ 1(r\ -\ 1)\ =\ (q\ -\ 1)(r\ -\ 1)\\
&(n\ -\ t)\ //\ p\ +\ 1\ =\ (q\ -\ 1)(r\ -\ 1)\
&\phi(n)\ =\ (p-1)\ *\ [(q-1)(r-1)]\
&\phi(n)\ =\ (p-1)\ *\ [(n\ -\ t)\ //\ p\ +\ 1]\
\end{aligned}
$$
观察公因数 是 Crypto 选手的直觉。看到 n 和 t 有明显的公共结构(都含有 p),第一时间想到的就是 GCD
1 | from Crypto.Util.number import * |
Fibonacci(爆破斐波那契数列周期)

解题技巧先放前面:
- 识别计算陷阱与核心矛盾:看到一个天文数字立即判定这是计算陷阱
- **建立 Pisano 周期的知识框架:**了解周期的基本性质
- **掌握求和公式的转化:**一旦找到周期和周期和,必须将求和公式转换为可计算的代数形式
- **大数运算:**CTF 中处理超大数论运算,首选
gmpy2库
题目代码如下
1 | from Crypto.Util.number import * |
现在分析代码
定义了一个斐波那契数列
1 | def F(x): |
斐波那契数列是一个递推数列,它的核心规则是:从第三项起,数列中的每一项都等于前两项之和
$$
\begin{aligned}
&F_n\ =\ F_{n-1}\ +\ F_{n-2}\ (n \geq 3)
\end{aligned}
$$
在 CTF 和数论领域,斐波那契数列的模运算性质至关重要:
Pisano 周期
- 定义: 斐波那契数列对任何正整数 n 取模后,都会变成一个循环数列。这个循环的长度就被称为 Pisano 周期,记作
$$
\begin{aligned}
&\pi(n)
\end{aligned}
$$
小模数 n 的生成
1 | # 2. 小模数 n 的生成 |
巨大的求和上限 r
1 | # 3. 巨大的求和上限 r |
S 的计算
1 | # 4. 关键泄露信息 S 的计算 (S是 p 的基础) |
生成巨大的素数 p
1 | # 5. 生成巨大的素数 p |
生成素数 q
1 | # 6. 生成素数 q |
首先要求出 p -> S -> r 这个顺序
先看 S 的计算方式
$$
\begin{aligned}
&S\ =\ \sum_{i-0}^{r-1}{F((i)\ \ (mod\ n))}
\end{aligned}
$$
求和次数 r 太大,但是,求和时使用的模数 n 却非常小(不到 65536)
这是整个解题的关键步骤。我们不能循环 r 次,但 n 很小,所以我们要找规律
Pisano 周期(重复的舞蹈)
当斐波那契数列对一个小数字 n 取模时,它的结果会不断重复,形成一个循环。这个循环的长度就是 Pisano 周期 T
- 斐波那契数列 mod n 就像一个舞者在一个很小的圆形舞台(模数 n)上跳舞。它跳了一段时间后,姿势和位置一定会回到起点 (1, 1)
- 周期 T 就是这支舞完整跳完一次所需要的步数
通过脚本实现
1 | a, b = 1, 1 |
因为 n 很小,这个循环很快就能结束,我们得到了周期 T 和跳一圈舞的总得分 Sum_{cycle}
现在我们有了舞蹈的规律,就可以计算 r 步的总得分 S:
- 完整周期数 k: k = r // T (看看 r 步能跳多少次完整的舞)
- 剩余步数 rem: rem = r % T (最后多出来没跳完的几步)
$$
\begin{aligned}
&S\ =\ (完整次数\ k\ *\ 一圈的总得分\ Sum_{cycle})\ +\ (剩余几步的得分\ Sum_{cycle})
\end{aligned}
$$
这一步就将天文数字 r 转换成了一个可计算的公式,得到了我们想要的秘密蓝图 S
| 脚本代码 | 作用 | 简单理解 |
|---|---|---|
p = next_prime(S ** 16) |
计算第一个秘密零件 p。 严格按照题目的公式,找到 S^{16} 之后的第一个素数 | |
q = N // p |
计算第二个秘密零件 q。 N 是 p 和 q 的乘积,所以 q = N / p | |
phi = (p - 1) * (q - 1) |
计算制造蓝图 \phi(N) | |
d = inverse(65537, phi) |
计算万能钥匙 d。 inverse 是求一个数在 \pmod{phi} 世界里的“倒数” |
|
m = pow(c, d, N) |
解密! 使用万能钥匙 d 打开保险箱 N 中的密文 c | |
long_to_bytes(m) |
将解密得到的数字 m 转换回 Flag 文本 |
完整代码如下
1 | from Crypto.Util.number import * |
easyRSA(线性组合泄露 d 与 φ(n))

1 | from Crypto.Util.number import * |
RSA 里,我们有:
p,q两个大素数n = p * qφ(n) = (p-1)*(q-1)公钥指数
e = 65537(题目固定)私钥指数
d满足e * d ≡ 1 (mod φ(n))
这等价于存在一个整数 k,使得:
1 | e * d = k * φ(n) + 1 |
即
1 | d = (k * φ(n) + 1) / e |
题目除了给 n 和密文 c,还给了
1 | x = 11 * d + 7 * φ(n) |
也就是说,x 是 d 和 φ(n) 的一个线性组合,系数分别是 11 和 7
将上面 d 的表达式代入 x
1 | x = 11 * [ (kφ + 1)/e ] + 7φ |
两边同时乘以 e,去掉分母
1 | e * x = 11 * (kφ + 1) + 7eφ |
展开
1 | e * x = 11kφ + 11 + 7eφ |
把常数项移到左边,含 φ 的项合并
1 | e * x - 11 = (11k + 7e) φ |
记 T = e x - 11,则
1 | T = (11k + 7e) * φ |
这是一个关键等式,T 是 φ 的整数倍,倍数 A = 11k + 7e 依赖于未知的 k
从 e * d = k φ + 1 得
1 | k = (e * d - 1) / φ |
因为 d < φ(实际上 d 是模 φ 下的逆元,通常比 φ 略小),所以
1 | e * d - 1 < e * φ - 1 → k < e |
所以思路:
计算
T = e * x - 11(已知x和e)对每个可能的
k从 0 到e-1,计算A = 11k + 7e如果
A能整除T,那么φ = T // A就是一个候选的欧拉函数值用这个
φ去分解n,因为p + q = n - φ + 1,p * q = n,所以p和q是二次方程X^2 - (p + q)X + n = 0的两个根判断
p和q是否为整数且满足p * q == n,若是,则分解成功
1 | from Crypto.Util.number import * |
Web
????(Shell 通配符绕过)

打开页面提示输入 yulige 获得 flag

但是前端限制了只能输入 4 字符,右键改源代码删掉

提交按钮被禁用了,删掉 disable 属性

提交后给出提示

访问过去给出了黑名单源码

1 | if(preg_match("/[A-Za-ko-z0-9]+/", $cmd)) |
它唯独漏掉了三个小写字母:l、m、n
1 | $blacklist = "~!@#%^$&\/*()()<>《》-_{}[]'/\":,"; |
同样漏掉了空格、?、.
所以我们可以利用 nl 这个命令读取文件,其文件名采用正则的形式代替
1 | # nl fl4g_is_here.php |

Pwn
欢迎来到 0xGame 平台(签到)
直接 nc 即可