34C3 Junior CTF: ARM2
問題
問題文
Can you reverse engineer this code and get the flag?
This code is ARM Thumb 2 code which runs on an STM32F103CBT6. You should not need such a controller to solve this challenge.
There are 5 stages in total which share all the same code base, so you are able to compare code from the first stage with all the other stages to see what code is actually relevant.
If you should need a datasheet, you can get it here.
In case you need to refresh your ARM assembly, check out Azeria's cool articles.
問題概要
- STM32F103CBT6 というマイコンで動く ARM Thumb 2 の実行形式ファイルが与えられる
解答例
指針
- Cutter を使って解析した。
解説
IDA の Free 版では ARM は解析できないので今回は Cutter (https://github.com/radareorg/cutter) という radare2 を GUI で使えるようにしたリバースエンジニアリングフレームワークを使った。
下記のリンクからダウンロードできる。
ARM Thumb-2 は 16 ビット長の命令セットなので次のように Advanced optins のところで Architecture: arm, Bits: 16 とすると正しく読み込むことが出来た。
Cutter の画面構成は次のようになっている。
逆アセンブルコードを読んでいくと Flag を表示してそうな関数 fcn.000000290
が見つかる。
以下 fcn.000000290
を丁寧に読んでいく。
push {r7, lr}
r7 レジスタと lr レジスタの値をスタックに push している。 lr レジスタはリンクレジスタと呼ばれ, 関数コール時にリターンアドレスを保持するレジスタである。
sub sp, 0x10
sp (スタックレジスタ) の値から 0x10 を減算している。
add r7, sp, 0
r7 レジスタに sp + 0 を代入している。
str r0, [r7, 4] str r1, [r7]
STR はレジスタの値を指定したアドレスに書き込む命令である。
r7 + 4 のアドレスに r0 の値を, r7 のアドレスに r1 の値を書き込んでいる。
bl fcn.00000428 bl fcn.00000334
bl は形式からも分かるように関数を呼ぶ命令である。
fcn.00000428
や fcn.00000334
が何をしているのかはここでは読み飛ばす。
ldr r0, [0x000002d4]
LDR は STR とは逆に指定したアドレスの値をレジスタに読み込む命令である。
ここでは 0x000002d4
のアドレスの値を r0 レジスタに読み込んでいる。
bl fcn.00000400
これも読み飛ばす。
ldr r3, [0x000002d8] str r3, [r7, 0xc] b 0x2c4
r3 レジスタに 0x000002d8
のアドレスの値を読み込み, r7 + 0xc のアドレスに r3 の値を書き込んでいる。
b は x86 の jmp 命令に対応している。
ldr r3, [r7, 0xc] ldrb r3, [r3] cmp r3, 0 bne 0x2ae
r7 + 0xc のアドレスの値を r3 レジスタに読み込み, ldrb で [r3] が指す値の 1 Byte を r3 レジスタに読み込み, cmp でその値が 0 と比較し 0 でなければ 0x2ae にジャンプしている。
次に 0x2ae を読んでいく。
ldr r3, [r7, 0xc] ldrb r3, [r3] eor r3, r3, 0x55 uxtb r3, r3 mov r0, r3 bl fcn.000003d4 ldr r3, [r7, 0xc] adds r3, 1 str r3, [r7, 0xc]
eor は排他的論理和をとる命令で x86 の xor に対応する。
r3 レジスタに r3 レジスタの値と 0x55 の XOR をとった値を代入している。
uxtb はゼロ拡張バイトと呼ばれるもので 8 ビット値を 32 ビット値に拡張しているらしい。
mov で r0 に r3 の値を代入している。
bl で fcn.000003d4
を呼んでいるがこれは多分出力なのかなと予想しつつ読み飛ばす。
ldr で r3 に r7 + 0xc アドレスの値を読み込み r3 に 1 を加算し, str で r7 + 0xc のアドレスの値に r3 の値を書き込んでいる。
ようはインクリメントをして読み込む文字を 1 つずらしている。
これを r3 が 0 になるまで繰り返している。
r3 の値は何か考える。 r3 の値の変化するコードのみを抽出した。
ldr r3, [0x000002d8] str r3, [r7, 0xc] ... ldr r3, [r7 + 0xc] ldrb r3 [r3]
これらの命令から r3 は [0x000002d8] が指すアドレス値が指す値であることが分かる。
0x00002d8 を見るとコードとして認識されているが Hex ダンプで見ると, これはアドレス値であることが分かる。
- 逆アセンブル画面
- hex ダンプ画面
8c 05 はリトルエンディアン表記であるからアドレス値は 0x058c となる。
0x58c にある値を Hex ダンプでみると次のような値が見つかった。
これを 1 Byte ずつ 0x0 がくるまで 0x55 で XOR を取ると Flag が得られた。
#!/usr/bin/env python2 # coding: utf-8 r3 = """66 61 16 66 0a 0d 65 27 0a 66 3b 36 27 2c 25 21 3c 65 3b 0a 64 26 0a 37 66 26 21 0a 36 27 2c 25 21 65 00""" print "".join([chr(int(i, 16) ^ 0x55) for i in r3.split() if int(i, 16)])
- 実行結果
$ python solve.py 34C3_X0r_3ncrypti0n_1s_b3st_crypt0