首页
社区
课程
招聘
[原创]InCTF 内核Pwn之 Kqueue
发表于: 2021-8-23 16:26 21193

[原创]InCTF 内核Pwn之 Kqueue

2021-8-23 16:26
21193

据说InCTF国际赛是印度的强网杯233333 。

官方WP: https://blog.bi0s.in/2021/08/17/Pwn/InCTFi21-Kqueue/

比赛的时候有点事情,没怎么看题,后面重新复现一下,感觉我的exp比官方的wp简单一些。

给出了源码。

在内核态实现了一个队列管理程序。

最多管理五个队列(其实是6个,他写的有问题,后面再说)。

每个队列由一个 (queue *) 查找,维护。

单个队列的管理结构是一个 queue

队列中的每一项是一个 queue_entry

我们主要关注:

首先,__builtin_umulll_overflow 是gcc 内置的用于检测乘法溢出的函数:

https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html

他做的事情就是去检测 sizeof(queue_entry) * (request.max_entries+1)是否乘法溢出(这个结果被放在space里)

问题在于:request.max_entries 本身并没有进行溢出检测。而它是一个32位无符号数,如果request.max_entries = 0xffffffff 那么 +1 后会造成整数溢出,通过检测。

而此时 request.max_entries 为一个极大值。

此时space变量计算错误(为0),导致 queue_size 为一个极小值。

queue_size = sizeof(queue)

进而queue_size也变成一个极小值

最后:queue->max_entries = 0xffffffff,导致循环被跳过,没有真正分配queue_entry.

值得一提的是,他这个函数后面写的也有问题,可以越界分配一个queue。。然后free掉就可以直接panic(好像是他自己写错了。。)

由于我们的构造,导致 queue_size 变成了一个极小值。进而此处 new_queue 分配过小 。而在for循环里又直接向new_queue的对应位置拷贝了数据。并且拷贝的数据是 kqueue_entry->data ,此时kqueue也是分配的有问题(具体可以回到create里,总之就是kqueue_entry没有正常分配空间)

运行后会panic掉,因为此时 kqueue_entry->data 不是一个合法的值。

根本原因是在:

中 kqueue_entry指针越界,访问了不合法位置的数据。

在堆上喷射大量的 seq_operations

通过堆溢出overwrite掉ops[0],即:void * (*start) (struct seq_file *m, loff_t *pos);

实现hijack rip。

一个poc如下:

https://paste.ubuntu.com/p/b3j29GhtQt/

过尝试,当我们第二次分配queue时,<u>有很大概率被分配到第一次的data数据的上方</u>。

我们将第一次的data数据进行恶意的构造,然后在第二次完成堆溢出,覆盖函数指针劫持rip,然后在用户态执行shellcode即可。

由于没有开启smep、smap,我们ret2usr之后在用户态再swapgs,iretq一下重新着陆到shell函数即可。

我的exp如下,感觉比官网的简单不少。只需要一次堆溢出就可以pwn。

效果:

图片描述

 
 
 
queue *kqueues[MAX_QUEUES] = {(queue *)NULL};
queue *kqueues[MAX_QUEUES] = {(queue *)NULL};
 
 
/* Sometimes , waiting in a queue is so boring, but wait , this isn't any ordinary queue */
 
typedef struct{
    uint16_t data_size;     //队列每一项entry的大小
    uint64_t queue_size; //队列整体的大小
    uint32_t max_entries;//队列最多的项数
    uint16_t idx;
    char* data;
}queue;
/* Sometimes , waiting in a queue is so boring, but wait , this isn't any ordinary queue */
 
typedef struct{
    uint16_t data_size;     //队列每一项entry的大小
    uint64_t queue_size; //队列整体的大小
    uint32_t max_entries;//队列最多的项数
    uint16_t idx;
    char* data;
}queue;
typedef struct queue_entry queue_entry;
struct queue_entry{
    uint16_t idx;                //当前entry的idx
    char *data;                    //当前entry维护的数据
    queue_entry *next;    //next指针
};
typedef struct queue_entry queue_entry;
struct queue_entry{
    uint16_t idx;                //当前entry的idx
    char *data;                    //当前entry维护的数据
    queue_entry *next;    //next指针
};
static noinline long create_kqueue(request_t request){
    long result = INVALID;
        // 最多是五个队列
    if(queueCount > MAX_QUEUES)
        err("[-] Max queue count reached");
    // 创建队列时元素可以等于1,不能小于1
    if(request.max_entries<1)
        err("[-] kqueue entries should be greater than 0");
    if(request.data_size>MAX_DATA_SIZE)
        err("[-] kqueue data size exceed");
    queue_entry *kqueue_entry;
 
    ull space = 0;
    if(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries+1),&space) == true)            // 整数溢出
        err("[-] Integer overflow");
 
    /* Size is the size of queue structure + size of entry * request entries */
    ull queue_size = 0;
    if(__builtin_saddll_overflow(sizeof(queue),space,&queue_size) == true)
        err("[-] Integer overflow");
 
    if(queue_size>sizeof(queue) + 0x10000)
        err("[-] Max kqueue alloc limit reached");
 
    queue *queue = validate((char *)kmalloc(queue_size,GFP_KERNEL));
    queue->data = validate((char *)kmalloc(request.data_size,GFP_KERNEL));
 
    queue->data_size   = request.data_size;    
    queue->max_entries = request.max_entries;  
    queue->queue_size  = queue_size;           
 
    kqueue_entry = (queue_entry *)((uint64_t)(queue + (sizeof(queue)+1)/8));
 
    queue_entry* current_entry = kqueue_entry;
    queue_entry* prev_entry = current_entry;
 
    uint32_t i=1;
 
    // [1,request.max_entries]
    for(i=1;i<request.max_entries+1;i++){
        if(i!=request.max_entries)
            prev_entry->next = NULL;
 
        current_entry->idx = i;
        current_entry->data = (char *)(validate((char *)kmalloc(request.data_size,GFP_KERNEL)));
 
        /* Increment current_entry by size of queue_entry */
        current_entry += sizeof(queue_entry)/16;
 
        /* Populate next pointer of the previous entry */
        prev_entry->next = current_entry;
        prev_entry = prev_entry->next;
    }
 
 
    // 这里尝试找到kqueue中一个不为NULL的项
    uint32_t j = 0;
    for(j=0;j<MAX_QUEUES;j++){
        if(kqueues[j] == NULL)
            break;
    }
    // breakfor循环后 j = MAX_QUEUES,不会触发下面的if
    if(j>MAX_QUEUES)
        err("[-] No kqueue slot left");
 
    // 导致我们越界分配了一个 queue?
    /* Assign the newly created kqueue to the kqueues */
    // queue *queue = validate((char *)kmalloc(queue_size,GFP_KERNEL));
    kqueues[j] = queue;
    queueCount++;
    result = 0;
    return result;
}
static noinline long create_kqueue(request_t request){
    long result = INVALID;
        // 最多是五个队列
    if(queueCount > MAX_QUEUES)
        err("[-] Max queue count reached");
    // 创建队列时元素可以等于1,不能小于1
    if(request.max_entries<1)
        err("[-] kqueue entries should be greater than 0");
    if(request.data_size>MAX_DATA_SIZE)
        err("[-] kqueue data size exceed");
    queue_entry *kqueue_entry;
 
    ull space = 0;
    if(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries+1),&space) == true)            // 整数溢出
        err("[-] Integer overflow");
 
    /* Size is the size of queue structure + size of entry * request entries */
    ull queue_size = 0;
    if(__builtin_saddll_overflow(sizeof(queue),space,&queue_size) == true)
        err("[-] Integer overflow");
 
    if(queue_size>sizeof(queue) + 0x10000)
        err("[-] Max kqueue alloc limit reached");
 
    queue *queue = validate((char *)kmalloc(queue_size,GFP_KERNEL));
    queue->data = validate((char *)kmalloc(request.data_size,GFP_KERNEL));
 
    queue->data_size   = request.data_size;    
    queue->max_entries = request.max_entries;  
    queue->queue_size  = queue_size;           
 
    kqueue_entry = (queue_entry *)((uint64_t)(queue + (sizeof(queue)+1)/8));
 
    queue_entry* current_entry = kqueue_entry;
    queue_entry* prev_entry = current_entry;
 
    uint32_t i=1;
 
    // [1,request.max_entries]
    for(i=1;i<request.max_entries+1;i++){
        if(i!=request.max_entries)
            prev_entry->next = NULL;
 
        current_entry->idx = i;
        current_entry->data = (char *)(validate((char *)kmalloc(request.data_size,GFP_KERNEL)));
 
        /* Increment current_entry by size of queue_entry */
        current_entry += sizeof(queue_entry)/16;
 
        /* Populate next pointer of the previous entry */
        prev_entry->next = current_entry;
        prev_entry = prev_entry->next;
    }
 
 
    // 这里尝试找到kqueue中一个不为NULL的项
    uint32_t j = 0;
    for(j=0;j<MAX_QUEUES;j++){
        if(kqueues[j] == NULL)
            break;
    }
    // breakfor循环后 j = MAX_QUEUES,不会触发下面的if
    if(j>MAX_QUEUES)
        err("[-] No kqueue slot left");
 
    // 导致我们越界分配了一个 queue?
    /* Assign the newly created kqueue to the kqueues */
    // queue *queue = validate((char *)kmalloc(queue_size,GFP_KERNEL));
    kqueues[j] = queue;
    queueCount++;
    result = 0;
    return result;
}
ull space = 0;
if(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries+1),&space) == true)            // 整数溢出
    err("[-] Integer overflow");
ull space = 0;
if(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries+1),&space) == true)            // 整数溢出
    err("[-] Integer overflow");
 
 
 
 
queue->max_entries = request.max_entries;
queue->max_entries = request.max_entries;
ull queue_size = 0;
if(__builtin_saddll_overflow(sizeof(queue),space,&queue_size) == true)
    err("[-] Integer overflow");
ull queue_size = 0;
if(__builtin_saddll_overflow(sizeof(queue),space,&queue_size) == true)
    err("[-] Integer overflow");
 
queue *queue = validate((char *)kmalloc(queue_size,GFP_KERNEL));
queue->queue_size  = queue_size;
queue *queue = validate((char *)kmalloc(queue_size,GFP_KERNEL));
queue->queue_size  = queue_size;
//request.max_entries+1 = 0
for(i=1;i<request.max_entries+1;i++){
      ......
    }
//request.max_entries+1 = 0
for(i=1;i<request.max_entries+1;i++){
      ......
    }
static noinline long save_kqueue_entries(request_t request){
  ......
  // 为此需要save的队列分配空间,size为queue->queue->size
    char *new_queue = validate((char *)kzalloc(queue->queue_size,GFP_KERNEL));
      // 先拷贝queue头数据,这里没有问题
    if(queue->data && request.data_size)
        validate(memcpy(new_queue,queue->data,request.data_size));
    else
        err("[-] Internal error");
 
      // 再拷贝所有queue的entry数据,这里发生了溢出
 
    uint32_t i=0;
    for(i=1;i<request.max_entries+1;i++){
        if(!kqueue_entry || !kqueue_entry->data)
            break;
        if(kqueue_entry->data && request.data_size)
            validate(memcpy(new_queue,kqueue_entry->data,request.data_size));
        else
            err("[-] Internal error");
        kqueue_entry = kqueue_entry->next;
        new_queue += queue->data_size;
    }
  ......
 
}
static noinline long save_kqueue_entries(request_t request){
  ......
  // 为此需要save的队列分配空间,size为queue->queue->size
    char *new_queue = validate((char *)kzalloc(queue->queue_size,GFP_KERNEL));
      // 先拷贝queue头数据,这里没有问题
    if(queue->data && request.data_size)
        validate(memcpy(new_queue,queue->data,request.data_size));
    else
        err("[-] Internal error");
 
      // 再拷贝所有queue的entry数据,这里发生了溢出
 
    uint32_t i=0;
    for(i=1;i<request.max_entries+1;i++){
        if(!kqueue_entry || !kqueue_entry->data)
            break;
        if(kqueue_entry->data && request.data_size)
            validate(memcpy(new_queue,kqueue_entry->data,request.data_size));
        else
            err("[-] Internal error");
        kqueue_entry = kqueue_entry->next;
        new_queue += queue->data_size;
    }
  ......
 
}
 
0xffffffffc00004ce <save_kqueue_entries+238>    call   memcpy <memcpy>
       dest: 0xffff88801e3b9fa0 ◂— sbb    al, 0x1d /* 0x232221201f1e1d1c */
       src: 0xdead000000000100
       n: 0x20
0xffffffffc00004ce <save_kqueue_entries+238>    call   memcpy <memcpy>
       dest: 0xffff88801e3b9fa0 ◂— sbb    al, 0x1d /* 0x232221201f1e1d1c */
       src: 0xdead000000000100
       n: 0x20
validate(memcpy(new_queue,kqueue_entry->data,request.data_size));
validate(memcpy(new_queue,kqueue_entry->data,request.data_size));
 
 
0xffffffffc00004ce <save_kqueue_entries+238>    call   memcpy <memcpy>
       dest: 0xffff88801dc10980 —▸ 0xffffffff812005d0 (single_start) ◂— xor    eax, eax /* 0x940f003e8348c031 */
       src: 0xffffea0000683e30 ◂— add    byte ptr [rax], al /* 0x100000000000000 */
       n: 0x20
0xffffffffc00004ce <save_kqueue_entries+238>    call   memcpy <memcpy>
       dest: 0xffff88801dc10980 —▸ 0xffffffff812005d0 (single_start) ◂— xor    eax, eax /* 0x940f003e8348c031 */
       src: 0xffffea0000683e30 ◂— add    byte ptr [rax], al /* 0x100000000000000 */
       n: 0x20
 
[    8.977709] RIP: 0010:0x100000000000000
[    8.978444] Code: Bad RIP value.
 
[    8.987225] Call Trace:
[    8.989460]  ? seq_read+0x89/0x3d0
[    8.989770]  ? vfs_read+0x9b/0x180
[    8.989895]  ? ksys_read+0x5a/0xd0
[    8.990136]  ? do_syscall_64+0x3e/0x70
[    8.990332]  ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
[    8.990577] Modules linked in: kqueue(O)
[    8.992286] ---[ end trace 8ca9e01e6f1c5a76 ]---
[    8.992629] RIP: 0010:0x100000000000000
[    8.977709] RIP: 0010:0x100000000000000
[    8.978444] Code: Bad RIP value.

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2021-8-23 16:28 被Roland_编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (2)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
有附件吗,谢谢
2021-9-3 09:01
0
雪    币: 2278
活跃值: (12799)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
3
pwn一直处在菜鸡水准
2021-9-6 19:27
0
游客
登录 | 注册 方可回帖
返回
//