นักพัฒนา: มันง่ายมากที่จะข้ามข้อจำกัด API ที่ซ่อนอยู่ของ Android

Android 9 Pie และ Android 10 ส่งคำเตือนหรือบล็อกการเข้าถึง API ที่ซ่อนอยู่โดยสิ้นเชิง ต่อไปนี้คือวิธีที่นักพัฒนาสามารถหลีกเลี่ยงข้อจำกัด API ที่ซ่อนอยู่ได้

ย้อนกลับไปเมื่อกว่าปีที่แล้ว และเราทุกคนรู้สึกตื่นเต้นที่ได้เห็นสิ่งที่จะเกิดขึ้นใน Android P เบต้า ผู้ใช้ต่างตั้งตารอที่จะมีฟีเจอร์ใหม่ๆ และนักพัฒนาก็ตั้งตารอที่จะมีเครื่องมือใหม่ๆ เพื่อทำให้แอปของตนดีขึ้น น่าเสียดายสำหรับนักพัฒนาบางราย Android P เบต้าตัวแรกมาพร้อมกับสิ่งที่น่ารังเกียจเล็กน้อย: ข้อจำกัด API ที่ซ่อนอยู่ ก่อนที่ฉันจะเจาะลึกถึงความหมายที่แท้จริง ให้ฉันอธิบายเกี่ยวกับบริบทของมันสักเล็กน้อย

ทั้งหมดนี้เกี่ยวกับอะไร?

นักพัฒนาแอป Android ไม่จำเป็นต้องเริ่มต้นใหม่เมื่อสร้างแอป Google มีเครื่องมือที่ช่วยให้การพัฒนาแอปง่ายขึ้นและเกิดซ้ำน้อยลง หนึ่งในเครื่องมือเหล่านี้คือ Android SDK SDK เป็นการอ้างอิงถึงฟังก์ชันทั้งหมดที่นักพัฒนาสามารถใช้ได้อย่างปลอดภัยในแอปของตน ฟังก์ชันเหล่านี้เป็นมาตรฐานใน Android ทุกรุ่นที่ Google อนุมัติ SDK ยังไม่ครบถ้วนสมบูรณ์ มีส่วนที่ "ซ่อน" บางส่วนในเฟรมเวิร์กของ Android ที่ไม่ได้เป็นส่วนหนึ่งของ SDK

ส่วนที่ "ซ่อน" เหล่านี้มีประโยชน์อย่างเหลือเชื่อสำหรับเนื้อหาที่มีการแฮ็กหรือระดับต่ำ ตัวอย่างเช่นของฉัน

แอพวิดเจ็ตลิ้นชัก ใช้ฟังก์ชันที่ไม่ใช่ SDK เพื่อตรวจจับเมื่อผู้ใช้เปิดแอปจากวิดเจ็ตเพื่อให้ลิ้นชักสามารถปิดได้โดยอัตโนมัติ คุณอาจกำลังคิดว่า: "ทำไมไม่ทำให้ฟังก์ชันที่ไม่ใช่ SDK เหล่านี้เป็นส่วนหนึ่งของ SDK ล่ะ" ปัญหาก็คือ ปฏิบัติการของพวกมันไม่สามารถคาดเดาได้เต็มที่ Google ไม่สามารถตรวจสอบให้แน่ใจว่าทุกส่วนของเฟรมเวิร์กทำงานได้บนอุปกรณ์ทุกเครื่องที่ได้รับการอนุมัติ ดังนั้นจึงเลือกวิธีการที่สำคัญกว่านั้นเพื่อรับการตรวจสอบ Google ไม่รับประกันว่ากรอบการทำงานส่วนที่เหลือจะมีความสอดคล้องกัน ผู้ผลิตสามารถเปลี่ยนหรือลบฟังก์ชันที่ซ่อนอยู่เหล่านี้ได้ทั้งหมด แม้แต่ใน AOSP เวอร์ชันต่างๆ คุณไม่มีทางรู้ได้เลยว่าฟังก์ชันที่ซ่อนอยู่จะยังคงอยู่หรือทำงานเหมือนเดิมหรือไม่

หากคุณสงสัยว่าทำไมฉันถึงใช้คำว่า "ซ่อน" นั่นเป็นเพราะฟังก์ชันเหล่านี้ไม่ได้เป็นส่วนหนึ่งของ SDK ด้วยซ้ำ โดยปกติแล้ว หากคุณพยายามใช้เมธอดหรือคลาสที่ซ่อนอยู่ในแอป ก็จะไม่สามารถคอมไพล์ได้ การใช้งานจำเป็นต้องใช้ การสะท้อน หรือ SDK ที่ได้รับการแก้ไข

เมื่อใช้ Android P Google ตัดสินใจว่าการซ่อนข้อมูลเหล่านั้นยังไม่เพียงพอ เมื่อเบต้าแรกถูกปล่อยออกมา มีการประกาศว่า ฟังก์ชันที่ซ่อนอยู่ส่วนใหญ่ (ไม่ใช่ทั้งหมด) ไม่สามารถใช้ได้กับนักพัฒนาแอปอีกต่อไป เบต้าแรกจะเตือนคุณเมื่อแอปของคุณใช้ฟังก์ชันที่อยู่ในบัญชีดำ เบต้าถัดไปทำให้แอปของคุณขัดข้อง อย่างน้อยสำหรับฉัน บัญชีดำนี้ค่อนข้างน่ารำคาญ ไม่เพียงแต่จะแตกหักไปบ้างเท่านั้น ท่าทางการนำทางแต่เนื่องจากฟังก์ชันที่ซ่อนไว้จะถูกขึ้นบัญชีดำตามค่าเริ่มต้น (Google ต้องอนุญาตพิเศษบางส่วนด้วยตนเองต่อรุ่น) ฉันจึงมีปัญหามากมายในการทำให้ Widget Drawer ทำงาน

ขณะนี้ มีวิธีแก้ไขบัญชีดำอยู่สองสามวิธี ประการแรกคือเพียงให้แอปของคุณกำหนดเป้าหมายไปที่ API 27 (Android 8.1) เนื่องจากบัญชีดำมีผลกับแอปที่กำหนดเป้าหมายไปที่ API ล่าสุดเท่านั้น น่าเสียดายกับของ Google ข้อกำหนด API ขั้นต่ำ สำหรับการเผยแพร่บน Play Store จะสามารถกำหนดเป้าหมาย API 27 ได้เป็นเวลานานเท่านั้น ณ วันที่ 1 พฤศจิกายน 2019การอัปเดตแอปทั้งหมดใน Play Store จะต้องกำหนดเป้าหมายเป็น API 28 หรือใหม่กว่า

วิธีแก้ปัญหาประการที่สองคือสิ่งที่ Google สร้างขึ้นใน Android คุณสามารถเรียกใช้คำสั่ง ADB เพื่อปิดการใช้งานบัญชีดำทั้งหมดได้ เหมาะอย่างยิ่งสำหรับการใช้งานส่วนตัวและการทดสอบ แต่ฉันสามารถบอกคุณได้โดยตรงว่าการพยายามให้ผู้ใช้ปลายทางตั้งค่าและเรียกใช้ ADB ถือเป็นฝันร้าย

แล้วมันทิ้งเราไปที่ไหน? เราไม่สามารถกำหนดเป้าหมาย API 27 ได้อีกต่อไปหากเราต้องการอัปโหลดไปยัง Play Store และวิธีการ ADB ก็ใช้งานไม่ได้ นั่นไม่ได้หมายความว่าเราไม่มีตัวเลือกเลย

บัญชีดำ API ที่ซ่อนอยู่ใช้กับแอปพลิเคชันผู้ใช้ที่ไม่อยู่ในรายการที่อนุญาตพิเศษเท่านั้น แอปพลิเคชันระบบ แอปพลิเคชันที่ลงนามด้วยลายเซ็นแพลตฟอร์ม และแอปพลิเคชันที่ระบุในไฟล์การกำหนดค่า ล้วนได้รับการยกเว้นจากบัญชีดำ น่าตลกที่ชุดบริการ Google Play ทั้งหมดระบุไว้ในไฟล์การกำหนดค่านั้น ฉันเดาว่า Google ดีกว่าพวกเราที่เหลือ

ยังไงก็มาพูดถึง blacklist กันต่อ ส่วนที่เราสนใจในวันนี้ก็คือแอปพลิเคชันระบบได้รับการยกเว้น นั่นหมายความว่า แอป System 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 ได้แล้ว มันดูยุ่งยากนิดหน่อย แต่เราสามารถเขียนฟังก์ชัน wrapper ได้ เพื่อจะได้ไม่ต้องสะท้อนซ้ำทุกครั้ง มันไม่ได้ยอดเยี่ยม แต่ก็ดีกว่าไม่มีอะไรเลย แต่จริงๆ แล้ว เรายังทำไม่เสร็จ มีวิธีที่ดีกว่าในการทำเช่นนี้ โดยให้เราใช้การสะท้อนแบบปกติหรือ SDK ที่ปรับเปลี่ยน เหมือนในสมัยก่อน

เนื่องจากการบังคับใช้ของบัญชีดำได้รับการประเมินต่อกระบวนการ (ซึ่งเหมือนกับต่อแอปในกรณีส่วนใหญ่) จึงอาจมีวิธีบางอย่างที่ระบบจะบันทึกว่าแอปที่เป็นปัญหาได้รับการยกเว้นหรือไม่ โชคดีที่มีและเราสามารถเข้าถึงได้ ด้วยการใช้แฮ็กแบบ double-reflection ที่เพิ่งค้นพบนี้ เรามีบล็อกโค้ดแบบใช้ครั้งเดียว:

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 Member weishu ผู้พัฒนา VirtualXposed และ Taichi สำหรับการค้นพบวิธีนี้ตั้งแต่แรกเริ่ม เราอยากจะขอบคุณ topjohnwu นักพัฒนาที่ได้รับการยอมรับ XDA สำหรับการชี้แนะวิธีแก้ปัญหานี้ให้ฉันทราบ ต่อไปนี้เป็นข้อมูลเพิ่มเติมเกี่ยวกับวิธีการทำงานแม้ว่าจะเป็นภาษาจีนก็ตาม ฉันด้วย เขียนเกี่ยวกับสิ่งนี้ใน Stack Overflowพร้อมตัวอย่างใน JNI เช่นกัน