看雪·深信服 2021 KCTF 春季賽 | 第九題設計思路及解析

Editor發表於2021-05-28

看雪·深信服 2021 KCTF 春季賽 | 第九題設計思路及解析

比賽進入尾聲,一切都是瞬息萬變。


比賽進行到第二天時,在我們以為選手們都進入瓶頸的時候,hzqmwne勢如破竹,一舉拿下本題,帶給我們驚喜。AceHub的發揮也依然非常穩定,順利拿下本題。


看雪·深信服 2021 KCTF 春季賽 | 第九題設計思路及解析


本題有超過1700支戰隊圍觀,最終兩支戰隊攻破成功。接下來我們一起來看看本題究竟有什麼獨特之處吧!



出題團隊簡介

看雪·深信服 2021 KCTF 春季賽 | 第九題設計思路及解析

出題人介紹: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名。



看雪·深信服 2021 KCTF 春季賽 | 第九題設計思路及解析

公眾號ID:ikanxue

官方微博:看雪安全

商務合作:wsc@kanxue.com


相關文章