2025XYCTF-wp
MISC
XGCTF
- 题目简介

- 根据题目告诉我们的内容,我们先要去
ctfshow找到西瓜杯的web方向,然后找到对应的题目

- 找到对应题目后我们还要去寻找
dragonkeep师傅的博客,很容易找到

- 找到后我们就利用博客的搜索引擎搜索题目关键字,这时我们就可以搜索到这样一个文章

- 点击,跳转到这个文章,然后按
f12,搜索flag,就会发现base64编码过后的flag,解码即可得到

1 | flag{1t_I3_t3E_s@Me_ChAl1eNge_aT_a1L_P1e@se_fOrg1ve_Me} |
Crypto
Division
- 题目描述如下:

- 接下来我们就来查看一下题目附件,题目附件如下:
- 该程序要给用户提供俩个选择,第一个选择是程序生成一个随机数,然后用户输入除数,计算出整除结果。程序使用
random.getrandbits生成的随机数 - 第二个选择是让我们输入一个数,如果这个数等于俩个随机数生成出来整除的结果,那么我们就能得到flag。
- 该程序要给用户提供俩个选择,第一个选择是程序生成一个随机数,然后用户输入除数,计算出整除结果。程序使用
1 | # -*- encoding: utf-8 -*- |
- 这一题并没有什么命令执行之类的逃逸,而是对伪随机数进行破解,在网上搜索到这篇文章:[GKCTF2021]random - 明客 - 博客园
- 有相关题目,这个题目讲的就是
random.getrandbits这个伪随机数的生成方式,即MT19937伪随机数生成算法。从文章中得知,我们需要知道624个uint32位的生成随机数,我们才能预测下一个随机数生成的结果。 - 这时我们就可以利用上面
python程序的选项1,接收624个32位的数据,然后通过算法进行预测。

-
文章还介绍了动手写
MT19937伪随机数生成算法的预测,或者是直接使用randcrack这个库进行一把梭。这里我选用的是randcrack库一把梭。 -
exp如下:
1 | from pwn import * |
- 接收完
624之后就可以预测了,这样我们就能得到正确的答案

- flag如下:
1 | XYCTF{f0765d12-ce78-4ad5-8905-455b398d7b78} |
Complex_signin
- 题目描述如下:

- 题目附件如下:
1 | from Crypto.Util.number import * |
- 附件中实现了复数域上的
RSA加密。在附件的代码中,我们能看到一个复数类,这个类里面的属性有re(实部)、im(虚部),然后还有几个方法: def __mul__即两个复数相乘运算,运算结果如下:
-
def __eq__,这个方法定义了判断两个复数是否相等。 -
def __rshift__(self, m)、def __rshift__(self, m)这两个定义了复数域上的位移操作,左移或者右移m位就相当于将实部和虚部都左移或者右移m位。 -
def __str__,返回的就是这个复数的字符表达形式。 -
def tolist,获取这个复数类的实部和虚部。 -
程序中还有一个函数
def complex_pow,实现的就是复数域上的模幂运算。 -
接下来我们查看加密过程,与正常
RSA加密没差,先是生成n,然后这里m即明文是复数,并且实部和虚部都是随机生成的一个整数。
1 | bits = 128 |
- 然后我们对
m进行rsa加密,加密过程,很显然是一个低指数加密,并且之后我们的m还泄露了高位。这时我们就会想到需要使用coppersmith攻击。但是这与我们之前做的一元coppersmith攻击不同。我们先将方程列好。
- 这时我们就列出了一个
coppersmith求解形式的方程,但是这时我们会发现这里面有俩个未知数,所以显而易见,可以尝试使用二元coppersmith攻击。网上有现成的脚本可以使用。(不懂原理,只会当脚本小子)。这边有俩个方程式,经过尝试,我们可以通过f1这个方程式求出x和y,或者通过f1-f2这个方程式求出x和y,但是使用f2这个方程式并不能解出x和y
1 | import itertools |
- 最终flag如下:
1 | b'XYCTF{Welcome_to_XYCTF_Now_let_us_together_play_Crypto_challenge}' |
reed
非预期
- 题目描述如下,同时发现是一个
nc题目:

- 查看题目附件,该程序是,附件的程序给了一个
PRNG类,进行伪随机数的生成,生成n轮的伪随机数,将模一轮生成的为随机数给a,将之后m轮的伪随机数生成给b,然后再进行一个线性仿射运算。
1 | import string |
- 这题的思路不知道是不是非预期,重点主要在仿射加密这边,仿射加密虽然
a和b比较大不好爆破,但是由仿射加密的公式
- 所以编写如下爆破脚本,得到符合要求的式子为:
1 | import libnum |

- 其中
flag为第二个式子即:
1 | XYCTF{114514fixedpointissodangerous1919810} |
正常解
- 赛后出题人没有给
exp,但是有提一嘴这题的思路,这题的思路是不动点法,打算也来使用正常方法打一下。 - 先来介绍一下不动点:

- 不动点也可以理解为函数与的交点。我们先来查看程序伪随机数生成的逻辑。
1 | class PRNG: |
- 这题不能找到直接的不动点,但是可以找到一个种子,利用该种子生成的随机数只有两种可能。
1 | import string |
- 此时我们使用
rand,randint()这个函数只会生成两个数
1 | 17593903 |
- 然后我们进行
nc连接,然后输入seed = 10701913,此时我们就可以得到密文

- 此时我们
a、b组合就只有4种可能,此时我们就可以很快得到正确的a、b的值
1 | import gmpy2 |

- flag:
1 | XYCTF{114514fixedpointissodangerous1919810} |
choice
- 这题的题目给了三个附件
choice.py、random.py、output.py

- 其中
random.py其实就是Python中实现随机数的库,而choice.py其实就是加密的代码,最后一个output.py是输出后的密文 - 接下来我们来看一下
choice这个代码:test生成的是一个字节串,按照255-0的顺序生成的字符串其具体形式如下图所示- 发现还是使用的
rand.getrandbits这个函数(与Division这题一样的函数),生成与msg长度一样的随机数,将它与msg进行异或操作 - 之后使用
rand.choice(test)选择这个字节串中的其中一个索引数据,选择了2496次
1 | from Crypto.Util.number import bytes_to_long |

- 接下来我们查看输出
1 | enc = 5042764371819053176884777909105310461303359296255297 |
- 这题的思路就还是一个伪随机数预测,而这题的伪随机数预测并不是正向预测,而是反向预测。而
Division这题是624个32位int类型,其实也就是2496字节。 - 而我们
choice也是2496字节,这时我们也满足预测的条件。我们还可以使用RandCrack这个库进行如下反推。 exp如下图所示:
Reverse
WARMUP
- 拿到附件后看到是
.vbs后缀的文件,运行一下就会出现这样的弹窗

- 上网搜索了一下关于
vbs的逆向发现这篇文章,直接抓住关键点

- 修改一下,再运行该程序

- 发现实现的是一个
RC4加密,密文为图中一长串的字符,密钥为rc4key

- 提取密文和密钥后在线
rc4解密,解密后就可以得到flag

- flag如下:
1 | flag{We1c0me_t0_XYCTF_2025_reverse_ch@lleng3_by_th3_w@y_p3cd0wn's_chall_is_r3@lly_gr3@t_&_fuN!} |
PWN
明日方舟模拟器(复现)
- 复现一下这题,其实看完这个代码后,发现这题其实算是最简单的一题。稍微代码审计一下就能看到漏洞点其实是一个栈溢出操作,并且其实还给了
system()函数。大大降低了难度。 - 可惜当时比赛的时候没有认真把
PWN牢下去,感觉对PWN没有大一那时候那么热爱了QAQ。希望能恢复大一的状态。 - 接下来还是老样子,查看一下保护机制。发现
canary和PIE都没有开启。

- 接下来逆向一下这个程序,逆向之后发现
csu这个gadget还在。

- 现在边运行程序,边对程序进行逆向分析,在分析的过程中,我们会发现程序运行的主要逻辑就是在
main函数中。




- 通过分析代码运行逻辑,最终发现栈溢出的点在这个位置


-
所以在进行栈溢出漏洞利用的时候,我们需要触发
[1]向好友炫耀 -
接下来我们再分析一下其他函数,通过运行程序我们会发现
employ()这个函数主要就是一个抽卡的功能。

- 通过分析程序,我们会发现
star()这个函数,是在抽卡中随机生成星星的数量,对漏洞的利用貌似并无什么作用,但是看了WP后才发现sum_count这个位于bss段的变量非常关键。

- 接下来我们寻找一下字符串,发现并没有
/bin/sh这个字符串,这时我们就需要写入字符串。所以这题思路就是利用栈迁移,
Ret2libc’s Revenge
- 这题打的是比较高版本的libc,所以我们使用
IDA反编译后该文件,就会发现这个程序并没有csu这个函数,所以我们就没办法使用csu中的gadget。这时我们就要去寻找其他的gadget。 - 我们先查看一下保护机制。
PIE和Canary都没有开启。

- 这时我们再反编译一下这个程序,查看一下这个程序的运行逻辑。
main函数还是比较简单的

- 查看
init函数,我们发现程序的标准输出和标准错误输出被设置了全缓冲,也就是在调用后俩个setvbuf时,程序会一共申请了两个size位为0x410大小的堆块,当做我们标准输出和标准错误的缓冲区 - 当我们的缓冲区满被要输出的字符串填满后,程序才会将要输出的内容输出给用户,或者程序运行结束之前,会将缓冲区的内容输出给程序。

- 查看
revenge,这里有俩个陌生的函数,feof(stdin)、fgetc(stdin),我们使用man命令查看一下它们的功能,feof(stdin)功能就是检查我们的stdin流输入的数据是否为EOF文件结束符,如果是文件结束符就表明结束输入,就返回非零。

fgetc(stdin)函数如下描述如下:就是读入输入流的下一个字符串,并将该字符串转换由原来的unsigned char类型转换为int类型。


- 知道这个函数的大概逻辑了,这个函数就是将用户输入的内容逐个字节存储到
v2这个数组中,并且没有限制长度。这时我们就可以进行无限长度的栈溢出 - 但是在溢出的时候我们要注意一下
v6这个变量,这个变量是控制我们将数据写入到栈中的具体位置,如果我们溢出的时候对这个以不合适的数据对这个进行栈溢出,就会出现一些问题,覆盖完v6这个变量后就重头开始溢出了,或者跳过返回地址继续溢出下去。

- 溢出之后我们就要寻找
gadgat了,我选取的是这几个gadget
1 | 0x4010ea nop ; add rsi, qword ptr [rbp + 0x20] ; ret |
-
我们再溢出的时候修改
var_4这个变量,使其跳过rbp指针指向的位置(我们接下去写入的时候,修改的就直接是返回地址。) -
之后我们向
[rbp + 0x20]这个位置写入puts_got表的地址这样我们就达到了pop rdi的效果。 -
之后就是多次调用
puts函数输出Ret2libc's Revenge,将缓冲区输出出来,这时我们的puts_addr也会被输出出来。我们就得到了libc的地址。之后就是构造system("/bin/sh")了 -
exp如下:
1 | from pwn import * |
- 得到flag:

1 | XYCTF{5f98f505-5509-4968-a91d-7c43782fe067} |
girlfriend
- 查看附件发现保护全开

- 接下来就反翻译该程序
main函数如下:

- 这时我们发现了该程序还加了沙箱

- 查看一下沙箱禁用规则,发现open禁了,我们可以使用
openat,然后read只允许标注输入流输入。execve也被禁了

- 然后查看一下程序是否有其他漏洞。发现在函数
GirlFriend()这边存在一个栈溢出,但是溢出的字节数很小,需要使用栈迁移

- 然后还发现
Reply()函数这边存在字符串格式化漏洞

- 这时我们的思路就是如下:
- 先进行选择
3利用字符串格式化漏洞泄露栈地址,泄露libc地址,泄露程序的地址 - 然后使用栈迁移构造
rop链,这次栈迁移选择迁移到栈上构造rop链,然后通过调用mprotece函数将bss段的权限改为可读可写可执行 - 之后返回到
main函数的开头,让程序重头开始运行 - 之后还需要调用选择
3,选择3的read也存在溢出,这样我们就可以修改选择一的flag标志位 - 接下去调用选择
1这次就是栈迁移到bss段上,写shellcode - 本次使用的是
openat和sendfile这俩个系统调用
- 先进行选择

- exp如下:
1 | from pwn import * |
- 最终得到flag

- flag如下:
1 | XYCTF{cfd378d0-6ac7-414c-bc09-3db96beff92d} |
EZ3.0
- 这题在这里就不多说了,放在
MIPS架构下的ROP链中详细说明 - 并且这题是原题,照着
exp打都能打出来。 - 所以这题直接就贴
exp:
1 | from pwn import * |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 iyheart的博客!

