読者の皆様、ご挨拶!
オフトップ
このトピックでは、私たちが所有するものに間接的にアクセスする方法についてお話したいと思います。
ソフトウェアは他人の財産です。誰かが座って頭を動かし、別の「奇跡」を生み出しました。
しかし、私たちのデバイスの外では、ソフトウェアについて話すことはできません。 この場合の「ソフトウェア」の概念は、「柔らかさ」にすぎません。
これは明らかです。
それが、私たちのデバイスで実行されているプログラムコード、魂が望むものすべてを処理する権利を持っている理由です...
イントロ
最初から始める価値があると思います。 それはすべて、悪名高いゲーム2048をめぐる他者のユーザー依存性の発見から始まりました。 もちろん、このゲームがなぜ人々を魅了するのか疑問に思っていたので、自分で試してみることにしました。 数分後、私はその本質を理解し、このゲームにも興味がありました。
私は認めなければなりません、私は5000点以上を得ることができませんでした、そして2048まではまだ長すぎました...
その間、周囲の「プレーヤー」は簡単に1万から1万6千ポイントを獲得しました。
まあ、しばらくして、このゲームの通過時間を「殺さない」ように、このゲームを愚かにハックするという考えがありました(完了するには長すぎますが、中毒性は劣りません)。 数日後、友人から同じことについて尋ねられました。
それは一つのことを意味していました:それはビジネスに取りかかる時です...
最初は何でしたか?
このハッキングは珍しいことからは始まりませんでした。 すべてがテンプレートに準拠していました。アプリケーションのAPKパッケージを取り出し 、 apktool.jarを使用してプライマリの逆コンパイルを実行し 、次にjd-guiツールを使用してセカンダリ(SmaliからJavaコードを取得する)を実行しました。 異常なことは何もありません。 さらに、私は長い間、少なくとも有用で興味深いものを見つけることを期待して、Javaクラスをサーフィンしました。 広告とGoogleライブラリの「トン」に加えて、私は何も気づかなかった。 最終的に、私はこのアプリケーションの内部、つまり興味深いことがすべて起こる場所でつまずきました:
しかし、ご覧のとおり、このように難読化されたコードをすべて調べたい人はほとんどいません。クラス、メソッド、および変数の元の名前は短い文字セットです。 この段階での研究は行き詰まりました...
私はこのビジネスを放棄して、もっと役に立つことをすることにしました。 しかし、数日後、私は再びこのトピックに戻ることにしました。
終わりの始まり
私は突然、アプリケーションによって保存されたデータを掘り下げることにしました。 これは、フィールド内のセルの色で始まり、現在のポイント数で終わる、すべてに関するデータです。 アプリケーションのデータ構造は次のとおりです。
順番に:
- /キャッシュフォルダーは空であるため、ほとんど関心がありません
- / filesには 、一見ゴミが保存されていますが、これは一見しただけです
- / libフォルダーには、Cocos2Dグラフィックスライブラリが含まれていますが、これも一般的には興味深いものではありません。
- さて、 / shared_prefsにはSharedPreferencesアプリケーションの現在の値が含まれています
このことから、関心のあるフォルダーはfilesとshared_prefsの 2つだけであると結論付けられます。
さて、それらの中身を見てください。
前者の場合、 save.plistファイルに関心があり、 後者の場合、唯一のCocos2dxPrefsFile.xmlファイルに関心があります。
彼らの名前は彼ら自身のために語っています。 テキストを引き伸ばさないために、両方のファイルに関する情報をすぐに提供します。
1) save.plist
ご想像のとおり、このファイルは終了する前にゲームの状態を保存する役割を果たします。 状態の保存には、競技場のセルの説明、元に戻す回数、現在のプレーヤー評価が含まれます。
2) Cocos2dxPrefsFile.xml
ここでは、アプリケーションはこれまでに達成された最大ポイント数に関するデータを保存します。
機能の1つは、これらのファイルが読み取り可能なXML形式で表示されることです。
1)Cocos2dxPrefsFile.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <int name="Score" value="4846" /> // <int name="ScoreSent" value="2410" /> <int name="BestBoxValue" value="512" /> <boolean name="FirstTime" value="true" /> </map>
2)ファイルsave.plist
(各
dict
タグは特定の時点の特定のセルに関するデータを保存します)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"/> <plist version="1.0"> <dict> <key>Main</key> <array> <dict> // <key>Index</key> <string>1</string> // ( 0..15 ) <key>Level</key> <string>3</string> // <key>Score</key> <string>1204</string> // <key>MaxUndo</key> <string>2</string> // Undo </dict> <dict> <key>Index</key> <string>4</string> <key>Level</key> <string>3</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> <dict> <key>Index</key> <string>0</string> <key>Level</key> <string>1</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> <dict> <key>Index</key> <string>3</string> <key>Level</key> <string>1</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> <dict> <key>Index</key> <string>2</string> <key>Level</key> <string>2</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> <dict> <key>Index</key> <string>14</string> <key>Level</key> <string>2</string> <key>Score</key> <string>1204</string> <key>MaxUndo</key> <string>2</string> </dict> </array> <key>Steps</key> <array> <dict> <key>Main</key> <array> <dict> <key>Index</key> <string>1</string> <key>Level</key> <string>10</string> </dict> <dict> <key>Index</key> <string>4</string> <key>Level</key> <string>10</string> </dict> <dict> <key>Index</key> <string>0</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>3</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>2</string> <key>Level</key> <string>1</string> </dict> </array> </dict> <dict> <key>Main</key> <array> <dict> <key>Index</key> <string>13</string> <key>Level</key> <string>10</string> </dict> <dict> <key>Index</key> <string>12</string> <key>Level</key> <string>10</string> </dict> <dict> <key>Index</key> <string>8</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>15</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>14</string> <key>Level</key> <string>1</string> </dict> <dict> <key>Index</key> <string>10</string> <key>Level</key> <string>1</string> </dict> </array> </dict> </array> </dict> </plist>
これを知って、ROOTデバイスに対する権限を持っているので、自分でゲームを簡単に調整できます。
しかし、誰もがルートにアクセスできるわけではありません。
そのため、このアプリケーション用のパッチを作成する必要があります。これは、幅広いユーザーが利用できます。
パッチを作成する
まず、少しの理論。
すべてのANDROIDアプリケーションには独自のサンドボックスがあり、このアプリケーション(またはrootユーザー)のみがアクセスできます。 サンドボックスは、OS- / data / data / *の中心にあるフォルダーです。 アスタリスクの代わりに、アプリケーションパッケージの名前が表示される場合があります。 たとえば、上のスライドから推測したように、ゲームパッケージ2048の名前はcom.estoty.game2048です。 したがって、ゲーム(およびルート)のみが/data/data/com.estoty.game2048フォルダーにアクセスできるため、上記のすべての特典にアクセスできます。
私たちは残っているように見えるでしょうか?
おそらく、同じパッケージを使用して独自のアプリケーションを作成することに気が付くでしょう。 ただし、偽のアプリケーションをコンパイルしてインストールすると、
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES
エラーが発生します。これは、ゲームに署名したキーが偽のアプリケーションに署名したキーと一致しないことを示しています。
そのため、APKでゲームを再構築し、ツールを使用してキーで署名するということだけが残っています。
JARSIGNER 、そして同じキーを持つ偽アプリ! さらに、Androidアプリケーションを再インストールしてもゲームデータ(つまり、サンドボックス)が削除されないことを知っているため、信頼性の高いOS Androidセキュリティポリシーに依存して、ゲームデータをデータに置き換えてから、元のゲームを起動できます。偽のデータを使用しました。
しかし、ゲームが再インストール時にデータを上書きしたらどうなるでしょうか? さて、今それを確認します!
まず、キー(証明書)を使用してゲームをサブスクライブします。 これを行うには、標準として逆アセンブル(必要に応じて逆コンパイル)してから、再度アセンブルしてから、キーで署名します。 このプロセスに精通していることを前提としています。 そうでない場合は、たとえばhereのように塗りつぶされています 。
jarsigner -keystore default.keystore -storepass *** -keypass *** 2048.apk default
そのため、デフォルトキーで署名されたAPKアプリケーションパッケージがあります。
次に、名前、パッケージ名、証明書(キー)が2048ゲームと同じである新しいAndroidアプリケーションを作成します。
パッチアプリケーションと2048ゲームの間には、システムレベルで違いはありません(つまり、Androidの場合、これらは再インストール、相互交換が可能な2つの同一のアプリケーションです)。
次に、パッチがどのように機能するかを考えます。
もちろん、すべてを簡単に行うことができます。たとえば、静的データをCocos2dxPrefsFile.xmlおよびsave.plistファイルに書き込みます 。これらのファイルは 、たとえば、競技場で多数のポイントと多数の数値を設定します。 しかし、それはクールではありません。 パッチを動的にすること、つまり、いつでも再コンパイル(再構築)せずに目的の評価値、フィールドセルなどを設定できるようにすることを提案します。
設計と設計に唾を吐き、パッチの機能のみに焦点を当てると、次のようなものが得られます。
パッチには、最も「有用な」機能が含まれています。
ユーザーは自分の最高の記録を変更し、現在の記録を変更し、競技場の各セルに数字を設定する機会があります!
これで十分です。
コッド
PSデバイスのAndroidアプリケーションの基本的な知識があることを前提としています。 したがって、実際にはコードに関するコメントはありません。
そのため、アプリケーションマニフェストでは何もしません。新しいプロジェクトを作成するときにパッケージ名は既にインストールされており、一般にアプリケーション名を変更する必要はありません。 パッチには特別な機能がないため、特定の
user-permissions
も必要ありません。
単一のボタンをクリックすると、
Patch
機能が呼び出されます。
private void Patch() { SharedPrefsPatch(); // 1 FilePatch(); // 2 AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setMessage("OK. Now you should install the original game without removing this app."); alert.setTitle("Success!"); alert.setCancelable(false); alert.setPositiveButton("Ready!", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); Main.this.finish(); } }); alert.show(); }
新しいレコードを設定します。
//1 private void SharedPrefsPatch() { SharedPreferences prefs = getSharedPreferences("Cocos2dxPrefsFile", Context.MODE_WORLD_READABLE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt("Score", Integer.parseInt(bestscore.getText().toString())); editor.putInt("ScoreSent", Integer.parseInt(bestscore.getText().toString())); editor.putInt("BestBoxValue", 1024); editor.putBoolean("FirstTime", true); editor.apply(); }
そして、私たちは望むように競技場を形成します(ご存知のように、すべてがXML形式です。以下の形式の不注意をおIびします)。
// 2 private void FilePatch() { try { FileOutputStream fop = openFileOutput("save.plist", MODE_WORLD_READABLE); OutputStreamWriter writer = new OutputStreamWriter(fop); writer.write("" + "" + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"/>\n" + "\n" + "<plist version=\"1.0\">\n" + " <dict>\n" + " <key>Main</key>\n" + " <array>" + "" + ""); for(int i=0;i<textboxCells.size();++i) if(textboxCells.get(i).getText().toString().trim().length()>0) writer.write("" + "" + "<dict>\n" + " <key>Index</key>\n" + " <string>"+i+"</string>\n" + " <key>Level</key>\n" + " <string>"+textboxCells.get(i).getText()+"</string>\n" + " <key>Score</key>\n" + " <string>"+currentscore.getText().toString()+"</string>\n" + " <key>MaxUndo</key>\n" + " <string>200</string>\n" + " </dict>" + ""); writer.write("" + "" + " </array>\n" + " <key>Steps</key>\n" + " <array>\n" + " <dict>\n" + "\n" + " </dict>\n" + " </array>\n" + " </dict>\n" + "</plist>" + ""); writer.flush(); writer.close(); } catch (FileNotFoundException e) { Log.i("ERRORMINOR", "********1"); } catch (IOException e) { Log.i("ERRORMINOR", "********2"); }
コンパイルします。 ゲーム2048と同じキーで署名(認証)します。
パフォーマンスを確認する
1)まず、ゲーム2048(ADBを使用して、初心者向け ) をインストールします。
adb install 2048.apk
そして実行:
素晴らしい、ネイティブでないキーによって署名されたゲームは動作します。
2)パッチをインストールします (Androidは自動的にアプリケーションを再インストールし、パッチで置き換えます。アプリケーションデータは保存されます):
adb install -r patch.apk
そしてそれを悪用する:
パッチ:
3)ゲームを再インストールします。
adb install -r 2048.apk
そして実行:
素晴らしい。 パッチは完全に機能します!
現時点では、コンソールですべてのアクションを実行しました(速度のため)が、実際にはすべてがはるかに単純です。ゲームとパッチをデバイスのメモリカードにドロップするだけです。たとえば、ゲームをインストールしてからパッチをインストールし、次にゲームを再度実行します。 この場合、Androidシステムは、アプリケーションが再インストールされるたびに警告を表示します。 もちろん、あなたも同意します。
これは、アプリケーションパッケージに課せられたAndroidセキュリティポリシーを使用してパッチを作成する方法です。
ここで、署名済みゲームのAPKをダウンロードできます。
そして、ここにパッチがあります
アウトロ
実際、このようなギャップはAndroidのせいではありません。 また、データの整合性をチェックせず、暗号化せずに、ソースの完全な委任状を使用してアプリケーションで使用するのは、アプリケーション開発者のせいだと思います...
さて、ここでは、Android OS用のアプリケーションを開発する際に考慮すべきセキュリティの別の側面を検討しました。
じゃあね!