本文进行一次利用 AI 从一点没看到独立复现 2 解题目的尝试,这里分享一下思路。题目本身的主要难度在逆向层面,需要分析每一个指令的参数和功能,并能找到指令中的漏洞
拿到题目之后看一下基本信息,docker 里在后台运行了 bin/server 程序,然后 xinetd 监听 8888 端口,启动 bin/client 程序。我们需要使用 nc 和 client 交互,client 再和 server 交互。
先 nc 上去看一下,这里是提前看了下字符串随便试了试,看起来像是从 client 向 server 发送 cmd 指令

经过几轮对话,让 AI 分析 client/server 的逻辑,基本功能就比较清晰了


接下来我们去分析每一个 CMD 对应的 ID,不过 AI 一开始并没有给出ID,我们需要人工干预一下。

继续让 AI 整理一下这两张表就有
CMD 的 ID 和参数格式都有了,接下来分析每个指令的详细功能
功能 : 初始化会话并生成会话密钥
关键代码路径 :
功能 : 注册一个新的 Profile
关键验证逻辑 :
返回 : "OK" + [4 字节 Profile ID]
功能 : 删除指定 Profile 中的指定 Blob
关键限制 :
可能的漏洞点 : Blob 的 end 指针被设置为 start,但内存未释放,可能导致 Use-After-Free
功能 : 显示指定 Profile 的所有 Blob 数据
数据格式 :
功能 : 更新或创建 Blob
Blob 数量限制 : 最多 64 个((v 152 - v 151) <= 0 x 3 F)
功能 : 执行操作或验证魔术字节
特殊参数 0 x 1337: Oracle 解锁功能的入口
Oracle 相关 :
功能 : 列出所有 Profile ID
返回格式 :
功能 : 断开连接
在表格里看到了一些比较奇怪的指令,重点测试 oracle 和 exec
在执行这个 oracle 指令之前需要先 oracle_unlock 解锁功能,笔者发现 oracle_leak idx 8 可以泄露出基地址,这里的 idx 是通过 register 注册的索引

测试的时候发现 exec 系列不知道怎么调用,继续让 AI 分析


这里因为不知道 param 和 op_type 是怎么传的,所以需要调试一下,在 0000000000005F6B 下断点,然后执行 exechex 1 2 aabbccddeeff

结合代码能看出来 op_type 是 data 部分的第一个字节,接着 4 字节是最后 payload 的长度


成功执行

在对话的过程中顺带帮我分析出来了一个栈溢出漏洞,还给出了栈布局

不过他认为可以修改返回地址,但这实际上是修改不到的,因为我们只能写入 0x140


指出这一点之后,AI 分析出来 v35 指针可以利用,也就是

测试之后发现成功控制了 call rax

在所有的指令功能都分析完之后,其实已经比较清晰了,先用 oracle 泄露地址,然后在利用 exec 的栈溢出漏洞。
AI 也给了攻击流程,和最终实现的 exp 已经没有太大差别了
题目开了沙箱,禁用了 exec,由于我们无法直接和 server 交互,所以不能用常规的 orw 来读 flag

结合 client 程序在启动的时候会读取并输出 client.log 的文件内容,可以把 flag 内容写入到 client.log 文件,rop 执行之后,再启动一个终端 nc 就能拿到 flag


整个复现过程,基本上只有 rop 是自己写的,绝大部分的分析过程都是 AI 在做,人工主要是测功能和指导 AI 的分析方向。目前 AI 针对一些比较小的程序,分析效果还是不错的。
.
├── bin
│ ├── client
│ └── server
├── ctf.xinetd
├── docker-compose.yml
├── dockerfile
└── start.sh
.
├── bin
│ ├── client
│ └── server
├── ctf.xinetd
├── docker-compose.yml
├── dockerfile
└── start.sh
| 命令名称 |
命令 ID |
功能描述 |
参数说明 |
数据包格式 |
| help |
- |
显示帮助信息 |
无参数 |
无 |
| hello |
1 |
发送 hello 消息 |
[client_name](可选,默认为"client") |
[client_name] |
| register |
16 |
注册操作(字符串方式) |
|
[4B 长度][数据] |
| register_hex |
16 |
注册操作(十六进制方式) |
|
[4B 长度][数据] |
| list |
22 |
列出所有项目 |
无参数 |
无 |
| partial_delete |
19 |
部分删除操作 |
|
[4B id1][4B id2] |
| show |
20 |
显示指定项目 |
|
[4B id] |
| update |
17 |
更新操作 |
|
[4B id1][4B id2][4B 长度][数据] |
| exec |
18 |
执行命令 |
|
[4B id][2B param][4B 长度][数据] |
| exechex |
18 |
执行命令(十六进制参数) |
|
[4B id][2B param][4B 长度][数据] |
| execf |
18 |
执行命令(带指针参数) |
|
[4B id][2B param][4B 长度][数据] |
| oracle_unlock |
- |
Oracle 解锁功能 |
无参数 |
无 |
| oracle_guess |
18 |
Oracle 猜测字节 |
|
[4B pid][2B 0x3733][4B 长度][payload] |
| oracle_leak |
18 |
Oracle 泄露字节 |
|
[4B id][4B长度][数据] |
| bye |
255 |
退出程序 |
无参数 |
无 |
session_key = generate_session_key(qword_FA40);
*a8 = session_key;
*a9 = session_key;
srv->session_mgr->session_key = session_key;
*a10 = 2;
session_key = generate_session_key(qword_FA40);
*a8 = session_key;
*a9 = session_key;
srv->session_mgr->session_key = session_key;
*a10 = 2;
crc32_value = calculate_crc32(data, length);
if ((session_mgr->session_key ^ 0x13579BDF) == crc32_value) {
session_mgr->session_key |= 0x0100;
profile_id = create_profile(session_mgr, data);
}
crc32_value = calculate_crc32(data, length);
if ((session_mgr->session_key ^ 0x13579BDF) == crc32_value) {
session_mgr->session_key |= 0x0100;
profile_id = create_profile(session_mgr, data);
}
"OK" + [4B length] + [blob1_data][blob2_data]...
"OK" + [4B length] + [blob1_data][blob2_data]...
if (BYTE1(session_mgr->session_key) && param == 0x1337) {
verify_magic_bytes(srv, id, &data, &result, session_key);
}
if (BYTE1(session_mgr->session_key) && param == 0x1337) {
verify_magic_bytes(srv, id, &data, &result, session_key);
}
[4B count] + [4B id1] + [4B id2] + ... + [4B idn]
[4B count] + [4B id1] + [4B id2] + ... + [4B idn]
High Address
┌─────────────────────────────────────────────────┐
│ │
│ Caller's Stack Frame │
│ │
├─────────────────────────────────────────────────┤ <- rbp+0x10
│ Return Address (8 bytes) │ <- rbp+0x08 [TARGET!]
├─────────────────────────────────────────────────┤
│ Saved RBP (8 bytes) │ <- rbp+0x00
├─────────────────────────────────────────────────┤
│ var_40: Stack Canary (8 bytes) │ <- rbp-0x40
├─────────────────────────────────────────────────┤
│ var_50: v37 (int, 4 bytes) │ <- rbp-0x50
├─────────────────────────────────────────────────┤
│ var_51: v36 (char, 1 byte) │ <- rbp-0x51
├─────────────────────────────────────────────────┤
│ ...other local variables... │
├─────────────────────────────────────────────────┤
│ var_148: v35 (function pointer, 8 bytes) │ <- rbp-0x148
├─────────────────────────────────────────────────┤
│ │
│ dest[64]: Target Buffer │ <- rbp-0x188
│ ┌──────────────────────────────────┐ │
│ │ 64 bytes buffer │ │
│ │ (but we copy up to 312 bytes!) │ │
│ └──────────────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ src: __m128i (16 bytes) │ <- rbp-0x198
├─────────────────────────────────────────────────┤
│ n: size_t (8 bytes) │ <- rbp-0x1A0
├─────────────────────────────────────────────────┤
│ p_src: void* (8 bytes) │ <- rbp-0x1A8
├─────────────────────────────────────────────────┤
│ ...more variables... │
└─────────────────────────────────────────────────┘
Low Address
Distance calculations:
- dest to saved rbp: 0x188 = 392 bytes
- dest to return addr: 0x188 + 8 = 400 bytes
- Overflow space: 312 - 64 = 248 bytes
- Can overwrite: 248 bytes beyond dest[64]
High Address
┌─────────────────────────────────────────────────┐
│ │
│ Caller's Stack Frame │
│ │
├─────────────────────────────────────────────────┤ <- rbp+0x10
│ Return Address (8 bytes) │ <- rbp+0x08 [TARGET!]
├─────────────────────────────────────────────────┤
│ Saved RBP (8 bytes) │ <- rbp+0x00
├─────────────────────────────────────────────────┤
│ var_40: Stack Canary (8 bytes) │ <- rbp-0x40
├─────────────────────────────────────────────────┤
│ var_50: v37 (int, 4 bytes) │ <- rbp-0x50
├─────────────────────────────────────────────────┤
│ var_51: v36 (char, 1 byte) │ <- rbp-0x51
├─────────────────────────────────────────────────┤
│ ...other local variables... │
├─────────────────────────────────────────────────┤
│ var_148: v35 (function pointer, 8 bytes) │ <- rbp-0x148
├─────────────────────────────────────────────────┤
│ │
│ dest[64]: Target Buffer │ <- rbp-0x188
│ ┌──────────────────────────────────┐ │
│ │ 64 bytes buffer │ │
│ │ (but we copy up to 312 bytes!) │ │
│ └──────────────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ src: __m128i (16 bytes) │ <- rbp-0x198
├─────────────────────────────────────────────────┤
│ n: size_t (8 bytes) │ <- rbp-0x1A0
├─────────────────────────────────────────────────┤
│ p_src: void* (8 bytes) │ <- rbp-0x1A8
├─────────────────────────────────────────────────┤
│ ...more variables... │
└─────────────────────────────────────────────────┘
Low Address
Distance calculations:
- dest to saved rbp: 0x188 = 392 bytes
- dest to return addr: 0x188 + 8 = 400 bytes
- Overflow space: 312 - 64 = 248 bytes
- Can overwrite: 248 bytes beyond dest[64]
payload = cyclic(0x140)
data = p8(0x41) + p32(len(payload)) + payload
run(f"exechex {idx} 2 {binascii.hexlify(data).decode()}")
payload = cyclic(0x140)
data = p8(0x41) + p32(len(payload)) + payload
run(f"exechex {idx} 2 {binascii.hexlify(data).decode()}")
┌─────────────────────────────────────────────────────┐
│ 攻击流程 │
└─────────────────────────────────────────────────────┘
1. CMD_HELLO (0x01)
↓
获取 session_key
↓
2. CMD_REGISTER_PROFILE (0x10)
↓
认证成功 (oracle_unlock)
↓
3. oracle_guess (CMD_EXEC_OP param=0x1337)
↓
暴力破解 magic_table[0..7]
↓
4. oracle_leak (CMD_EXEC_OP)
↓
泄露栈地址、堆地址、canary
↓
5. CMD_EXEC_OP (param=0x42, op_code=0x41)
↓
触发栈溢出
↓
6. ROP Chain执行
↓
获取Shell
┌─────────────────────────────────────────────────────┐
│ 攻击流程 │
└─────────────────────────────────────────────────────┘
1. CMD_HELLO (0x01)
↓
获取 session_key
↓
2. CMD_REGISTER_PROFILE (0x10)
↓
认证成功 (oracle_unlock)
↓
3. oracle_guess (CMD_EXEC_OP param=0x1337)
↓
暴力破解 magic_table[0..7]
↓
4. oracle_leak (CMD_EXEC_OP)
↓
泄露栈地址、堆地址、canary
↓
5. CMD_EXEC_OP (param=0x42, op_code=0x41)
[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-11-2 16:59
被Th3S编辑
,原因: 更新