看雪·深信服 2021 KCTF 春季賽 | 第九題設計思路及解析
比賽進入尾聲,一切都是瞬息萬變。
比賽進行到第二天時,在我們以為選手們都進入瓶頸的時候,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
相關文章
- 看雪·深信服 2021 KCTF 春季賽 | 第十題設計思路及解析2021-05-31
- 看雪·深信服 2021 KCTF 春季賽 | 第七題設計思路及解析2021-05-25
- 看雪·深信服 2021 KCTF 春季賽 | 第八題設計思路及解析2021-05-25
- 看雪·深信服 2021 KCTF 春季賽 | 第六題設計思路及解析2021-05-21
- 看雪·深信服 2021 KCTF 春季賽 | 第三題設計思路及解析2021-05-14
- 看雪·深信服 2021 KCTF 春季賽 | 第四題設計思路及解析2021-05-17
- 看雪·深信服 2021 KCTF 春季賽 | 第五題設計思路及解析2021-05-17
- 看雪·深信服 2021 KCTF 春季賽 | 第二題設計思路及解析2021-05-12
- 看雪·眾安 2021 KCTF 秋季賽 | 第九題設計思路及解析2021-12-09
- 看雪·眾安 2021 KCTF 秋季賽 | 第十題設計思路及解析2021-12-16
- 看雪·眾安 2021 KCTF 秋季賽 | 第七題設計思路及解析2021-12-03
- 看雪·眾安 2021 KCTF 秋季賽 | 第六題設計思路及解析2021-12-01
- 看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析2021-11-29
- 看雪·眾安 2021 KCTF 秋季賽 | 第四題設計思路及解析2021-11-25
- 看雪·眾安 2021 KCTF 秋季賽 | 第三題設計思路及解析2021-11-22
- 看雪·眾安 2021 KCTF 秋季賽 | 第十一題設計思路及解析2021-12-15
- 今天中午12點,看雪.深信服 2021 KCTF春季賽,等你問鼎江湖!2021-05-10
- 看雪.紐盾 KCTF 2019 Q3 | 第九題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q2 | 第九題點評及解題思路2019-07-04
- 2020 KCTF秋季賽 | 第五題設計及解題思路2020-11-30
- 2020 KCTF秋季賽 | 第二題設計及解題思路2020-11-23
- 2021看雪KCTF逆向WP2021-05-21
- 看雪2022 KCTF春季賽完美收官!排行榜新鮮出爐2022-06-14
- 看雪CTF.TSRC 2018 團隊賽 第九題『諜戰』 解題思路2018-12-19
- 看雪.紐盾 KCTF 2019 Q3 | 第四題點評及解題思路2019-09-29
- 看雪.紐盾 KCTF 2019 Q3 | 第七題點評及解題思路2019-09-30
- 看雪.紐盾 KCTF 2019 Q3 | 第一題點評及解題思路2019-09-25
- 看雪.紐盾 KCTF 2019 Q3 | 第六題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第八題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q2 | 第七題點評及解題思路2019-07-02
- 看雪.紐盾 KCTF 2019 Q2 | 第六題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路2019-07-05
- 看雪.紐盾 KCTF 2019 Q2 | 第八題點評及解題思路2019-07-03
- 看雪.紐盾 KCTF 2019 Q2 | 第一題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第三題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第五題點評及解題思路2019-07-01