なんか、ここのところセブンペイがらみのネタで、主に小波感を連発してきましたが。
本来は、マルウェア解析を主としたバイナリ解析のためのメモ・・・のはずなので、今回はちゃんと技術ネタでいきます。
ちょっと仕事で、ARMの解析をすることになりました。
もっとも、今回はマルウェアではなく、モジュールの安全性の確認です。
ただ、私もARMのアセンブラまではそんなに勉強しておらず・・・というので、デコンパイル機能があるGhidraの出番となりました。折角操作メモ書くくらいには習熟したんだもの、使い倒していかないとねw
その結果、便利なんですが問題が発生しました。
デコンパイルで、ミスがあったのです。
主に引数の解析で、そのパラメータの紐付きが取れないと解析が上手くいきません。今回はそのメモとなります。
なお、Ghidraは2019年の春に導入した「Version 9.0.2 Build PUBLIC 2019-Apr-03 1342 EDT」です。
将来的に、このエラーは修正される可能性があります。
Ghidraを使うメリット
改めて言うまでもないんですが、Ghidraの最大のメリットはデコンパイル機能です。
C言語ライクに変換してくれるため、C言語が読める人には、ほとんどの人にはこちらのほうが可読性が上がり、効率があがります。
「え、あなたアセンブラの方が読みやすいんじゃないの!?」
とか言われますが、別にそんなことはありません(・・・まあ、最近x86ならぶっちゃけそんな変わらん気もしてきたけど・・・)。
静的解析ではとても便利な機能なのですが、デコンパイルコードにミスがあった場合、解析にも問題がでてしまいます。
そして、バイナリの実行形式ファイルのデコンパイルは、残念ながら完璧なものはありません。
(Javaに関しては、デコンパイルはコメント以外全部元にもどりますが、あれは厳密には実行用のバイナリではなく、中間言語的なものです。ただ、最近では難読化、デコンパイル対策もあるようで、イロイロカオスっぽいですね。)
今回は、解析していたら、ARM64のバイナリではデコンパイルミスがあり、見つけたというお話です。
デコンパイルしてみると
今回の記事のきっかけになったのはお仕事だったのですが、流石にお仕事のモジュールを晒すわけにもいきません。
そこで、ラズベリーパイの64bit版Linuxのモジュールを使って、同様の現象がおきないか確認してみました。
archlinux Raspberry Pi
https://wiki.archlinux.jp/index.php/Raspberry_Pi
このページにある以下のモジュール(ただし、2019年8月12日現在)をダウンロードしました。
http://os.archlinuxarm.org/os/ArchLinuxARM-rpi-3-latest.tar.gz
gz、tarを解凍すると、Linuxの実行モジュールがでてきます。
今回は、みなさんおなじみの cp コマンドをターゲットにしてみました。
Ghidraを起動、プロジェクトを作成し、/usr/binにある cp の実行モジュールを読み込ませ、解析します。解析のオプションはデフォルトのままとしています。
(モジュールを読ませて解析させるまでの手順は過去記事を参照。)
その結果を見てみると、やはりありました。
引数のパラメータが合わない・・・!?
例として、 FUN_001044f8 を見てみます。
39行目で、 FUN_0010c898 を引数なしでコールしています。
では、呼び出し先を見てみましょう。
・・・あれ?引数として、char *pcParm1を取ることになっている!?
でも、呼び出し側では引数が何かを指定いない・・・。
これでは、このコードを読む限りでは、何のパラメータを渡しているのかが分からず、関数の処理を解析しても、肝心の何のデータが処理されるのかが分かりません。
このケースでは、呼び出される側の FUN_0010c898 のアセンブラコードを見ることで、パラメータが分かります。
この対比を見る限りでは、pcParm1は x0 レジスタの値だということが分かります。
このため、呼び出し元の FUN_001044f8 で、x0に何を設定しているかを確認することになります。
すると、直前のmemcpyの後、x0にパラメータを設定している様子はありません。
ARMは、サブルーチンをコールした際、原則としてはリターン値を x0 に設定しているようです。
このため、memcpyのリターン値をそのまま FUN_0010c898 の第1引数に渡すため、x0にパラメータを設定する処理自体が無いということになったようです。
この結果、このコードをデコンパイルするツールは引数なしと判定したのではないかと思われます。
どうも、直前の返り値をそのまま次の第1引数に渡すようなパターンのときに発生しているような気がします(あくまで私の感想・・・)。
恐らく、デコンパイルの処理はシーケンシャルにやっていて、関数に当たった際に関数から先に解析し、その関数のプロトタイプを解析(要は、関数の引数や返り値をC言語のヘッダファイルのプロトタイプのように解析)してから、呼び出し元のパラメータ解析をやっているわけではないようですね。
関数がどんな引数を取るかわからないまま解析してしまうので、このようなケースでミスしやすいのかもしれません。
このため、こういったケースがあるということを知った上で、実際には必要な引数パラメータがどこから来るかを判断する必要があること、その導き方は知っておく必要がある、ということで、今回のメモ作成となりました。
ついでにメモメモにゃ!
原因と真のパラメータの求め方が分かったのですが。
折角解析しても、大体人間って忘れますよね・・・。
ということで、解析結果もメモしておきましょう、という話です。
Ghidraの機能紹介でも触れた、コメント機能が役に立ちます。
コメント機能で引数について、最低でも自分には分かるように覚え書きをしておきましょう。
お仕事では、gdbで解析する際に、Ghidraから得られる情報やらを使ってやってみたところ、割といい感じでした。
この手順については、また折を見てメモしたいと思います。
思うだけで、ほんとにするかは分からないので、期待しないで待っていてください。