Androidアプリのハッキング

この記事では、Googleの標準ソリューションを統合せずにプログラムをハッキングから保護する方法について簡単に説明し、動作するコードの例を示します。 面白い? カットをお願いします!







Googleアプリケーションライセンスの弱点は何ですか?





実際、このメカニズムは開発者によく知られており、そのハッキングは難しくありません。 必要なのは、apktoolをダウンロードし、LicenseCheckerクラスを見つけて、checkAccessメソッドを少し調整するだけです。



自分の保護を実現する方法は?





明らかに、どんな防御も破られる可能性があります。 この方法は特効薬ではありませんが、生命に対する権利があります。 アプリケーションの一意性を検証するには、このアプリケーションが署名された証明書を確認するのが理にかなっています。 証明書情報はPackageInfoから読み取ることができます。



PackageInfo info =getPackageManager().getPackageInfo(getPackageName(), 0); Signature[] signatures = info.signatures;
      
      







配列のゼロ要素には、アプリケーションが署名されたキーに関する必要な情報が含まれます。 キー自体を保存します。すぐに必要になります。

Javaでこのようなチェックを行うことは理にかなっていないのは理にかなっています。apktoolを使用した同様の手法は数分で保護を埋めるからです。 したがって、このチェックはアクティブレベルに転送する必要があります。



 const char* rsa = "PUT_YOUR_RSA_KEY_HERE"; jint verifyCertificate(JNIEnv *env, jobject obj, jobject cnt) { jclass cls = env->GetObjectClass(cnt); jmethodID mid = env->GetMethodID(cls, "getPackageManager", "()Landroid/content/pm/PackageManager;"); jmethodID pnid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;"); if (mid == 0 || pnid == 0) { return ERROR; } jobject pacMan_o = env->CallObjectMethod(cnt, mid); jclass pacMan = env->GetObjectClass(pacMan_o); jstring packName = (jstring) env->CallObjectMethod(cnt, pnid); /*flags = PackageManager.GET_SIGNATURES*/ int flags = 0x40; mid = env->GetMethodID(pacMan, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); if (mid == 0) { return ERROR; } jobject pack_inf_o = (jobject) env->CallObjectMethod(pacMan_o, mid, packName, flags); jclass packinf = env->GetObjectClass(pack_inf_o); jfieldID fid; fid = env->GetFieldID(packinf, "signatures", "[Landroid/content/pm/Signature;"); jobjectArray signatures = (jobjectArray) env->GetObjectField(pack_inf_o, fid); jobject signature0 = env->GetObjectArrayElement(signatures, 0); mid = env->GetMethodID(env->GetObjectClass(signature0), "toByteArray", "()[B"); jbyteArray cert = (jbyteArray) env->CallObjectMethod(signature0, mid); if (cert == 0) { return ERROR; } jclass BAIS = env->FindClass("java/io/ByteArrayInputStream"); if (BAIS == 0) { return ERROR; } mid = env->GetMethodID(BAIS, "<init>", "([B)V"); if (mid == 0) { return ERROR; } jobject input = env->NewObject(BAIS, mid, cert); jclass CF = env->FindClass("java/security/cert/CertificateFactory"); mid = env->GetStaticMethodID(CF, "getInstance", "(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;"); jstring X509 = env->NewStringUTF("X509"); jobject cf = env->CallStaticObjectMethod(CF, mid, X509); if (cf == 0) { return ERROR; } //"java/security/cert/X509Certificate" mid = env->GetMethodID(CF, "generateCertificate", "(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"); if (mid == 0) { return ERROR; } jobject c = env->CallObjectMethod(cf, mid, input); if (c == 0) { return ERROR; } jclass X509Cert = env->FindClass("java/security/cert/X509Certificate"); mid = env->GetMethodID(X509Cert, "getPublicKey", "()Ljava/security/PublicKey;"); jobject pk = env->CallObjectMethod(c, mid); if (pk == 0) { return ERROR; } mid = env->GetMethodID(env->GetObjectClass(pk), "toString", "()Ljava/lang/String;"); if (mid == 0) { return ERROR; } jstring all = (jstring) env->CallObjectMethod(pk, mid); const char * all_char = env->GetStringUTFChars(all, NULL); char * out = NULL; if (all_char != NULL) { char * startString = strstr(all_char, "modulus:"); char * end = strstr(all_char, "public exponent"); bool isJB = false; if (startString == NULL) { //4.1.x startString = strstr(all_char, "modulus="); end = strstr(all_char, ",publicExponent"); isJB = true; } if (startString != NULL && end != NULL) { int len; if (isJB) { startString += strlen("modulus="); len = end - startString; } else { startString += strlen("modulus:"); len = end - startString - 5; /* -5 for new lines*/ } out = new char[len + 2]; strncpy(out, startString, len); out[len] = '\0'; } } env->ReleaseStringUTFChars(all, all_char); char * is_found = strstr(out, rsa); //      if (IS_DEBUG) { return is_found != NULL ? 0 : 1; } else { return is_found != NULL ? 1 :0; } }
      
      







次に何をする?





値を返す前に、機能の一部を同じライブラリに転送し、証明書の信頼性を確認する必要があります。 そして、潜在的なハッカーを混乱させるために必要なファンタジーを忘れないでください。 このアプリケーションのコピーが本物ではないと判断したとします。 これについて明示的に話すことは意味がありません。プログラムのアクションにランダムな要素を追加する方がはるかに楽しいです。 たとえば、ゲームを持っている場合、対戦相手に力を加えたり、プレーヤーをより脆弱にすることができます。 たとえば、10%のケースでランダムドロップを追加することもできます(zabayevskiy habrayuzerからの公正な発言:ランダムドロップは、プログラムのカルマを台無しにします。) それはすべてあなた次第です。



可能な実装の簡単な例を次に示します。



まず、ライブラリ呼び出しを行ってデータを取得するクラスを作成します。



 public class YourClass { static { System.loadLibrary("name_of_library"); } public native int getSomeValue(); public native void init(Context ctx); }
      
      







initメソッドは、証明書認証を毎回呼び出さないために必要です。



ネイティブメソッドの実装:



 jint isCertCorrect = 0; JNIEXPORT void JNICALL Java_com_your_package_YourClass_init(JNIEnv *env, jobject obj, jobject ctx) { isCertCorrect = verifyCertificate(env, obj, ctx); } JNIEXPORT jint JNICALL Java_com_your_package_YourClass_getSomeValue(JNIEnv *env, jobject obj) { if (isCertCorrect ) { //    } else { //    }
      
      







この保護オプションは、逆アセンブルによってハッキングすることもできますが、Javaレベルで保護を実装する場合とはまったく異なるレベルの知識とはるかに長い時間が必要です。 特定の手段がある場合は、Cコードの難読化ツールを購入するのが理にかなっています。その場合、ハッキングは簡単な作業とはほど遠いでしょう。



サーバー部分が存在する場合のアプリケーション保護。





アプリケーションロジックの一部がサーバーに実装されている場合、証明書認証をサーバーに発行することは理にかなっています。 または、次のアクションアルゴリズムを使用できます。



  1. サーバーは秘密および公開RSAキーを生成します。
  2. クライアントはサーバーにリクエストを送信し、公開鍵を受信します。
  3. クライアントでは、String getInfo(String publicKey);という形式の関数を持つネイティブライブラリを実装します。 関数は証明書を読み取り、ランダム変数を追加し、RSA公開キーを使用して受信した文字列を暗号化します。

  4. クライアントは、受信した文字列を送信して、サーバーに新しい要求を作成します。 サーバーはデコードを実行し、証明書をランダム変数から分離し、その真正性をチェックします。
  5. スキャンの結果に応じて、サーバーは次のすべてのクライアント要求に応答します。




このメカニズムがHabrの読者がAndroidアプリケーションからの収益を増やすのに役立つことを願っています。



All Articles