看雪·深信服 2021 KCTF 春季赛 | 第九题设计思路及解析

发布者:Editor
发布于:2021-05-28 16:09

比赛进入尾声,一切都是瞬息万变。


比赛进行到第二天时,在我们以为选手们都进入瓶颈的时候,hzqmwne势如破竹,一举拿下本题,带给我们惊喜。AceHub的发挥也依然非常稳定,顺利拿下本题。



本题有超过1700支战队围观,最终两支战队攻破成功。接下来我们一起来看看本题究竟有什么独特之处吧!



出题团队简介

出题人介绍:Ex(星盟安全团队团长),初代战队队长,热爱物理学,喜欢钻研技术,痴迷于pwn技术的研究,主要擅长pwn方向。



赛题设计思路


出题思路:


模拟NtHeap的unlink


因为Windows上面的程序跑在 Linux 上面挺麻烦的,所以出题人就自己根据NtHeap的一些结构体规则,简单复刻出一个“只有一个双向循环链表”的Heap结构,由于NtHeap的结构体比较特殊,所以出题人在编写 Heap 结构体操作的时候直接使用宏来实现,对于有源码的出题人本人来说这样的代码还算直观,但是对于没有源码进行分析的师傅来说就比较抽象了。


思路的话和 Windows 的 unlink 很类似,这里就不赘述了。


unlink 漏洞


attachment 文件夹"class="anchor" href="#attachment 文件夹">attachment 文件夹


给用户的源程序


docker 文件夹"class="anchor" href="#docker 文件夹">docker 文件夹


用于启动docker。

 

例如:

$ cd docker$ docker build -t kctf .$ docker run -dti -p55555:55555 kctf
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <sys/mman.h>
typedef struct chunk{    size_t pre_size, size;    struct chunk *fd, *bk;}chunk;
#define POINT(chk) ((chunk*)(((char *)(chk)) + 0x10))#define UNPOINT(chk) ((chunk*)(((char *)(chk)) - 0x10))#define CHUNK_SIZE(size) (((size) & 0xf) ? (((size) | 0xf) + 0x11) : ((size) + 0x10))#define NEXT_CHUNK(chk) ((chunk *)(((char *)chk) + (chk->size & 0xf0)))#define PRE_CHUNK(chk) ((chunk *)(((char *)chk) - (chk->pre_size & 0xf0)))#define ASSERT(value, expected, error_info) if((value) != (expected)){ fprintf(stderr, "Error Report: %s\n", (error_info)); exit(1); }#define SIZE_CHECK(size) ASSERT((!((size) & 0xf0)), 0, "Size check! ")
void *top_chunk;chunk *link_hint[0x10];
chunk free_list;
void my_free(char *mem){    unsigned int size;    chunk *victim, *temp, *next_chunk, *pre_chunk;    victim = (chunk *)(mem - 0x10);    if(mem == NULL)    {        return;    }
    ASSERT((size_t)victim & 0xf, 0, "Wrong ptr!");    ASSERT(victim->size & 0xe, 0, "Wrong size!");    next_chunk = NEXT_CHUNK(victim);
    // check    if(next_chunk != top_chunk)    {        ASSERT(next_chunk->size & 0xe, 0, "Wrong next_chunk size!");        SIZE_CHECK(next_chunk->size);        if((next_chunk->size & 1))        {            ASSERT(victim->size & 0xf0, next_chunk->pre_size & 0xf0, "Wrong heap! ");        }    }
    if(victim->size & 1)    {        pre_chunk = PRE_CHUNK(victim);        ASSERT(victim->size & 0xe, 0, "Wrong pre_chunk size!");        SIZE_CHECK(pre_chunk->size);        ASSERT(victim->pre_size & 0xf0, pre_chunk->size & 0xf0, "Wrong heap! ");    }

    size = victim->size & 0xf0;
    temp = UNPOINT(free_list.bk);    // insert
    if(temp == POINT(&free_list) || (temp->size & 0xf0) >= size)    {        ASSERT(temp->size & 0xe, 0, "Wrong size!");        ASSERT(temp->fd, POINT(&free_list), "Double link list error!");
        victim->bk = POINT(temp);        victim->fd = POINT(&free_list);        temp->fd = POINT(victim);        free_list.bk = POINT(victim);    }    else    {        while(temp->bk != POINT(&free_list))        {            temp = UNPOINT(temp->bk);            if((temp->size & 0xf0) >= size)            {                ASSERT(UNPOINT(temp->bk)->fd, POINT(temp), "Duoble link list error!");                ASSERT(UNPOINT(temp->fd)->bk, POINT(temp), "Double link list error!");
                victim->bk = POINT(temp);                victim->fd = temp->fd;                UNPOINT(temp->fd)->bk = POINT(victim);                temp->fd = POINT(victim);            }        }
        if(temp->bk == POINT(&free_list))        {            ASSERT(UNPOINT(temp->bk)->fd, POINT(temp), "Duoble link list error!");            ASSERT(UNPOINT(temp->fd)->bk, POINT(temp), "Double link list error!");
            victim->bk = POINT(&free_list);            victim->fd = POINT(temp);            temp->bk = POINT(victim);            free_list.fd = POINT(victim);        }    }
    next_chunk->pre_size = victim->size & 0xf0;    //    next_chunk->size |= 1;    link_hint[(victim->size & 0xf0) >> 4] = victim;}
void *my_malloc(unsigned int n){    unsigned int size;    chunk *temp, *victim,  *next_chunk, *pre_chunk;
    size = CHUNK_SIZE(n);    temp = UNPOINT(free_list.bk);    while((temp != &free_list) && (temp->size & 0xf0) <= size)    {        if((temp->size & 0xf0) == size)        {            ASSERT(UNPOINT(temp->bk)->fd, POINT(temp), "Double link list error!");            ASSERT(UNPOINT(temp->fd)->bk, POINT(temp), "Double link list error!");            // more checks            if(link_hint[(size & 0xf0) >> 4] == temp)            {                SIZE_CHECK(UNPOINT(temp->bk)->size);                SIZE_CHECK(UNPOINT(temp->fd)->size);                ASSERT(UNPOINT(temp->bk)->size & 0xe, 0, "Wrong heap!");                ASSERT(UNPOINT(temp->fd)->size & 0xe, 0, "Wrong heap!");
                victim = UNPOINT(temp->fd);
                if(victim != &free_list)                {                    next_chunk = NEXT_CHUNK(victim);
                    // check                    if(next_chunk != top_chunk)                    {                        ASSERT(next_chunk->size & 0xe, 0, "Wrong next_chunk size!");                        SIZE_CHECK(next_chunk->size);                        if((next_chunk->size & 1))                        {                            ASSERT(victim->size & 0xf0, next_chunk->pre_size & 0xf0, "Wrong heap! ");                        }                    }
                    if(victim->size & 1)                    {                        pre_chunk = PRE_CHUNK(victim);                        ASSERT(victim->size & 0xe, 0, "Wrong pre_chunk size!");                        SIZE_CHECK(pre_chunk->size);                        ASSERT(victim->pre_size & 0xf0, pre_chunk->size & 0xf0, "Wrong heap! ");                    }                }


                victim = UNPOINT(temp->bk);
                if(victim != &free_list)                {                    next_chunk = NEXT_CHUNK(victim);
                    // check                    if(next_chunk != top_chunk)                    {                        ASSERT(next_chunk->size & 0xe, 0, "Wrong next_chunk size!");                        SIZE_CHECK(next_chunk->size);                        if((next_chunk->size & 1))                        {                            ASSERT(victim->size & 0xf0, next_chunk->pre_size & 0xf0, "Wrong heap! ");                        }                    }
                    if(victim->size & 1)                    {                        pre_chunk = PRE_CHUNK(victim);                        ASSERT(victim->size & 0xe, 0, "Wrong pre_chunk size!");                        SIZE_CHECK(pre_chunk->size);                        ASSERT(victim->pre_size & 0xf0, pre_chunk->size & 0xf0, "Wrong heap! ");                    }                }            }
            // unlink            UNPOINT(temp->bk)->fd = temp->fd;            UNPOINT(temp->fd)->bk = temp->bk;
            next_chunk = NEXT_CHUNK(temp);            next_chunk->size &= 0xf0;
            return POINT(temp);        }        else        {            temp = UNPOINT(temp->bk);        }    }
    temp = top_chunk;    ASSERT(temp->size & 0xf0, 0, "Top chunk error!");    temp->size |= size;    top_chunk = (char *)top_chunk + size;
    return POINT(temp);}

void initial(){
    setvbuf(stdin, NULL, _IONBF, 0);    setvbuf(stdout, NULL, _IONBF, 0);    setvbuf(stderr, NULL, _IONBF, 0);
    alarm(60);}
int read_n(char *buf, int size){    int bytes;
    bytes = read(0, buf, size);    if(bytes <= 0)    {        exit(EXIT_FAILURE);    }    if(buf[bytes - 1] == '\n')    {        buf[bytes - 1] = '\0';    }
    return bytes;}
unsigned int get_int(){    char buf[0x100];    memset(buf, 0, sizeof(buf));    read_n(buf, sizeof(buf) - 1);    return atol(buf);}
#define LENGTH 0x10char *ptr[LENGTH];unsigned int ptr_size[LENGTH];
void add(){    unsigned int index, i, size;
    for(i = 0; i < LENGTH; i++)    {        if(!ptr[i])        {            index = i;            break;        }    }
    if(index >= LENGTH)    {        puts("No space!");        return;    }
    printf("Size: ");    size = get_int();    if(size >= 0xf0)    {        puts("Invalid size!");        return;    }
    ptr[index] = my_malloc(size);    ptr_size[index] = size;    printf("Content: ");    read_n(ptr[index], size);}
void delete(){    unsigned int index;    printf("Index: ");    index = get_int();    if(index < LENGTH && ptr[index])    {        my_free(ptr[index]);    }    else    {        puts("Invalid index!");    }}
void edit(){    unsigned int index;    printf("Index: ");    index = get_int();    if(index < LENGTH && ptr[index])    {        printf("Content: ");        read_n(ptr[index], ptr_size[index]);    }    else    {        puts("Invalid index!");    }}
int main(){    char buf[0x100];    int option = 0;    unsigned int length;    initial();
    puts("KCTF\n");
    top_chunk = mmap(NULL, 0x40000, 3, 0x22, -1, 0);    ((size_t*)top_chunk)[1] = 0x20;    top_chunk = (size_t*)top_chunk + 4;    free_list.size = 0x20;    free_list.bk = POINT(&free_list);    free_list.fd = POINT(&free_list);
    do    {        printf( "1. add\n"                "2. delete\n"                "3. edit\n"                "4. exit\n"                "Your choice: ");
        option = get_int();        switch(option)        {        case 1:            add();            break;        case 2:            delete();            break;        case 3:            edit();            break;        case 4:            break;        default:            puts("Invalid choice!");            break;        }
        puts("");    }while(option != 4);
    puts("Goodbye!");    return 0;}

赛题解析


本赛题解析由看雪论坛 mb_mgodlfyn 给出:


图片


解析文章请看本帖子:https://bbs.pediy.com/thread-267806.htm



往期解析


1. 看雪·深信服 2021 KCTF 春季赛 | 第二题设计思路及解析

2. 看雪·深信服 2021 KCTF 春季赛 | 第三题设计思路及解析

3. 看雪·深信服 2021 KCTF 春季赛 | 第四题设计思路及解析

4. 看雪·深信服 2021 KCTF 春季赛 | 第五题设计思路及解析

5. 看雪·深信服 2021 KCTF 春季赛 | 第六题设计思路及解析

6. 看雪·深信服 2021 KCTF 春季赛 | 第七题设计思路及解析

7. 看雪·深信服 2021 KCTF 春季赛 | 第八题设计思路及解析


主办方


看雪CTF(简称KCTF)是圈内知名度最高的技术竞技之一,从原CrackMe攻防大赛中发展而来,采取线上PK的方式,规则设置严格周全,题目涵盖Windows、Android、iOS、Pwn、智能设备、Web等众多领域。


看雪CTF比赛历史悠久、影响广泛。自2007年以来,看雪已经举办十多个比赛,与包括金山、360、腾讯、阿里等在内的各大公司共同合作举办赛事。比赛吸引了国内一大批安全人士的广泛关注,历年来CTF中人才辈出,汇聚了来自国内众多安全人才,高手对决,精彩异常,成为安全圈的一次比赛盛宴,突出了看雪论坛复合型人才多的优势,成为企业挑选人才的重要途径,在社会安全事业发展中产生了巨大的影响力。


合作伙伴


图片


深信服科技股份有限公司成立于2000年,是一家专注于企业级安全、云计算及基础架构的产品和服务供应商,致力于让用户的IT更简单、更安全、更有价值。目前深信服在全球设有50余个分支机构,员工规模超过7000名。



公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com



声明:该文观点仅代表作者本人,转载请注明来自看雪