Javaソースコード難読化ツール自作入門 – 第3回

コラム概要

■こんな方におすすめ:

  • 難読化ツールを自作してみたい
  • 手動で行っている難読化を自動化したい

■難易度:★

■ポイント:

  • ソースコード難読化ツールは簡単に作れる。
  • パーサ・難読化コード作成モジュール・ソースコード変更モジュールという観点で見ると、
    ソースコード難読化ツールは理解しやすい。
  • おそらくパーサが最も難しいので、最初は非対応の範囲を広くとる形で作ればよい。

この記事は「Javaソースコード難読化ツール自作入門」シリーズの第1回です。
他の回はこちらからご覧ください。

Javaソースコード難読化ツール自作入門 – 第1回
Javaソースコード難読化ツール自作入門 – 第2回

難読化については、マーケティング担当者コラムにも記事がございます。
難読化をアプリのセキュリティ対策ツールとして検討中の方や
既に対策として実施されている方向けのコラムとなっています。
ご興味のある方は、ぜひご覧ください。

難読化だけで大丈夫?!アプリに施すセキュリティ対策の落とし穴


さて、お待たせしました。 「Javaソースコード難読化ツール自作入門」第3回です。

前回は、以下を説明しました。

・Javaソースコードから難読化対象を見つけ出す機能(第1回の続き)
・難読化対象に対応した関数呼び出しと関数定義を作成する機能
・関数呼び出しで難読化対象を置換してソースコードを変更する機能
・クラスの終わりを見つけ出す機能
・関数定義を(クラスの終わりに)挿入する機能

今回は、今までに紹介してきた実装例を組み合わせた定数難読化プログラムの例示と動作確認を行います。
また、最後に改良のヒントを共有して「Javaソースコード難読化ツール自作入門」を締めくくります。


4.動作確認

さて、難読化ツールの部品が揃いました。
それぞれを組み合わせたものが下記です。

定数難読化プログラムの例:

//(前略)
#define iLenInputFilePath	500
#define iMaxLinesInFile		(10 * 1000)
#define iMaxLettersInLine	(10 * 1000)

int iNumOfDecFunc = 0;
char szDecFuncDef[iMaxLinesInFile][iMaxLettersInLine];

void obfConstInLine(char *szLine);
//(中略)

int main(int argc, char **argv) {
	//(中略)
	srand((unsigned int)time(NULL));
	char szLine[iMaxLettersInLine];
	while (fgets(szLine, iMaxLettersInLine, fp)) {
		obfConstInLine(szLine);
	}
	//(中略)
}

bool bInComment = false;
void obfConstInLine(char *szLine) {
	if (*szLine == '\n') {
		printf("%s", szLine);
		return;
	}
	if (bIsClassEnd(szLine)) {
		injectDecFuncDef();
		printf("%s", szLine);
		return;
	}

	int iLenLine = strlen(szLine);
	for (int i = 0; i < iLenLine; i++) {
		//skip comment
		char *pcPlaceInSzLine = pcSkipIfComment(&szLine[i]);
		i += (pcPlaceInSzLine - &szLine[i]);

		//skip single quotation
		pcPlaceInSzLine = pcSkipIfSingleQuotation(&szLine[i]);
		i += (pcPlaceInSzLine - &szLine[i]);

		//skip stirng literal
		pcPlaceInSzLine = pcSkipIfStringLiteral(&szLine[i]);
		i += (pcPlaceInSzLine - &szLine[i]);

		//skip case n:
		if (i) {
			if (bIsCase(&szLine[i])) {
				break;
			}
		}

		//find and obf const
		if (!i) {
		} else if (bIsNumChr(szLine[i]) && bIsSeparator(szLine[i - 1])) {
			char szConst[iMaxLettersInLine];
			pcGetFirstSeparatedElement(&szLine[i], szConst);
			if (bIsIntNum(szConst)) {
				int iConst = (int)strtol(szConst, NULL, 0);
				
				char szFuncCall[100];
				int iRand, iCorrectionVal;
				makeDecFuncCall(iConst, szFuncCall, &iRand, &iCorrectionVal);
				makeDecFuncDef(iCorrectionVal);
				printf("//dec: %d\n", iRand - iCorrectionVal);

				char *pcPlaceInSzLine2 = szReplace(&szLine[i], szConst, szFuncCall);
				i += (pcPlaceInSzLine2 - &szLine[i]);
			} else {
				i += (pcFindSeparator(&szLine[i]) - &szLine[i]);
			}
		}
	}
	printf("%s", szLine);
}
//(後略)

上記プログラムの動作確認を行います。

サンプルJavaコード:

public class test {
	public static void main(String[] args) {
		hoge();
	}

	static void hoge() {
		int iA = 0;
		iA = 1 - 2 - -5;	//= 1 - 2 + 5
		iA = 1 - 2 - - -5;	/*= 1 - 2 - 5*/ iA = 10;

		char chA = '\"'; String strA = "1234\n";
		switch (iA) {
			case 10:
				System.out.println("iA = 10");
				break;
			default:
				System.out.println("iA = ?");
		}
	}
}


難読化結果:

public class test {
	public static void main(String[] args) {
		hoge();
	}

	static void hoge() {
//dec: 0
		int iA = func0000(530090);
//dec: 1
//dec: 2
//dec: 5
		iA = func0001(166900) - func0002(331909) - -func0003(492950);	//= 1 - 2 + 5
//dec: 1
//dec: 2
//dec: 5
//dec: 10
		iA = func0004(894684) - func0005(975861) - - -func0006(550598);	/*= 1 - 2 - 5*/ iA = func0007(839692);

		char chA = '\"'; String strA = "1234\n";
		switch (iA) {
			case 10:
				System.out.println("iA = 10");
				break;
			default:
				System.out.println("iA = ?");
		}
	}

	static int func0000(int iRand) {
		return iRand - 530090;
	}
	//(中略)
	static int func0007(int iRand) {
		return iRand - 839682;
	}
}

想定通りの難読化が行われていることが確認できました。

ここでは割愛しますが、逆コンパイル結果が難読化されていることも、
種々の逆コンパイルツールによって確認できます。

5. おわりに

さて、非常に簡単な難読化ツールの作り方について、説明は以上になります。
意外と簡単に作れるということが、お分かりいただけたのではないかと思います。

今回紹介したこの簡単なプログラムを拡張・改良していくことによって、
より強力で使いやすいツールにすることや様々な難読化機能を追加することができるでしょう。
例えば、関数内部の計算をランダムにしたり、二重に処理するだけで強度は上がります。
また、パーサに手を加えることで、
他の様々な難読化(例えば文字列暗号化や制御フローフラット化)に応用できます。

本コラムの内容はいかがだったでしょうか。
筆者としては、様々な応用と実用に耐えうる枠組みを紹介したつもりですが、これに囚われる必要はありません。
ぜひ、自由な発想で素晴らしい難読化ツールを生み出してください。
このコラムがその一助になれば、望外の喜びです。