ctf4b 2020 Reversing 復習

明日ctf4b 2021やるらしいので去年の問題を復習する。

2021-09-21 : 再走しました (Link)

mask

やるだけ。

>>> import string
>>> enc_a = "atd4`qdedtUpetepqeUdaaeUeaqau"
>>> enc_b = "c`b bk`kj`KbababcaKbacaKiacki"
>>> enc_l = len(enc_a)
>>> for i in range(enc_l):
...     for char in string.printable:
...             if chr(ord(char) & 0x75) == enc_a[i] and chr(ord(char) & 0xeb) == enc_b[i]:
...                     flag.append(char)
...
>>> ''.join(flag)
'ctf4b{dont_reverse_face_mask}'

yakisoba

Ghidraで開いて、findしたいアドレスとavoidしたいアドレスを探す。

f:id:m0pisec:20210521134223p:plain

今回の場合、0x4006d2をfindしたくて、0x4006f7をavoidしたい。

>>> import angr
>>> p = angr.Project("./yakisoba")
WARNING | 2021-05-21 13:37:30,151 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
>>> e = p.factory.entry_state()
>>> simgr = p.factory.simulation_manager(e)
>>> s = simgr.explore(find=0x4006d2, avoid=0x4006f7)
>>> s.found[0].posix.dumps(0)
b'ctf4b{sp4gh3tt1_r1pp3r1n0}\x00\xd9\xd9\xd9\xd9'

gh0st

流し見して、そういえばあったな~と懐かしみを感じつつ取り組むもGhostScriptなにもわからない状態に陥ったのでbruteで解きました。(語彙力baby) 効率カスですが、まあいいか!

>>> with open('output.txt','r') as f:
...     data = f.readline()
... 
>>> output = data[:-2].split(' ')
>>> import string
>>> import subprocess
>>> from subprocess import PIPE
>>> flag = 'ctf4b{'
>>> for i in range(6,len(output)):
...     for pchar in string.printable:
...             proc = subprocess.run("echo '{}' | gs ./chall.gs".format(flag+pchar), shell=True, stdout=PIPE, stderr=PIPE, text=True)
...             if proc.stdout[207:-2] == ' '.join(output[:i+1]):
...                     flag = flag + pchar
...                     print(flag)
...                     break
... 
ctf4b{s
ctf4b{st
ctf4b{st4
ctf4b{st4c
ctf4b{st4ck
ctf4b{st4ck_
ctf4b{st4ck_m
ctf4b{st4ck_m4
ctf4b{st4ck_m4c
ctf4b{st4ck_m4ch
ctf4b{st4ck_m4ch1
ctf4b{st4ck_m4ch1n
ctf4b{st4ck_m4ch1n3
ctf4b{st4ck_m4ch1n3_
ctf4b{st4ck_m4ch1n3_1
ctf4b{st4ck_m4ch1n3_1s
ctf4b{st4ck_m4ch1n3_1s_
ctf4b{st4ck_m4ch1n3_1s_4
ctf4b{st4ck_m4ch1n3_1s_4_
ctf4b{st4ck_m4ch1n3_1s_4_l
ctf4b{st4ck_m4ch1n3_1s_4_l0
ctf4b{st4ck_m4ch1n3_1s_4_l0t
ctf4b{st4ck_m4ch1n3_1s_4_l0t_
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_f
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fu
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}

siblangs

まずBytecode Viewerで適当に眺めていたら es/o0i/challengeapp/nativemodule/ValidateFlagModule.class という怪しさの塊のアレを見つける。

f:id:m0pisec:20210521150543p:plain

コピペして適当に一部書き換えてこれを実行する。

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

class Solve {
    public static void main(String[] args) throws Exception {
        byte[] var3 = new byte[]{95, -59, -20, -93, -70, 0, -32, -93, -23, 63, -9, 60, 86, 123, -61, -8, 17, -113, -106, 28, 99, -72, -3, 1, -41, -123, 17, 93, -36, 45, 18, 71, 61, 70, -117, -55, 107, -75, -89, 3, 94, -71, 30};
        SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
        Cipher var4 = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec var5 = new GCMParameterSpec(128, var3, 0, 12);
        var4.init(2, secretKey, var5);
        var3 = var4.doFinal(var3, 12, var3.length - 12);
        System.out.println(new String(var3));
    }
}

flagの後半らしきものを手に入れたので、前半も探す。
assets/index.android.bundle 以外に怪しそうな場所がなかったのでde4jsにかけて ctf4b で検索をかけると怪しいコードが出てくる。

            function v() {
                var t;
                (0, l.default)(this, v);
                for (var o = arguments.length, n = new Array(o), c = 0; c < o; c++) n[c] = arguments[c];
                return (t = y.call.apply(y, [this].concat(n))).state = {
                    flagVal: "ctf4b{",
                    xored: [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27]
                }, t.handleFlagChange = function (o) {
                    t.setState({
                        flagVal: o
                    })
                }, t.onPressValidateFirstHalf = function () {
                    if ("ios" === h.Platform.OS) {
                        for (var o = "AKeyFor" + h.Platform.OS + "10.3", l = t.state.flagVal, n = 0; n < t.state.xored.length; n++)
                            if (t.state.xored[n] !== parseInt(l.charCodeAt(n) ^ o.charCodeAt(n % o.length), 10)) return void h.Alert.alert("Validation A Failed", "Try again...");
                        h.Alert.alert("Validation A Succeeded", "Great! Have you checked the other one?")

xorかけてるだけなので、お手軽にflagをゲットする。

>>> xstr1 = [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27]
>>> xstr2 = "AKeyForios10.3"
>>> flag = ""
>>> for i in range(len(xstr1)):
...     flag = flag + chr(xstr1[i] ^ ord(xstr2[i % len(xstr2)]))
...
>>> flag
'ctf4b{jav4_and_j4va5cr'

と、いうわけでflagは ctf4b{jav4_and_j4va5cr1pt_3verywhere} です。

sneaky

実行すると謎な蛇ゲームがスタートした。ハイスコアを取れとのことなので、Ghidraを眺めるもよくわからず。gdbに投げると

Starting program: /home/mopisec/sneaky
[Attaching after process 95657 fork to child process 95661]
[New inferior 2 (process 95661)]
[Detaching after fork from parent process 95657]
[Inferior 1 (process 95657) detached]

Thread 2.1 "sneaky" received signal SIGSEGV, Segmentation fault.

アー、strace

mopisec@ubuntu-pc:~$ strace ./sneaky
・
・略
・
ptrace(PTRACE_ATTACH, 95701)            = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=95701, si_uid=1000, si_status=SIGSTOP, si_utime=0, si_stime=0} ---
wait4(95701, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGSTOP}], 0, NULL) = 95701
ptrace(PTRACE_CONT, 95701, NULL, 0)     = 0
[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 95701
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=95701, si_uid=1000, si_status=0, si_utime=4, si_stime=4} ---
exit_group(0)                           = ?
+++ exited with 0 +++

出たなptrace。ptraceの位置をGhidraのSearch Memoryで探す。

b8  65  00       MOV        EAX ,0x65
0f  05           SYSCALL

が見つかればいいので、こんな感じで

f:id:m0pisec:20210521160530p:plain

見つけた。あとは参照元を何回か辿って、呼び出し元をnopにする。
(Ghidraでパッチを当てたらセグフォしたので、とりあえずIDAで。)

f:id:m0pisec:20210521172422p:plain

ptraceには対応したので、具体的にスコアを書き換えたいと思います。 aScoreDに格納するスコアはr13レジスタから参照されていて、[rbx+20h] からmov命令で何らかの値を格納していることがわかるため、スコアは [rbx+20h] に格納されていることが予想できます。

f:id:m0pisec:20210521173257p:plain

あとはgdbでコネコネして(適当に999999をスコアとして突っ込んで)

f:id:m0pisec:20210521174534p:plain

終わり。

感想

楽しかった!!!
明日は頑張ろう。