2025-强网杯S9-wp
PWN
flag-market(复现)
这类对flag文件读取的题目之前在susctf,这边做到过一题也是简单的溢出类题型,但是这次牢不动了。溢出点找出来了,flag残留在堆上也找出来了,就是没看出来溢出后可以利用printf那边的格式化字符串漏洞泄露flag。
flag-market分析
- 先查看一下保护机制,发现
没有开PIE。

- 接下来就是运行一下这个程序,算是一个循环菜单题,但是这个菜单只有两个选项。

- 接下来逆向一下这个程序,首选发现了一个关键点,我们输入选项
1,使用255购买flag就会使得程序输出flag。

- 但是触发了这个输出flag功能,其实没啥用处,因为遇到
{就不会再将flag输出下去了,并且在逐个输出flag之前程序已经将flag这个文件描述符给关闭了。

- 尝试运行的时候会出现这样的情况,发现还有一个输入点

- 接着继续逆向这个程序,发现这个输入点存在
scanf的一个溢出漏洞,可以实现.data段溢出。

- 然后注意到上面存在一个
printf函数,并且scanf输入的时候可以溢出修改format,这样很可能就是一个字符串格式化漏洞。


flag-market调试
- 接下来调试一下,先看看是否能溢出修改
format那边的字符串。

- 发现
format那边的字符串是已经因为溢出而被修改了。

- 接下来其实可以利用字符串格式化漏洞直接输出flag,但是flag存放在什么位置呢,接下来我们要寻找一下,由于flag直接会存放在栈上,现在我们看看栈上的flag还存不存在。其实栈上的flag不会存在的,因为在出现
error的时候会先将v9清零。

- 接下来需要看看哪里还存放着flag,由于我们知道前面是有打开flag文件,需要添加一个
IO_FILE结构体,而这个结构体需要用堆存放,这个时候我们来看看堆。果然,堆上有flag的残留数据,所以我们需要泄露的是堆上的这个字符串。

- 由于地址随机偏移的问题,程序每次运行时堆地址都不是固定的,所以我们要看看能不能先泄露出堆地址(所以先要使用printf先泄露堆地址),看看栈上是否有存放堆地址,发现栈上是存在堆地址的。
- 并且在printf输出的这个位置
rsp的地址比存放堆地址的栈地址更低


- 这样就可以先使用
%9$p将堆地址给泄露出来。但是还存在一个问题,我们只能利用1次scanf溢出,所以这次溢出还需要输入%xxx$s。

- 接下来就需要调试栈上哪个地址是有效的保存字符串的地址,以便我们在第一次printf触发格式化字符串漏洞的时候
%xx$s不会导致程序崩溃,但是栈上发现没有什么有效地址。但是发现``

- 但是发现这个地方我们是可以直接控制的,可以直接输入一个地址,使得
printf触发%xx$s的时候不会导致程序崩溃。并且在泄露地址之后我们还可以将这个地址修改为flag所存放的堆地址,非常巧妙。


flag-market-EXP
就直接按照上面调试的思路去编写exp即可
- exp如下:
1 | from pwn import * |

ez-stack(复现)
ez-stack分析
ez-stack调试
ez-stack-EXP
Crypto
check-little(复现)
- 题目附件如下:
1 | from Crypto.Util.number import * |
check-little分析
- 这题感觉有点幽默,通过对代码的分析,首先我们是先要求解RSA加密获取
key。但是这个题目就给了n、c,并且n是很难硬分解出来的。2025.12.2是可以用factordb查到的,但是当时比赛是查不到的。 - 这里其实有个非常小细节就是
gcd(c,N)!=1,这其实就说明了c就是N的一个因子,这样N就可以直接被分解了,key直接可以求了,剩下的就是正常的RSA解密和AES解密操作了。
check-little—exp
1 | from Crypto.Util.number import * |
ezran(复现)
-
唉,MT19937可恶啊。这题其实是鸡块师傅的这题改编的:2024-同济大学第二届网络安全新生赛CatCTF-wp-crypto | 糖醋小鸡块的blog
-
当时8月底打一场国外的比赛遇到的一题MT19937就是参考鸡块师傅的博客,当时数据量要
3w多位才能完全恢复出状态,而这题位数还是不太够,所以需要进行解空间的爆破。其实有个妙妙小工具也能梭出来gf2bv,当时我使用工具的能力比较弱(其实是github项目用少了),所以当时没解出来。附件如下:
1 | from Crypto.Util.number import * |
ezran分析
-
感觉分析写到后面逻辑性有点问题
-
根据题目附件的代码就会发现这个其实是
MT19937的伪随机数预测的题目。首先来看看到:
- 然后会接下来其实要注意到的是下面这个式子,因为我们得到的
gift就是与下面这个式子相关的。
- 首先注意到,其中
257是一个素数,由欧拉判别定理可以得到如下结论,若,则是模p的平方剩余有:
- 然后再注意到是模p的平方非剩余有:
- 所以当,也就是是
128的整数倍的时候就只会出现上面两种可能,也就是i是64的整数倍的时候会出现上面的两种可能。此时当转换为这个范围内就有:
-
注意到结果会
&0xff,这就说明一点异或只会改变低8位的数据,而高8位的数据是始终泄露出来的,所以就有如下结论:- 当是一般情况的时候,的高8位始终能泄露出来,低8位不知道情况
- 当是64的倍数的时候,的高15位始终能泄露出来,低1位不知道情况
-
所以对于
3108个的,可以泄露的位数为:- 一般情况
3108×8=24864,这种情况是包可以恢复出来的。 - 特殊情况
i是64的整数倍时,还可以泄露出 - 最终可以泄露:
24864+336=25200
- 一般情况
-
已知
19968位是绝对可以恢复出初始状态的,这种就是直接解出矩阵方程即可,已知,获取,就可以解出这个线性方程(也就是state即状态方程),其中这个应该需要使用黑盒测试来获取(详细原理到时候会开一篇MT19937的文章介绍):
-
接下来第一部分恢复已经分析完了,接下来一步就是分析
shuffle()这个函数,该函数可以将m这个列表随机打乱,随机打乱使用到的随机数也是来自于上面MT19937的状态,所以只要恢复了MT19937的状态shuffle()部分其实很好解决。 -
接下来先尝试恢复一下:
1 | import sys |
- 在恢复的时候求解出线性方程组时,就会发现一个问题泄露的数据量不够,导致解出来的线性方程组的解的秩(或者解集维数不为0),导致方程组的解不唯一。所以还需要想办法对这个解进行爆破等操作。
- 此时我们先看看这个方程解出来的秩是多少,这里也稍微介绍一下核
kernel的概念,因为s是该方程组的一个特解,所以如果直接求s的秩的话一般都是满秩的,所以我们需要求s对应的核空间的一个元素,这样才能得到该方程组的秩,其实也可以使用矩阵计算秩。- 核的定义:设是域上线性空间到的一个线性映射,则的子集,称为的核。
- 从上面核的定义中可以知道,核其实就是一个映射到空间且满足解的集合。
- 所以我们先将该特解映射到其核空间中看看维数是否为
0,如果维数为0那么解唯一,不为0,那么解就不唯一,需要爆破解。
1 | R = vector(GF(2),R) |

- 从上面可以得到解集
s的维数不为零,这就导致约束太少,求得的解集自由度比较高。现在就要想如何进行爆破操作,在学习线性代数的时候我们有学习到下面俩个知识点:- 齐次线性方程组解的结构:将主元使用非主元的未知数表示,构成一个通解也构成一个线性空间。
- 非齐次线性方程组解的结构:对应齐次线性方程组的通解加上非齐次线性方程组的一个特解,构成一个线性流型(线性流型并不是线性子空间)
- 此时我们需要先构造出该方程(也就是非齐次线性方程组)的通解,根据通解取遍历所有解,从而爆破出正确的解。首先我们特解
s已经被解出来了,现在就是要看看通解。 - 并且由于该方程是在有限域上的解,所以解集是有限的,通过排列组合可以得到解向量中自由解每个都可以取
0或1,这样就可以得到维数与解的总数二者之间的关系为:
1 | R = vector(GF(2),R) |

- 虽然说总特解数量还是比较多,但是其实可以爆破出来的,这里直接使用
itertools库中的product将所有自由变量的取值都遍历一遍,下面做一个示例:
1 | from itertools import product |
- 接下来就需要计算出该核空间的一组基,如下所示(只是一个例子):
- 最后遍历所有自由度加上通解即可开始爆破状态:
1 | from itertools import product |
ezran-EXP
- 完整exp如下:
1 | from Crypto.Util.number import * |
- 最终flag为:
1 | flag{7hE_numbEr_0f_biT5_i5_Enou9h_@L5o_ThE_r4nk_must_3n0ugh} |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 iyheart的博客!

