Automotive CTF Japan予選writeup

[第１回「Automotive CTF Japan」の参加者を募集します。 （METI/経済産業省）](https://www.meti.go.jp/policy/mono_info_service/mono/automobile/Automated-driving/automotive_ctf_japan.html)

beaさん、laysakuraさん、hamayanhamayanさん、tkitoさんとのチームTeamONEで参加。全完5位（国内4位）。決勝進出。

国内上位5チームが決勝進出で、決勝の優勝賞金は10万円。決勝の上位2チームがアメリカ開催のAutomotive CTF 2024の出場権を得られ、旅費も出してくれるらしい。そこでの優勝賞金は35,000ドル。自動車業界のお金すご。

チームに誘われるまでこんなCTFがあることを知らなかった。CTFガチ勢がいないなら楽勝では？　と思ったけど、蓋を開けてみれば、普通にいつもの強いチームがいたわ。

自動車には関係の無い普通のCTFのような問題もある。自動車関係のCANのログの解析とかの問題が多い。CANは今どきのハイテクな自動車の各コンポーネントが通信をするプロトコル。過去のCTFで1回くらいは見たことがある気がする。

私が解いた問題のwriteup。

## Misc

### Gameboy Game

ゲームボーイのゲームのチート。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/dbe343c8-4a28-712a-8170-7698a71fb03d.png)

カラーコーンの上の石みたいなのがバッテリー。バッテリーにぶつかると1ポイント獲得で、32767点取って矢印のところに行けばクリア。ただし、カラーコーンに当たるとスコアがリセットされる。

[BGB GameBoy Emulator \(current version: BGB 1.6.4\)](https://bgb.bircd.org/)

を使った。

右クリック → Other → cheat searcher　でメモリの探索ができる。ある値のアドレスを探し、しばらく経った後に、過去に探したアドレスの中から今の値で絞り込むということができる。これでスコアのアドレスを探した。0xCB93がスコアのアドレス。

右クリック → Debugger　で、このアドレスの値を書き換えてクリア。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/7deb5134-a1f6-c831-0dc9-73266b5130bb.png)

`BH{CARS_HATE_CONEZ?}`

### Signal Sleuth

> キーフォブからいくつかのキャプチャを取得しました。しかし、車両のIDを特定するために助けが必要です。
>
> 目標は、キャプチャを復調し、ビットストリームを取得したら、そのビットストリームのどの部分がコマンドの部分に対応しているかを特定することです。フラグは「bh{}」なしで16進数で提出してください。例えば、「AABB」のように。
>
> ヒント: エンコーディングを考慮するのを忘れないでください。また、これらのキャプチャは20 MS/sで記録されていますが、これについては特に知る必要はありません。

「キーフォブ」というのは自動車の鍵。

与えられたバイナリを符合付き32ビット整数の並びとして見て、正負を0と1に変換するとこうなる。

```
 :
10100100000000111000011101011100111110100101010000101011001111101010010000011111
11101001011001001011011101001110111000000110111010011101001101000001011010110101
00110001101000000000110111001100101010011000100001010001101010111001100111000000
11000000111011101000110010111011000100011011100101110100111100001001011010100100
01100110011001011110111000111111101101110011010101010101000010101001010110101001
00111100000111011001101010100100100101000101000001110001001010001010111001000000
00001000000100111101110011000101100101001010100101100011000111001100011010011110
01001100111101010100100110111001001100001010110010011010101010000110110010100000
01010001101111011111111011111011111100001101000001000110000100100001101011101110
01000101001001000110001000110001110101101100001011111101011110011001001110011011
01111111111111110001001011100101000111000000000011000101011000001101110001101000
10110011101100101100100001110010100000101000100011011111011111100000111110110001
00110111100000010011010000000011110111110000000000000010000111111111111111111110
00000000000000000000001111111111111111111111100000000000000000000001111111111111
11111111100000000000000000000001111111111111111111111000000000000000000000001111
11111111111111111100000000000000000000001111111111111111111111000000000000000000
00000111111111111111111111100000000000000000000000000000000000000000111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111110000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000001111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000011111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111100000000000000000000000000000
 :
```

`0` か `1` が並んでいるところを `1`、ランダムっぽいところを `0` とするとこうなる。

```
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000001
11111110000000011111111000000001111111100000000111111110000000011111111000000001
11111110000000011111111000000001111111100000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000111111111111111100000000111111110000000011111111000000001
11111110000000011111111000000000000000011111111000000001111111100000000111111110
00000001111111111111111000000001111111100000000000000001111111111111111000000000
00000001111111100000000111111111111111100000000111111110000000011111111000000001
11111110000000011111111000000001111111100000000111111110000000011111111000000001
11111110000000000000000111111111111111100000000111111110000000011111111000000000
00000001111111111111111000000001111111100000000000000001111111100000000111111110
00000001111111111111111000000001111111100000000000000001111111100000000111111111
11111110000000011111111000000001111111100000000111111110000000000000000111111110
00000001111111111111111000000000000000011111111000000001111111100000000111111110
00000001111111100000000111111111111111100000000111111110000000011111111000000000
00000001111111111111111000000001111111100000000000000001111111111111111000000000
00000001111111100000000111111110000000011111111111111110000000011111111000000001
11111110000000011111111000000001111111100000000111111110000000000000000111111111
11111110000000011111111000000000000000011111111111111110000000000000000111111111
 :
```

`00000000` を `0` 、 `11111111` を `1` とするとこう。

```
00000000000000000000101010101010101000000000000000000110101010100101010110100110
01011010101010101010100110101001101001010110100101101010100101100101010101101010
01101001100101011010101010100110100110011001011010100000000000000000011010101010
01010101101001100101101010101010101010011010100110100101011010010110101010010110
01010101011010100110100110010101101010101010011010011001100101101010000000000000
00000110101010100101010110100110010110101010101010101001101010011010010101101001
01101010100101100101010101101010011010011001010110101010101001101001100110010110
10100000000000000000011010101010010101011010011001011010101010101010100110101001
10100101011010010110101010010110010101010110101001101001100101011010101010100110
1001100110010110101000000000000000000000
```

`1010101010101010` をスタートコードとして、以降は `01` を `0` 、 `10` を `1` とするとこうなる。

```
11111111011111000011010011111111101110110001100111100100000111011010001111110110
10100111011111000011010011111111101110110001100111100100000111011010001111110110
10100111011111000011010011111111101110110001100111100100000111011010001111110110
10100111011111000011010011111111101110110001100111100100000111011010001111110110
10100111
```

16進数に変換。

```
FF7C34FFBB19E41DA3F6A77C34FFBB19E41DA3F6A77C34FFBB19E41DA3F6A77C34FFBB19E41DA3F6A7
```

```solve.py
import sys
import struct

d = open(sys.argv[1], "rb").read()
D = ""
for i in range(0, len(d), 4):
    v = struct.unpack("<i", d[i:i+4])[0]
    if v<0:
        D += "0"
    else:
        D += "1"
#print(D)

d2 = ""
for i in range(0, len(D), 250):
    if "0"*100 in D[i:i+250] or "1"*100 in D[i:i+250]:
        d2 += "1"
    else:
        d2 += "0"
#print(d2)

d2 = "0"+d2
d3 = ""
for i in range(0, len(d2), 8):
    if d2[i:i+8].count("0")>=7:
        d3 += "0"
    elif d2[i:i+8].count("1")>=7:
        d3 += "1"
    else:
        d3 += "?"
#print(d3)

i = 0
while d3[i:i+16]!="1010101010101010":
    i += 1
d4 = ""
while i+2<=len(d3):
    if d3[i:i+2]=="01":
        d4 += "0"
    elif d3[i:i+2]=="10":
        d4 += "1"
    #else:
    #    d4 += "?"
    i += 2
#print(d4)

d5 = ""
for i in range(0, len(d4), 8):
    d5 += f"{int(d4[i:i+8], 2):02X}"
print(d5)
```

施錠の通信が1個、解錠の通信が2個与えられているので、それぞれデコード。

```shell-session
$ python3 solve.py signal_sleuth_capture_lock_1.bin
FF7C34FFBB19E41DA3F6A77C34FFBB19E41DA3F6A77C34FFBB19E41DA3F6A77C34FFBB19E41DA3F6A7
$ python3 solve.py signal_sleuth_capture_unlock_1.bin
FF7C34FFCC0E361B3913DF7C34FFCC0E361B3913DF7C34FFCC0E361B3913DF7C34FFCC0E361B3913DF
$ python3 solve.py signal_sleuth_capture_unlock_2.bin
FF7C34FFCC22FE2F59FAF37C34FFCC22FE2F59FAF37C34FFCC22FE2F59FAF37C34FFCC22FE2F59FAF3
```

「車両のID」ということは全てに共通している部分でしょう。

`7C34`

### Rolling Thunder

> この問題は、「Signal Sleuth」を解読した後に解くことを想定しています！
> 同じキーフォブから10個のアンロック時のキャプチャがあります。パート1で解析したのと同じキーフォブです。ピアレビューされた業界認定の暗号を使用するのではなく、自分で作ったものを使うのでしょうか;)
> この問題の目標は、キーフォブに実装されているローリングコードを生成するアルゴリズムを解明することです。それが分かったら、次に送信される予定でまだ使用されていないローリングコードを特定してください。最後のキャプチャが最新のものであり、最後のコードが使用された最後のコードであると仮定できます。パート1と同じように、フラグは「bh{}」なしで16進数で提出してください。例えば、「AABBCCDDEEFF」のように。
> 現実の世界で、もしこれが本物のキーフォブでできれば車を盗めてしまうということに気をつけてください!!

解錠の信号が1通りだと、それをキャプチャして送るリプレイ攻撃で簡単に鍵が開けられてしまう。そこで、車と鍵で共通のルールを決めておき、解錠のコードを更新していくらしい。

とりあえずSignal Sleuthのスクリプトでデコード。

```shell-session
$ for a in *.bin; do echo -n "$a "; python3 solve.py $a; done
rolling_thunder_capture_unlock_0.bin FF7C34FFCC0203040506157C34FFCC0203040506157C34FFCC0203040506157C34FFCC020304050615
rolling_thunder_capture_unlock_1.bin FF7C34FFCC0304050615297C34FFCC0304050615297C34FFCC0304050615297C34FFCC030405061529
rolling_thunder_capture_unlock_2.bin FF7C34FFCC0405061529507C34FFCC0405061529507C34FFCC0405061529507C34FFCC040506152950
rolling_thunder_capture_unlock_3.bin FF7C34FFCC05061529509D7C34FFCC05061529509D7C34FFCC05061529509D7C34FFCC05061529509D
rolling_thunder_capture_unlock_4.bin FF7C34FFCC061529509D367C34FFCC061529509D367C34FFCC061529509D367C34FFCC061529509D36
rolling_thunder_capture_unlock_5.bin FF7C34FFCC1529509D36677C34FFCC1529509D36677C34FFCC1529509D36677C34FFCC1529509D3667
rolling_thunder_capture_unlock_6.bin FF7C34FFCC29509D3667C87C34FFCC29509D3667C87C34FFCC29509D3667C87C34FFCC29509D3667C8
rolling_thunder_capture_unlock_7.bin FF7C34FFCC509D3667C87B7C34FFCC509D3667C87B7C34FFCC509D3667C87B7C34FFCC509D3667C87B
rolling_thunder_capture_unlock_8.bin FF7C34FFCC9D3667C87BCD7C34FFCC9D3667C87BCD7C34FFCC9D3667C87BCD7C34FFCC9D3667C87BCD
rolling_thunder_capture_unlock_9.bin FF7C34FFCC3667C87BCD4A7C34FFCC3667C87BCD4A7C34FFCC3667C87BCD4A7C34FFCC3667C87BCD4A
```

違っている部分はここ。

```
020304050615
030405061529
040506152950
05061529509D
061529509D36
1529509D3667
29509D3667C8
509D3667C87B
9D3667C87BCD
3667C87BCD4A
```

先頭5バイトは、直前のコードの末尾5バイト。残り1バイトくらいスコアサーバー総当たりでも良い気はするが……ちゃんと考えると、直前のコードの各バイトの合計。

このスクリプトで、問題と同じコード10個と、その次のコードが得られる。

```solve2.py
T = [1, 2, 3, 4, 5, 6]
for i in range(11):
    T = T[1:]+[sum(T)%256]
    print("".join(f"{t:02X}" for t in T))
```

```
020304050615
030405061529
040506152950
05061529509D
061529509D36
1529509D3667
29509D3667C8
509D3667C87B
9D3667C87BCD
3667C87BCD4A
67C87BCD4AF7
```

ということで `67C87BCD4AF7` をサブミットするが不正解。え？？？

そのまま放置していて、hamayanhamayanさんに「今なら通るかも」と言われてサブミットしたら通った。スコアサーバー側のフラグが修正されたというアナウンスがあった。

すでに通していたチームが「こちとらめっっっちゃ時間を掛けてguessしたんだぞ :anger: 」と怒っていた。修正前は何だったのか気になる。

`67C87BCD4AF7`

### FW

> 暗号化されたパスワードを作成しましたが、すぐに削除されてしまいました。復元してもらえますか？

問題ファイルはルーターのファームウェア。firmware-mod-kitで展開。

https://github.com/rampageX/firmware-mod-kit

```
$ ./extract-firmware.sh /mnt/c/documents/ctf/blockhardor2/FW/file.bin
Firmware Mod Kit (extract) 0.99, (c)2011-2013 Craig Heffner, Jeremy Collake

Scanning firmware...
 :
```

「暗号化されたパスワード」はゴミ箱にある。

```
$ cat fmk/rootfs/root/.local/share/Trash/files/passphrase
wChTlPfMopGGTfAlPIyfHTfKpNNKCMpNoj
```

暗号化されているからどうしろと……。

展開されたファイルを眺めていて、/bin にgenerateという謎のファイルがあることに気が付いた。

```shell-session
$ ls -al fmk/rootfs/bin/
total 624
 :
lrwxrwxrwx  1 root root      7 Sep  8 04:32 fgrep -> busybox
lrwxrwxrwx  1 root root      7 Sep  8 04:32 fsync -> busybox
-rwx--x--x  1 root root    666 Jul 18 06:27 generate
lrwxrwxrwx  1 root root      7 Sep  8 04:32 grep -> busybox
lrwxrwxrwx  1 root root      7 Sep  8 04:32 gunzip -> busybox
 :
```

これが（なぜか）Pythonのバイトコードだったので、[pycdc](https://github.com/zrax/pycdc)でデコンパイル。

```shell-session
$ ./build/pycdc ../generate
# Source Generated with Decompyle++
# File: generate (Python 3.10)

import random

def generatePW():
    pw = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    key = random.randint(0, 999)
    let = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
    shift = let[key:] + let[:key]
    logbook = dict(zip(shift, let))
    None(None((lambda .0 = None: [ logbook[_] for _ in .0 ])(pw)))
```

あとは総当たり。

```solve.py
X = "wChTlPfMopGGTfAlPIyfHTfKpNNKCMpNoj"
let = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

for i in range(len(let)):
    D = dict(zip(let[i:]+let[:i], let))
    print("".join(D[x] for x in X))
```

```shell-session
$ python3 solve.py
wChTlPfMopGGTfAlPIyfHTfKpNNKCMpNoj
vBgSkOeLnoFFSezkOHxeGSeJoMMJBLoMni
 :
dj}A2w{t56nnA{h2wpf{oA{r6uurjt6u50
ci|z1v`s45mmz`g1voe`nz`q5ttqis5t4~
bh{y0u_r34lly_f0und_my_p4ssphr4s3}
ag`x~t^q23kkx^e~tmc^lx^o3rrogq3r2|
9f_w}s]p12jjw]d}slb]kw]n2qqnfp2q1{
 :
```

`bh{y0u_r34lly_f0und_my_p4ssphr4s3}`

## xNexus

Nexusスマホではない。

[xNexus - VicOne](https://vicone.com/jp/products/xnexus)

主催の企業が作っているシステムのアカウントが与えられて、それで調査などをするジャンル。

車の情報が吸い上げられている。頼もしいような怖いような……。

### Web RCE Anomaly

> xNexusにログとして記録されたRCEは、どういうエクスプロイトが使用されましたか？

ログインしてあちこち眺めていたら、Shell Shockのという名前があって、それで通った。チームのDiscordに「解けました」って報告しようとしたら、10分前に「xNexusやります」という書き込みがあった。すみません。

`bh{shellshock}`

## Reversing

> ある自動車販売店が、すべてのアカウントのパスワードを生成し、機密ファイルを暗号化するためのカスタムツールを開発しました。残念ながら、機密情報である販売店情報を含む重要なZIPファイルのパスワードを紛失しました。販売店は、パスワードを2024年6月27日木曜日の午前11時37分29秒から11時37分45秒の間に生成しました。いくつかのワークアラウンドを試した結果、ツールが午前11時37分29秒に「GiGMQKEinc」というパスワードを生成し、午前11時37分45秒に「QR36qTdbHM」というパスワードを生成したことがわかりましたが、これらはZIPファイルの正しいパスワードではありませんでした。
> 販売店がパスワードを回復し、ZIPファイルを解読するのを手伝ってもらえますか？
> 注：すべての時間はESTです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/017be825-fc1b-90a1-011e-5807dca44a17.png)

ツールが.NET製で、ネイティブコードのDLLを呼び出している。

時刻をシードにパスワードを生成しているっぽい。15通りだけだし、PCの時刻設定を変えて手でポチポチしても何とかなりそう。でも、PCがおかしなことになりそう。

パスワード生成処理を解析するのも面倒なので、DLLをの時刻を取得する部分（2箇所あった）を書き換えることにした。

DLLが読み込まれている間は書き換えられないし、Pythonの `ctypes` でDLLをアンロードする方法も分からないから、2段構えで。

```gen.py
import os

for i in range(1719502649,1719502649+30):
    os.system(f"py gen2.py {i}")
```

```gen2.py
import sys
import ctypes
import struct
import time

x = int(sys.argv[1])

with open("Dll1.dll", "rb+") as f:
    f.seek(0x420)
    f.write(b"\xb8"+struct.pack("<I", x)+b"\x90")
    f.seek(0x580)
    f.write(b"\xb8"+struct.pack("<I", x)+b"\x90")
buff = ctypes.create_string_buffer(11)
ctypes.cdll.LoadLibrary(".\\Dll1.dll").GeneratePassword(buff, 10)
password = buff.raw[:-1].decode()
print(x, password)
```

```
>py gen.py
1719502649 GiGMQKEinc
1719502650 KEincNurv3
1719502651 Nurv3QR36q
1719502652 QR36qTdbHM
1719502653 TdbHMX4NiD
1719502654 X4NiD0qVqZ
1719502655 0qVqZ3Mx1m
1719502656 3Mx1m7DaCc
1719502657 7DaCcaZiez
1719502658 aZiezdmUlW
1719502659 dmUlWgc2Wi
1719502660 gc2WikyEy9
1719502661 kyEy9nVM9v
1719502662 nVM9vqLpgS
1719502663 qLpgSt8xSI
1719502664 t8xSIxu9t5
1719502665 xu9t5AkhAs
```

`kyEy9nVM9v` でZIPが開けた。

`BH{WannaPasswd_Decrypt0r}`

### Car Game

> あなたは他の車両を攻撃しようしている危険なハイウェイドライバーです。 1337ポイントでAUGUSTより高得点を取れますか？
> Ubuntu 22.04でテスト済みです。VMを使用することをお勧めします。おそらく ‘libsdl2-*’ と ‘libcjson-dev’ が必要になるでしょう。

サーバーにスコアを送っている。スコアと名前の他に、 `se` と `t` というパラメタがある。これが適当だと、 `CHEATER` と返ってくる。

`se` がシード値のようなもので、これを元に、1点取るごとに `t` を更新している。

```solve.py
r = 0
t = 0
sc = 0

for i in range(1338):
    r = (r+1)*0xdead%2**32
    t = ((t<<16)+(t<<6)+[390, 330, 280][r%0x10000*3//0x10000])%2**256
    sc += 1

print(f"t: {t:64x}")
print(f"sc: {sc}")
print(f"se: 0")
```

```shell-session
$ python3 solve.py
t: a6746291b2a9f509ba3412e06fbccaabe0e1a5b4008a47e5a2c41ec382b6c306
sc: 1338
se: 0

$ curl -H 'Content-Type: application/json' -d '{"n": "a", "t": "a6746291b2a9f509ba3412e06fbccaabe0e1a5b4008a47e5a2c41ec382b6c306", "sc": 1338, "se": 0}' http://celsius.blockharbor.io:6000/postScoreboard
{"text":"bh{l00k_ou7_fo4_syb1l_a77ack5}"}
```

`bh{l00k_ou7_fo4_syb1l_a77ack5}`

### Power

> なんてPOWERfulなバイナリーなんでしょうか。 何をするのだろう？

PowerPC。

関数テーブルのようなものを使っているのと、PowerPCへの対応がいまいちで、Ghidraがあまり当てにならない。

laysakuraさん「VMでは？」

言われてみればたしかに。Reversingの典型。

Ghidraが当てにならないから、PowerPCのアセンブラを読んで、VM上で動くコードの逆アセンブラを書いた。

```dis_.py
import struct

with open("power", "rb") as f:
  print("# mem[0x1c]: sp")
  print("# mem[0x28]: ip")
  print("# mem[0x28]: flags")
  print()

  f.seek(0xae4)
  ip = 0

  while True:
    oldip = ip

    t = f.read(1)[0]
    ip += 1

    addr = False
    if t==0x00:
      op = "inc"
      n = 1
    elif t==0x01:
      op = "dec"
      n = 1
    elif t==0x02:
      op = "push"
      n = 1
    elif t==0x03:
      op = "pop"
      n = 1
    elif t==0x04:
      op = "strlen"
      n = 1
    elif t==0x05:
      op = "add"
      n = 2
    elif t==0x06:
      op = "addb"
      n = 2
    elif t==0x07:
      op = "sub"
      n = 2
    elif t==0x08:
      op = "subb"
      n = 2
    elif t==0x09:
      op = "mul"
      n = 2
    elif t==0x0a:
      op = "xor"
      n = 2
    elif t==0x0b:
      op = "xorb"
      n = 2
    elif t==0x0c:
      op = "shl"
      n = 2
    elif t==0x0d:
      op = "shr"
      n = 2
    elif t==0x0e:
      op = "shlmask"
      n = 2
    elif t==0x0f:
      op = "shrmask"
      n = 2
    elif t==0x10:
      op = "and"
      n = 2
    elif t==0x11:
      op = "movb"
      n = 2
    elif t==0x12:
      op = "mov1"
      n = 2
    elif t==0x13:
      op = "mov2"
      n = 2
    elif t==0x14:
      op = "syscall"
      n = 1
    elif t==0x15:
      op = "callhost?"
      n = 1
    elif t==0x16:
      op = "end"
      n = 0
    elif t==0x17:
      op = "cmp"
      n = 2
    elif t==0x18:
      op = "jeq"
      n = 1
      addr = True
    elif t==0x19:
      op = "jne"
      n = 1
      addr = True
    elif t==0x1a:
      op = "jmp"
      n = 1
      addr = True
    else:
      raise "error"

    args = []
    for _ in range(n):
      t = f.read(1)[0]
      ip += 1
      if t==3:
        t = f.read(1)[0]
        ip += 1
        if t==1:
          v = f.read(1)[0]
          ip += 1
        elif t==4:
          v = struct.unpack(">I", f.read(4))[0]
          ip += 4
        else:
          v = 0
        if addr:
          args += [f"0x{v:x} # {(ip+v-3)%0x100000000:08x}"]
        else:
          args += [f"0x{v:x}"]
      elif t==2:
        t = f.read(1)[0]
        ip += 1
        args += [f"mem[0x{t:x}]"]
      elif t==1:
        t1 = f.read(1)[0]
        ip += 1
        t2 = f.read(1)[0]
        ip += 2
        args += [f"mem[mem[0x{t1:x}]+mem[0x{t2:x}]]"]
      else:
        args += [f"mem[0]"]
    args = ", ".join(args)

    print(f"{oldip:08x}: {op} {args}")
    if op=="end":
        break
```

結果はこんな感じ。

```
# mem[0x1c]: sp
# mem[0x28]: ip
# mem[0x28]: flags

00000000: jmp 0x197 # 0000019b
00000007: mov1 mem[0x2c], mem[0x1c]
0000000c: sub mem[0x2c], 0x40
00000012: mov1 mem[0x8], mem[0x2c]
00000017: sub mem[0x2c], 0x40
0000001d: mov1 mem[0x4], mem[0x2c]
00000022: xor mem[0xc], mem[0xc]
00000027: xor mem[0x10], mem[0x10]
0000002c: movb mem[mem[0x8]+mem[0xc]], 0xfa
00000034: inc mem[0xc]
00000037: movb mem[mem[0x8]+mem[0xc]], 0x11
0000003f: inc mem[0xc]
00000042: movb mem[mem[0x8]+mem[0xc]], 0xc0
0000004a: inc mem[0xc]
0000004d: movb mem[mem[0x8]+mem[0xc]], 0xde
00000055: inc mem[0xc]
00000058: movb mem[mem[0x4]+mem[0x10]], 0xb6
00000060: inc mem[0x10]
00000063: movb mem[mem[0x4]+mem[0x10]], 0x5d
 :
```

ジャンプのアドレスの相対から絶対への変換がなんかずれているが、まあ良いだろう。

なお、このアセンブルコードは66 MBある。

大半がこういう処理の繰り返し。

```
 :
00000811: xor mem[0x18], mem[0x18]
00000816: mov2 mem[0x14], mem[mem[0x2c]+mem[0x24]]
0000081d: add mem[0x18], mem[0x14]
00000822: shl mem[0x18], 0x8
00000828: inc mem[0x24]
0000082b: mov2 mem[0x14], mem[mem[0x2c]+mem[0x24]]
00000832: add mem[0x18], mem[0x14]
00000837: shl mem[0x18], 0x8
0000083d: inc mem[0x24]
00000840: mov2 mem[0x14], mem[mem[0x2c]+mem[0x24]]
00000847: add mem[0x18], mem[0x14]
0000084c: shl mem[0x18], 0x8
00000852: inc mem[0x24]
00000855: mov2 mem[0x14], mem[mem[0x2c]+mem[0x24]]
0000085c: add mem[0x18], mem[0x14]
00000861: inc mem[0x24]
00000864: sub mem[0x18], 0x36b87e69
0000086d: shrmask mem[0x18], 0x22d99277
00000876: sub mem[0x18], 0x40bc0884
0000087f: add mem[0x18], 0x5570b28e
00000888: cmp mem[0x18], 0x2bc4639e
00000891: jne 0xadb093 # 00adb928
00000898: xor mem[0x18], mem[0x18]
0000089d: mov2 mem[0x14], mem[mem[0x2c]+mem[0x24]]
000008a4: add mem[0x18], mem[0x14]
000008a9: shl mem[0x18], 0x8
000008af: inc mem[0x24]
000008b2: mov2 mem[0x14], mem[mem[0x2c]+mem[0x24]]
000008b9: add mem[0x18], mem[0x14]
000008be: shl mem[0x18], 0x8
000008c4: inc mem[0x24]
000008c7: mov2 mem[0x14], mem[mem[0x2c]+mem[0x24]]
000008ce: add mem[0x18], mem[0x14]
000008d3: shl mem[0x18], 0x8
000008d9: inc mem[0x24]
000008dc: mov2 mem[0x14], mem[mem[0x2c]+mem[0x24]]
000008e3: add mem[0x18], mem[0x14]
000008e8: inc mem[0x24]
000008eb: sub mem[0x18], 0xef320f9e
000008f4: sub mem[0x18], 0xb7c8cf35
000008fd: shrmask mem[0x18], 0xa111b92e
00000906: mul mem[0x18], 0xa14680d
0000090f: cmp mem[0x18], 0x3ba2b904
00000918: jne 0xadb010 # 00adb92c
 :
```

/flag.png を読み込んで、4バイトずつ、ちょっと変換して値を比較している。

逆変換。

```solve2.py
C = list(open("dis.txt"))
p = 0
while not C[p].startswith("000004b3:"):
    p += 1

f = open("flag.png", "wb")

while True:
    p2 = p
    while C[p2].split()[1]!="cmp":
        p2 += 1

    for i in range(p, p2+1)[::-1]:
        _, op, _, y = C[i].split()
        y = int(y, 16)
        if op=="add":
            x = (x-y)%0x100000000
        elif op=="sub":
            x = (x+y)%0x100000000
        elif op=="mul":
            x = x*pow(y, -1, 0x100000000)%0x100000000
        elif op=="xor":
            x ^= y
        elif op=="shlmask":
            x = x>>(y&0x1f)|x<<(0x20-(y&0x1f))&0xffffffff
        elif op=="shrmask":
            x = x<<(y&0x1f)&0xffffffff|x>>(0x20-(y&0x1f))
        elif op=="cmp":
            x = y
        else:
            print(op, i)
            raise op
    
    p = p2+18
    f.write(x.to_bytes(4, "big"))
```

これで、/flag.png が得られた。

![flag.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/773b977b-7ff8-e231-5aa9-41f3e3789731.png)

`BH{u_g0t_th3_p0w3r}`