次のC言語のコードでは、デッドコードとして.longディレクティブを使用して無意味なデータを埋め込んでいます。
b skip_deadcodeにより、実行フローはskip_deadcodeラベルにジャンプし、デッドコード部分を飛ばします。
このようにすることで、ディスアセンブラはデッドコード部分を命令として誤認識し、解析できなくなります。
コンパイラは、最適化を行う際にsqrt()の結果が5以下であることを計算しないため、この条件分岐は最適化では消えることはありません。
単に意味のない条件分岐を挿入するだけでは最適化で消されますが、実行しなければ結果が分からない関数(ここではsqrt())を組み合わせることで、最適化を回避しています。
意味のない条件分岐を挿入し、無意味なコードを混入させるのは、Bogus Control Flowと呼ばれる制御フローに対する難読化技術です。
ここでは、アンチディスアセンブルのために命令として実行できないバイト列を埋め込んでいますが、処理のロジックの文脈を無視した意味不明な命令を挿入することで制御フローの難読化を強化することもできます。
#include <math.h>#include <stdio.h>intmain() {
int a = 3;
int b = 2;
int c = 5;
if (c < sqrt(a*a+b*b)) {
__asm__ volatile(
".long 0x01020304\n"".long 0x05060708\n"
);
}
printf("Hello, world!\n");
return0;
}
/* WARNING: Control flow encountered bad instruction data */
undefined8 main(void)
{
double dVar1;
dVar1 = sqrt(13.0);
if (dVar1 <= 5.0) {
puts("Hello, world!");
return0;
}
/* WARNING: Bad instruction - Truncating control flow here */halt_baddata();
}
WARNING: Control flow encountered bad instruction dataとWARNING: Bad instruction - Truncating control flow here
という警告がコード中に表示されています。
これはディスアセンブルできなかったデータが含まれており、ディスアセンブルできなかった部分を無視してデコンパイルを続行していることを示しています。
コードの一部がディスアセンブルできないため、その部分を切り捨ててデコンパイルを行っていることが、コメントからも読み取れます。
GhidraのデコンパイラはBogus Control Flowと命令として実行できないバイト列の挿入には強いようです。
まとめ
ドキュメントが少ないARM32/ARM64環境でのアンチディスアセンブル技術を紹介しました。
今回紹介した命令として実行できないバイト列をデッドコードとして埋め込む手法は、アンチディスアセンブルのテクニックとしてはIDA ProとGhidraに対して有効であることが確認できました。
また、Bogus Control Flowを組み合わせることで、IDA ProのGraph viewの表示を妨害することもできました。
しかし、Ghidraに対しては、デコンパイルを妨害する効果はなく、デコンパイル結果は正常に表示されました。
意外なことにGhidraのデコンパイラは、今回紹介した手法に対しては強いようです。