CTF Reversing Challenges List Baby writeupまとめ

この記事はCTF Advent Calendar 2018の14日目の記事です。 N4NUさんが作ってくださった Reversing Challenges ListのBabyを解いたのでそのメモ/writeupです。

github.com

SRM

Windowsバイナリで起動するとメ一ルアドレスとシリアルの入力が求められる。正しいシリアルがフラグ。

バイナリを読むと1文字づつ入力文字列らしきメモリとの比較が行われている。比較の条件式は足し算か引き算かただの文字との比較なので各条件式に合うようなフラグを復元させることができる。 以下がそのスクリプト

#!/usr/bin/python
flag = [" "] * 0x10
flag[0] = 'C'
flag[b - 0x231] = chr(0x9b - 0x43)
flag[1] = chr(0x57 + 3)
flag[b - 0x232] = chr(0x9b - ord(flag[1]))
flag[b - 0x23e] = chr(0x3a - 1)
flag[b - 0x233] = chr(0x9b - ord(flag[b - 0x23e]))
flag[b - 0x23d] = 'd'
flag[b - 0x234] = chr(0x9b - 0x64)
flag[b - 0x23c] = 'm'
flag[b - 0x235] = chr(0xc8 - 0x81)
flag[b - 0x23b] = chr(0x44 + 0x2d)
flag[b - 0x236] = chr(0xaa - ord(flag[b - 0x23b]))
flag[b - 0x23a] = '4'
flag[b - 0x237] = chr(0x9b - 0x34)
flag[b - 0x239] = 'c'
flag[b - 0x238] = chr(0x9b - 0x63)
print(flag)
print(''.join(flag))

あの練習としてソルバを書いてみた。

import angr
proj = angr.Project('./RM.exe')
state = proj.factory.entry_state()

ebp_base = 0x1000
str_base = ebp_base - 0x240
str_len = 0x10

state.regs.ebp = ebp_base
state.regs.eip = 0x401407

for i in range(str_len):
    state.mem[str_base + i].char = state.solver.BVS('flag_%d' % i, 8)

goal = [0x401506]
fail = [0x4013fe]

simgr = proj.factory.simulation_manager(state)
e = simgr.explore(find=goal, avoid=fail)

if len(e.found) > 0:
    s = e.found[0]
    ans = ''.join([s.mem[str_base + i].char.concrete.decode()
                   for i in range(str_len)])
    print(ans)
else:
    print('Not Found')

dMd

入力文字のmd5の値がb781cbb29054db12f88f08c6e161c199になるようなものを入力すればよい。 ぐぐるb781cbb29054db12f88f08c6e161c199md5(md5("grape"))らしい。 答え

#/bin/sh
echo -n $(echo -n grape | md5sum | cut -d ' ' -f1) | ./dMd

spim

入力文字列のi番目の文字をiとのxorでエンコ一ド/デコ一ドするプログラムのアセンブリが渡される。

inp = 'IVyN5U3X)ZUMYCs'
def enc(s):
    chars = list(s)
    for i in range(len(chars)):
        chars[i] = chr(ord(chars[i]) ^ i)
    return ''.join(chars)

print(enc(inp))

filechecker

だいぶ前に解いて概要も忘れてしまった。ソルバはこれ

import string
import sys
mem = [0x12ee, 0x12e0, 0x12bc, 0x12f1, 0x12ee, 0x12eb, 0x12f2, 0x12d8, 0x12f4, 0x12ef, 0x12d2, 0x12f4, 0x12ec, 0x12d6, 0x12ba]

def po(i, c):
    rax = i
    edx = mem[rax]
    rax = ord(c)
    ecx = edx + rax
    edx = 0x354ac933
    eax = ecx
    eax, edx = (eax * edx) & 0xffffffff, (eax * edx) >> 32
    edx = edx // (1 << 0xa)
    eax = ecx
    eax = eax // (1 << 0x1f)
    edx = edx - eax
    eax = edx
    eax, edx = (eax * 0x1337) & 0xffffffff, (eax * 0x1337) >> 32
    ecx = ecx - eax
    eax = ecx
    return eax


ans = ''
for i in range(0x0f):
    for c in map(chr, range(0x100)):
        if po(i, c) == 0:
            ans += c
            break
    else:
        print("Failed, i: %d" % i)
        sys.exit(1)
print(ans)
f = open('.password', 'w')
f.write(ans)
f.close()

ServerfARM

だいぶ前に解いて概要も忘れてしまった。 armアセンブリ。3回正しい入力を与えると正しい出力(フラグ)が帰ってくる。 アセンブリを読んで頑張ると正しい入力がわかる。ただstringsでも3回目の正しい出力とかはわかるというかそれらしい候補が見つかるので少しアセンブリを読むのはサボれる。

Nuit du Hack step1

中でstrcmp("Much_secure__So_safe__Wow", argv[1])をしているのがわかる。Much_secure__So_safe__Wowがフラグ。

Nuit du Hack step2

argv[1]をverifyしている。 バイナリを読むと以下のような入力を与えるとverifyに成功することがわかる。

s = ['A'] * N
s[0] = 'P'
s[3] = chr(0x64)
s[6] = chr(0x70)
s[5] = chr(0x5f)
s[1] = 'a'
s[7] = s[1]
s[10] = s[1]
s[9] = s[3]
s[4] = 'i'
s[2] = 'n'
s[8] = chr(ord(s[7]) + 0xd)
print(''.join(s) + '\0')

Nuit du Hack step3

argv[1]にフラグを入力する。 入力一文字毎にフラグをエンコ一ドして比較する処理があるのでそこを頑張ってデコ一ドさせる。 フラグの各インデックス毎に異なる比較関数が使われているのでそこを特定してangrでソルバを書いて解かせた。

import angr
p = angr.Project('./stage3')
BASE = 0x400000
str_ptr = 0x6040c0


def solve(start, dest, fail):
    state = p.factory.blank_state(addr=start)
    eax = state.solver.BVS('eax', 8)
    state.regs.eax = eax

    simgr = p.factory.simulation_manager(
        state
    )
    e = simgr.explore(
        find=[dest],
        avoid=[fail],
    )

    if len(e.found) > 0:
        s = e.found[0].solver.eval_upto(eax, 3)
        return s[0]
    else:
        print('Not Found')
        return None


# start, dest, fail
steps = [
    (0x400818, 0x40084a, 0x400848, 0),
    (0x40087e, 0x4008b5, 0x4008b3, 1),
    (0x4008e2, 0x400914, 0x400912, 2),
    (0x400941, 0x400978, 0x400976, 3),
    (0x4009a5, 0x4009d6, 0x4009d4, 4),
    (0x400a03, 0x400a3a, 0x400a38, 5),
    (0x400a67, 0x400a9e, 0x400a9c, 6),
    (0x400acb, 0x400b02, 0x400b00, 7),
    (0x400b2f, 0x400b61, 0x400b5f, 8),
    (0x400b8e, 0x400bc5, 0x400bc3, 9),
    (0x400bf2, 0x400c24, 0x400c22, 10),
    (0x400c51, 0x400c83, 0x400c81, 11),
    (0x400cb0, 0x400cfa, 0x400cf8, 12),
    (0x400d27, 0x400d59, 0x400d57, 13),
    (0x400d86, 0x400dbd, 0x400dbb, 14),
    (0x400dea, 0x400e1c, 0x400e1a, 15),
    (0x400e49, 0x400e7b, 0x400e79, 16),
    (0x400ea8, 0x400eda, 0x400ed8, 17),
    (0x400f07, 0x400f39, 0x400f37, 18),
    (0x400f66, 0x400f98, 0x400f96, 19),
    (0x400fc5, 0x400ffc, 0x400ffa, 20),
]
ans = ['\0'] * len(steps)
for start, dest, fail, index in steps:
    tmp = solve(start, dest, fail)
    print(chr(tmp))
    ans[index] = chr(tmp)
ans += ['\0']
print(''.join(ans))

Android App

apkファイルの中にあるarmライブラリがあり入力文字列のverify関数がある。 ライブラリ内のIsCorrect関数な最初の方にメモリ上に何か文字列を入れて何かをしている。 格納された文字列はef57f3fe3cf603c03890ee588878c0ecでありandroid端末にその文字列を入れて"Login"ボタンを押すとverifyに通りフラグが表示される。 フラグ: Sharif_CTF(833489ef285e6fa80690099efc5d9c9d)

感想

windowsやarmの環境構築、angrとかandroid周りの勉強に役立った。

明日は@N4NUさんの「Reversing Challenges Listの更新」です。