type
status
date
slug
summary
tags
category
icon
password
UAF漏洞
这类漏洞的基础与原理就是在申请堆块后没有在使用free函数释放这个堆块的同时将在申请是用于指向这个堆块的指针一并清0,使得在后面的程序中还能使用这个指针对程序中的数据造成泄露或执行的操作。
(想自己写一个例子,发现能力不足,还是从网上抄一下算了)
如上代码所示,指针p1申请内存,打印其地址,值
然后释放p1
指针p2申请同样大小的内存,打印p2的地址,p1指针指向的值
Gcc编译,运行结果如下:
p1与p2地址相同,p1指针释放后,p2申请相同的大小的内存,操作系统会将之前给p1的地址分配给p2,修改p2的值,p1也被修改了。
由此我们可以知道:
1.在free一块内存后,接着申请大小相同的一块内存,操作系统会将刚刚free掉的内存再次分配。
根本原因是dllmalloc:
当应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。
2.通过p2能够操作p1,如果之后p1继续被使用(use after free),则可以达到通过p2修改程序功能等目的。
大致便是如此,现在我们便来整一道题来练一练。
来自buuctf上的actf_2019_babyheap一道基础的uaf堆的题。
老规矩先checksec一下,只差pie没开了。
执行一下,经典的菜单题
那便放入ida中看一看。
main函数,稍微修改了一下,没什么意义。
有4个选项,1是创建日志,2是清除日志,3是打印日志,4是结束程序。那边一个一个来看
关于这个创建日志的函数便有很多值得注意的地方,这里面ptr是个bss段的空地址,程序以这里作为申请堆的指针处,在一开始程序通过
*(&ptr + i) = malloc(0x10uLL);
申请0x10大小的堆,这句话简单来说就是,在ptr+i的地方放这申请的0x10堆的数据段的地址,也通过*(&ptr + i)作为指针指向这个堆。下一句
,这里我们知道
指向的是0x10堆的数据区的起始地址,因此
指的就是堆的数据区中的下一个字节(大小为0x10,实际就是两个字节),这句的本意是将
这个作为指针指向sub_40098A函数,起始就是在一开始申请的堆的数据段的第二个字节处放入sub_40098A函数的起始地址。关于sub_40098A函数这个函数(在后文会有答案,用于打印内容)
接下来的几句大致意思也就是将输入的数作为数值去申请相应大小的堆,并输入数据进去。这理要注意的是这里用
*v0
作为指针指向新申请堆块,在之前用v0 = *(&ptr + i)
将最开始申请的堆块的数据区的第一个字节的地址赋给v0,这里的真正含义便是以最开始申请的堆块的数据区的第一个字节作为指针指向后面申请的堆,便是在第一个堆的数据区中放上第二个堆的数据区的起始地址。入图所示(虚线等多余的地方先不用管)。于此便完成了程序中日志创建的堆管理。在来看第二个选项,清理堆
这里会更具我们输入的数字从而清理第几个堆块,需要注意的是由于之前我们在创建堆的时候程序用来统计堆的个数的数据的起始是从0开始的,所以这里要清理第一个堆要输入的数,应该是0,后面的也要相应减一。
在知道要清理的堆块后,程序便会开始使用free函数清理堆块,这里的
**(&ptr + v1))
这个其实就是指*v0
用来指向由我们自己创建的堆的指针,而第二个*(&ptr + v1)
则是用来指向在上一个函数中一开始就创建用来存放第二个堆块的指针的大小为0x10得堆块,这里一次性清理两个堆块,把我们使用创建日志的函数中创建的两个堆一次性清理,但是我们可以注意到在清理了这个堆块的同时,并没有把指向这两个堆的指针同时清零,我们可能还有再次调用这两个指针的可能,这里便出现的uaf漏洞的可能,至于能不能使用还要继续往下看。突然想起来在这里差了一步,忘记查找程序中有没有后门函数。
那好,system函数和/bin/sh都有了,那我们只要能在后面使程序执行system(/bin/sh)便可以满足程序的需求。现在的问题就在与我们要如何才能使程序执行这个命令的问题了。
现在我们的选项函数还有两个函数没有看,第4个不用管,就是简单的退出函数。
现在来重点看最后一个打印内容选项的函数。
根据用户输入的数判断要打印的堆块,然后重点来了。
使用的打印函数并不是直接使用某个函数,而是使用两个指针从而完成函数调用的功能
(*(&ptr + v1) + 1)
这里我们前文提过,(&ptr + v1)
是程序自己申请的0x10大小的堆块的指针,而+1则代表他的下一字节。根据前文我们知道这里被放入了一个函数的起始地址((*(&ptr + i) + 1) = sub_40098A;
),而这个sub_40098A函数的内容
其实就是完成一个打印的功能的函数,不过这里在程序中使用了一个指针指向他,并通过指针调用。而
**(&ptr + v1)
这个就是指向在之前根据我们自己输入的大小申请的相应大小的堆块。所以这里就用这两个指针完成了对堆内容的打印。并且这里的这两个指针是存放在同一个堆块之中,那我们便可以想是否能够修改这个堆块中的这两个指针的内容从而执行我们的后门函数,从而达到目的。
在这里结合之前发现的uaf漏洞,会发现这里会出现一个致命的问题,在理论上我们之前申请堆块用的指针被存放在ptr及其后面的位置中,程序为了方便管理会依次使用ptr后的空间,并不是覆盖。然后在我们使用清理堆块的选项时,并没有将指针一同清零,因此,在ptr的地址中依然有指向堆块的指针存在,并没有被清理,然后我们在调用打印的函数时,依然能调用已经被free掉的堆块,并执行其中的内容,于是我们对本题的攻击便基本成型。
大致的思路如图,根据这个来描述针对本题的攻击。
在程序中先使用创建日志的函数,申请一个大小>0x18的堆(这里必须要让程序配一个与0x10大小不同的堆块,多的8可以输入到下一个chunk中的prev size字段,一旦超过8便只能从新申请更大的堆块),随便填一点内容就行,这样程序便会像上面的ptr指向的两个堆块一样出现这样的结构,然后在执行同样的操作,再申请一个大于0x18的堆,这样程序的ptr和ptr+1地段都会出现上文中的结构。
在之后我们通过程序中的清理函数的选项将我们刚刚申请的这4个堆块都free掉。于是程序中的fast bin中便会出现上面左边的结构,这4个堆依次连接,便于程序的下次快速调用,这里我们虽然将我们之前申请的堆块free掉了,但是ptr中的指针这些都没有被清零。
然后我们在使用这个选项向程序在申请大小为0x10的堆块,这里程序为了快速分配会先从而fast bin中寻找有没有满足要求的chunk,于是之前最后进入fast bin中的原本ptr +1指向的chunk便会被分配出去,然后由于我们还需要一个大小为0x10的chunk,于是之前ptr指向的chunk,便会被分配出去(这里就是为了避免我们自己申请的堆被分配出去所以申请的必须要大于0x18),用于作为我们自己申请的堆存储我们填入的数据,于是程序便构成ptr+2中由虚线指向的结构。此时如果我们向我们自己申请的堆中填入system和/bin/sh的地址那么,便覆盖ptr指向的堆中的数据,
此时由于ptr中的指针并没用清零,于是我们再次使用打印的选项并想要打印第一个堆块中的内容时程序会开始执行ptr指向的堆块中的命令来用于打印(理论上来说,如果我们应该将ptr中的指针清零,从而使程序不能完成我们这个打印已经被free掉的堆的操作,但是由于ptr中指针没有被清零于是程序就认为那个堆依然存在从而去执行堆中的指针,从而打印内容),但是我们此时修改了这个堆的内容导致程序直接执行了system(/bin/sh)这个后门函数从而满足了我们的要求。
exp如下
这道题就是一个最基础的uaf漏洞的题,写的比较详细,往对以后的这种漏洞有所启迪。
在libc版本为2.27的题目中,这种uaf漏洞,能使用tcachebins这样一种结构
这种结构出现在大小小于0x400的chunk在被free后链接而成(大于0x400的在free后会成为unsortedbin),在这种结构中,chunk的fd指针由于指向下一个chunk的原本的的数据段,现在的fd段(如果下面没有chunk了,这个fd指针将会指向0)。这里面的chunk在再次使用时遵循的是先进先出的规律,当再次malloc时,先吧上面一个满足大小的chunk拿出来使用,然后在将下面一个满足大小的chunk拿出来使用。
这里如果我们便可以通过uaf漏洞,将chunk中fd指针的值修改为我们需要的地址空间然后,malloc到这块空间,便可以对其进行修改。
(这里好像不用在意要修改的地址空间上两位的值能不能构成chunk的prev size和size都可以,malloc到那里直接修改,但是在其他版本可能要满足条件)
unlink
现简单说一下这一类题的大致问题在哪里,与一般的解决办法。
要能使用这种解决办法的题,会以下的几种重要的东西是我们能够使用的,才能使用这种办法。
- 能连续申请几个连续的堆,
- 对于指向申请的堆的指针,会在bss段上有一个固定且已知的地点存放,并且存放的方式是连续的。
- 必须在申请的堆中有堆溢出,用于修改下一个堆的prev size段空间
对于这一类的题,最重要的就是在我们申请的堆中间,通过我们自己伪造一个fake chunk与bss段的存放对指针的地址,构成一个含有3个chunk的双向链表,然后在申请的宁外一个堆中使用堆溢出,从而修改下一个堆中的prev size段,并使大小为从要修改这个chunk的prev size一直到,我们伪造的fake chunk的prev size这两个地址的距离差大小,这样使得我们在后面free掉我们修改了prev size 的chunk时,程序会将从这个chunk一直到我们伪造的fake chunk的这一段地址都认为是之前已经free的chunk(prev size中记录的是上一个free chunk的大小),从而使这一个刚刚释放的free chunk,直接就把上面的一整段被识变为free chunk的地址合并了(连续的两个free chunk会被程序合并),但是我们在之前伪造的fake chunk已经与存放指针的地址,构成了一个双向链表,所以在这里fake chunk被下面的free chunk合并时,原本与之构成双项链表的那两个空间,由于与之相连的chunk被拿走了,使得那两个空间,会有一定的改变,而改变的结果就会使原本存放指针的地方存放其他地址空间,从而使用这个地址修改函数地址。
注意一下,当这道题的libc版本为2.23时,我们用于free的chunk的大小必须要大于fastbin(大于等于0x80)的大小,否则不能出现unlink的情况
Off-By-One
这个漏洞在很多的地方都算是比较常见的吧,不过在之前的栈的时候属于不太好利用的一种,不过在堆中由于堆的特殊性导致,这个漏洞难够在很多地方发挥出意想不到的作用。
这个漏洞的具体就是使用read等函数向某块地址写入数据时,如果使用了循环的方式,而循环的次数的大小与那块地址的大小相同,如我们有的是大小为16的数组,我们通过循环向这里写入数字,而我们的循环的次数是由我们数组的大小决定,这里简单的说就是对循环的次数没有进行严格的检查,像循环的次数由变量x决定,并且循环的此时就等于这个x的值,这里x=16,看起来好像没什么问题但是会发现,由于程序中的这些数组的起始都是从0开始,包括循环的+
unsortedbin泄露libc地址
关于这种bin的具体结构等之后学会了在细讲,这里先讨论如何利用这种bin将libc的地址泄露。
由于在不同的libc版本中unsortedbin的产生不同,这里将针对不同libc版本进行分别讲解。
一,libc-2.27
在2.27的libc版本中又一种tcachebins的存在,这会导致数据区小于0x400,整体小于0x410的chunk在free后被放入tcachebins这里面,供下次使用,因此我们这里首先要先malloc一块大小大于0x400的chunk,同时这个chunk还不能与top chunk相邻,否则在free后会被top chunk直接合并,因此我们必须保证这个大于0x400的chunk与top chunk中间还有一个chunk用于隔离这两个chunk。就像满足这样的条件
然后我们将大于0x400的这块chunk,free掉,就会得到一块被放置在unsortedbin中,且满足条件的free
而此时这个free掉的chunk中的fd和bk指针由于只有这一个chunk在unsortedbin中,所以他们同时指向的都是main_arena+96的地址
并且这个地址在程序中与libc的基地址的距离是始终保持相同的,只要能泄露这个便可以知道程序中的libc的基地址,在其他版本中其实也如此,不过在有的地方也有不同。
house of orange
今天做题的时候刚好遇到这个知识点,那就现在把他写一下。
这个的作用并不能直接作用于程序上实现shell,这个漏洞的作用只是在于特殊情况下泄露libc的地址,其实这里用到也就是创造unsortedbin,从而泄露libc的地址,我们知道在上面的使用unsortedbin泄露libc地址的方法前提是要能创造一个大小满足free后进入unsortedbin的大小的chunk并且这个chunk还不能与top chunk相邻的的条件的chunk,并且还要能将这个chunk,free掉使得它进入到unsortedbin中,然后在将fd指针的值打印出来,这样才能泄露libc的值,而这里我们就是在面对没有free函数的情况下,依然能产生unsortedbin从而泄露libc的值。
具体的不细将,直接将做法。
关于这种做法目前我之在libc-2.23上做过,关于其他的libc可能有所不同,请注意。这里也是按在libc-2.23上来讲。
一,第一步堆溢出,修改top chunk的size值
这里必须有一个靠近top chunk的chunk能被我们写入数据进去,同时这个chunk要能进行溢出,溢出的大小要能刚好修改到top chunk的size段。
关于修改的这个top chunk的size的大小并不是随意的,也是有要求的,
这是未修改的top chunk的大小,size段的大小为0x20fe0,这里我们要修改成0xfe1,如下图,从而使程序能将top chunk的大小识别为0xfe0(那一个字节用于放数据,不计入总大小)
关于为什么要修改top chunk的大小为这个数据,我解释不好,有兴致的可以参考一下这篇文章House of Orange - CTF Wiki (ctf-wiki.org)
什么是对齐到内存页呢?我们知道现代操作系统都是以内存页为单位进行内存管理的,一般内存页的大小是 4kb。那么我们伪造的 size 就必须要对齐到这个尺寸。在覆盖之前 top chunk 的 size 大小是 20fe1,通过计算得知 0x602020+0x20fe0=0x623000 是对于 0x1000(4kb)对齐的。
因此我们伪造的 fake_size 可以是 0x0fe1、0x1fe1、0x2fe1、0x3fe1 等对 4kb 对齐的 size。
以上便是引用那里面的话,这里关于修改的大小我目前使用0x0fe1是成功的,使用了0x1fe1不能成功,原因未知。有时间研究一下。
二,申请一个大小大于这个top chunk的堆块
这里我申请的大小为0xff0,这是申请后的样子
就是这样我们便能够在没有使用free函数的情况下也能产生一个unsortedbin,并且其中fd和bk指针都指向main_arena+88的地址空间。
现在虽然产生了unsortedbin但是我们会发现,此时我们如果只有一个用于存放最近的指针的空间,会发现这个指针指向的chunk时是下面的的那个chunk,所以这个没有用。
三,再申请一个chunk
这个新申请的chunk的大小,没什么限制,别太大太小就行,这里我申请的大小为0x40
申请之后会发现存放指针的地址空间,存放的新的chunk指针为unsortedbin上面的新chunk,并且这个新chunk的内容也大有搞头,这里按理论来说这个新chunk的数据段前两个字节存放的都是0x00007614787c4188这个数据,(这里因为这道题在申请chunk时,必须要输入数据,所以这里的数据段第一个字节,不能使用,只能使用第二个字节的数据),这个地址指向的为main_arena+1640,这个地址与libc基地址的距离是固定的。
同时这个chunk的第3,第4字节存放的数据时这个chunk的头地址。
就这样只要我们能将这个新chunk的内容打印出来,接收第2字节的main_arena+1640的地址,第3字节的chunk的头地址,那就可以跟具这个地址与libc基地址的差得到基地址的大小(这个差值是固定不变的),还有chunk的地址也能拿到
- 修改top chunk的size大小为0xfe1
- 申请一个大小大于0xfe0的chunk
- 在申请一个0x20的chunk
- 打印并接收新chunk的内容(注意如果一定要输入数据,注意不要过多,影响第二字节的内容)
- 减去相应的差值得到libc基地址。
Fastbin Attack
这一类有四种小类,这里将分批讲解。
Fastbin Double Free
这是一种利用比较多的漏洞,这种漏洞的实现的条件比较苛刻,但其攻击的效果也属于比较明显的便于利用的一种,这种的攻击的手段需要的有uaf漏洞,只有在有uaf漏洞的基础上以及比较早的比如libc2.23这些比较早期的版本下才能实现这种攻击的手段,具体的现在来讲。
既然是Double Free,那就必须要进行双重释放,对同一个堆块进行两次free,中间必须要free得有一个或多个chunk这样才能实现对同一个chunk进行两次释放,
这里借用一下之前文章中的图片讲解一下,在fastbin中的结构是用chunk中的fd指针指向之前一个free的指针的头地址,然后bin的指针指向最近free的chunk的头地址,对于最后一个chunk中的fd指针则为0,就这样可以再fastbin中构成由最开始的bin中的指针开始依次由fd指针相连的chunk链,就这样在malloc相同的大小的chunk时便可以在直接从这个fastbin中依次取出来用就行。
就这样我们先free两个chunk这时bin指向第二个free的malloc,这个malloc的fd指针指向第一个chunk的头地址,第一个chunk的fd指针为0。
这时我们就对第一个free的chunk进行二次free,于是bin中的指针就会回到第一个chunk的头地址,同时这个chunk的fd指针也会因为在之前有已经free掉的chunk,从而指向第二个chunk的头地址,
就这样原本的fastbin中的结构,由
bin-->第二个chunk-->第一个chunk
,变成bin-->第一个chunk-->第二个chunk-->第一个chunk
这样的双重free结构,这样当我们向程序再申请一个相同大小的chunk时,程序会先从bin的指向中取出第一个chunk以供使用,但是由于之前的双重释放从而使得bin中的结构即使取出第一个chunk后依然有第一个chunk的存在,
bin-->第二个chunk-->第一个chunk
是这样的结构,虽然第一个chunk被我们取了出来但是在bin中依然有他的存在,并且此时第一个chunk中的fd指针在fastbin中由于是有用的指向了第二个chunk的头地址。
这里如果我们在程序的其他地方构造一个fake_chunk(这个地方可以是bss段上,甚至可以是栈上的地址),并使得第一个chunk中fd指针就指向这个fake_chunk的头地址,而这个fake_chunk的构造很简单,就是确保size段的大小和前两个chunk的size一样,同时其他的保持为0就行,然后在修改了chunk中的fd段后bin中的结构就会改变为如下的结构。
这样之后,我们再向程序连续申请两个大小相同的chunk后bin就会指向我们fake_chunkde1,然后在申请一个就会使我们刚刚伪造的fake_chunk,被我们申请为一个新的chunk,从而可以修改这个地址的内容。
House Of Spirit
这个方法我个人感觉很奇妙,虽然这种方法有种脱裤子放屁的感觉(在更多的地方感觉使用fd修改可能更多,不过这种方法的思想还很值得学习的)。
这种方法的主要过程就是在使用free函数释放某一个chunk,我们通过在bss段或其他的地址伪造一段chunk,在使用于释放的那个指针指向的就是我们伪造的这个chunk,通过这样使得我们这伪造的这个chunk被挂入进fastbin中的单项链表中,被程序识别为一个free掉的chunk,在后期我们在向程序申请相同大小的chunk时,能直接将那块地址作为chunk以供我们要使用,从而是我们能修改那块区域和使用其中的数据段。
我之前说的脱裤子放屁的感觉就是在这里,我们本可以直接修改fastbin中的bk段直接指向伪造的chunk,但我们却要将那块伪造的chunk释放来挂入fastbin中,这不是脱裤子放屁,这是什么。不过并不是所有题都能直接修改free chunk的,所以这种方法的出现以可以理解。
关于这种方法在伪造chunk时有几个需要注意的点,这里我直接吵ctfwiki上了,原文在这里
要想构造 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 的情况。
在借用一篇大佬写的解释好好说话之Fastbin Attack(2):House Of Spirit_fastbin attack house of spirit-CSDN博客(这个是真的nb大佬,写的文章都太好了)
1、fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理
IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的,这个标志位位于size低二比特位
2、fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
因为fake_chunk可以在任意可写位置构造,这里对齐指的是地址上的对齐而不仅仅是内存对齐,比如32位程序的话fake_chunk的prev_size所在地址就应该位
0xXXXX0
或0xXXXX4
。64位的话地址就应该在0xXXXX0
或0xXXXX8
3、fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐
fake_chunk如果想挂进fastbin的话构造的大小就不能大于
0x80
,关于对齐和上面一样,并且在确定prev_size的位置后size所在位置要满足堆块结构的摆放位置4、fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem
fake_chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。最大不能超过av->system_mem,即128kb。next_chunk的大小一般我们会设置成为一个超过fastbin最大的范围的一个数,但要小雨128kb,这样做的目的是在chunk连续释放的时候,能够保证伪造的chunk在释放后能够挂在fastbin中main_arena的前面,这样以来我们再一次申请伪造chunk大小的块时可以直接重启伪造chunk
5、fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况
这个检查就是fake_chunk前一个释放块不能是fake_chunk本身,如果是的话_int_free函数就会检查出来并且中断。可以参考篇文章好好说话之Fastbin Attack(1):Fastbin Double Free
这里在讲讲我的自己的简单补充,伪造的chunk最好如下,fake_prev_size的值为0,fake_size可以根据自己的需求来调整,但不能超过fastbin的要求,还有这里填的是0x几0不用加1。在整个fake_chunk结束的下一行便要是宁一个fake chunk,这个只要整prev_szie 和size段就好,这个的prev szie是之前伪造的那个chunk的整体大小,然后size这个的大小在32位系统我使用了0x100是可行的,不知道在64位的系统中可以不,到时候在仔细研究一下。
就这样我们伪造一个chunk然后把这个chunk的数据段起始地址,当做这个chunk的指针放入free函数中,这样这个chunk变回被程序当做free chunk挂入fastbin中,然后我们在申请这个大小的chunk,便可以把这个地址当做新被chunk,被我们使用。
Alloc to Stack
Arbitrary Alloc
这还有两种方法,这两种方法的大致差别不大,都是在程序中寻找一块可用的地址然后修改fastbin的bk指针使其指向这个地址,在fastbin中增加这块地址,在之后将这块地址申请出来作为一个新chunk使用。
关于这块这地址的检查,不需要多的就一个对size段的大小检查,因此我们这可以在malloc_hook的地址上面通过偏移地址使得size只有0x7几,这样只要我们申请的chunk大小为0x70,程序就会直接把那块区域分配给我们使用。
2017 0ctf babyheap
这里讲一个泄露libc手法,是在做这道题时遇到的,这个手法还是比较可以的。
关于这种手法的使用条件,为
- 可以申请多个chunk大小不一
- 每一个chunk都可以进行堆溢出
- 可以打印chunk中的内容
其实有这些条件这道题就可以用这种泄露的办法将libc地址泄露。
这里就拿这道题的条件分析,(这里面的图我从大佬的博客里面偷的,写的太好的这个大佬,强推!!!原文在这里好好说话之Fastbin Attack(4):Arbitrary Alloc_好好说话 ctf-CSDN博客,和上面的那篇是同一个作者,膜拜大佬。)
这道题我们不知道堆的指针在哪,同时也没有uaf漏洞给我们使用,那我们要泄露libc的地址,虽然这里我们能创造unsortbin,但我们并不知道这个chunk的地址故不能直接泄露他,这里便可以用这个方法泄露libc的地址
这里我们先申请5个chunk前4个大小为0x20,最后一个0x90,申请后的样子如下
形成这样的结构后,我们在依次free掉chunk3和chunk2这两个chunk,使得在fastbin中能形成一个链表,为什么要先free chunk3在chunk2,这里是为了在chunk2中出现bk指针执向chunk3头地址(fastbin的结构使后free的chunk中的bk指向前一个free的chunk的头地址),然后我们在利用chunk1的堆溢出,从而直接修改chunk2中的bk指针,使其指向chunk5的头地址。
这里虽然我们并不知道chunk5的准确头地址,但这里因为堆的对齐导致堆块的地址即使在每一次程序的加载都会有变化,但末尾的后3位是固定,这里我们只需要将chunk2的bk指针的后两位数字覆盖为chunk5的头地址的后两位,这样就可以改变fastbin中的结构使得chunk2后直接是chunk5的地址
这里我们还需要做的就是再利用chunk4中的堆溢出将chunk5的size段改为0x21,这里修改是为使chunk5的大小与chunk2的大小保持一致,这样我们能在之后的申请中将chunk再一次申请出来被程序再次记录下,
修改后的结构如上,这样chunk2和chunk5的大小一致,我们可以直接向程序申请这个大小的堆块从而使得程序中的第3个指针的位置存放的也是第5个chunk的指针,也就是chunk5的重启
再这样之后再程序中就有两个地址同时记录则chunk5的地址,分别为地址空间为3和5的指针,不过由于这个地址我们并不知道但并不影响我们对其的引用。其实到这里剩下的就很简单了,我们再一次利用chunk4的堆溢出修改chunk5的size段的数据恢复为0x91,然后我们在申请一个chunk块,这个会从top chunk分出来,用以避免chunk5 free时直接与top chunk合并,之后我们直接free掉chunk5这样,chunk5就回进入unsortbin中,产生指向main_arena的fd指针,这里我们知道程序在free掉后会同时清楚相应位置存放的指针,但是这里由于我们之前的操作导致在程序中不只有记录chunk5的位置有指向chunk5的指针,还有记录chunk3的位置有指向chunk5的指针,这样就到导致我们可以通过打印chunk3的方法从而打印出chunk5中的内容,这样就可以通过unsortbin的bk指针得知libc的地址。
补充一下图,恢复chunk5的大小
free chunk5,产生unsortbin
在次感谢holk大佬的博客,跪谢!
其实这种方法的主要目的就在与使用堆溢出修改fastbin中fd的指向,在申请相同chunk区域,使得程序中两个地址同时记录着同一块chunk的指针,然后利用这块chunk产生unsortbin,在利用后面存放这块chunk的指针打印chunk中的内容,从而实现libc地址的泄露。
巧妙的方法,感觉要长脑子了。
Unsorted Bin Attack
这个手法咋说呢,感觉有点鸡肋,单独使用的作用并不是那么的大,可能在大部分时候要配和着其他的手法来使用才算可以发挥作用,或许可以用来在有的时候用于泄露libc的地址,但条件感觉要的有点多,在没有在实际的题目的使用过。并且这种攻击的手法之前好像看见有人说从libc2.28开始就不能再使用,在系统中多了对Unsorted Bin 的大小检测的函数,这种攻击的方式便不能再使用所以对题目的要求也较高,就有点鸡肋。不过既然有种手法就还是学一下,至少知道这种漏洞的存在。
前话到此为止,现在开始讲有关这种手法的操作。
关于Unsorted Bin这是一个神奇的bin,我们知道有的chunk由于大小限制所以在释放后会被放入的Unsorted Bin中作为其中的一部分free chunk以供下次申请时快速使用,但是其实在没有chunk进入到unsorted bin的时候他自己本身就是一个单独的chunk结构,不过不参与到chunk的分配只作用来作为unsorted bin的基础机构,如下图
(这个图还是偷的csdn的大佬holk,再次跪谢大佬,跪谢大佬无私,原文链接好好说话之Unsorted Bin Attack_unsortedbin attack-CSDN博客)
这个就是unsorted bin结构,而当有free chunk进入到unsorted bin中时这两个的结构变回发生新的变化,
此时chunk_400是我们程序中由free从而进入到unsorted bin中chunk,这个chunk的fd和bk指针都会指向unsorted bin的结构chunk的头地址,而我们知道unsorted bin的结构在程序中的位置在main_arena+88的地方,这就是为什么我们能通过泄露unsorted bin的fd和bk指针从而获取到程序的libc地址。
我们关于这个漏洞的手法就正式从这里开始,这里我们如果修改了我们free进unsorted bin中的这个chunk的bk指针,使其指向宁一个地址那么程序便会直接将那个地址默认为一个新free进unsorted bin中的chunk,从而形成一个新的结构。
这里的bk指向的chunk中对任何一个的数值没有要求只要修改bk的指向,程序就会默认这个地址为一个新的chunk被收录进unsorted bin(这个在libc2.23中是如此其他版本还不知道),但是这里在个填入的地址后期并不会被申请出来。这里还有一个重要的点,就是在我们堆溢出修改chunk_400时我们不必保存fd指针的指向一直指向unsorted bin的头地址,我们可以直接覆盖为0。这样也不影响后面的操作。
现在便是这种方法的最后一步,由于在unsorted bin中对free chunk的申请保持的是一种
FIFO
(先进先出)的利用手法,所以即使在这里我们将一个新的地址作为新的free chunk挂入unsorted bin中,只要我们能在申请到与chunk_400同样大小的chunk那程序依然会先从unsorted bin中将chunk_400拿出给我们使用,而在这里一旦将chunk_400拿出来unsorted bin中变回对结构有新的变化,变化的结果如下变化之后unsorted bin的fd指针指向chunk_400的头地址,bk指针指向我们创造的fake chunk的头地址,而对我们来说最终要的便是我们的fake chunk的bk指针被修改为指向unsorted bin的头地址。
就这样我们成功做到将我们写入chunk_400中的地址的下两位修改为unsorted bin的头地址,但是由于这个地址在程序中随机化的,所以我们并不能保证这个数值的大小。虽然这个地址是main_arena+88,与libc的距离是固定不变的,但我们并不知道具体的大小,所以这里是有一点鸡肋的存在,但或许可以通过打印修改的这里的地址获取到libc的地址,但这并不好用。
好,到这里这种手法就差不多结束了,总结一下,这种手法能做的就是将程序中的某一块地址的内容修改为main_arena+88的地址, 是一个比较大的数。
- 产生unsorted bin
- 修改这个unsorted bin的bk指针指向我们要修改的地址-0x10的地址
- 重新通过malloc启用unsorted bin中的那个chunk
关于unsorted bin
这里讲一下有关于unsorted bin中的chunk的分配有关的事情,我们知道除了再有tcache bin的情况下一般只要大于fastbin的范围(0x80)的free chunk会被放入到unsorted bin中,但unsorted bin并不会长期存放,只会作为一个暂时的chunk存放地,在之后如果有需要malloc时,程序会先看fastbin中是否有符合要求的free chunk ,然后在在unsorted bin中寻找。
这里我们假设在unsorted bin中有两个chunk,一个为chunk(P1)0x390(<0x3F0),另一个为chunk(P2)0x410(>0x3F0)这两个chunk,并且小的chunk在第一位大的在第二位。这时我们向程序申请1个0x90的chunk,程序会在unsorted bin中进行寻找,而寻找的过程并不简单。
- 从unsorted bin中拿出最后一个chunk(P1)
- 把这个chunk(P1)放进small bin中,并标记这个small bin中有空闲的chunk(小于0x3F0)
- 从unsorted bin中拿出最后一个chunk(P2)(P1被拿走之后P2就作为最后一个chunk了)
- 把这个chunk(P2)放进large bin中,并标记这个large bin有空先的chunk(大于0x3F0)
- 现在unsorted bin中为空,从small bin中的P1中分割出一个小chunk,满足请求的P4,并把剩下的chunk(0x390 - 0xa0后记
P1_left
)放回unsorted bin中
这个过程是比较复杂的,我这里讲的依然只是其中的一种情况,还有很多情况没有说。
malloc.c中从unsorted bin中摘除chunk完整过程代码
上面这是完整的原代码有时间再详细分析一下。
tcache attack
fast bin=0~0x80
small bin<0x3F0
large bin>0x3F0
tcache bin 最多有7个chunk,多的要放在其他bin中。
tcache poisoning
这个漏洞的的利用是很方便的,同时效果也很强,不过这个漏洞的很大问题在于这个目前只能在libc2.27的版本上利用,在高一点的版本都对其有检测,不能利用,这是很重要的一点。
这个漏洞利用的是在tcache中的链表对chunk的检测不完全而到导致的,先来讲一下tcache bin中的chunk结构。
当程序中有被free的程序进入到tcache bin中,当进入的数量大于两个后程序会将这里chunk通过他们的fd指针链接起来,而链接的过程是,由后一个进入tcache bin的chunk在fd中产生一个指向上一个chunk的数据段的指针,依次相连从而构成tcache bin,并且bin的记录的是新进入的chunk,在后面的程序有需要chunk时,程序变会从记录的第一个开始,依次分配chunk以供使用,所以其分配结构为先进后出,后进先出的结构。
那么既然知道了tcache bin中的chunk链表结构,便会发现一个很严重的问题,这里tcache bin上的chunk是有fd指针指向下一个chunk的数据段,来相连的。并且在libc.2.27这个版本中有一个很大的问题,在于使用fd指向的下一个chunk,在上一个chunk没分配出去后,将这个fd指向的记录在bin中时,程序不会有任何检测,同样的在malloc那块地址时,也不会对那块地址有任何检测,因此这里我们的这个漏洞便出现了。
到我们使用堆溢出或uaf将tcache bin中的chunk的fd指针,修改为一个我们需要修改的地址后(这里由于程序对那个地址没有一点检测,同时fd指针指向的是chunk的数据段,因此我们直接修改为要修改的地址就行),程序便会直接将那块地址记录在tcache bin中,我们一直申请向程序申请与之前的free chunk相同的chunk,那程序便会现将前面的chunk分出去,然后变会来到被我修改了的fd指针,并因为程序对chunk的不检查,从而导致我们输入到fd的地址被直接分配成我们需要的chunk以供我们使用,这样我们便可以修改那块地址。
总结一下
- 产生tcache bin中的chunk链表
- 将tcache bin中的chunk的fd指针修改为我们要修改的地址
- 申请大小与修改的chunk相同的chunk,一直申请到修改的fd指针被分配出去
- 我们要的地址被认作chunk分配给我们使用,从而修改那块地址
这个方法是很简单的一种,但限制也很大几乎只能在libc.2.27的条件下使用。
tcache dup
这个漏洞也是一个几乎只能在libc.2.27使用的漏洞。
这里利用的是在tcache bin对free进的函数没有然后检测从而利用的漏洞
这个漏洞也算是一个比较离谱的漏洞,这个简单说就是对同一个chunk连续两次free,在连续两次申请相同大小的chunk,从而导致程序中的后面两个malloc的指针同时指向一个chunk。这里的由于程序对这个释放的过程并不会有检测从而导致,我们能同时连续对一块chunk释放两次。中间甚至不用free其他的chunk从而间隔。
所以这个漏洞就是对同一个chunk,free两次,然后在申请相同大小的chunk两次,这两次的指针会指向同一个chunk。
tcache house of spirit
这个漏洞和上一个一样也是只能在libc.2.27的上使用的漏洞。
这个漏洞的利用很简单,只要我们在程序的某一个地方伪造一个fake_chunk然后将这个chunk直接free进tcache bin中,在后面的malloc中申请我们fake_chunk的大小chunk,这样就可以把这个fake_chunk当做一个真正的chunk分配给我们使用。
关于这个fake_chunk的伪造条件只有一个,就是要保证这个fake_chunk的size为一个相对正确的chunk大小,这样就可以满足要求了。
- 找到要伪造的chunk的地址,就这个地址的size为一个正确的地址。
- 将这个fake_chunk的数据段地址使用free函数将这个fake_chunk挂入tcache bin中
- 申请我们刚刚free的fake_chunk的大小的chunk
这样我们fake_chunk就会被当做chunk供我们使用。
tcache stashing unlink attack
这个漏洞的利用相较上面的几种就相对来说要复杂一点,并且利用的东西也多了很多,
- 当使用calloc分配的堆块时会从small bin中获取(不从tcache bin中拿)
- 获取一次之后会将small bin中其余堆块挂进tcache中(前提tcache中有相同堆块的链表,且其中chunk大小相同有剩余)
- 在将small bin中其余堆块挂进tcache中这个过程中只会对第一个挂进去的chunk进行完整性检查,后面的不做检查
这里其实就已经很明显的告诉我们这里我们可以在程序中,产生small bin后在small bin中修改chunk的控制字段,从而使伪造的fake_chunk挂入small bin中,在使用calloc分配一个相同大小的chunk,从而使small bin的剩下的chunk挂入tcache bin中,这里注意一定要保证我们伪造的fake_chunk之前还有一个正常的chunk,只有这样才能保证我们的fake_chunk能顺利挂入tcache bin,然后被分配出来。
这里具体的根据这个案例来分析这种漏洞的利用(注意要使用libc.2.27来编译这个程序),
LCTF2018 PWN easy_heap
这个方向在wiki上的第一道题就是这个,这是一个极好的题目,其中对于如何泄露libc的手法很是奇妙,很值得仔细写一写。
先感谢hollk大佬的文章(补题)LCTF2018 PWN easy_heap超详细讲解_lctftk-CSDN博客写的很详细,膜拜大佬。
这道题的整体逻辑还是比较简单的,就是可以申请10个chunk,这10个chunk的大小固定,都为0xf8(数据段大小)。然后这10个chunk的指针和大小都会被记录进最开始的一个chunk中,然后我们可以对这10个chun进行,free,puts,其中的内容在最开始申请的时候就会被输入其中,后期不能更改。
整体看完程序会发现漏洞就一个,在chunk申请的时候能输入多少多少数据是由我们输入的大小决定的,这里由于我们这个chunk的大小为固定的0xf8,我们能输入的数据的最大大小也为0xf8,然后这里有个很大的问题在于用于向chunk读入的内容的函数,使用的是个循环函数,这里只要我们输入的大小为0xf8这个循环就会出现一个类似于
off-by-one
漏洞的null-byte-overflow
漏洞(wiki上这么叫,虽然我感觉没什么差别),这里会对下一个chunk的size的最后两个位址覆盖为\x00
,这里覆盖的这个位置正好是chunk中由于检测上一个chunk是否为free chunk的inuse标志位,当程序检测到这个chunk的上一个chunk为free chunk时,这个位址的值为0,否则为1。然后就会发现,好了没有其他漏洞。是的,这道题的漏洞就只有这一个可以覆盖inuse标志位为0的漏洞,那么现在我们就要想办法看看能不呢使用什么方法来让我们能在程序中出现一个chunk既能有指针能调用,同时自身又在unsorted bin中,这样我们便可以直接通过打印这个chunk从而实现得到libc地址。
这里有一个需要先讲的知识点那就是当我们将数个相同的大小并且并且相邻的chunk被挂入unsorted bin中时,程序会为了后面的malloc方便会直接将这几个chunk在unsorted bin中合并成一个大的chunk,同时对于其中的之前的chunk会对其控制字段进行一定的改变,将其中的prev size会更具前面的free chunk的大小修改,同时size的inuse标志为0。
这里就有一个很大的问题如果我们在后期能将这个合并的大chunk,在后期将第一个chunk挂入unsorted bin中,同时有将最后一个chunk挂入其中,并修改其中的所有chunk1的控制字段与合并时相同(prev size和size的内容相同)程序会默认在unsorted bin中形成之前的那个大chunk,从而使我能对中间的那个chunk进行Double Free和泄露libc地址。
以下由于在程序中chunk的次序是由0开始,所以这里要注意下面的第几个chunk是从0开始数的第几,不是从1开始,要注意这一点避免认错chunk
- 申请10个chunk
- 将前6个chunk和第9个chunk,free进入tcachebin中填满,再将第6到第8个chunk,free进入unsorted bin中。这里678这3个chunk被free到unsorted bin会合并为一个大chunk,使得chunk7和chunk8的prve size 和size的值分别被修改为0x100,0x100和0x200,0x100。chunk6的size为0x300。
- 在向程序申请10个chunk,使得chunk成新的排列。
- 在free前6个chunk和第7个chunk以填满tcachebin(这里最后一个填chunk8,是为了在后面可以直接申请这个从而覆盖chunk9的inuse标志位为0,让程序以为chunk8也是free chunk),再将chunk7也free掉使其进入unsorted bin,同时这样也能修改chunk7的inuse标志位也为0,
- 再向程序malloc一个chunk,程序会将上一个中chunk8拿给我们使用(chunk8在tcachebin的第一位,第一个分配)这里直接使用漏洞修改chunk9中的inuse标志位,使其认为上一个chunk为free。
- free掉chunk6的chunk用于填满tcachebin中刚刚分配的那一个,然后在free掉chunk9的chunk。
这里由于之前我们通过第二步的3个chunk的合并,使得上面第3步后的chunk789的chunk中的prev size和size都被修改了。然后在第4步时我们又将chunk7 free进unsorted bin,这样使得chunk8中的size的inuse标志位为0。此时这两个chunk的控制字段与第2步的相同了,此时我们在free掉chunk9的chunk,正常情况下由于chunk8在第5步时不是free chunk了,所以这里chunk9的size的inuse标志位为1。即使我们在这里free了chunk9也不会有什么问题发生,但是由于我们在第5步时将chunk9的size的inuse标志位覆盖为0了,于是这里我们在将chunk9 free时就会使程序中的unsorted bin中又出现第2步的3个合并的大chunk,但是chunk8却在第5步时被程序拿给我们了。
- 将tcachebin中的7个chunk和unsorted bin中的第一个chunk7的chunk都malloc出来那此在程序中。unsorted bin的第一个hunk便是以第3歩中的chunk8为起始地址,但是在新的chunk0中记录的正好就是chunk8的指针。
- 打印chunk0的内容,从而得到libc的地址。
以上就是这道题中关于libc的泄露,只是基本步骤,具体的有时间再仔细分析一下,这个方法还是很奇妙的,利用到unsorted bin中chunk的合并,从而使的程序中误认为有大free chunk的出现。
这道题我写的比较简陋,只是为了我自己方便研究这个方法,更好的推荐看这个地方Tcache attack - CTF Wiki (ctf-wiki.org)和上面提到的大佬的博客讲的更加直观与详细。
HITCON 2018 PWN baby_tcache
又来讲一讲题的新的新做法,其实也不新,不过是之前那道题的plus版本,并且这到题我感觉重点其实在于通过IO_FILE输出的方式进行泄露libc地址,这种方法我现在学的也不是很懂。这道题到写这篇文章的时候,我能把libc的地址泄露出来,但问题在于我这里泄露之后程序就好像不能再继续进行下去了之后的堆申请这些都进行不下去,很好奇为什么,等之后有时间将这个IO_FILE系列的在仔细学一下,在回来解决这个问题。
这里虽然我没有彻底将这道题整完,不过到把libc泄露出来之后后面的就是几步的事情,所以这道题最大的问题就与如何将这libc的地址泄露出来。
这里还是先讲一下知识点,在之前我们提到了有关于unsorted bin的合并这个点,之前好像写的有点问题,那就是合并的时候中间的chunk是不用关注是否有prev size和size是否满足free的条件的。只要在unsorted bin中有一开始的那个chunk,然后我们要合并的大chunk的最后一个chunk的prev size和size的条件是满足这个合并的要求的就行。我们要对最后一个chunk的修改时prev size要修改为前面的chunk的总大小,size的最后一位要为0(inuse标志位)。这样修改好后我们先将最前面的chunk free掉,在free我们修改的这个chunk(必须要保证这两个chunk的大小都是能直接进入unsorted bin的,中间的chunk大小可以不管),这样程序变会直接在unsorted bin中形成一个大chunk。
关于IO_FILE输出的方式进行泄露libc地址这个方法的原理我还不太清楚这里先讲一下怎么使用吧。
先来看正常情况下我们修改的地方
这个_IO_2_1_stdout_就是我们要修改的地方,现在是正常的情况下(这里可以直接使用x/20gx stdout 这个命令查这个这个地址),而我们对于要泄露libc的修改条件为
- _IO_2_1_stdout_=0xfbad1800
- _IO_2_1_stdout_+8=0
- _IO_2_1_stdout_+16=0
- _IO_2_1_stdout_+24=0
还有这个_IO_2_1_stdout_+32这个地址的修改是最复杂的,这里我们只修改这个地址的最后一个字节,其他的都不修改,这个的修改要更具情况来看,修改为执行一个又libc地址的地方,这里我们选择为c8(这里修改的地址程序会直接从这里开始打印内容,具体为什么,等我后面彻底学会了在来解释)。
这就是修改后的样子
就这样修改后程序变回从0x7b3dae5ec7c8开始打印一直到遇到那个0a结束。
这里会有一个问题我们都不知道程序的libc地址,我们还怎么修改这个_IO_2_1_stdout_的内容?这里又是一个新的想法和思路,我们知道对于处于unsorted bin中的chunk,只要你是第一个chunk就会在fd指针指向main_arena+96这个地址,如图
这里你仔细看会发现这个main_arena+96的地址为
0x7b3dae5ebca0
,而_IO_2_1_stdout_的地址为0x7b3dae5ec760
这两个地址的差别就在于最后4为数。main_arena+96为bca0,_IO_2_1_stdout_为c760。这里虽然程序有地址随机化这个保护,但这里程序对与最后4位数字是不会改变的,因此我们只要将unsorted bin中的chunk的fd指针指向的main_arena+96的地址的最后4为数字修改为c760,这样就可以知道_IO_2_1_stdout_的地址。这里有一个很奇妙的方法,我们知道在tcachebins中对bk指针指向的chunk没有太多的检查(这道题的环境是libc.2.27,前面好像忘记说了,这里讲的利用方法都是基于libc.2.27这个版本来的),我们既然要修改_IO_2_1_stdout_地址的内容,那这里就要想是不是可以通过将这个地址成为tcachebins中chunk的bk指针,然后就可以直接通过申请chunk来使程序将那块地址分配给我当chunk使用,这样我们就可以直接修改那块地址内容。
结合上面说的,我们可以现在程序中选定一块chunk,先将这块chunk free进tcachebins中。然后在通过修改这块chunk的前后chunk,使得联通这个chunk一起在unsortedbin中形成一个大chunk。然后向unsortedbin中申请chunk使得之前我们选定的chunk成为unsortedbin的第一个chunk,这样程序为因为这个chunk在unsortedbin为第一个chunk从而向其中的bk指针注入main_arena+96(0x7b3dae5ebca0),同时因为这个chunk还在tacahebins中,故在tacahebins中又会有我们选定的chunk的bk指针执向main_arena+96(0x7b3dae5ebca0)的结构,如图
这里我们选定chunk同时在unsortedbin和tcachebins都有。这里我还要修改unsorted bin中的chunk的fd指针指向的main_arena+96的地址的最后4为数字修改为c760,这里我们可以像unsortedbin中申请一个chunk(注意这里申请的chunk的大小一定要注意,必须大于tcachebins中的大小,使得程序能从unsortedbin中分配这个chunk)然后我们在修改我们申请的chunk的bk位得最后4个数字,使得其指向_IO_2_1_stdout_。如图
这样就可以使_IO_2_1_stdout_的地址出现在我们程序中。我们在这里向程序连续两次申请chunk,就可以得到以_IO_2_1_stdout_为chunk地址的chunk,从而做的我们的修改的目的。
知识点讲完了,来简单说一下这道题的流程。
- 申请7个chunk,最终大小(带控制字段)分别为0x500,0x40,0x50,0x60,0x70,0x500,0x80
- 将第4个chunk(从0开始数)大小为0x70的chunk free掉,在申请出来用于修改第5个chunk的prev size为0x660(前面的chunk总和0x500+0x40+0x50+0x60+0x70=0x660),在将size的inuse标志位覆盖为0
- free掉第2个chunk,使其进入tcachebins。在依次free第0个chunk和第5个chunk,使其在unsortedbin中形成从chunk0到chunk5的大chunk
- 在申请相依大小的chunk(我这里为0x530注意控制字段的影响)从unsortedbin出来,使chunk2成为unsortedbin的头chunk。
从而在chunk2的bk中有main_arena+96的地址,并在tcachebins中有链表指针
- 在将第4个chunk,free进tcachebins中,使得我们之后从unsortedbin中申请chunk修改地址后,chunk4的bk指针又在tcachebins中出现于第4步的情况。为libc泄露后修改hook做准备。
- 申请相应的chunk大小(0xa0),修改tcachebins中chunk2的bk指针的最后4个数字为c760,使其指向_IO_2_1_stdout_的地址。同时也让chunk4成为unsortedbin的头地址,出现于第4步的情况。
- 在连续申请两次大小为0x40的chunk(最终大小满足0x50),使得在第二次时程序将_IO_2_1_stdout_的地址当做chunk给我们使用。并修改。
- _IO_2_1_stdout_=0xfbad1800
- _IO_2_1_stdout_+8=0
- _IO_2_1_stdout_+16=0
- _IO_2_1_stdout_+24=0
- _IO_2_1_stdout_+32的最后两个数字为c8
在修改
第一次申请
第二次申请,
- 当程序执行完这个后没救会把从0x78b2103ec7c8到0x78b2103ec7e3的数据都打印出来。这里打印的前8个数据(0x78b2103eba00)这个与libc基地址的距离是固定的(0x3Eba00,这个最好根据程序来看)。这样我们就可以应该不使用puts等函数将libc的基地址打印出来。
这里我不知道为什么我的程序打印结束后就卡在这里了,后面直接不执行了,等我以后把这个流程的具体学会了在回来解释。
- 后面的就很简单了,还记得chunk4这个吗,我们之前让他在tcachebins又在unsortedbin,形成double free。这样我们只要先从unsortedbin中把他申请出来,并修改bk指针为free_hook的地址,使得在tcachebins形成一个新的链表。
- 然后在从tcachebins中申请两次使得free_hook的地址成为一个chunk供我们使用,在向其中注入one_gadget地址,在调用free函数,从而执行one_gadget拿到shell
这里总感觉我的修改_IO_2_1_stdout_的内容从而泄露libc的过程又点问题,同时这个的原理也不清楚,等以后再找来学一学,重新整一下这个题。不过这里有管tcachebins和unsortedbin中同时出现一个chunk,从而修改bk指针的方法还是很奇妙的值得一学。
large bin attack
这个手法在目前的liunx版本中一直存在,但在不同的版本中有不同的过程,并且实行的结果也不一样。其主要区别在于glibc2.31这一个版本为间隔,在这个版本之前可以使用这种攻击手法达成两个地方的任意写,但在这个版本之后就只能做到一个地方的修改了。
在glibc2.31之前的这个手法等后面在写,因为最近在看的题目中对这个手法的高版本利用比较多,这个手法可以辅助后面的攻击,因此对这个手法的理解要加深便于后面的使用。
glibc2.31及其以上的利用
可以借助这个来讲解
large_bin是一种堆分配的管理方式,是双向链表,用于管理大于某个特定大小阈值的内存块。一般而言,进入large_bin的最低字节为0x200(512)。但由于引入了tcache_bin,使得在tcache_bin尚未填满的情况下,进入large_bin的最低字节为0x410(1040),所以一般我们设置大堆块都是0x410起步
这里简单来说这个过程就是
- 申请一个大于0x410的chunk1,在申请一个小于chunk1的chunk2(注意不要太大),这里这两个chunk的大小都必须要大于0x410使其满足进入large_bin的条件,中间要随便申请一个chunk用于隔离。
2,然后把chunk1释放掉,因为大小大于0x410,会被放入unsortedbin中
3,之后我们申请一个比chunk1和chunk2都要大的chunk,用于把chunk1从unsortedbin放入largebins中
4,然后我们再将chunk2释放掉使其进入unsortedbin中
5,至此我们就可以开始这个手法最重要的一步,我们现在要把chunk1的bk_nextsize修改为我们想要放置修改的地址的-0x20的地址。
就像此时我们想要在0x7fffffffddd0这个地址上放入我们的数据,我就要把chunk1(在largebins中)的bk_nextsize修改为0x7fffffffddd0-0x20的0x7fffffffddb0地址。
6,最后我们在申请一个比chunk1和chunk2都要大的chunk,就会把chunk2从unsortedbin放入largebins中,然后我们之前在chunk1的bk_nextsize中放的地址的下0x20的位置就会被写入chunk2的起始地址
于是这样我们可以实现在任意地址写入chunk2的起始地址的目的。
简单过程就是先申请一个chunk1,在申请一个chunk2,注意chunk2要小于chunk1,这两的大小都要大于0x410,同时相差不要太大。
然后free掉chunk1,在申请一个大于chunk1的chunk,使chunk1进入largebins中,然后在free掉chunk2。
修改chunk1的bk_nextsize为我们想要写入大的地址-0x20的地址,在申请一个大于chunk1的chunk。此时chunk2进入largebins中,同时chunk1的bk_nextsize中的数据+0x20的地址会被写入chunk2的起始地址。
这个手法在glibc2.31及以上的版本中就是实现一个在任意地址写入堆地址的目的,这个手法主要是为了和后面的对io的利用做铺垫。
- 作者:wgiegie
- 链接:https://tangly1024.com/article/865b1101-0819-470d-aabe-41c304df9d64
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。