文章目錄
- 一、問題背景
- 二、代碼實現
- 三、代碼詳解
一、問題背景
在 Android
中,可以使用 Android Studio
編寫 Java
應用程序,通過編譯打包成 apk
文件,然后將文件推送至 /data/local/tmp
等可執行的目錄或安裝打包出來的應用,隨后使用 app_process
命令即可運行此 Java 程序
,命令格式如下:
app_process -Djava.class.path=${apk路徑} /system/bin 主類的全限定名
例如如下:
在 Android Studio
中編寫一個 Hello World
程序:
package com.teleostnaclobject HelloWorld {@JvmStaticfun main(args: Array<String>) {println("Hello World!!!")}
}
編譯完成之后,推送至 /data/local/tmp
,隨后使用命令執行 app_process -Djava.class.path="/data/local/tmp/HelloWorld.apk" /system/bin com.teleostnacl.HelloWorld
可以看到其可以正常執行 Java
代碼并輸出 Hello World!!!
這個方式啟動的 Java
程序將集成 shell
的 用戶組
和 用戶
,可以執行高權限的命令,實現應用的提權,流行于各種工具箱中。但是由于這樣啟動的 Java
程序是沒有Application
Activity
和其它四大組件的,無法直接拿到 Context
,而對于很多 Android API
來說都需要用到 Context
的。那么有沒有辦法去拿到一個 Context
呢?本文將介紹一種可以在通過 app_process
命令啟動的 Java 程序中獲取到 Context
的方式。
二、代碼實現
先直接上代碼,看如何在代碼中構造一個 Context
val context: Context? by lazy {try {Looper.prepareMainLooper()// ActivityThread activityThread = new ActivityThread();val activityThreadCls = Class.forName("android.app.ActivityThread")val activityThreadConstructor: Constructor<*> = activityThreadCls.getDeclaredConstructor()activityThreadConstructor.isAccessible = trueval activityThread = activityThreadConstructor.newInstance()val getSystemContextMethod = activityThreadCls.getDeclaredMethod("getSystemContext")getSystemContextMethod.invoke(activityThread) as Context} catch (e: Exception) {e.printStackTrace()null}
}
參考:scrcpy 的 Workarounds.java
此方法的原理是通過 ActivityThread.getSystemContext()
來構造獲取 Context
,但是由于 ActivityThread
的構造方法和 getSystemContext()
方法都是被打上了 @UnsupportedAppUsage
注解的,外部無非直接調用,因此需要通過反射來構造。
三、代碼詳解
我們可以通過分析 Android App
的啟動流程來了解到 Context
被構造出來的過程。
我們知道 Android App
是基于 Java 編寫的一種特殊的程序,其跟 Java
程序一樣,需要通過 main
方法來作為應用程序的唯一入口。在 APP 啟動時,會先通過 AMS
請求啟動應用,通過 Zygote
使用 fork()
方法復制自身,創建新的應用進程,此時會加載 Android Runtime (ART)
進行加載 Java 虛擬機和核心庫
,再調用 ActivityThread
的 main()
方法,這就是應用進程的入口點。
我們來看一下 ActivityThread
的 main()
實際的實現:
public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// Install selective syscall interceptionAndroidOs.install();// CloseGuard defaults to true and can be quite spammy. We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format "seq=114"long startSeq = 0;if (args != null) {for (int i = args.length - 1; i >= 0; --i) {if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq = Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}
在這段代碼中,前幾行都是為了初始化相關的模塊,以便后續在 Android 應用中可以正常使用相關的功能,隨后 調用 Looper.prepareMainLooper();
將主線程的 Looper
給準備好,隨后
ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);
構建了一個 ActivityThread
,并調用 attach
方法將其綁定。最后調用 Looper.loop();
開始循環處理主線程的消息。
我們再來看 attach
方法:
這個方法中的邏輯較多,核心的就是給 sCurrentActivityThread
和 mSystemThread
進行賦值,并將ApplicationThread
attach
到 AMS
中,向 AMS
注冊應用進程。最后添加與 View
有關的配置修改的回調。
我們再看與 Context
有關的邏輯:
@UnsupportedAppUsage
ActivityThread() {mResourcesManager = ResourcesManager.getInstance();
}@Override
@UnsupportedAppUsage
public ContextImpl getSystemContext() {synchronized (this) {if (mSystemContext == null) {mSystemContext = ContextImpl.createSystemContext(this);}return mSystemContext;}
}
可以看到 getSystemContext()
就是一個獲取 Context
的關鍵方法,而在其內部調用 ContextImpl.createSystemContext();
方法,并傳遞了一個 ActivityThread
的實例。
ActivityThread
的構造方法和 getSystemContext()
方法都是被打上了 @UnsupportedAppUsage
注解的,且構造方法是使用 default
的限定符,外部無法直接實例化。而 ActivityThread
的類被打上了@hide標記,整個類對于外部來說都無法使用。
因此在構造 Context
的時候,需要通過 val activityThreadCls = Class.forName("android.app.ActivityThread")
來獲取到類對象,再改變構造方法的可訪問性來實例化 ActivityThread
對象
val activityThreadConstructor: Constructor<*> = activityThreadCls.getDeclaredConstructor()
activityThreadConstructor.isAccessible = true
val activityThread = activityThreadConstructor.newInstance()
再然后反射調用 getSystemContext()
方法
val getSystemContextMethod = activityThreadCls.getDeclaredMethod("getSystemContext")
getSystemContextMethod.invoke(activityThread) as Context
直接這樣構造出來之后,在使用上可能會因為缺少相關的初始化而不能使用,可以參考 scrcpy 的 Workarounds.java 將 ActivityThread.sCurrentActivityThread
和 activityThread.mSystemThread
都賦值:
// ActivityThread.sCurrentActivityThread = activityThread;
val sCurrentActivityThreadField = activityThreadCls.getDeclaredField("sCurrentActivityThread")
sCurrentActivityThreadField.isAccessible = true
sCurrentActivityThreadField.set(null, activityThread)// activityThread.mSystemThread = true;
val mSystemThreadField = activityThreadCls.getDeclaredField("mSystemThread")
mSystemThreadField.isAccessible = true
mSystemThreadField.setBoolean(activityThread, true)