比赛进入尾声,一切都是瞬息万变。
比赛进行到第二天时,在我们以为选手们都进入瓶颈的时候,hzqmwne势如破竹,一举拿下本题,带给我们惊喜。AceHub的发挥也依然非常稳定,顺利拿下本题。
本题有超过1700支战队围观,最终两支战队攻破成功。接下来我们一起来看看本题究竟有什么独特之处吧!
出题人介绍:Ex(星盟安全团队团长),初代战队队长,热爱物理学,喜欢钻研技术,痴迷于pwn技术的研究,主要擅长pwn方向。
出题思路:
模拟NtHeap的unlink
因为Windows上面的程序跑在 Linux 上面挺麻烦的,所以出题人就自己根据NtHeap的一些结构体规则,简单复刻出一个“只有一个双向循环链表”的Heap结构,由于NtHeap的结构体比较特殊,所以出题人在编写 Heap 结构体操作的时候直接使用宏来实现,对于有源码的出题人本人来说这样的代码还算直观,但是对于没有源码进行分析的师傅来说就比较抽象了。
思路的话和 Windows 的 unlink 很类似,这里就不赘述了。
unlink 漏洞
给用户的源程序
用于启动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
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