開発者: Android の隠れた API 制限を回避するのは非常に簡単です

Android 9 Pie および Android 10 では、警告が表示されるか、非表示の API へのアクセスが完全にブロックされます。 開発者が隠れた API 制限を回避する方法を次に示します。

1 年以上前のことを思い出して、私たちは皆、Android P ベータ版に何が登場するのか楽しみにしています。 ユーザーは新機能を楽しみにしており、開発者はアプリをより良くするための新しいツールを楽しみにしています。 一部の開発者にとって残念なことに、最初の Android P ベータ版には、隠れた API 制限というちょっとした意地悪なサプライズが伴いました。 それが正確に何を意味するのかを説明する前に、そのコンテキストについて少し説明しましょう。

これは一体どういうことなのでしょうか?

Android アプリ開発者は、アプリを作成するときに最初から始める必要はありません。 Google は、アプリ開発を容易にし、反復性を減らすためのツールを提供しています。 これらのツールの 1 つは Android SDK です。 SDK は基本的に、開発者がアプリで安全に使用できるすべての機能へのリファレンスです。 これらの機能は、Google が承認する Android のすべてのバージョンに標準装備されています。 ただし、SDK はすべてを網羅しているわけではありません。 Android のフレームワークには、SDK の一部ではない「隠された」部分がかなりの数あります。

これらの「隠された」部分は、よりハッキングな内容や低レベルのものに非常に役立ちます。 たとえば、私の ウィジェットドロワーアプリ 非 SDK 関数を使用して、ユーザーがウィジェットからアプリを起動したときを検出し、ドロワーが自動的に閉じられるようにします。 「これらの非 SDK 関数を SDK の一部にすればいいのでは?」と思われるかもしれません。 問題は、彼らの作戦が完全には予測できないことです。 Google は、承認したすべてのデバイスでフレームワークのすべての部分が動作することを確認することはできないため、より重要なメソッドが検証対象として選択されます。 Google は、フレームワークの残りの部分が一貫性を保つことを保証しません。 メーカーはこれらの隠れた機能を変更したり、完全に削除したりすることができます。 AOSP のバージョンが異なっていても、隠し関数がまだ存在するかどうか、または以前のように機能するかどうかはわかりません。

なぜ「非表示」という言葉を使ったのか疑問に思っている方は、これらの関数が SDK の一部ですらないからです。 通常、アプリ内で隠しメソッドまたはクラスを使用しようとすると、コンパイルに失敗します。 それらを使用するには次のものが必要です 反射 または修正された SDK。

Android P では、Google は単に非表示にするだけでは十分ではないと判断しました。 最初のベータ版がリリースされたとき、 と発表されました アプリ開発者は、ほとんど (すべてではない) 隠し機能を使用できなくなりました。 最初のベータ版では、アプリがブラックリストに登録された機能を使用した場合に警告が表示されます。 次のベータ版では単純にアプリがクラッシュしました。 少なくとも私にとって、このブラックリストはかなり迷惑でした。 かなり壊れただけでなく、 ナビゲーションジェスチャー, しかし、非表示の機能はデフォルトでブラックリストに登録されているため(Googleはリリースごとに一部を手動でホワイトリストに登録する必要がある)、ウィジェットドロワーを機能させるには非常に苦労しました。

ブラックリストを回避するにはいくつかの方法がありました。 1 つ目は、ブラックリストは最新の API をターゲットとするアプリにのみ適用されるため、単純にアプリを API 27 (Android 8.1) をターゲットにしたままにすることでした。 残念ながら、Google では API の最小要件 Play ストアで公開する場合、長期間にわたって API 27 のみをターゲットにすることが可能です。 2019年11月1日現在、Play ストアへのすべてのアプリの更新は API 28 以降をターゲットにする必要があります。

2 番目の回避策は、実際には Google が Android に組み込んだものです。 ADB コマンドを実行してブラックリストを完全に無効にすることができます。 これは個人的な使用やテストには最適ですが、エンドユーザーに ADB をセットアップして実行させるのは悪夢であると直接言えます。

それでは、私たちはどうなるでしょうか? Play ストアにアップロードする場合、API 27 をターゲットにすることはできなくなり、ADB メソッドは実行できなくなります。 ただし、選択肢がないわけではありません。

非表示の API ブラックリストは、ホワイトリストに登録されていないユーザー アプリケーションにのみ適用されます。 システム アプリケーション、プラットフォーム署名で署名されたアプリケーション、構成ファイルで指定されたアプリケーションはすべてブラックリストから除外されます。 面白いことに、Google Play Services スイートはすべてその構成ファイルで指定されています。 Google は他の人よりも優れていると思います。

とにかく、ブラックリストについて話を続けましょう。 今日私たちが興味があるのは、システムアプリケーションが免除されるということです。 これは、たとえば、システム UI アプリがシステム パーティション上にあるため、必要なすべての隠し機能を使用できることを意味します。 明らかに、アプリをシステム パーティションにプッシュするだけでは済みません。 それには、root、優れたファイルマネージャー、権限の知識が必要です... ADBを使うともっと簡単です。 ただし、少なくとも非表示の API ブラックリストに関する限り、システム アプリになるための唯一の方法ではありません。

7 段落前に私が反省について言及したときのことを思い出してください。 反射が何なのかわからない場合は、読むことをお勧めします このページ, ですが、簡単にまとめておきます。 Java では、リフレクションは、通常はアクセスできないクラス、メソッド、およびフィールドにアクセスする方法です。 それは信じられないほど強力なツールです。 その段落で述べたように、リフレクションは非 SDK 関数にアクセスする方法でした。 API ブラックリストはリフレクションの使用をブロックしますが、 ダブル-反射。

さて、ここで少し奇妙なことになります。 通常、隠しメソッドを呼び出したい場合は、次のようなことを行います (これは Kotlin ですが、Java も同様です)。

val someHiddenClass = Class.forName("android.some.hidden.Class")
val someHiddenMethod = someHiddenClass.getMethod("someHiddenMethod", String::class.java)

someHiddenMethod.invoke(null, "some important string")

ただし、API ブラックリストのおかげで、ClassNotFoundException が発生するだけです。 ただし、2 回反映すると、正常に動作します。

val forName = Class::class.java.getMethod("forName", String:: class.java)
val getMethod = Class::class.java.getMethod("getMethod", String:: class.java, arrayOf>()::class.java)
val someHiddenClass = forName.invoke(null, "android.some.hidden.Class") asClass
val someHiddenMethod = getMethod.invoke(someHiddenClass, "someHiddenMethod", String::class.java)

someHiddenMethod.invoke(null, "some important string")

変ですよね? はい、でもいいえでもあります。 API ブラックリストは、誰が関数を呼び出しているかを追跡します。 ソースが除外されていない場合、クラッシュします。 最初の例では、ソースはアプリです。 ただし、2 番目の例では、ソースはシステム自体です。 リフレクションを使用して必要なものを直接取得するのではなく、システムに必要なものを取得するように指示するために使用しています。 隠し関数の呼び出し元はシステムであるため、ブラックリストはもう影響しません。

これで完了です。 API ブラックリストを回避する方法ができました。 少し不格好ですが、毎回二重反映する必要がないようにラッパー関数を書くことができます。 素晴らしいことではありませんが、何もしないよりはマシです。 しかし実際には、これで終わりではありません。 これを行うには、古き良き時代のように、通常のリフレクションまたは修正された SDK を使用できる、より良い方法があります。

ブラックリストの適用はプロセスごとに評価されるため (ほとんどの場合、アプリごとと同じです)、問題のアプリが免除されているかどうかをシステムが記録する何らかの方法がある可能性があります。 幸いなことに、それは存在しており、私たちはアクセスすることができます。 新たに発見された二重反射ハックを使用すると、1 回限り使用できるコード ブロックが得られます。

val forName = Class::class.java.getDeclaredMethod("forName", String:: class.java)
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String:: class.java, arrayOf>()::class.java)

val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") asClass
val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) asMethod

val vmRuntime = getRuntime.invoke(null)

setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))

厳密に言えば、これはアプリが API ブラックリストから除外されていることをシステムに伝えているわけではありません。 実際には、ブラックリストに登録すべきでない関数を指定するために実行できる別の ADB コマンドがあります。 それが上記の利点を利用しているものです。 このコードは基本的に、システムがアプリに対して除外すると判断したものをすべてオーバーライドします。 最後に「L」を渡すと、すべてのメソッドが免除されることを意味します。 特定のメソッドを除外する場合は、Smali 構文を使用します。 Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

これで実際には完了です。 カスタムの Application クラスを作成し、そのコードを onCreate() メソッド、そしてバン、もう制限はありません。


このメソッドを最初に発見した XDA メンバーの weishu (VirtualXused と Taichi の開発者) に感謝します。 また、この回避策を教えてくれた XDA 認定開発者、topjohnwu にも感謝します。 仕組みについてもう少し詳しく説明します、中国語ですが。 私も これについては Stack Overflow で書きました、JNI の例も示します。