プログラム系統備忘録ブログ

記事中のコードは自己責任の下でご自由にどうぞ。

SECCON 2020 OnlineCTF SCSBX:Reversing write-up

SECCON 2020 OnlineCTFに参加しました。welcome, surveyの他にはrev1問だけ解けたので、その1問のwrite-up記事です。

コンテスト概要

Rulesページより引用:

Game Format: Jeopardy
Date:  Sat, 10 Oct 2020, 06:00 (UTC) to Sun, 11 Oct 2020, 06:00 (UTC)
Duration: 24 hours
Language: English

問題の説明

rev SCSBX:Reversing

SecCon SandBoX is an open-sourced virtual machine!
./scsbx seccon.bin

問題文中のリンクのtar.gzファイルから、c++ソースコード・バイナリ(ELF)・seccon.bin(入力用プログラム)・docs.md等を入手できました。 docs.mdの内容を以下に記述します。

# Brief Documentation for SCSBX

## SCSBX
### What is SCSBX?
SCSBX is a virtual machine for SCArch-32, a virtual architecture
designed for SECCON Online CTF 2020.
SCArch-32 is a stack machine with 32-bit address space.

### Direct Memory Access
SCSBX allows the program to directly access to the host memory.
This may sound dangerous but the program can only use the 32-bit
address space. Since SCSBX runs on a 64-bit machine with PIE enabled,
there's no way the user can read/write the VM memory directly.

## Design
### Instruction
Every instruction except for `push` is 1-byte long.
`push` accepts a 4-byte value to push into the stack.
**SCSBX is proudly open-sourced!**
Check the source code for more details.

### Memory Layout
(略)

seccon.binの逆コンパイル

docs.mdの説明より、push以外は1byte命令、pushのみは1byte+データ4byteだと分かります。 ソースコード中のscsbx.hppにて各種命令のオペコードが定義されていたため、テキストエディアの矩形選択やマクロを使って以下のPythonスクリプトを記述しました:

#!/usr/bin/env python3
with open("seccon.bin", "rb") as f:
    pc = 0
    while True:
        d = f.read(1)
        pc += 1
        if len(d) == 0: break
        ins = d[0]
        print(f"{pc-1}\t: ", end="") # どうやら調整が必要みたいです
        if ins == 0x20:
            value = f.read(4)
            if len(value) < 4:
                print("Wrong len in push!")
                exit
            pc += 4
            v = int.from_bytes(value, byteorder="little")
            print(f"push {v}(={hex(v)})")
        elif ins == 0x21: print("POP")
        elif ins == 0x22: print("DUP")
        elif ins == 0x23: print("XCHG")
        elif ins == 0x24: print("LOAD32")
        elif ins == 0x25: print("LOAD64")
        elif ins == 0x26: print("STORE8")
        elif ins == 0x27: print("STORE16")
        elif ins == 0x28: print("STORE32")
        elif ins == 0x70: print("SHOW")
        elif ins == 0x30: print("JMP")
        elif ins == 0x31: print("JEQ")
        elif ins == 0x32: print("JGT")
        elif ins == 0x33: print("JGE")
        elif ins == 0x34: print("CALL")
        elif ins == 0x40: print("ADD")
        elif ins == 0x41: print("SUB")
        elif ins == 0x42: print("MUL")
        elif ins == 0x43: print("DIV")
        elif ins == 0x44: print("MOD")
        elif ins == 0x50: print("NOT")
        elif ins == 0x51: print("AND")
        elif ins == 0x52: print("OR")
        elif ins == 0x53: print("XOR")
        elif ins == 0x54: print("SHL")
        elif ins == 0x55: print("SHR")
        elif ins == 0x56: print("ROL")
        elif ins == 0x57: print("ROR")
        elif ins == 0x60: print("READ")
        elif ins == 0x61: print("WRITE")
        elif ins == 0x62: print("MAP")
        elif ins == 0x63: print("UNMAP")
        elif ins == 0x64: print("EXIT")
        else:
            print(f"unknown ins! {ins}")
            exit

実行結果は248行になりました(詳細はメモ含めて本ブログ最後に記載)。後は、scsbxにデバッグ出力を追加したりしながら、ひたすら逆コンパイル結果にスタックの処理内容をメモしながら読み解いていきました。

処理内容概略

メモリ関連の使い方を読み解いた内容は以下になります:

  • 最初に0xdead0000からメモリを確保する
  • その後のメモリマップは以下の通り
    • [0xdead0000, 0xdead0004) :: 522のブロックで 0x06d35bcd を初期値として書き込み、その後534のブロックでLOAD,STOREで更新する
    • [0xdead000a, 0xdead004a) :: 66でのREAD結果を書き込み、その後ループで更新する
    • [0xdead004a, 0xdead008a) :: 598のブロックで書き込んだ後は固定、最後に[0xdead000a, 0xdead004a)と一致するか比較される

JMP命令等によるフロー制御を読み解いた内容は以下になります:

main:
  0~77
    (10でのmapで0xdead0000メモリを確保)
    (11でcall 598)
    (17でcall 522)
    (23~55で"FLAG:"出力)
    (55~66で[0xdead000a, 0xdead004a)へREAD結果格納)
    (62でのpush 8がループ変数)
    iループ(8→1):
      270~291
      78~162
        (114で [0xdead000a+8*(8-i)] のLOAD)
        (151で [0xdead000e+8*(8-i)] のLOAD)
        (152のpush 3がループ変数)
        jループ(3→1):
          221~242
          163~220
            (186で534へのcallを含む、つまり[0xdead0000]を更新する)
      243~291
        (249で [0xdead000e+8*(8-i)] = 計算結果1 なSTORE)
        (256で [0xdead000a+8*(8-i)] = 計算結果2 なSTORE)
  292~308 (292で一旦スタックは空になる、298で8をpush, 293で0をpush)
    iループ(8→1):
      405~426
      309~426
        (全体的に: [0xdead000a, 0xdead004a)と[0xdead004a, 0xdead008a)の一致判定、後者の[0xdead004a, 0xdead008a)は初期化時から不変なのでそれ依存で分かりそう)
  427~437
  if 一致: 477~515
  else:    438~476
  516~521(exit)

def 522:
  [0xdead0000]の初期値設定

def 534:
  534~597
    (539で[0xdead0000]をLOAD)
    (581で[0xdead0000]へ ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea) としてSTORE)
    (597で スタックトップに「~((呼び出し時のtop) xor (更新後の[0xdead0000]))」をpushした状態でreturn)

def 598:
  598~774 ([0xdead004a, 0xdead008a)の設定、これらの値は以降変わらない)

フラグの計算

上記の読み解いた結果から、以下のことが分かります。

  • 入力を8byte単位で処理
  • 計算結果が特定8byteに一致すること必須
  • それを0x40byte分繰り返す

今回の場合、フラグ形式がSECCON{の7文字から始まる事がわかっています。それを利用して順方向処理の動作確認をして、その後に逆方向の処理を実装しました:

#!/usr/bin/env python3

# 0xdead0000の値の推移
dead0000 = [0x6d35bcd,0x2a5d2289,0x2f6875f9,0x2fad9e73,0x5b9550f,0x26c9c89f,0x11c0d0f,0x20e72c5d,0x13c96e3b,0x22929531,0x28cc5725,0x12466b89,0xc06913b,0x253aa61b,0x12a3213b,0x23b9fa5d,0xda0ec51,0x294b7005,0x2bbf4a7d,0x2d748c31,0x2b8ac467,0x478d51b,0x25080a67,0x629db31,0x3635d3b,]
print(len(dead0000))  # 25

# [0xdead004a, 0xdead008a)の設定値
expected = [0x46761223,0x54bea5c5,0x7a22e8f6,0x5db493c9,0x055d175e,0x022fcd33,0x42c46be6,0x6d10a0e8,0x53f4c278,0x7279ec2a,0x5491fb39,0x49ac421f,0x49ab3a37,0x47855812,0x5718bb05,0x0540fb5b,]
print(len(expected)) # 16

# loopCount: iループの回数、iは8→1のデクリメントだけど、ここでは0~7を前提
def convert(lower, upper, loopCount):
    for i in range(3):
        dead = dead0000[(3*loopCount)+i+1]
        xor1 = upper ^ dead
        tilde = ~xor1 # (((~(xor1))+0x100000000)&0xFFFFFFFF)
        # print(f"dead={hex(dead)}")
        # print(f"xor1={hex(xor1)}")
        # print(f"tilde={hex(tilde)}")
        [lower, upper] = [upper, lower ^ tilde]
        # print(f"lower={hex(lower)}")
        # print(f"upper={hex(upper)}")
        # print()
    return [lower, upper]

# loopCount: iループの回数、iは8→1のデクリメントだけど、ここでは0~7を前提
def invert(lower, upper, loopCount):
    for i in range(3):
        tilde = upper ^ lower
        xor1 = ~tilde
        [lower, upper] = [xor1 ^ dead0000[(3*loopCount)+(2-i)+1], lower]
    return [lower, upper]

def get_bytes(s):
    return int.from_bytes(s.encode("utf-8"), byteorder="little")
def get_str(n):
    return n.to_bytes(length=4, byteorder="little").decode("utf-8")

# for c in range(0x20, 0x7f):
#     [lower, upper] = convert(get_bytes("SECC"), get_bytes("ON{"+chr(c)), 0)
#     if lower==expected[0] and upper==expected[1]:
#         print(c, chr(c))        # "T"

for i in range(8):
    [lower, upper] = invert(expected[2*i], expected[2*i+1], i)
    print(f"{get_str(lower)}{get_str(upper)}", end="")
print()

上記を実行してフラグが手に入りました: SECCON{TfuRYYVaz8Us696t3JWNxZZPsXEmdL7cCmgzpgxXKarUOnIwhSj9tQ}

感想

  • スタックマシンの処理を追うのは難しかったです
  • とくにDUPとXCHGを追うのが勘違いが頻発してとても難しかったです
  • それでもこういった処理を追うのはまさにリバーシングで楽しめました
  • だいぶ難しく感じたのですが、それでもwelcome/surveyを除いたら、3番目に解かれている問題なんですね、皆様凄い
  • SECCONはとても難しい、他の問題は手も足も出ませんでした

ここまでを導出するために使用した逆コンパイルコードとメモ結果

この記事はこのセクションで終わりです。 自分用メモなので他の方が理解できるかどうかは保証できません。スタック箇所のメモは左がtopです。

0   : push 4096(=0x1000) // map size
5   : push 3735879680(=0xdead0000) // map address
10  : MAP
11  : push 598(=0x256)
16  : CALL
17  : push 522(=0x20a)
22  : CALL

// ここから書き込み文字列構築
23  : push 1195461702(=0x47414c46)
28  : push 3735879684(=0xdead0004) // store address
33  : STORE32
34  : push 8250(=0x203a)
39  : push 3735879688(=0xdead0008) // store address
44  : STORE16
45  : push 6(=0x6) // write size
50  : push 3735879684(=0xdead0004) // write size address
55  : WRITE
// ここまで書き込み

56  : push 64(=0x40) // read size!!!!!!
61  : push 3735879690(=0xdead000a) // read address!!!!
66  : READ

67  : push 8(=0x8) // カウンター
72  : push 270(=0x10e)
77  : JMP
// ここまで最初の処理

// pc291で不一致に来る(多分8..1(inclusive)でデクリメントするカウンターiが入っている)
78  : push 0(=0x0) // [0, i]
83  : DUP          // [i, i]
84  : push 8(=0x8) // [8, i, i]
89  : SUB          // [8-i, i, ...]
90  : push 8(=0x8)
95  : MUL          // [8*(8-i), i, ...]
96  : push 0(=0x0)
101 : DUP          // [8*(8-i), 8*(8-i), i, ...]
102 : push 3735879690(=0xdead000a)
107 : ADD          // [0xdead000a+8*(8-i), 8*(8-i), i]
108 : push 0(=0x0)
113 : DUP          // [0xdead000a+8*(8-i), 0xdead000a+8*(8-i), 8*(8-i), i]
114 : LOAD32       // {[0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 8*(8-i), i}
115 : push 0(=0x0)
120 : XCHG         // {[0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 8*(8-i), i}
121 : push 2(=0x2)
126 : XCHG         // {8*(8-i), 0xdead000a+8*(8-i), [0xdead000a+8*(8-i)], i}
127 : push 3735879694(=0xdead000e)
132 : ADD          // {0xdead000e+8*(8-i), 0xdead000a+8*(8-i), [0xdead000a+8*(8-i)], i}
133 : push 0(=0x0)
138 : DUP          // {0xdead000e+8*(8-i), 0xdead000e+8*(8-i), 0xdead000a+8*(8-i), [0xdead000a+8*(8-i)], i}
139 : push 3(=0x3)
144 : XCHG         // {[0xdead000a+8*(8-i)], 0xdead000e+8*(8-i), 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
145 : push 1(=0x1)
150 : XCHG         // {0xdead000e+8*(8-i), [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
151 : LOAD32       // {[0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
152 : push 3(=0x3) // {j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
157 : push 221(=0xdd)
162 : JMP

// cp242のjのデクリメントループ(初期値3、1含めてここまで来る) {j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
// 2番めは、最初は「次の4文字」、2回目以降は (4文字 xor ~(次の4文字 xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))) (つまり4文字と次の4文字をベースに色々計算した結果、[0xdead0000]は毎回更新されるので注意)
163 : push 2(=0x2)
168 : DUP             // {[0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
169 : push 2(=0x2)
174 : DUP             // {[0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
175 : push 0(=0x0)
180 : DUP            // {[0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}

181 : push 534(=0x216)
186 : CALL // 中で0xdead0000へのload, storestoreしている、

{~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea)), [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
187 : push 2(=0x2)
192 : DUP            // {[0xdead000a+8*(8-i)], ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea)), [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
193 : XOR            // { ([0xdead000a+8*(8-i)] xor ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))), [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
194 : push 4(=0x4)
199 : XCHG           // { [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, ([0xdead000a+8*(8-i)] xor ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))), [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
200 : POP            // { [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, ([0xdead000a+8*(8-i)] xor ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))), [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
201 : push 4(=0x4)
206 : XCHG           // { [0xdead000a+8*(8-i)], [0xdead000a+8*(8-i)], j, ([0xdead000a+8*(8-i)] xor ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))), [0xdead000e+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
207 : POP
208 : POP
209 : push 1(=0x1)  // {1, j, ([0xdead000a+8*(8-i)] xor ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))), [0xdead000e+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
214 : push 1(=0x1)
219 : XCHG          // {j, 1, ([0xdead000a+8*(8-i)] xor ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))), [0xdead000e+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
220 : SUB           // 実質j--しての {j, ([0xdead000a+8*(8-i)] xor ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))), [0xdead000e+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}

// cp157からjmpして来た時: {j(=3), [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
// cp163から滑ってきた時: {j, ([0xdead000a+8*(8-i)] xor ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea))), [0xdead000e+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
// つまりjが1減って、元2番めが新3番目にずれて、元2番めの値を元に新2番めの値が決まる、4~6番目の値は不変
// 実質jが0ならcp243へ、1以上ならcp163へ、って分岐
221 : push 0(=0x0)
226 : DUP             // {j, j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
227 : push 0(=0x0)
232 : push 163(=0xa3) // f
237 : push 243(=0xf3) // t
242 : JEQ

// ここまでjループ(ここはもう0にあったあと、つまり3回回るはず)
// {0(==j), (計算結果3), (計算結果2), 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
243 : push 4(=0x4)
248 : XCHG             // {0xdead000e+8*(8-i), (計算結果3), (計算結果2), 0xdead000a+8*(8-i), 0, i}
249 : STORE32          // {(計算結果2), 0xdead000a+8*(8-i), 0, i}
250 : push 1(=0x1)
255 : XCHG             // {0xdead000a+8*(8-i), (計算結果2), 0, i}
256 : STORE32          // {0, i}
257 : POP              // {i}
258 : push 1(=0x1)
263 : push 1(=0x1)
268 : XCHG
269 : SUB              // 実質i--からの{i}

// ここへpc72からjmpしてくる(事前にpush 8付き)→8はcounteでは?4*8=32文字?
// 上から滑ってくる場合でも{i},だけ
// iが0なら292へ[i]残して, それ以外なら[i]残して78へ
270 : push 0(=0x0)
275 : DUP // {i, i}
276 : push 0(=0x0) // b
281 : push 78(=0x4e) // if NOT
286 : push 292(=0x124) // if equal
291 : JEQ

// 291のpush結果が一致している場合にここに来る
292 : POP // {}
293 : push 0(=0x0)
298 : push 8(=0x8)     [8, 0]
303 : push 405(=0x195)
308 : JMP

// // pc426のJEQのfalse branch
309 : push 0(=0x0)
314 : DUP                          // {i, i, 0}
315 : push 1(=0x1)
320 : push 1(=0x1)
325 : XCHG                         // {i, 1, i, 0}
326 : SUB                          // {i-1, i, 0}
327 : push 8(=0x8)
332 : MUL                          // {8*(i-1), i, 0}
333 : push 3735879690(=0xdead000a)
338 : ADD                          // {0xdead000a+8*(i-1), i, 0}
339 : push 0(=0x0)
344 : DUP                          // {0xdead000a+8*(i-1), 0xdead000a+8*(i-1), i, 0}
345 : push 64(=0x40)
350 : ADD                          // {0xdead004a+8*(i-1), 0xdead000a+8*(i-1), i, 0}
// LOAD64なので2byte分積まれる
// iがデクリメントするループなので後ろの方から積まれることに注意!(つまり、i=8時は {0x5718bb05, 0x0540fb5b, ...}が積まれる)
351 : LOAD64                       // {[0xdead004a+8*(i-1)], [0xdead004e+8*(i-1)], 0xdead000a+8*(i-1), i, 0}
352 : push 2(=0x2)
357 : XCHG                         // {0xdead000a+8*(i-1), [0xdead004e+8*(i-1)], [0xdead004a+8*(i-1)], i, 0}
358 : LOAD64                       // {[0xdead000a+8*(i-1)], [0xdead000e+8*(i-1)], [0xdead004e+8*(i-1)], [0xdead004a+8*(i-1)], i, 0}
359 : push 2(=0x2)
364 : XCHG                         // {[0xdead004e+8*(i-1)], [0xdead000e+8*(i-1)], [0xdead000a+8*(i-1)], [0xdead004a+8*(i-1)], i, 0}
365 : SUB                         // { ([0xdead004e+8*(i-1)]-[0xdead000e+8*(i-1)]), [0xdead000a+8*(i-1)], [0xdead004a+8*(i-1)], i, 0}
366 : push 2(=0x2)
371 : XCHG                        // {[0xdead004a+8*(i-1)], [0xdead000a+8*(i-1)], ([0xdead004e+8*(i-1)]-[0xdead000e+8*(i-1)]), i, 0}
372 : SUB                         // { ([0xdead004a+8*(i-1)]-[0xdead000a+8*(i-1)]), ([0xdead004e+8*(i-1)]-[0xdead000e+8*(i-1)]), i, 0}
373 : OR                          // { (つまり8バイトが一致していたら0), i, 0}
374 : push 1(=0x1)
379 : XCHG                        // { i, (正解なら0), 0}
380 : push 2(=0x2)
385 : XCHG                        // { 0, (正解なら0), i}
386 : OR                          // { (正解なら0), i }
387 : push 1(=0x1)
392 : XCHG                        // { i, (正解なら0) }
393 : push 1(=0x1)
398 : push 1(=0x1)
403 : XCHG
404 : SUB                         // 実質i--, { i, (正解なら0) }

// pc308から {i, 0}で来る、スタックトップが0かどうかで分岐(→これもカウンターでは?)
// 上から流れてくることもある { i, (正解なら0) }
405 : push 0(=0x0)     // 
410 : DUP              // {i, i, 0}
411 : push 0(=0x0)     // b
416 : push 309(=0x135) // if NOT
421 : push 427(=0x1ab) // if eq, ここに飛ぶときは(正解なら0)が0のままであってほしい!
426 : JEQ

// pc426のJEQのtrue branch {i, (正解なら0) }
427 : push 438(=0x1b6) // if not
432 : push 477(=0x1dd) // if eq, 一致してほしい!!!!!!
437 : JEQ

// pc437のfalse branch, 来てほしくない
438 : push 1852797527(=0x6e6f7257) // "Wron"
443 : push 3735879680(=0xdead0000)
448 : STORE32
449 : push 169943399(=0xa212167) // "g!!\n"
454 : push 3735879684(=0xdead0004)
459 : STORE32
460 : push 8(=0x8)
465 : push 3735879680(=0xdead0000)
470 : WRITE
471 : push 516(=0x204)
476 : JMP // exit 0

// pc437のJEQのfalse branch、ここに来てほしい!
477 : push 1920102211(=0x72726f43) // "Corr"
482 : push 3735879680(=0xdead0000)
487 : STORE32
488 : push 175399781(=0xa746365) // "ect\n"
493 : push 3735879684(=0xdead0004)
498 : STORE32
499 : push 8(=0x8)
504 : push 3735879680(=0xdead0000)
509 : WRITE
510 : push 516(=0x204)
515 : JMP // exit 0

// pc476 or 515からのjmp(そしてここで終わる)
516 : push 0(=0x0)
521 : EXIT

// pc16→pc522へcall
// storeのみの関数
522 : push 114514893(=0x6d35bcd) // value
527 : push 3735879680(=0xdead0000) // address
532 : STORE32
533 : JMP

// [0xdead0000]の内容を ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea) で更新する
// スタックトップに ~((呼び出し時のtop) xor (更新後の[0xdead0000])) をpushした状態でreturn
// pc186→pc534、多分入力結果を加工する奴, {(ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
534 : push 3735879680(=0xdead0000)  //最初は0x6d35bcdだけどこの関数末尾で更新してるね
539 : LOAD32                        // {[0xdead0000], (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
540 : push 1919(=0x77f)
545 : MUL                           // {0x77f*[0xdead0000], (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
546 : push 810(=0x32a)              // {0x32a, 0x77f*[0xdead0000], (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
551 : push 1(=0x1)
556 : XCHG                          // {0x77f*[0xdead0000], 0x32a, (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
557 : SUB                           // {(0x77f*[0xdead0000]-0x32a), (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
558 : push 811512810(=0x305eb3ea)   // {0x305eb3ea, (0x77f*[0xdead0000]-0x32a), (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
563 : push 1(=0x1)
568 : XCHG                          // {(0x77f*[0xdead0000]-0x32a), 0x305eb3ea, (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
569 : MOD                           // {((0x77f*[0xdead0000]-0x32a)%0x305eb3ea), (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
570 : push 0(=0x0)
575 : DUP                          // {((0x77f*[0xdead0000]-0x32a)%0x305eb3ea), ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea), (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}

// ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea)へ更新
// 初回は0x6d35bcd→2a5d2289へ更新
576 : push 3735879680(=0xdead0000)
581 : STORE32                      // {((0x77f*[0xdead0000]-0x32a)%0x305eb3ea), (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}

// return address以下がかかわるのはここからだけ
582 : push 2(=0x2)
587 : DUP                          // {[0xdead000e+8*(8-i)], ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea), (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
588 : XOR                          // {([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea)), (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
589 : NOT                          // { ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea)), (ra), [0xdead000e+8*(8-i)], [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
590 : push 2(=0x2)
595 : XCHG                         // { [0xdead000e+8*(8-i)], (ra), ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea)), [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
596 : POP                          // {(ra), ~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea)), [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}
597 : JMP // {~([0xdead000e+8*(8-i)] xor ((0x77f*[0xdead0000]-0x32a)%0x305eb3ea)), [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], j, [0xdead000e+8*(8-i)], [0xdead000a+8*(8-i)], 0xdead000a+8*(8-i), 0xdead000e+8*(8-i), i}

// pc11→pc598へcallする
// このブロックはstore32だけして終わり
598 : push 1182143011(=0x46761223) // value
603 : push 3735879754(=0xdead004a) // address
608 : STORE32
609 : push 1421780421(=0x54bea5c5)
614 : push 3735879758(=0xdead004e)
619 : STORE32
620 : push 2049108214(=0x7a22e8f6)
625 : push 3735879762(=0xdead0052)
630 : STORE32
631 : push 1572115401(=0x5db493c9)
636 : push 3735879766(=0xdead0056)
641 : STORE32
642 : push 89986910(=0x55d175e)
647 : push 3735879770(=0xdead005a)
652 : STORE32
653 : push 36687155(=0x22fcd33)
658 : push 3735879774(=0xdead005e)
663 : STORE32
664 : push 1120168934(=0x42c46be6)
669 : push 3735879778(=0xdead0062)
674 : STORE32
675 : push 1829806312(=0x6d10a0e8)
680 : push 3735879782(=0xdead0066)
685 : STORE32
686 : push 1408549496(=0x53f4c278)
691 : push 3735879786(=0xdead006a)
696 : STORE32
697 : push 1920592938(=0x7279ec2a)
702 : push 3735879790(=0xdead006e)
707 : STORE32
708 : push 1418853177(=0x5491fb39)
713 : push 3735879794(=0xdead0072)
718 : STORE32
719 : push 1236025887(=0x49ac421f)
724 : push 3735879798(=0xdead0076)
729 : STORE32
730 : push 1235958327(=0x49ab3a37)
735 : push 3735879802(=0xdead007a)
740 : STORE32
741 : push 1199921170(=0x47855812)
746 : push 3735879806(=0xdead007e)
751 : STORE32
752 : push 1461238533(=0x5718bb05)
757 : push 3735879810(=0xdead0082)
762 : STORE32
763 : push 88144731(=0x540fb5b)
768 : push 3735879814(=0xdead0086)
773 : STORE32
774 : JMP