難読化手法(Software Obfuscation)
ソフトウェアの難読化に興味を持ったので、どういう手法があるのか調べてみました。 結構面白かったです。 この分野は勉強していくと非常に面白そうですね。 (メチャメチャ奥が深そうですね)
以下に、簡単な手法をいくつかピックアップしてみました。 様々な手法を組み合わせて、ぐちゃぐちゃの読みにくい出力を生成できるそうです。 また、自動的に出力をぐちゃぐちゃにするツールも色々あるようです。
名前マングリング
何をするかバレバレの名前をバイナリ中に残してしまうと、すぐに解析されてしまいます。 それを防ぐために関数名などをマングリングしてしまいます。
例えば、以下のようなコードがあったとします。 secret_key_funcという秘密鍵を扱う関数があるとします。 例えば、これをdefineでhogera()に変えてしまうと、何をしたい関数なのかが多少わかりにくくなります。
#include <stdio.h>
#define secret_key_func() hogera()
void
secret_key_func()
{
printf("function to handle secret\n");
}
int
main()
{
secret_key_func();
}
上記C言語のコードを gcc -S にかけると以下のようになります。 defineが無い場合、call _hogera は call secret_key_func になります。
.section .rdata,"dr"
LC0:
.ascii "function to handle secret\12\0"
.text
.globl _hogera
.def _hogera; .scl 2; .type 32; .endef
_hogera:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $LC0, (%esp)
call _printf
leave
ret
.def ___main; .scl 2; .type 32; .endef
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
call __alloca
call ___main
call _hogera
leave
ret
.def _printf; .scl 3; .type 32; .endef
無意味なコードの挿入
コード中に必ず真になったり、必ず偽になるような条件文を入れることによってコードを読みにくくする手法も一般的だそうです。 以下のサンプルでは、aという変数に5を入れて、それを二乗したものの平方根と5を比べています。 5の二乗の平方根は必ず5なので、「aaa」という文字列が必ず表示されます。
#include <stdio.h>
#include <math.h>
int
main()
{
int a = 5;
if (sqrt(a * a) == 5) {
printf("aaa\n");
} else {
printf("hage\n");
}
}
無意味な計算式の挿入
全体の動作には影響を与えないような、無意味な計算式や処理を混ぜることによって出力バイナリを読みにくくできます。 無意味なものを挿入する系の難読化をライブラリなどに対して行う事によって、finger printを改変してしまい、どのソフトウェア(ライブラリ)を使っているかを解りにくくするという利用法もあるそうです。
#include <stdio.h>
int
main()
{
int i = 0;
int a, b, c, d;
a = 7;
b = 0xffff;
c = 0x12345678;
d = 0xdeadbeef;
a = a + c;
d >>= 2;
b = a * 2;
c = b ^ a;
a++;
printf("%d\n", i);
return 0;
}
文字列の変換
stringsなどで簡単に文字列を解析されないように、変数を内部でいじるという手法も良く使われるそうです。 以下の例では、非常に単純にXORしているだけですが、もっと複雑な変換などをすれば多少解析がしにくくなります。
#include <stdio.h>
int
main()
{
int i;
unsigned char buf[] =
{0xd9, 0xcf, 0xc9, 0xd8,
0xcf, 0xde, 0x8a, 0xdd,
0xc5, 0xd8, 0xce, 0xaa,
0x00};
for (i = 0; i < strlen(buf); i++) {
buf[i] ^= 0xaa;
}
printf("%s\n", buf);
}
無駄に例外を投げる
無駄に例外を投げたうえで、来るはずが無い例外でcatchするコードを追加する事で読みにくくできるそうです。
#include <iostream>
int
main()
{
try {
throw 1;
} catch (char ch) {
printf("hoge\n");
} catch (double d) {
printf("hogera\n");
} catch (int err) {
if (err == 1) {
printf("aaa\n");
} else {
printf("bbb\n");
}
}
return 0;
}
NullPointerExceptionをわざと発生させる
Javaにも色々と難読化手法があるようです。 無駄に例外を発生させる手法と同じですが、わざとNullPointerException(いわゆる「ぬるぽ」)を発生させることで読みにくくできるそうです。
class a {
public static void main(String argv[])
{
String hage = null;
try {
hage.substring(0,1);
} catch (NullPointerException e) {
System.out.println("aaa");
}
return;
}
}
最近のエントリ
- 日本のIPv6採用状況が50%を超えている件について
- 「ピアリング戦記」の英訳版EPUBを無料配布します!
- IPv4アドレス移転の売買価格推移および移転組織ランキング100
- 例示用IPv6アドレス 3fff::/20 が新たに追加
- ShowNet 2024のL2L3
- ShowNet 2024 ローカル5G
過去記事