
この記事の目的は、アプリケーションを保護するためにクラスファイルを暗号化する機能を備えた難読化ツールを使用することと、無意味なお金の浪費から開発者に警告することです。
バイトコードをリバースエンジニアリングから保護し、この保護を回避する問題については、Dmitry Leskov-
Protect Your Java Code-through Obfuscators And Beyondの基本的な作業で詳しく説明され
ています。
クラスファイルの暗号化メカニズムは、クラスの内容が暗号化されていることを前提としており、特殊なClassLoaderまたはJVMTIインターフェイスを介してアプリケーションを起動すると、復号化されたバイトコードがJava仮想マシンにロードされます。
こうした保護のバイパス方法については上記の記事で詳しく説明していますが、幸いにも残念なことに、JVMと対話し、デバッグモードまたは外部エージェントの存在を監視するネイティブコンポーネントを含む製品が多数あります。 しかし、これは、そのような製品の開発者のすべての保証にもかかわらず、バイトコードをまったく保護しません。
クラスファイルを暗号化するすべての難読化ツールの脆弱性を実証するには、保護するアプリケーションを-XX:+ TraceClassLoadingオプションで実行し、暗号化されたすべてのクラスファイルがこのJVMトレースレベルで安全に表示されることを確認します。 さらに進んで、OpenJDKソースコードを取得し、ロードされたクラスファイルのバイトコードアップロードを挿入します。
実験では、Debian Linux 6.0.5(安定版)とOpenJDK7ソースバンドルを使用します。 他のプラットフォームのソースからJDKをインストールする手順は、
OpenJDK Build READMEから入手できます。
OpenJDKソースコードへの変更の数を最小限に抑えるために、オプション-XX:+ TraceClassLoadingが有効になっている場合、作業ディレクトリに関連するすべてのロードされたクラスのバイトコードをclasses.dumpファイルに保存します。 ファイル構造は次のとおりです。
{ int lengthClassName, byte[] className, int lengthByteCode, byte[] bytecode }, { next record … }, …
アセンブリ用の環境を準備します。
次に、OpenJDKソースコードと
パッチをダウンロードする必要があります。これにより、次のコードがClassFileParser :: parseClassFile関数に、hotspot / src / share / vm / classfile / classFileParser.cppファイルに追加されます。
// dumping class bytecode // dump file format: // length of the class name - 4 bytes // class name // length of the class bytecode - 4 bytes // byte code // ... next class ... ClassFileStream* cfs = stream(); FILE * pFile; int length = cfs->length(); int nameLength = strlen(this_klass->external_name()); pFile = fopen("classes.dump","ab"); // size of the class name fputc((int)((nameLength >> 24) & 0XFF), pFile ); fputc((int)((nameLength >> 16) & 0XFF), pFile ); fputc((int)((nameLength >> 8) & 0XFF), pFile ); fputc((int)(nameLength & 0XFF), pFile ); // class name fwrite (this_klass->external_name() , 1, nameLength, pFile ); // size of the class bytecode fputc((int)((length >> 24) & 0XFF), pFile ); fputc((int)((length >> 16) & 0XFF), pFile ); fputc((int)((length >> 8) & 0XFF), pFile ); fputc((int)(length & 0XFF), pFile ); // class bytecode fwrite (cfs->buffer() , 1 , length, pFile ); fclose(pFile);
JDKが正常に動作することを確認します。
パッチを適用してビルドを実行する
次に、コンパイルされたJREのbinディレクトリに移動します。$ OPENJDK_SRC / build / linux-i586 / j2re-image / bin /
正常性をテストするには、単一のパラメーター-XXを指定してjavaを実行します:+ TraceClassLoading:
そして、classes.dumpを見ると、JREが起動時にロードするすべてのクラスファイルが含まれています。
そして今、最も興味深いのは、暗号化されたバイトコードでJavaアプリケーションを使用することです。たとえば、この関数で難読化ツールの試用版を使用できます。 明らかな理由から、特定の名前については言及しませんが、「バイトコード暗号化」というキーでGoogleを検索するだけで十分です。 SomeClassGuard.jar内の階層com / **** / someclassguard / engineには暗号化されたクラスファイルが含まれています。デコンパイラーを設定するか、HEXビューアーでファイルヘッダーを確認することで、これを自分で確認できます。
次に、SomeClassGuard.jarを実行します。
次に、SomeClassGuard.jarの実行後に取得したclasses.dumpファイルを解凍する必要があります。このために、小さなJavaプログラムを作成します。
package openjdkmod; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class ClassesDumpExractor { public static void main(String[] args) throws FileNotFoundException, IOException { if (args.length != 2) { System.err.println("Usage openjdkmod.ClassesDumpExtractor <classes.dump file> <out dir>"); System.exit(-1); } File classesDumpFile = new File(args[0]); if (!classesDumpFile.exists()) { System.err.println("Source file: " + args[0] + " not found!"); System.exit(-1); } File outDir = new File(args[1]); if (!outDir.exists()) { outDir.mkdirs(); } DataInputStream din = new DataInputStream(new FileInputStream(classesDumpFile)); while (true) { try { int classNameLength = din.readInt(); byte[] classNameBytes = new byte[classNameLength]; din.readFully(classNameBytes); String className = new String(classNameBytes); System.out.println("className:" + className); int classLength = din.readInt(); byte[] classBytes = new byte[classLength]; din.readFully(classBytes); File parentDir = className.indexOf(".")>0?new File(outDir, className.substring(0,className.lastIndexOf(".")).replace(".", File.separator)):outDir; if(!parentDir.exists()) parentDir.mkdirs(); File outFile = new File(parentDir, (className.indexOf(".")>0?className.substring(className.lastIndexOf(".")+1):className)+".class"); FileOutputStream outFos = new FileOutputStream(outFile); outFos.write(classBytes); outFos.close(); } catch (EOFException e) { din.close(); return; } } } }
そして、パラメーターを指定して実行します:
出力では、復号化されたクラスファイルを含むディレクトリを取得します。
結論
暗号化によるクラスファイルの保護は、完全に無意味で、危険であり、高価です(少なくとも無料ではありません)。
バイトコードを保護する必要がある場合:
1)ネイティブコードでバイトコードコンパイラを使用します。
2)従来の難読化ツールと文字列暗号化機能を備えた難読化ツールの組み合わせ。
スーパープロテクションの場合:安全なストレージとバイトコードの実行をサポートする外部デバイスを使用します。
上記で使用した方法論は、操作中にどのバイトコードがロードされているかを確認する必要がある場合に、さまざまなアプリケーションをデバッグするために使用できます。
注1:
JVM内のクラスのストレージ形式を少し掘り下げる必要がありますが、JDKソースコードを変更せずに同じ結果を得ることができます-sun.misc.Unsafeクラスを使用します。
注2:
もちろん、著者はこの記事に含まれるデータの使用について責任を負いません。
注3 :ここから取られた元の画像:
it.wikipedia.org/wiki/File :Netbeans-Duke.png