編輯:關於Android編程
寫了一個月應用層代碼,感覺寫嘔了,最近在研究插件化動態加載方面的東西。
本文需要解決的作業:在Activity自身的跳轉中進行Hook。
先簡要說下遇到的幾個坑以及後面的學習整理:
Activity 啟動流程和Context類詳解:
比較重要的點是要理清Context的繼承關系:

由圖可一目了然Activity的繼承關系,由於Activity是繼承自Context的包裝類ContextWrapper的,在我們的Activity得到Context對象時,會通過重寫的attachBaseContext方法得到Context實例。
這也就是在第一個例子中為什麼在attachBaseContext中執行hook方法的原因:

然而這裡要特別注意一點,作者的tainn的例子中使用的是ApplicationContext,然而作業使用到的並不是ContextImpl的mInstrumentation而是自己的mInstrumentation,我們來看看Activity中調用attachBaseContext調用的地方:

可以看到attachBaseContext是在Activity的成員變量mInstrumentation初始化之前進行調用的,所以不能像作者一樣在attachBaseContext中執行hook方法,這裡我選擇了在執行startActivity之前調用以保證能hook到。
作業流程:
1.首先修改MainActivity中的點擊事件和hook觸發方法的:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: 16/1/28 支持Activity直接跳轉請在這裡Hook
// 家庭作業,留給讀者完成.
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(300, 100);
btnParams.setMargins(10, 10, 10, 10);
// Button tv = new Button(this);
// tv.setLayoutParams(btnParams);
// tv.setText("測試1");
// linearLayout.addView(tv);
Button tv2 = new Button(this);
tv2.setLayoutParams(btnParams);
tv2.setText("家庭作業");
linearLayout.addView(tv2);
setContentView(linearLayout);
// tv.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// Intent intent = new Intent(Intent.ACTION_VIEW);
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// intent.setData(Uri.parse("http://www.baidu.com"));
// // 注意這裡使用的ApplicationContext 啟動的Activity
// // 因為Activity對象的startActivity使用的並不是ContextImpl的mInstrumentation
// // 而是自己的mInstrumentation, 如果你需要這樣, 可以自己Hook
// // 比較簡單, 直接替換這個Activity的此字段即可.
// getApplicationContext().startActivity(intent);
// }
// });
tv2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(MainActivity.this,OtherActivity.class);
// 在這裡進行Hook
Log.d("xiaonangua","開始hook");
try {
MyHookHelper.attachContext(MainActivity.this);
} catch (Exception e) {
Log.d("xiaonangua",e.getMessage().toString());
e.printStackTrace();
}
startActivity(i);
}
});
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
// HookHelper.attachContext();
} catch (Exception e) {
Log.d("xiaonangua",e.getMessage().toString());
e.printStackTrace();
}
}
}
2.與tainn作者分析Context的調用鏈不同,我們先來看看Activity.startActivity()源碼:
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
.....
接下來要做的就是把mInstrumentation換成我們修改過的代理對象,通過反射得到Activity的Instrumentation字段然後創建代理對象並偷梁換柱QAQ:
public class MyHookHelper {
public static void attachContext(Activity mActivity) throws Exception{
// 先獲取到當前的ActivityThread對象
Class activityClass = Class.forName("android.app.Activity");
// 拿到原始的 mInstrumentation字段
Field mInstrumentationField = activityClass.getDeclaredField("mInstrumentation");//獲取屬性
//打破封裝
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(mActivity);
// 創建代理對象
Instrumentation myEvilInstrumentation = new MyEvillnstrumentation(mInstrumentation);
// 偷梁換柱
mInstrumentationField.set(mActivity, myEvilInstrumentation);
}
}
代理類基本和作者的一樣,加了一個小字段而已:
public class MyEvillnstrumentation extends Instrumentation {
private static final String TAG = "xiaonangua";
// ActivityThread中原始的對象, 保存起來
Instrumentation mBase;
public MyEvillnstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// Hook之前, XXX到此一游!
Log.d(TAG, "\n南瓜執行了startActivity, 參數如下: \n" + "who = [" + who + "], " +
"\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
"\ntarget = [" + target + "], \nintent = [" + intent +
"], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
// 開始調用原始的方法, 調不調用隨你,但是不調用的話, 所有的startActivity都失效了.
// 由於這個方法是隱藏的,因此需要使用反射調用;首先找到這個方法
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
Log.d("xiaonangua",execStartActivity.toString());
return (ActivityResult) execStartActivity.invoke(mBase,who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
Log.d("xiaonangua",e.getMessage().toString());
return null;
// 某該死的rom修改了 需要手動適配
//throw new RuntimeException("do not support!!! pls adapt it");
}
}
}
然後啟動app,運行,打印log如下:
10-28 16:51:02.520 26242-26242/com.weishu.upf.dynamic_proxy_hook.app2 D/xiaonangua: 開始hook
10-28 16:51:02.530 26242-26242/com.weishu.upf.dynamic_proxy_hook.app2 D/xiaonangua: 南瓜執行了startActivity, 參數如下:
who = [com.weishu.upf.dynamic_proxy_hook.app2.MainActivity@3ec0c1af],
contextThread = [android.app.ActivityThread$ApplicationThread@2f6ff9bc],
token = [android.os.BinderProxy@2222360],
target = [com.weishu.upf.dynamic_proxy_hook.app2.MainActivity@3ec0c1af],
intent = [Intent { cmp=com.weishu.upf.dynamic_proxy_hook.app2/.OtherActivity }],
requestCode = [-1],
options = [null]
Android中的全局變量與局部變量使用小結
全局變量顧名思義就是在整個的類中或者可在多個函數中調用的變量。也稱為外部變量。局部變量則是特定過程或函數中可以訪問的變量。聲明一個變量是很 容易的,但是講到使用的時候,卻
Android仿QQ消息提示實現彈出式對話框
本文在《7種形式的Android Dialog使用實例》在這篇文章的基礎進行學習,具體內容如下1.概述 android原生控件向來以丑著稱(新推出的Material D
Android高手之路之Android中Intent傳遞對象的兩種方法Serializable,Parcelable
Android中的傳遞有兩個方法,一個是Serializable,另一個是Parcelable。 Serializable是J2SE本身就支持的。而Parc
Android新浪微博客戶端(七)——ListView中的圖片異步加載、緩存
我們都知道對每一個Weibo Item都有用戶頭像,而且每一條微博還可能帶有圖片。如果在加載列表的同時加載圖片,這樣有幾個缺點,第一很費事,界面卡住,用戶體