編輯:關於android開發
0x00
閱讀本文前,建議讀者首先閱讀Android加殼原理,參考文章Android中的Apk的加固(加殼)原理解析和實現。如果沒有看過這篇文章,本文理解起來比較困難。
0x01
下面我們來分析脫殼代碼為什麼要這樣寫,核心脫殼代碼在ProxyApplication類裡面,首先執行成員方法attachBaseContext,然後執行成員方法onCreate。
那麼attachBaseContext是什麼時候被執行的呢,為什麼先於onCreate執行呢?那就需要看Android的源碼了,我們選用的是Android2.3源碼。
我們首先看一張圖,這張圖表述了從桌面啟動一個應用Activity的啟動過程。
圖 1
其中當執行到ApplicationThread.bindApplication時,會向ActivityThreadl類的Handler對象mH發送消息。
public final void bindApplication(String processName, ApplicationInfo appInfo, List代碼位於frameworks\base\core\java\android\app\ActivityThread.java。providers, ComponentName instrumentationName, String profileFile, Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, int debugMode, boolean isRestrictedBackupMode, Configuration config, Map services) { if (services != null) { // Setup the service cache in the ServiceManager ServiceManager.initServiceCache(services); } AppBindData data = new AppBindData(); data.processName = processName; data.appInfo = appInfo; data.providers = providers; data.instrumentationName = instrumentationName; data.profileFile = profileFile; data.instrumentationArgs = instrumentationArgs; data.instrumentationWatcher = instrumentationWatcher; data.debugMode = debugMode; data.restrictedBackupMode = isRestrictedBackupMode; data.config = config; queueOrSendMessage(H.BIND_APPLICATION, data); }
queueOrSendMessage向ActivityThreadl類的Handler對象mH發送消息。
private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) { synchronized (this) { if (DEBUG_MESSAGES) Slog.v( TAG, "SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj); Message msg = Message.obtain(); msg.what = what; msg.obj = obj; msg.arg1 = arg1; msg.arg2 = arg2; mH.sendMessage(msg); } }代碼位於frameworks\base\core\java\android\app\ActivityThread.java。
handler處理BIND_APPLICATION的流程如下。
private final class H extends Handler { ...... } public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what); switch (msg.what) { ...... case BIND_APPLICATION: AppBindData data = (AppBindData)msg.obj; handleBindApplication(data); break; ...... } }代碼位於frameworks\base\core\java\android\app\ActivityThread.java。
繼續看handleBindApplication,其中data就是ApplicationThread.bindApplication生成的AppBindData對象。
private final void handleBindApplication(AppBindData data) { mBoundApplication = data; ...... data.info = getPackageInfoNoCheck(data.appInfo); ...... Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; ...... try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } }代碼位於frameworks\base\core\java\android\app\ActivityThread.java。
首先把data賦值給了AppBindData對象mBoundApplication,然後通過getPackageInfoNoCheck得到的LoadedApk對象復制給data.info,之後調用data.info.makeApplication生成Application對象,我們下面來分析下data.info.makeApplication這個方法。
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } Application app = null; String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } try { java.lang.ClassLoader cl = getClassLoader(); ContextImpl appContext = new ContextImpl(); appContext.init(this, null, mActivityThread); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } } mActivityThread.mAllApplications.add(app); mApplication = app; ...... return app; }代碼位於frameworks\base\core\java\android\app\LoadedApk.java。
首先通過mApplicationInfo.className獲取application的名字,在本例中是ProxyApplication。然後通過getClassLoader獲取了ClassLoader對象,那麼我先來分析下getClassLoader的實現。
public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader != null) { return mClassLoader; } ...... }我們姑且認為ClassLoader對象mClassLoader不為空,返回LoadedApk對象的成員變量mClassLoader。
返回到makeApplication,繼續看,首先生成了ContextImpl對象,最終調用了mActivityThread.mInstrumentation.newApplication來生成Application對象,並把生成的Context對象放到這個Application對象中。這部分可參考博客Android中Context詳解 ---- 你所不知道的Context。再附一張Context類圖,幫大家理解。
那麼我們講了這麼多,到底是什麼時候執行的ProxyApplication類的方法attachBaseContext的呢?答案就在mActivityThread.mInstrumentation.newApplication,我們繼續分析此方法。
public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return newApplication(cl.loadClass(className), context); }代碼位於frameworks\base\core\java\android\app\Instrumentation.java。
其中context對象就是我們剛剛生成的,繼續分析newApplication方法。
static public Application newApplication(Class clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); return app; }代碼位於frameworks\base\core\java\android\app\Instrumentation.java。
final void attach(Context context) { attachBaseContext(context); }代碼位於frameworks\base\core\java\android\app\Application.java
答案在此揭曉,此時調用了ProxyApplication類的方法attachBaseContext,注意此時還沒有調用ProxyApplication類的方法onCreate。
0x02
知道了執行到ProxyApplication類的方法attachBaseContext之前的流程,我們接下來重點分析下這個方法。
protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { ...... // 配置動態加載環境 Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493 String packageName = this.getPackageName();//當前apk的包名 //下面兩句不是太理解 HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mPackages"); WeakReference wr = (WeakReference) mPackages.get(packageName); //創建被加殼apk的DexClassLoader對象 加載apk內的類和本地代碼(c/c++代碼) DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect( "android.app.LoadedApk", wr.get(), "mClassLoader")); //base.getClassLoader(); 是不是就等同於 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//? //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader ----有點c++中進程環境的意思~~ RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader); Log.i("demo","classloader:"+dLoader); ...... } catch (Exception e) { Log.i("demo", "error:"+Log.getStackTraceString(e)); e.printStackTrace(); } }代碼位於Android中的Apk的加固(加殼)原理解析和實現。
省略部分的代碼還請大家參考Android中的Apk的加固(加殼)原理解析和實現。
首先通過反射調用了ActivityThread類的currentActivityThread方法,該方法是靜態的,返回當前的ActivityThread,代碼如下:
public static final ActivityThread currentActivityThread() { return sThreadLocal.get(); }代碼位於frameworks\base\core\java\android\app\ActivityThread.java。
然後再獲取ActivityThread的成員變量mPackages,mPackages也位於frameworks\base\core\java\android\app\ActivityThread.java中:
final HashMap他是一個HashMap,鍵是包名,值是LoadedApk的軟引用。然後通過當前的包名在HashMap中獲取對應LoadedApk的軟引用。> mPackages = new HashMap >();
然後根據要加載的apk,也就是實際要執行的apk,生成DexClassLoader對象,其中parentClassLoader就是剛剛獲取的LoadedApk對象中的mClassLoader變量。
大家可能會有個疑問,這裡獲取的LoadedApk對象和data.info對象是一樣的麼?答案是一樣的,代碼的關鍵在handleBindApplication中getPackageInfoNoCheck,代碼如下:
private final LoadedApk getPackageInfo(ApplicationInfo aInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode) { synchronized (mPackages) { WeakReference代碼位於frameworks\base\core\java\android\app\ActivityThread.java。ref; if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); packageInfo = new LoadedApk(this, aInfo, this, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); if (includeCode) { mPackages.put(aInfo.packageName, new WeakReference (packageInfo)); } else { mResourcePackages.put(aInfo.packageName, new WeakReference (packageInfo)); } } return packageInfo; } }
這裡生成了一個LoadedApk對象,並以當前包名為鍵,LoadedApk對象為值存入了mPackages這個HashMap中,並且返回LoadedApk對象,並賦值給data.info。
data.info = getPackageInfoNoCheck(data.appInfo);
回到attachBaseContext中,最後把這個新生成DexClassLoader對象賦值給LoadedApk對象的mClassLoader變量,也就是更新了這個mClassLoader變量。
0x03
執行完mActivityThread.mInstrumentation.newApplication,返回到makeApplication,繼續執行下面兩句代碼:
mActivityThread.mAllApplications.add(app); mApplication = app;執行完data.info.makeApplication,我們返回到handleBindApplication(代碼請參考上面),繼續執行下面一句代碼:
mInitialApplication = app;這三行代碼對於理解ProxyApplication類的onCreate方法有幫助,此時application是ProxyApplication,我們要把它替換為我們自己的application,本例中為MyApplication。
0x04
執行完data.info.makeApplication,我們返回到handleBindApplication(代碼請參考上面),繼續執行mInstrumentation.callApplicationOnCreate(app),此時ProxyApplication類的onCreate方法開始執行。
@Override public void onCreate() { { //loadResources(apkFileName); Log.i("demo", "onCreate"); // 如果源應用配置有Appliction對象,則替換為源應用Applicaiton,以便不影響源程序邏輯。 String appClassName = null; try { ApplicationInfo ai = this.getPackageManager() .getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = ai.metaData; if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) { appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。 } else { Log.i("demo", "have no application class name"); return; } } catch (NameNotFoundException e) { Log.i("demo", "error:"+Log.getStackTraceString(e)); e.printStackTrace(); } //有值的話調用該Applicaiton Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {}); Object mBoundApplication = RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mBoundApplication"); Object loadedApkInfo = RefInvoke.getFieldOjbect( "android.app.ActivityThread$AppBindData", mBoundApplication, "info"); //把當前進程的mApplication 設置成了null RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", loadedApkInfo, null); Object oldApplication = RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mInitialApplication"); //http://www.codeceo.com/article/android-context.html ArrayList mAllApplications = (ArrayList) RefInvoke .getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mAllApplications"); mAllApplications.remove(oldApplication);//刪除oldApplication ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke .getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplicationInfo"); ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke .getFieldOjbect("android.app.ActivityThread$AppBindData", mBoundApplication, "appInfo"); appinfo_In_LoadedApk.className = appClassName; appinfo_In_AppBindData.className = appClassName; Application app = (Application) RefInvoke.invokeMethod( "android.app.LoadedApk", "makeApplication", loadedApkInfo, new Class[] { boolean.class, Instrumentation.class }, new Object[] { false, null });//執行 makeApplication(false,null) RefInvoke.setFieldOjbect("android.app.ActivityThread", "mInitialApplication", currentActivityThread, app); ...... app.onCreate(); } }代碼位於Android中的Apk的加固(加殼)原理解析和實現。
首先appClassName為MyApplication,然後依然是通過反射調用了ActivityThread類的currentActivityThread方法,該方法是靜態的,返回當前的ActivityThread對象。
再通過反射獲取當前ActivityThread對象的mBoundApplication變量,這個mBoundApplication對象還記得麼?是在handleBindApplication被賦值的。
private final void handleBindApplication(AppBindData data) { mBoundApplication = data; ...... }然後再獲取mBoundApplication對象裡面的info,這個info實際上就是data.info,是LoadedApk對象。
data.info = getPackageInfoNoCheck(data.appInfo); ...... Application app = data.info.makeApplication(data.restrictedBackupMode, null);繼續執行,把LoadedApk對象中的mApplication變量設置為null,為什麼要這麼做呢?我們稍後解釋。
繼續執行到如下函數:
Object oldApplication = RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mInitialApplication"); //http://www.codeceo.com/article/android-context.html ArrayList mAllApplications = (ArrayList) RefInvoke .getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mAllApplications"); mAllApplications.remove(oldApplication);//刪除oldApplication這幾句函數實際上就對應0x03中函數,從原來ActivityThread對象中移除了原有的application對象。
回到onCreate中繼續看,我們先過掉幾行代碼,直接看makeApplication生成新的application對象。在解釋這個對象的生成過程中,我們會講解在生成此對象前的一些操作的意義。
既然要重新生成,那麼我們首先看一下makeApplication的實現:
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } Application app = null; String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } try { java.lang.ClassLoader cl = getClassLoader(); ContextImpl appContext = new ContextImpl(); appContext.init(this, null, mActivityThread); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } } mActivityThread.mAllApplications.add(app); mApplication = app; ...... return app; }首先mApplication對象需為null,這就是為什麼剛剛把LoadedApk對象中的mApplication變量設置為null的原因。
然後需要獲取ApplicationInfo對象mApplicationInfo的成員變量className,因為現在我要啟動是被加殼的apk中MyApplication,所以我們要把名字設置為MyApplication。這就是下面幾行代碼的作用。
ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke .getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplicationInfo"); ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke .getFieldOjbect("android.app.ActivityThread$AppBindData", mBoundApplication, "appInfo"); appinfo_In_LoadedApk.className = appClassName; appinfo_In_AppBindData.className = appClassName;代碼位於ProxyApplication類的onCreate方法中。
對了,還有一點忘記說了,在makeApplication時,getClassLoader獲得的ClassLoader對象,已經被替換為DexClassLoader對象,這個對象加載的是被加殼的apk。
0x05
那麼MyApplication類什麼時候執行onCreate呢?答案在ProxyApplication類的onCreate方法最後,會調用app.onCreate()。
0x06
那麼什麼時候開啟MainActivtiy呢?怎麼樣開啟的呢?
我們再一次看圖1,從桌面啟動一個應用Activity的啟動過程,怎麼開啟的MainActivity呢?
private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... Activity a = performLaunchActivity(r, customIntent); ...... }
private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ActivityInfo aInfo = r.activityInfo; if (r.packageInfo == null) { r.packageInfo = getPackageInfo(aInfo.applicationInfo, Context.CONTEXT_INCLUDE_CODE); } ComponentName component = r.intent.getComponent(); if (component == null) { component = r.intent.resolveActivity( mInitialApplication.getPackageManager()); r.intent.setComponent(component); } if (r.activityInfo.targetActivity != null) { component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity); } Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); r.intent.setExtrasClassLoader(cl); if (r.state != null) { r.state.setClassLoader(cl); } } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { ...... mInstrumentation.callActivityOnCreate(activity, r.state); } catch {} }
代碼位於frameworks\base\core\java\android\app\ActivityThread.java。
此時獲取的Classloader對象已經被替換為DexClassLoader對象,這個對象加載的是被加殼的apk。
但是此時獲取的activity信息為什麼是MainActivity呢?答案在AndroidManifest.xml裡面。
<activity android:name="com.example.forceapkobj.MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN"> <category android:name="android.intent.category.LAUNCHER"> </category></action></intent-filter> </activity> <activity android:name="com.example.forceapkobj.SubActivity"></activity>MainActivity啟動起來後,被加殼的apk就可以正常工作了。
仿《雷霆戰機》飛行射擊手游開發--游戲簡介,《雷霆戰機》射擊手 某年某月某日,在好友的“蠱惑”下,本人加入了手游開發大軍
FFmpeg使用手冊 - FFmpeg 編碼支持與定制3.1 FFmpeg本身支持一些編碼、封裝與協議,但是支持的依然有限,有些是因為licence,有些是因為相對來說比
安卓 應用程序修改圖標不更新,安卓圖標自己在做項目時,真機測試時想更換應用程序的圖標(虛擬機更換後可以更新),但是更換後重新運行並沒有更新圖標。經過嘗試,最終通過重啟手機
實現Android K的假裝沉浸式,android假裝沉浸在Android 5.0之後引入了MD風格,並且狀態欄沉浸也成為了一種設計習慣。而停留在之Android L之前