ブーツを売ろうとしているウイルスをどのように捕まえたか





私は、秋が始まると、路上で過ごす時間を減らしようとする人々の一人です。 モスクワでは、これは難しいことではありません。自宅からオフィスへのルートとその逆のルートに限定されます。 ただし、特に私のような職場が窓際にあり、蒸れを訴える同僚がオフィスを換気するように要求している場合は、湿気の多い天候が部屋に不快感を引き起こす可能性があります。 脾臓に落ちないように、この秋にワードローブを更新しました。



不必要なものの運命について考えて、私はそれらをどうするべきか考えました:それを捨てて、ぼろきれに切り分けて、それを引き継ぐために私の弟にそれを与えますか? しかし、一つには、これらの方法はどれも適切ではありませんでした。それらはまともなサイズのレザーブーツ44でしたが、順番に退屈でした。 Avitoで販売することにしました。 写真をアップロードし、偽名(情報セキュリティは同じです)を示し、ブーツを外して、他のいくつかのことをして、寝ました。 これにより、隠れた脅威に対するアプリケーションの長い分析が行われることをどのようにして知りましたか?







楽しい驚き



疑わしい電話が数回あった翌日、次の内容の興味深いSMSメッセージを受け取りました。







数日後、別の同様のメッセージを受け取りました。







誰かが何らかの形でインターネット上で私にお金を送金できることに驚いた(明らかに、私はただ一人の老人です-私は今でも紙の本を使っています)、SMSのリンクをクリックしました。



その後、Androidアプリケーション(apkファイル)をダウンロードするよう提案されました。 ファイルをうまくダウンロードすると、次のことがわかりました。







信頼できます! すぐにすべてをインストールして終了したかったのです。

しかし、ここでは、いつものように、何らかの理由で迷惑なAndroidオペレーティングシステムがファイルを実行できませんでした。 「もうお金をくれ!」私はdした。 私は設定に行き、いくつかのオプション「不明なソース」をオンにしなければなりませんでしたが、電話は2018年に本当に愚かですか? ちなみに、私の電話はAndoid 6.0.1のXiaomi Remdiです(技術者向けの注意)。







これに続いて、一連の奇妙なイベントが発生しました。 電話は信頼できないソースを報告し続けました。 しかし、Avitoは信頼できるソースです! 私はそれをグーグルで調べ、これを回避する方法を見つけ、設定でオフにする必要がありました。 すぐに、私がインストールしなかった特定のアンチウイルスが現れました。







最後に、私は切望された標準のインストールウィンドウを見ました- 今日では許可を見るのは意味がありません 、今では携帯電話へのフルアクセスを与えるまでノートブックさえ起動しません。 アプリケーションのインストール完了と開始の楽しい瞬間! 約束されたお金を楽しみにしていました。 さらに、アプリケーションは管理者権限を要求しましたが、喜んで同意しました。 残念なことに、アプリケーションは奇妙な動作をし、支払いもしたくなかったため、すぐに共通画面のアプリケーションのリストから完全に消えました。











ネタバレ
その後、別の携帯電話-Android 4.4.2搭載のLenovoをチェックしました。 インストール許可のリストははるかに大きいことが判明しました。 また、Play ProtectionとAnti-Virusが干渉することはなく、信頼できないソースからのインストールのみを許可する必要があります。











利他主義





だから、私たちは何に来ましたか:





プログラマーでよくあることですが、問題はコードエラーだと思いました。 アプリケーションを携帯電話で実行しているときに発生するエラーを特定し、それに関するレポートを開発者に送信することにしました。



ネタバレ
今では、無関心な人はほとんど残っていません。そのうちの一人は私です。



このタイプのアプリケーションは、インターネットが接続されているときに機能することは明らかなので、最初は電話とアプリケーションサーバー間のトラフィックを分析してみました。



プロキシ構成のテクニカルガイド
電話がサーバーからホームルーター、ローカルインターネットプロバイダー、インターネットバックボーン、またはアプリケーションサーバーに至るまでのパスに沿った任意のポイントで、インターネットトラフィックを聞くことができます。



問題は次のとおりです。



  • この機器へのアクセスが必要です。
  • 目的のアプリケーションのトラフィックを残りのトラフィックから分離する必要があります。
  • 暗号化の場合(そして2018年にはすべてが暗号化されます)-キーの知識が必要です。


もっと古典的な方法で、自分のラップトップ上のプロキシサーバーを使用して、電話でプロキシを構成することにしました。 暗号化の問題を軽減するために、証明書をインポートすることにしました。アプリケーショントラフィックを分離するために他のアプリケーションを起動しませんでした。

プロキシサーバープログラムとしてげっぷスイートを選びました。 プロキシサーバーを構成し、証明書を電話にエクスポートしました。











特殊なソフトウェアをセットアップした後、アプリケーションがサーバーに送信するリクエストを確認できました。







ご覧のとおり、データは送信されず(IP列に不明なホスト値が示されます)、さらに、アプリケーションレベルで追加の暗号化が存在するため、分析できません。 誤って判断すると、電話はサーバーとそのサブドメインのIPアドレスhttps://*.sky-sync.pwをそのドメイン名で判別できませんでした。



これは、次のオプションのみを意味する可能性があります。





DNSサーバーの問題の仮定を確認するために、さまざまな大規模なDNSサーバー(Google、Yandex、OpenDNS(通常はローカルDNSが検閲されています))からリクエストを作成しようとしました。







ここでは、この名前について何も知らないことがわかります。 次に、ドメイン登録に関するwhois情報を確認しました。







奇妙なことに、ドメインは登録されています。つまり、ローカルではない可能性が高いですが、ドメインが解決されないため、レジストラによって不正使用のためにブロックされた可能性があります。 しかし、何のために? 彼は何をしたのですか?



このアプリケーションがすべて同じものであり、どのようにお金を稼ぐことができるかを知るために、リバースエンジニアリングの魔法を使用することにしました。



ディープダイビング



あなたがヒューマニストであり、この場所まで読んだことがあるなら、これは良いことです-その後の開発のために、あなたは死後の賞に値します。



ツールキット



アプリケーションの「内部」を調べるには、専用のツールをダウンロードする必要があります。 個別にダウンロードできます:





または、すべてが一度にある製品(通常は有料)を使用できます。 私はJebDecompilerが好きです :apkアプリケーションを彼の入力に送信するだけで、タブ内のすべてをきちんと整理できます。また、smaliとその中の逆コンパイルされたJavaコードを切り替えるのが便利です。



別に、私は注意したい:





老人だけが戦いに行く



復習



逆コンパイルされたコードを開くと、難読化されていることがすぐにわかります。







どうすればそれを理解できますか?





プログラマーが最初にコードを開発するほど狂っていることはまずありません。 おそらく、彼は一般に入手可能な難読化ツールの1つを使用しました。 このステップは、たとえば知的財産を保護するためにコード分析を複雑にするために非常に一般的ですが、研究者にとってはどのような「ねじれ」です。



メインの暗号化機能に注目しましょう。







暗号化機能自体は、3行の入力を受け入れます(それ以上の場合、残りは意味をなしません)。



  1. 暗号文
  2. キー
  3. CBCの初期化ベクトル-AES


この関数は、プログラムで少なくとも213回参照されます。







通常のコード分析の重要なキーであることに注意してください。 次に、プログラムを分析する次の方法があると考える必要があります



  1. 関数のロジックを復元し、静的分析ですべての呼び出しを収集し、行を解読します。 難しくて長いかもしれませんが、100%の結果が得られます。
  2. アプリケーションのsmali-codeを変更し、再度コンパイルし、アプリケーションを実行して、ログ内の復号化された行をキャッチします。 簡単ですが、特定の起動時にアプリケーションがどのように動作するかは不明であり、全体像が表示されない場合があります(すべての関数の呼び出しを取得しない)。 さらに、証明書の適用および(または)整合性による自己チェックに問題がある可能性があります。
  3. 関数のロジックを復元するのが難しい場合は、すべての関数呼び出しを収集し、これらの関数自体を必要なパラメーターで直接ダイナミクスでプルできます(たとえば、 Fridaソフトウェアを使用します。


最も厳しい信頼性の高い方法として、方法番号1を選択します。



難読化解除



すぐに予約を行うと、難読化解除は長くて退屈なプロセスになることが多いため、時間枠を適切に評価する必要があります。 私たちの分析では、漠然とした理想的なオプションのために1ヶ月間混乱するよりも、少なくとも何らかの方法ですべての行を解読し、松葉杖の方法でも最小限の時間でそれを行うことで十分です。



定性的難読化解除は完全なリバースエンジニアリングの場合に理にかなっています。たとえば、知的財産泥棒は競合他社のソリューションをコピーしようとする場合、または1人の難読化者によって処理されるプログラムを分析する必要がある場合にこれを行う必要がありますが、これはそうではありません。



JEB Decompiler v.1.4 decompiler後のソースコード



ネタバレ
 public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; String v7 = args[1]; String v0 = args[2]; if(v10 == null) { goto label_9; } if(v10.length() != 0) { goto label_11; } goto label_9; label_11: IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); goto label_15; } catch(NoSuchPaddingException v3) { } catch(NoSuchAlgorithmException v3_1) { } String v11 = ""; goto label_10; label_15: SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); if(v2.length <= 0) { goto label_48; } v4 = 0; v6 = v2.length - 1; label_29: if(v6 < 0) { goto label_38; } if(v2[v6] != 0) { goto label_33; } } catch(Exception v3_2) { goto label_51; } ++v4; label_33: --v6; goto label_29; label_38: if(v4 <= 0) { goto label_48; } try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch(Exception v3_2) { label_51: v11 = ""; goto label_10; } label_48: v11 = new String(v2); goto label_10; label_9: v11 = ""; label_10: return v11; } }
      
      





デコンパイラノート
ところで、dex2jarはしばしばクラッシュします。 そのため、下の図では、dex2jarバージョン2.0が対処できず、smali-codeを発行したことがわかります。







ソースからコンパイルされた最新バージョンは、この関数の逆コンパイルされたコードを生成しましたが、他の多くのコードを逆コンパイルできませんでした(トリックです)。











ボトムライン:デコンパイラーの選択を慎重に検討してください-これにより多くの時間を節約でき、smaliコードを分析するよりも簡単になります。




そのため、このコードをIDEに貼り付けただけでは、エラーのため機能しません。



覚えておくことが重要です。デコンパイラーは、開発者が作成した有効なコードを生成する必要はありません。 彼は分析の命の恩人であり、コードがどのように書かれるかについて推測します。 ほとんどの場合、コンパイラによる最適化の後、元のコードを復元するタスクは完全に簡単になります。



悪い逆コンパイルの例:



 if(v10 == null) { goto label_9; } if(v10.length() != 0) { goto label_11; } goto label_9; … label_9: v11 = ""; return v11;
      
      





私たちは、それがひどく、動作不能であることがわかりました。 書き直します:



 if ((v10 == null) || (v10.length() == 0)) { return ""; }
      
      





これで、より明確になりました。通常の入力チェックを次に示します。 この場合、次のものが必要です。





その結果、以下が得られます。



 package com.company; //package isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq; import java.util.Base64; //import android.util.Base64; //import bnxvhlyg.nkhoirul.zfxogwqi.mdpqejcw.srnepbly.pcbvwxrs.vixdqclm.wnuqvrhp.bnvceayd.bwdoclkr; //   import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public abstract class Main { public Main() { super(); } //hex to ascii public static byte[] xkvasepi(String str) { byte[] v0 = null; if(str != null && str.length() >= 2) { int v2 = str.length() / 2; v0 = new byte[v2]; int v1; for(v1 = 0; v1 < v2; ++v1) { v0[v1] = ((byte)Integer.parseInt(str.substring(v1 * 2, v1 * 2 + 2), 16)); } } return v0; } public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; //text String v7 = args[1]; //key String v0 = args[2]; //IV //check if ((v10 == null) || (v10.length() == 0)) { return ""; } IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); } catch(NoSuchPaddingException v3) { return ""; } catch(NoSuchAlgorithmException v3_1) { return ""; } SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); //v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); v2=v1.doFinal(xkvasepi(v10)); //check if(v2.length <= 0) { return new String(v2); } } catch(Exception v3_2) { return ""; } v4=0; for (v6=v2.length-1;v6>=0;v6--){ if (v2[v6]==0) ++v4; } if(v4 > 0) { try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch (Exception v3_2) { return ""; } } v2 = Base64.getDecoder().decode(v2); return new String(v2); } public static void main(String[] args) { // write your code here System.out.println(podxiwkt(new String[] { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu" })); } }
      
      



{ "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc"、 "637904cd08aeb2d3f6a21b5c7e84f519"、 "8f4c796d5a3120eb"、 "zcmwgvdn"、 "mkngbsyr"、 "rwcdaieu"}))。 package com.company; //package isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq; import java.util.Base64; //import android.util.Base64; //import bnxvhlyg.nkhoirul.zfxogwqi.mdpqejcw.srnepbly.pcbvwxrs.vixdqclm.wnuqvrhp.bnvceayd.bwdoclkr; // import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public abstract class Main { public Main() { super(); } //hex to ascii public static byte[] xkvasepi(String str) { byte[] v0 = null; if(str != null && str.length() >= 2) { int v2 = str.length() / 2; v0 = new byte[v2]; int v1; for(v1 = 0; v1 < v2; ++v1) { v0[v1] = ((byte)Integer.parseInt(str.substring(v1 * 2, v1 * 2 + 2), 16)); } } return v0; } public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; //text String v7 = args[1]; //key String v0 = args[2]; //IV //check if ((v10 == null) || (v10.length() == 0)) { return ""; } IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); } catch(NoSuchPaddingException v3) { return ""; } catch(NoSuchAlgorithmException v3_1) { return ""; } SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); //v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); v2=v1.doFinal(xkvasepi(v10)); //check if(v2.length <= 0) { return new String(v2); } } catch(Exception v3_2) { return ""; } v4=0; for (v6=v2.length-1;v6>=0;v6--){ if (v2[v6]==0) ++v4; } if(v4 > 0) { try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch (Exception v3_2) { return ""; } } v2 = Base64.getDecoder().decode(v2); return new String(v2); } public static void main(String[] args) { // write your code here System.out.println(podxiwkt(new String[] { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu" })); } }





このコードは正常に実行されます。 彼の仕事が明確になった後、それは単純化および単純化され、プログラマーによって書かれたおそらく簡潔な種類になります(もちろん、彼の手が最初に曲がっていなかった場合を除きます)。



ご注意
ところで、この場合、文字列の復号化は、オンラインリソースの束を使用して実証できます。 プログラム内で暗号化された文字列を呼び出す例:







ここでは、まず初期化ベクトルを16進形式に変換する必要があります。







すべての値を置き換えます:







最後に、base64からデコードします。







その結果、通常の文字列が取得され、呼び出しが意味のあるものになります。



したがって、コード全体を調べてすべての暗号化された文字列を収集する必要がありますが、今ではそれらを個別に復号化できます。 ここで重要な点は、「コードの変更とコメント」の段階で、smaliレベルとJavaレベル(smaliの逆コンパイル)の両方で作業できることです。



修正のプラス 短所Mods
スマリ 変更を加え、dexで再コンパイルし、新しい行で逆コンパイルできます。 smaliコードを扱うのは必ずしも簡単ではありません。 変更が正しくない場合、アプリケーションはビルドされません
Java 多くの場合、高レベルの操作からデータを抽出する方がはるかに簡単です。 ほとんどのJavaコードビューアーは編集できません。


別の行の例



 vcgrnfjx.execSQL(nvhdzjfo.xipswfqb(new String[]{"f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6","378f40211b6e32a5406cd97e85bcf9ad","6378a459b1c20edf", "gexnfwok", "meazfhdp", "bsmotaxn"})
      
      





ここから、次のコードに正規表現を書くよりも、正規表現を使用してパラメーターを選択するのが非常に簡単です。



スマリコード例1



 00000280 new-instance v13, Ljava/lang/StringBuilder; 00000284 invoke-direct {v13}, Ljava/lang/StringBuilder;-><init>()V 0000028A const/4 v14, 0x6 0000028C new-array v14, v14, [Ljava/lang/String; 00000290 const/4 v15, 0x0 00000292 const-string v16, "f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6" 00000296 aput-object v16, v14, v15 0000029A const/4 v15, 0x1 0000029C const-string v16, "378f40211b6e32a5406cd97e85bcf9ad" 000002A0 aput-object v16, v14, v15 000002A4 const/4 v15, 0x2 000002A6 const-string v16, "6378a459b1c20edf" 000002AA aput-object v16, v14, v15 000002AE const/4 v15, 0x3 000002B0 const-string v16, "gexnfwok" 000002B4 aput-object v16, v14, v15 000002B8 const/4 v15, 0x4 000002BA const-string v16, "meazfhdp" 000002BE aput-object v16, v14, v15 000002C2 const/4 v15, 0x5 000002C4 const-string v16, "bsmotaxn" 000002C8 aput-object v16, v14, v15
      
      







スマリコード例2



 0000008E new-array v0, v0, [Ljava/lang/String; 00000092 move-object/from16 v89, v0 00000096 const/16 v90, 0x0 0000009A const-string v91, "4500b5e2e2ad26b7545eb54d70ab360ae28c9d031e2afcc3f6a2b2ac488ea440" 0000009E aput-object v91, v89, v90 000000A2 const/16 v90, 0x1 000000A6 const-string v91, "da96f678922d4b07350b3a184ecc1f5e" 000000AA aput-object v91, v89, v90 000000AE const/16 v90, 0x2 000000B2 const-string v91, "0cf69e3d2745a1b8" 000000B6 aput-object v91, v89, v90 000000BA const/16 v90, 0x3 000000BE const-string v91, "jhiqsaoe" 000000C2 aput-object v91, v89, v90 000000C6 const/16 v90, 0x4 000000CA const-string v91, "khbqxurn" 000000CE aput-object v91, v89, v90
      
      







スマリコード例3



 00000D3E new-array v0, v0, [Ljava/lang/String; 00000D42 move-object/16 v298, v0 00000D48 const/4 v0, 0x0 00000D4A move/16 v299, v0 00000D50 const-string v0, "b286945744e085f4d5c19916fd261481" 00000D54 move-object/16 v300, v0 00000D5A move-object/from16 v0, v300 00000D5E move-object/from16 v1, v298 00000D62 move/from16 v2, v299 00000D66 aput-object v0, v1, v2 00000D6A const/4 v0, 0x1 00000D6C move/16 v299, v0 00000D72 const-string v0, "df6883742b2911ac5ac7b4dee065390f" 00000D76 move-object/16 v300, v0 00000D7C move-object/from16 v0, v300 00000D80 move-object/from16 v1, v298 00000D84 move/from16 v2, v299 00000D88 aput-object v0, v1, v2 00000D8C const/4 v0, 0x2 00000D8E move/16 v299, v0 00000D94 const-string v0, "90a463ce2df17b58" 00000D98 move-object/16 v300, v0 00000D9E move-object/from16 v0, v300 00000DA2 move-object/from16 v1, v298 00000DA6 move/from16 v2, v299 00000DAA aput-object v0, v1, v2 00000DAE const/4 v0, 0x3 00000DB0 move/16 v299, v0 00000DB6 const-string v0, "cupyzsgq" 00000DBA move-object/16 v300, v0 00000DC0 move-object/from16 v0, v300 00000DC4 move-object/from16 v1, v298 00000DC8 move/from16 v2, v299 00000DCC aput-object v0, v1, v2
      
      







ご覧のとおり、内部変数が変化し、コマンドのシーケンスが変化し、引数の数も変化します。さらに、プログラムでは、復号化関数は直接ではなく、レイヤーの関数を通じて呼び出されます。 このコンストラクトを検索するルールを自分で記述し、他の関数から文字列をキャプチャする際の間違いを避け、すべてを迅速に実行してください( 幸運 )。



トラップ計画:



  1. 逆コンパイルされたコードからすべての値を抽出します。
  2. 解読する。
  3. smaliコードで暗号文を開いているものに置き換えます。 たとえば、最初の演算子の代わりに置き換えます。 (関数呼び出し全体を切り取り、復号化された文字列を返されたままにしておく方が専門的ですが、それでもプログラムを壊す大きなリスクがあります)。
  4. smaliコードをdexファイルに集めましょう。
  5. 開始したコードアナライザーをさらに調べると便利です。


すべての逆コンパイルされたコードを1つのファイルに収集すると、約20,000行を取得します。これは、手動分析に多くの時間を必要とし、販売するブーツより明らかにコストがかかります。 まず、すべての行を正規表現で収集します。







私たちは593試合に加えて、この規則に該当しなかったダースを見ると、家族には黒い羊がいます。 例:







合計422の一意の行の並べ替え、フィルター処理:







前に復元した復号化関数を通過します。 結果:







Pythonを使用して、暗号文をsmali-codeで開いているものに置き換えます。



 import os words_replace=dict() words_replace["0018aacad3d146266317d8d8c51785fd"]="imei" words_replace["016d15e4d0a72667c61428e736a6f3b8"]="WakeLock" words_replace["032c534efb6c9990cd845a08c5a08b95"]="inbox" #…  .. # smali- #      def change(path): print("file="+path) file_handle = open(path, 'r') context_full = file_handle.read() file_handle.close() for i in words_replace: context_full=context_full.replace(i, words_replace[i]) #print (i+""+words_replace[i]) file_handle = open(path, 'w') context_full = file_handle.write(context_full) file_handle.close() #      smali- for top, dirs, files in os.walk('C:\\work\\test'): for nm in files: path=os.path.join(top, nm) print (path) change(path)
      
      





dexでsmaliファイルを収集します。







これは何らかの形で分析できます(構造全体から最初の引数を読み取ることにより)。







分析



そのため、コードの読み取り可能な行数は20,000行前後になりますが、完全な分析を実行するタスクには価値がありません。 機能全体を理解する必要があります。 ここでは、実際には、Javaソースコードを読み取る機能のみが必要です。 コードを歩き回り、相互参照を調べ、変数と関数の名前を変更します。



Androidアプリケーション、特に大規模なアプリケーションを分析する最良の方法は何ですか?



オプション1:マニフェストファイルから移動できます



たとえば、LAUNCHERから順番に呼び出しのチェーン全体を解こうとします。 ところで、プログラムの線形実行を変更できる「受信者」と「サービス」がまだあることを忘れないでください。







完全なマニフェストファイル
 <?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft"> <uses-permission android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.permission.C2D_MESSAGE"/> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.QUICKBOOT_POWERON"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <permission android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.permission.C2D_MESSAGE" android:protectionLevel="signature"/> <application android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/tgiwmpqy" android:noHistory="true"> <activity android:configChanges="orientation" android:excludeFromRecents="true" android:label="@string/tgiwmpqy" android:launchMode="singleTop" android:name="zemquyog.csrtmnak.xrkfygen.wkahrnjd.acnfunjh.rgipxbuf.lruiwxeg.blqndche.dcjihbou" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:configChanges="orientation" android:launchMode="singleTop" android:name="xbfrscou.hxrvwnoi.djvpcqri.enlnrfio.aoegxbiu.heywzmnb.znfnxcht.nazcxobq" android:screenOrientation="portrait"/> <activity android:configChanges="orientation" android:launchMode="singleTop" android:name="hcfkagds.timkagsd.oetvghzr.fcioynvl.psynofdj.slcghdjz.tapnwsdk.gzvwnban.htenafdb.qwebhzgy" android:noHistory="true" android:screenOrientation="portrait"/> <activity android:configChanges="orientation" android:excludeFromRecents="true" android:launchMode="singleTop" android:name="njfbwmre.voefarqx.ftuxvngl.wrmshxqj.zdenywgn.eiwyunlg.jysgkbam.yrijthab.vstqxpuo.iplamgxf" android:priority="2147483647" android:screenOrientation="portrait"/> <receiver android:name="gfbaznoc.asyoqtnm.kbetoqca.mqysobzu.gqwfibrv.dorxijuk.wgzkmiep.ywnnurzv.csfpqhrn" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="@string/pkzrlscm" android:resource="@xml/ynqukvnb"/> <intent-filter android:priority="2147483646"> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <receiver android:name="ykwbodxc.gymjhibn.kgmdfqor.hbasvmfz.yegkmaif.ortzknvm.quplincn.cuxytvhs.fqonzuts.cyuoxgqi.znumwyct" android:permission="com.google.android.c2dm.permission.SEND"> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE"/> <action android:name="com.google.android.c2dm.intent.REGISTRATION"/> <action android:name="com.google.android.c2dm.intent.UNREGISTRATION"/> <category android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft"/> </intent-filter> </receiver> <receiver android:enabled="true" android:exported="true" android:name="kqwihjot.nvkqjloc.grjnyknm.owydvckh.mugknwdx.enhcyvja.mhvbpcue.ztbwjhfo"> <intent-filter android:priority="2147483646"> <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.USER_PRESENT"/> <action android:name="android.intent.action.BATTERY_OKAY"/> <action android:name="android.intent.action.BATTERY_LOW"/> <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> <action android:name="android.intent.action.APP_ERROR"/> <action android:name="android.intent.action.HEADSET_PLUG"/> <action android:name="android.intent.action.PHONE_STATE"/> <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> <action android:name="android.intent.action.TIME_TICK"/> <action android:name="android.intent.action.SCREEN_ON"/> <action android:name="android.intent.action.SCREEN_OFF"/> <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <action android:name="android.net.wifi.WIFI_STATE_CHANGED"/> <action android:name="android.intent.action.DREAMING_STOPPED"/> <category android:name="android.intent.category.HOME"/> </intent-filter> </receiver> <receiver android:name="btnsxnuz.wmjizbky.lynvjxqz.zinomjuv.yizlgcnf.qwoikgnc.wnrskjea.wfqgmeny.lcgvqrms.ocwkgblp"> <intent-filter android:priority="2147483646"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver> <service android:name="ltvsrezg.ehxndrat.twnnyxrf.nqynefws.dhbalcnr.ynjkuxod.nhoxmsbq.nackoyhn.voycgfek.znhwkqba.taxvnfyn"/> <service android:name="rbnakfzo.qsreiubk.pwvlnngs.twoxnhfv.mftarcnd.pfioxcub.xjlaftqr.nxrqvlwh"/> <service android:enabled="true" android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.ugshpjvo"/> </application> </manifest>
      
      







オプション2:興味深い行から移動できます







解読された文字列の一部
 system_update.apk () () , error = , unregistered = ,  .permission.C2D_MESSAGE //sky-sync.pw/ //sms/inbox /system_update.apk ALLCONTACTS ALLMSG AUTHENTICATION_FAILED Acquiring wakelock Application BLOCKER_BANKING_START BLOCKER_EXTORTIONIST_START BLOCKER_STOP BLOCKER_UPDATE_START Banking CHANGE_GCM_ID CONTACTS CONTACTS_PRO CREATE TABLE IF NOT EXISTS END Error|No process list|No access Extortionist Foreground GCM returned invalid number of GCMBaseIntentService GCMBroadcastReceiver GCMIntentService- GCMRegistrar GCM_LIB GET MESSAGE Mobile Network NEWMSG Not retrying failed operation ONLINE PAGE POST Process finished with exit code 0 RESTART Received deleted messages Registering receiver Releasing wakelock SERVICE_NOT_AVAILABLE SSL START STOP Saving regId on app version Scheduling registration retry, backoff = Setting registeredOnServer status as Stop System UNBLOCK UPDATE UPDATE_PATTERNS URL UTF-8 Update WakeLock Wakelock reference is null Wi-Fi WiMax _success add_msg_ok address android.intent.action.QUICKBOOT_POWERON answer_text answer_to api_url app appVersion application application/vnd.android.package-archive apps_list ask backoff_ms blocker blocker_banking blocker_banking_autolock blocker_banking_forced_access blocker_banking_success blocker_extortionist blocker_extortionist_autolock blocker_extortionist_forced_access blocker_extortionist_success blocker_update blocker_update_forced_access blocker_update_success body build callback cardSuccess check com.android.settings com.google.android.c2dm.intent.RECEIVE com.google.android.c2dm.intent.REGISTER com.google.android.c2dm.intent.REGISTRATION com.google.android.c2dm.intent.UNREGISTER com.google.android.gcm com.google.android.gcm.intent.RETRY com.google.android.gsf com.htc.intent.action.QUICKBOOT_POWERON command command_receive contactslist country data date delete deleted_messages device_block disableDataConnectivity enableDataConnectivity error failure file deleted. first_start force-locked gafzpjxb.cix gcm gcm_id gcm_register gcm_register_ok getITelephony get_message_list id integer primary key autoincrement, id=? imei immunity inbox init_bootable init_imei is_admin is_awake_display is_imunnity is_locked is_network_type is_top_activity job job_date job_id komgejif.hqr locked message message_delivered message_type method model msg msg_id msglist name not nypjtinq.nvp ok onServer onServerExpirationTime onServerLifeSpan operator org.android.sys.admin.disabled org.android.sys.admin.enabled org.android.sys.admin.request org.android.sys.command.receive org.android.sys.launch.first org.android.sys.sms.pro.sent org.android.sys.sms.push org.android.sys.sms.sent outbox page params pattern patterns personal phone phone_list privet process_list protocol qwertyuiopasdfghjklzxcvbnm receive regId regex register register_ok registrationId = registration_id repeat resetting backoff for ru save_contacts_list save_message_history sender sent sent_status sid ss status stop_blocker text text, text/html time token total_deleted type unknown unregistered until url useragent utf-8 value version xpls yes   !     ...                   !  ?                !
      
      





オプション3:興味深いリソース(アセット、ライブラリ)から移動できます



この場合、オプション3が推奨されました。 / assetフォルダー(apkコンテナー)には3つの興味深いhtmlファイルがあります。 ブラウザでのビューは次のとおりです。











公式のAvito支払い振替プログラムには疑わしいと思われますが、そうではないでしょうか? キーを押してSberbankロゴ付きの銀行口座データをページに送信するとどうなるかを追跡しましょう。 JavaScriptはsendCardData()



関数を呼び出します。







そして、 ok.performClick()



呼び出しを介してJavaコードに渡されます。







Javaコードでは、処理が実行されます。







さらに、これらはすべてmcrypt



クラスで暗号化されます。







関数内では、データは以前に検討されたのと同じ方法で暗号化されます:







しかし、残りの部分では、キーはハードワイヤードです:







オンラインリソースを介して復号化を試みます。







そして、base64から変換します。 成功! すべてのアプリケーションデータを復号化できます。以前にキャプチャされたトラフィックでチェックされます。



アプリケーションは、すべてのイベントについてサーバーに報告します
 { "sid":15, "imei":"861117030537111", "phone":"System", "message":"     22.10.2018 23:30:47", "time":"1540240247", "msg_id":1, "status":"unknown", "type":"inbox", "method":"message" }
      
      





また、実行中のすべてのアプリケーションを定期的に転送します
 { "sid": 15, "imei": "861117030537111", "country": "ru", "operator": "MTS RUS", "phone": "", "model": "Xiaomi Redmi 3X", "version": "6.0.1", "application": "", "build": "30.0.2", "process_list": [ "Background|com.android.bluetooth|com.android.bluetooth.hid.HidService", "Background|com.android.settings:remote|com.android.settings.wifi.MiuiWifiService", "Background|com.android.phone|org.codeaurora.ims.ImsService", "Background|system|com.qualcomm.location.LocationService", ..., "Background|xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft|ltvsrezg.ehxndrat.twnnyxrf.nqynefws.dhbalcnr.ynjkuxod.nhoxmsbq.nackoyhn.voycgfek.znhwkqba.taxvnfyn" ], "apps_list": [ "com.introspy.config", "com.google.android.youtube", "com.google.android.googlequicksearchbox", "org.telegram.messenger", ..., "com.google.android.inputmethod.latin", "jakhar.aseem.diva" ], "method": "register" }
      
      





ダイナミクスに銀行データ入力のウィンドウがある場合、データはトラフィックにあります。したがって、これは「フィッシング」アプリケーションであると結論付けることができます。



マニフェストファイルには非常に多くのアクセス許可があり、アプリケーションには豊富な機能があることに気づいた人たちです。別の記事で機能の詳細な分析を行います。それまでの間、成功!



結論



私はブーツを売らなかったことに失望しています。そして、結論は次のとおりです。





PSこの記事をユーモラスな形式で少し書き、できるだけ簡単に送信しようとしました。金曜日に「リバースエンジニアリングの難読化された悪意のあるAndroidアプリケーション」というタイトルの深刻なロングライドを読みたくはないからです。



All Articles