개발자: Android의 숨겨진 API 제한을 우회하는 것은 매우 쉽습니다.

Android 9 Pie 및 Android 10은 경고를 표시하거나 숨겨진 API에 대한 액세스를 완전히 차단합니다. 개발자가 숨겨진 API 제한을 피할 수 있는 방법은 다음과 같습니다.

1년여 전의 일을 회상하며, 우리 모두는 Android P 베타에 어떤 기능이 추가될지 기대하고 있습니다. 사용자는 새로운 기능을 기대하고 있으며 개발자는 앱을 더 좋게 만들 수 있는 몇 가지 새로운 도구를 기대하고 있습니다. 불행하게도 일부 개발자에게는 첫 번째 Android P 베타에는 숨겨진 API 제한이라는 약간의 놀라움이 있었습니다. 이것이 정확히 무엇을 의미하는지 알아보기 전에, 그 맥락에 대해 조금 설명하겠습니다.

이게 다 뭐예요?

Android 앱 개발자는 앱을 만들 때 처음부터 시작할 필요가 없습니다. Google은 앱 개발을 더 쉽고 덜 반복적으로 만드는 도구를 제공합니다. 이러한 도구 중 하나가 Android SDK입니다. SDK는 본질적으로 개발자가 앱에서 안전하게 사용할 수 있는 모든 기능에 대한 참조입니다. 이러한 기능은 Google이 승인한 모든 Android 변형에 표준으로 제공됩니다. 하지만 SDK가 완전하지는 않습니다. Android 프레임워크에는 SDK의 일부가 아닌 "숨겨진" 부분이 꽤 많이 있습니다.

이러한 "숨겨진" 부분은 더 해킹적이거나 낮은 수준의 작업에 매우 유용할 수 있습니다. 예를 들어, 내 위젯 서랍 앱 비 SDK 기능을 사용하여 사용자가 위젯에서 앱을 실행하는 시기를 감지하여 서랍이 자동으로 닫힐 수 있도록 합니다. "이러한 비 SDK 기능을 SDK의 일부로 만드는 것은 어떨까요?"라고 생각할 수도 있습니다. 글쎄요, 문제는 그들의 작동이 완전히 예측 가능하지 않다는 것입니다. Google은 프레임워크의 모든 부분이 승인된 모든 단일 기기에서 작동하는지 확인할 수 없으므로 더 중요한 방법을 선택하여 확인합니다. Google은 프레임워크의 나머지 부분이 일관성을 유지한다고 보장하지 않습니다. 제조업체는 이러한 숨겨진 기능을 변경하거나 완전히 제거할 수 있습니다. 다른 버전의 AOSP에서도 숨겨진 기능이 여전히 존재하는지, 예전처럼 작동하는지 알 수 없습니다.

내가 왜 "숨겨진"이라는 단어를 사용했는지 궁금하다면 이러한 기능이 SDK의 일부도 아니기 때문입니다. 일반적으로 앱에서 숨겨진 메서드나 클래스를 사용하려고 하면 컴파일에 실패합니다. 이를 사용하려면 다음이 필요합니다. 반사 또는 수정된 SDK.

Android P를 통해 Google은 이를 숨기는 것만으로는 충분하지 않다고 결정했습니다. 첫 번째 베타가 출시되었을 때, 라고 발표됐다 대부분의 (전부는 아님) 숨겨진 기능을 앱 개발자가 더 이상 사용할 수 없습니다. 첫 번째 베타에서는 앱이 블랙리스트 기능을 사용할 때 경고를 표시했습니다. 다음 베타에서는 앱이 다운되었습니다. 적어도 나에게는 이 블랙리스트가 꽤 짜증스러웠다. 꽤 많이 깨졌을 뿐만 아니라 탐색 제스처, 그러나 숨겨진 기능은 기본적으로 블랙리스트에 등록되어 있기 때문에(Google은 일부 릴리스별로 수동으로 화이트리스트를 작성해야 함) Widget Drawer가 작동하도록 하는 데 많은 어려움을 겪었습니다.

이제 블랙리스트를 해결하는 몇 가지 방법이 있었습니다. 첫 번째는 블랙리스트가 최신 API를 대상으로 하는 앱에만 적용되므로 앱을 API 27(Android 8.1)을 대상으로 유지하는 것이었습니다. 불행하게도 Google의 최소 API 요구 사항 Play 스토어에 게시하려면 오랫동안 API 27만 타겟팅할 수 있습니다. 2019년 11월 1일 현재, Play 스토어의 모든 앱 업데이트는 API 28 이상을 대상으로 해야 합니다.

두 번째 해결 방법은 실제로 Google이 Android에 내장한 것입니다. 블랙리스트를 완전히 비활성화하려면 ADB 명령을 실행할 수 있습니다. 개인적인 사용과 테스트에는 좋지만 최종 사용자가 ADB를 설정하고 실행하도록 하는 것은 악몽이라는 것을 직접 말씀드릴 수 있습니다.

그러면 우리는 어디로 가는가? Play 스토어에 업로드하려는 경우 더 이상 API 27을 타겟팅할 수 없으며 ADB 메서드는 실행 가능하지 않습니다. 그렇다고 해서 우리에게 선택의 여지가 없다는 의미는 아닙니다.

숨겨진 API 블랙리스트는 화이트리스트에 없는 사용자 애플리케이션에만 적용됩니다. 시스템 애플리케이션, 플랫폼 서명으로 서명된 애플리케이션, 구성 파일에 지정된 애플리케이션은 모두 블랙리스트에서 제외됩니다. 재미있게도 Google Play 서비스 제품군은 모두 해당 구성 파일에 지정되어 있습니다. 나는 Google이 우리보다 낫다고 생각합니다.

어쨌든, 블랙리스트에 대해 계속 이야기해 봅시다. 오늘 우리가 관심을 갖는 부분은 시스템 애플리케이션이 면제된다는 것입니다. 예를 들어 시스템 UI 앱은 시스템 파티션에 있기 때문에 원하는 숨겨진 기능을 모두 사용할 수 있습니다. 분명히 앱을 시스템 파티션에 푸시할 수는 없습니다. 루트, 좋은 파일 관리자, 권한에 대한 지식이 필요합니다... ADB를 사용하는 것이 더 쉬울 것입니다. 하지만 이것이 우리가 시스템 앱이 될 수 있는 유일한 방법은 아닙니다. 적어도 숨겨진 API 블랙리스트에 관한 한 말입니다.

제가 성찰을 언급했던 일곱 문단 이전으로 마음을 돌려 보십시오. 성찰이 무엇인지 모른다면 읽어보기를 권한다. 이 페이지, 여기에 간단한 요약이 있습니다. 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이 발생하게 됩니다. 그러나 두 번 반영하면 정상적으로 작동합니다.

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 블랙리스트는 누가 함수를 호출하는지 추적합니다. 소스가 면제되지 않으면 충돌이 발생합니다. 첫 번째 예에서 소스는 앱입니다. 그러나 두 번째 예에서는 소스가 시스템 자체입니다. 우리가 원하는 것을 직접적으로 얻기 위해 리플렉션을 사용하는 대신, 우리는 우리가 원하는 것을 얻기 위해 시스템에 지시하기 위해 리플렉션을 사용하고 있습니다. 숨겨진 함수 호출의 출처가 시스템이기 때문에 블랙리스트는 더 이상 우리에게 영향을 미치지 않습니다.

이제 끝났습니다. 이제 API 블랙리스트를 우회할 수 있는 방법이 생겼습니다. 조금 투박하지만 래퍼 함수를 ​​작성하면 매번 다시 반영할 필요가 없습니다. 좋지는 않지만 없는 것보다는 낫습니다. 하지만 실제로는 끝나지 않았습니다. 옛날처럼 일반 리플렉션이나 수정된 ​​SDK를 사용하는 더 좋은 방법이 있습니다.

블랙리스트 시행은 프로세스별로 평가되므로(대부분의 경우 앱별과 동일) 시스템에서 문제의 앱이 면제되는지 여부를 기록할 수 있는 방법이 있을 수 있습니다. 다행히도 존재하며 우리가 접근할 수 있습니다. 새로 발견된 이중 반사 핵을 사용하여 일회용 코드 블록을 얻었습니다.

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() 방법과 bam, 더 이상 제한이 없습니다.


이 방법을 처음 발견한 VirtualXposed 및 Taichi의 ​​개발자인 XDA 회원 weishu에게 감사드립니다. 또한 이 해결 방법을 알려준 XDA 인정 개발자 topjohnwu에게도 감사의 말씀을 전하고 싶습니다. 작동 방식에 대한 자세한 내용은 다음과 같습니다., 중국어로 되어 있지만. 나도 Stack Overflow에 이에 ​​대해 썼습니다., JNI의 예도 있습니다.