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.

Challenge binary

問題概要

  • 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 とすると正しく読み込むことが出来た。

f:id:kira000:20180111201351p:plain

Cutter の画面構成は次のようになっている。

f:id:kira000:20180111201922p:plain

アセンブルコードを読んでいくと Flag を表示してそうな関数 fcn.000000290 が見つかる。

f:id:kira000:20180111202509p:plain

以下 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.00000428fcn.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 ダンプで見ると, これはアドレス値であることが分かる。

f:id:kira000:20180111211724p:plain

  • hex ダンプ画面

f:id:kira000:20180111211814p:plain

8c 05 はリトルエンディアン表記であるからアドレス値は 0x058c となる。

0x58c にある値を Hex ダンプでみると次のような値が見つかった。

f:id:kira000:20180111212350p:plain

これを 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

参考文献