堆的House Of Force,House of spirit,Off by one 学习
0x00:
这其实是接着上个《SploitFun Linux x86 Exploit 开发系列教程》的后续实验,书上关于堆的很多漏洞现在已经不可用或需要特定环境,所以故拿ctf题目继续学习。
堆的House Of Force,House of spirit,Off by one 学习
House Of Force
漏洞原理
House Of Force 是一种堆利用方法,但是并不是说 House Of Force 必须得基于堆漏洞来进行利用。如果一个堆 (heap based) 漏洞想要通过 House Of Force 方法进行利用,需要以下条件:
1.能够以溢出等方式控制到 top chunk 的 size 域
2.能够自由地控制堆分配尺寸的大小
House Of Force 产生的原因在于 glibc 对 top chunk 的处理,根据前面堆数据结构部分的知识我们得知,进行堆分配时,如果所有空闲的块都无法满足需求,那么就会从 top chunk 中分割出相应的大小作为堆块的空间。
那么,当使用 top chunk 分配堆块的 size 值是由用户控制的任意值时会发生什么?答案是,可以使得 top chunk 指向我们期望的任何位置,这就相当于一次任意地址写。然而在 glibc 中,会对用户请求的大小和 top chunk 现有的 size 进行验证
1 |
|
然而,如果可以篡改 size 为一个很大值,就可以轻松的通过这个验证,这也就是我们前面说的需要一个能够控制 top chunk size 域的漏洞。
1 |
|
一般的做法是把 top chunk 的 size 改为 - 1,因为在进行比较时会把 size 转换成无符号数,因此 -1 也就是说 unsigned long 中最大的数,所以无论如何都可以通过验证。
1 |
|
之后这里会把 top 指针更新,接下来的堆块就会分配到这个位置,用户只要控制了这个指针就相当于实现任意地址写任意值 (write-anything-anywhere)。
与此同时,我们需要注意的是,topchunk 的 size 也会更新,其更新的方法如下
1 |
|
所以,如果我们想要下次在指定位置分配大小为 x 的 chunk,我们需要确保 remainder_size 不小于 x+ MINSIZE。
2016 BCTF bcloud
程序分析
检查程序,程序只开启了canary和nx保护
然后用ida分析,程序刚开始会创建一个名字,这里最多读取64个字节的&s,然后创建v2,这里malloc是在读取&s之后的,如果我们输入了64个字节&s后的/x00截断符就会被v2的地址覆盖,strcpy就会把&s和v2的地址复制进去
然后下面的函数就会输出&s就会把v2地址输出
还有一处漏洞在设置org和host处,这里也是和上面一样的漏洞,当输入0x40个&s时,后面v2的值就会把末尾的\x00给覆盖,strcpy就会继续往下复制,而v2分配的最晚,没有堆被free,因此v2分配的堆快是与top chunk相邻的堆快,这里如果复制了0x40个数据,之后的v2的值就会覆盖top chunk的头部的pre_size,之后v3的内容就会先覆盖topchunk的size,这样我们就可以把topchunk的size覆盖成FF FF FF FF,也就是-1。
New_note函数
这里它不会检查我们输入的长度,就满足了house of force的第二个条件,我们可以自定义分配的堆大小
注意这里在创建堆快时,会把堆快的指针存放在0x804b120处,还有堆快的大小在0x804b0a0处,注意这里malloc的堆快大小是v2+4,也就是我们输入的大小+4
Show选项
这个选项什么都没输出
Edit选项
Delete选项
Syn选项,没有什么用
利用思路
-
首先,我们要在输入name的时候输入0x40大小的的参数,然后就会泄露出name创建的内存空间的指针(第一个堆快的指针),利用这个我们可以计算top chunk的位置。
-
然后在输入org以及host的时候,我们先输入0x40个数据,然后v2指针的位置就会覆盖输入数据末尾的/x00截断符,然后strcpy就会继续往下复制,这里v2是后创建的堆快,所以和top chunk相邻,这时候v2的指针覆盖了top chunk的prev_size的部分,v3内容就会覆盖top chunk的size
- 我们先把top chunk的size覆盖成FF FF FF FF 这样就可以通过top chunk分配的验证,然后选择new一个chunk,大小为0x804b0a0 -8 - (泄露的第一个堆快地址- 8+ 0x48*3) - 12我们要把top chunk分配到0x804b0a0处,这样就可以控制分配的堆快大小以及位置了。
计算方法:泄露的地址-8是因为还有堆头,3个0x48是name+host+org的大小,-12是因为top chunk的堆头+程序中malloc+4,-8是为了让写入的数据刚好在0x804b0a0。
-
然后我们可以先把free_ got地址劫持为puts_plt的地址,然后调用delete函数泄露atoi的地址,就可以计算libc基址,得到system的地址了。
-
我们得先修改3个堆快的大小为足够大小,然后指针改为atoi_got,free_got,atoi_got,利用puts泄露一个atoi的地址,另外一个用来修改atoi的地址为system_addr,然后在开始调用atoi时输入/bin/sh就可以执行system(/bin/sh)拿到shell
Gdb调试
开始输入的0x40个a,泄露的第一块堆的地址
开始时输入name,host,org创建的三个堆块,topchunk在org的下方,也就是0xa0090d8处
通过计算偏移,第一次new调用malloc把topchunk分配到了0x804b098处
我们就可以从0x804b0a0处开始修改,先把前三个堆快大小修改为足够大(能修改got表大小即可),然后修改0x804b120处的堆指针为atoi_got,free_got,atoi_got
之后把free_got修改为puts_plt泄露的atoi地址
Payload
1 |
|
House of Spirit
漏洞原理
该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。
要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即
· fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
· fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
· fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
· fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem 。
· fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。
2014 hack.lu oreo
程序分析
这个程序只开启了nx和canary保护
然后拖入ida进行分析
程序有5个功能
添加功能
这里可以看出来这个分配内存的大致结构了
0 description
0x19 name
0x34 前一个内存的地址
0x38
((_DWORD )chunk_addr_dword_804A288 + 13) = v1;,这里是把v1=之前的chunk_addr_dword_804A288(这个地方记录上次分配的内存的地址)处的地址+134(0x34)的地方赋值成了当前分配的内存的地址,这里DWORD是4字节,所以是chunk_addr_dword_804A288+134的位置
然后这里读取name的时候存在溢出,这里可以读入56个字节,但是从chunk_addr_dword_804A288+25到+0x38只有31个字节,可以通过溢出覆盖到前一个内存的地址
还有一点是这里每创建一个堆快就会把dword_804A2A4的值加1
show展示功能
从最后一个创建的堆开始,根据堆中的前项指针*((_DWORD *)chunk_addr_dword_804A288 + 13),依次显示之前创建的堆块。
Order(删除)功能
依次free掉创建的堆快
Leave功能
这里是往804A2A8的地方写入输入的内容,最大128字节,而0x804a2a8这个地方一开始是0x0804a2c0,也就是往0x0804a2c0写入输入的内容
利用思路
-
首先我们得泄露出libc的基地址,用来计算system的地址,思路是利用输入name的溢出,把堆的前项指针改成printf函数的got表地址,然后通过show选项,打印出printf的真实地址用来计算libc的基址。
-
之后利用House of Spirit,伪造一个fastbin,然后free后把我们可以分配的内存改到0x804A2A8,因为这个地方存放的是leave功能写入的地址,如果我们能控制这个地方,然后改成我们想要修改的函数的got表地址就可以进行利用
-
伪造fastbin的方法:
首先每创建一个堆快就会把dword_804A2A4的值加1,而这个位置相当于我们在0x804A2A0伪造的chunk的size值,因为我们创建的chunk是0x40大小的,所以我们得先创建0x40个chunk把这里的值改成0x40,因为这里我们伪造的chunk的size位是0x804A2A4。注意这里创建的chunk的每个前项指针得设置成NULL,这样在free的时候就不会把依次free所有的chunk了。
然后还需要绕过一个检查:
· fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem 。
这里我们利用leave函数的功能进行绕过,这个选项是在0x0804a2c0处开始修改,得修改伪造的chunk的下一个size位为合适的大小
- 成功free后,我们下一次申请的内存地址就会从 0x0804a2a8开始了,然后把这个地方改为strlen的got表,调用leave选项的函数,这里修改完后就会直接调用strlen函数,相当于system(输入的内容),所以这里要用system的地址+’;bin/sh’,用来拿到shell,用分号是分开调用,这里实际会调用system(system地址)+system(bin/sh)
Gdb调试
首先创建4个chunk进行实验,可以看到0x0804a2a8的位置存储的是0x0804a2c0,0x0804a2a4位置存储的是4。
查看一个chunk,可以看到和我们猜测的结构一致
使用leave功能,可以看到输入的内容存放在了0x804a2c0的位置
开始时利用name的溢出把第一个申请的chunk的前项指针覆盖成put的got表地址
创建40个chunk,然后用leave写入0x804a2c0伪造fastbin的内存情况
Free之后成功把0x804a2a0的内存地址放入了fastbin
Payload脚本
1 |
|
实验十一-Off-By-One 漏洞(基于堆)
漏洞原理
off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括
- 使用循环语句向堆块中写入数据时,循环的次数设置错误(这在 C 语言初学者中很常见)导致多写入了一个字节。
- 字符串操作不合适
一般来说,单字节溢出被认为是难以利用的,但是因为 Linux 的堆管理机制 ptmalloc 验证的松散性,基于 Linux 堆的 off-by-one 漏洞利用起来并不复杂,并且威力强大。
off-by-one 利用思路
1.溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
2.溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use 位被清,这样前块会被认为是 free 块。(1) 这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。(2) 另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的大小与prev_size 是否一致。(glibc2.28前版本有效)
下面是ctf例题:
Asis CTF 2016 b00ks
程序分析
该程序是64位,开启了nx和pie,注意这里开启了Full RELRO保护,不能进行复写got表的操作,需要考虑劫持之类的操作
主函数如下
有几个功能,增,删,改,查看,还有个修改authorname的功能,这个函数在一开始就会调用一次,而我们的漏洞点就是出在这里。函数内部如下
sub_9f5内容
可以看到是把我们的输入一个个进行读取,但是这里有一个漏洞,如果我们输入的是32个字节,因为循环是从0开始,当到31的时候我们输入的读取就已经结束了,但是循环还会继续一次,最后会把*buf的第33位置为\x00,这就是off by one的null字节溢出。
新建book的函数
要求输入一个name和一个description,name的大小说是最大为32,但是实际上这里并没有检查,可以创建我们想要的大小,description也一样。
Name和description创建完后还会创建一个结构体用来存储book的指针
这个结构体大概内容是
0x0 book的id
0x8 book的name的地址
0x10 book的description的地址
0x18 book的size
然后会把这个结构体的指针放在*((_QWORD *)off_202010 + v2)的位置,这个位置实际上就是author_name(off_202018)的位置+0x20的地方,也就是说如果输入了32个字节的author_name,那么最后的\x00就会覆盖到第一个创建的book的低地址。
删除函数
编辑函数,根据off_202010位置存放的结构体指针进行编辑
展示book的函数,这里还会把author_name展示出来
选项5是和开头一样的修改author_name的函数,就不再展示
利用思路
1.首先因为author_name紧接着就是book1结构体的地址,如果溢出了一个\x00就会覆盖到book1的结构体地址,,这个后面会用到,因为我们是先输入的name,那么如果先输入32字节的name,再创建book1的话,book1的地址就会覆盖name末尾的截断字符,如果选择5输出了author_name的话,就可以输出book1的地址了。
2.libc****基址泄露方法
因为题目开启了pie,而且没有特别明显的libc基址泄露,但是这里有一个技巧,如果我们申请的内存块足够大,就会调用mmap来扩展内存,由于调用mmap分配的内存和libc基址的偏移是固定的,我们只要泄露book2分配内存的地址就可以知道libc基址
验证:这里创建book2申请了2个大小0x21000的空间
选择Book2的name存储的地址是 0x00007f9899a27010,libc加载的基址是
0x00007f9899475000,偏移为0x00007f9899a27010-0x00007f9899475000=0x5b2010
第二次申请的结果
Name的内存地址是0x00007f4a27340010,libc基址是0x00007f4a26d8e000,偏移为0x00007f4a27340010-0x00007f4a26d8e000=0x5b2010,可以看到2次申请偏移是一样的
可以看到这里偏移是一样的
3.泄露book2****地址的方法
这里可以利用off by one的漏洞,因为我们的选项5可以重新修改一开始设置的author_name,然后让末尾的\x00覆盖掉book1的结构体指针(这里要注意,我们可以修改的区域是book1的name和description,创建book1的时候要让这两个区域能修改到覆盖后的book1的结构体指针),这样就可以在这个区域伪造一个fake chunk,把name和description的指针修改为book2结构体指针的name和description的地址(由于每次申请的结构体是0x20,所以可以通过泄露的book1结构体的地址进行计算),就可以利用printf选项打印出book2申请的name和description的地址了,然后利用这个地址计算libc的基址。
4.拿到shell
这里需要利用__free_hook函数,当调用free函数的时候__free_hook函数不为NULL会优先调用__free_hook****里面所写的内容,我们可以利用编辑功能把book2的结构体中指向description的指针修改为__free_hook函数,再利用编辑功能编辑book2,修改__free_hook的内容为onegadget的execve("/bin/sh", rsp+0x30, environ),然后执行free book2,当free了book2的description指针的时候就会执行__free_hook里的内容拿到shell。
Gdb调试
验证输入author_name的off by one
首先在一开始输入author_name的时候输入32个A,然后创建book1和book2,这里需要用到find命令寻找A存储的地方
可以看到book1的地址覆盖了author_name末尾的\x00
这时候选择打印功能就会输出book1结构体的内容了
然后再次选择修改author_name输入32个A导致了book1结构体地址末尾被覆盖成了\x00(这里重新调试了,可以看到如果分配的内存大小固定的话,重新调试末尾的地址是一样的)
这里查看分配的堆快,我们需要把description的区域分配到能够控制到覆盖后的(这里是0x559595831100)的地方,就可以伪造book1结构体的chunk了
这里编辑book1的description+0x20的偏移就可以编辑到通过off by one 覆盖的0x559595831100的地方进行伪造chunk了(注意这里需要之前创建description有足够的大小)
然后伪造book1的结构体把name和description的位置写入book2的name和description的结构体指针的地址(相对book1结构体地址的偏移为0x38和0x40)
再次选择打印就会打印出book2通过mmap分配的内存的地址了
之后的利用就和利用思路一样,利用编辑功能把book2的结构体(这里是伪造book1的description)中指向description的指针修改为__free_hook函数,再利用编辑功能编辑book2,修改__free_hook的内容为onegadget的execve("/bin/sh", rsp+0x30, environ),然后执行free book2,当free了book2的description指针的时候就会执行__free_hook里的内容拿到shell。
Payload脚本
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!,本博客仅用于交流学习,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 文章作者拥有对此站文章的修改和解释权。如欲转载此站文章,需取得作者同意,且必须保证此文章的完整性,包括版权声明等全部内容。未经文章作者允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。若造成严重后果,本人将依法追究法律责任。 阅读本站文章则默认遵守此规则。