Android Hook实现无清单启动Activity

正常开发中,所有Activity都要在AndroidManifest.xml中进行注册,才可以正常跳转。通过hook,可以绕过系统对activity注册的检测,即使不注册,也可以正常跳转。

整体思路

我们已经实现Activity启动流程的hook,最终采用的方案,是Hook AMS,实现全局startActivity动作的劫持。现在就从这个AMShook为起点,来实现无清单启动Activity

我们既然侦测到了startActivity这个方法的调用,那么自然就可以拿到里面的实参,比如Intent。Intent是跳转意图,从哪里来,跳到哪里去的信息,都包含在Intent里面。而manifest Activity的检测,也是要根据Intent里面的信息来的。

所以,要骗过系统,假装我们跳的Activity是已经注册过的,那么只需要将Intent里面的信息换成已经在manifest中注册的某个Activity即可(如APP启动的LauncherActivity)

确定思路:

    1. 在AMS的hook函数中,将真实的Intent中的信息,替换成manifest中已有的Activity信息,骗过系统的检测机制。
    1. 虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到”假”的Activity,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将真实的Activity信息,还原回去,跳到原本该去的Activity

对应的核心代码,其实也就两句话:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* hookAMS AMS(ActivityManagerService)兼容 26以上,以及26以下的版本(SDK 26对AMS实例的获取进行了代码更改)
* 在已经能够实现全局hook MS的方案下,进一步改造,实现无清单启动Activity
*/
public static void hook(Context context) {

hookAMS(context);//使用假的Activity,骗过AMS的检测

hookActivityThread_mH(context);

hookPMAfter28(context);//由于AppCompatActivity存在PMS检测,如果这里不hook的话,就会包PackageNameNotFoundException
}

源码索引

调用流程图

下图大致画出:从Activity.startActivity动作开始,到Activity.startActivityForResult,到最终跳转动作的最终执行者的全过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}

cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
1
2
3
4
5
6
7
8
9
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, who,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, who, requestCode,
ar.getResultCode(), ar.getResultData());
}
  • execStartActivity只是对一些参数的合法性校验,如果不合法,那就会直接抛出异常,比如之前的have you declared this activity in your AndroidManifest.xml

  • sendActivityResult才是真正的跳转动作执行者

execStartActivity

先进入Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity看看,既然是合法性校验,且看它是如何校验的。

这是Instrumentation.javaexecStartActivity方法

1
2
3
4
5
6
7
8
9
10
11
12
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityTaskManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}

结论:它是通过AMS去校验的,AMS startActivity会返回一个int数值,随后checkStartActivityResult方法会根据这个int值,抛出响应的异常,或者什么都不做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static void checkStartActivityResult(int res, Object intent) {
if (!ActivityManager.isStartResultFatalError(res)) {
return;
}

switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
case ActivityManager.START_PERMISSION_DENIED:
throw new SecurityException("Not allowed to start activity "
+ intent);
case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
throw new AndroidRuntimeException(
"FORWARD_RESULT_FLAG used while also requesting a result");
case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
case ActivityManager.START_NOT_VOICE_COMPATIBLE:
throw new SecurityException(
"Starting under voice control not allowed for: " + intent);
case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:
throw new IllegalStateException(
"Session calling startVoiceActivity does not match active session");
case ActivityManager.START_VOICE_HIDDEN_SESSION:
throw new IllegalStateException(
"Cannot start voice activity on a hidden session");
case ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION:
throw new IllegalStateException(
"Session calling startAssistantActivity does not match active session");
case ActivityManager.START_ASSISTANT_HIDDEN_SESSION:
throw new IllegalStateException(
"Cannot start assistant activity on a hidden session");
case ActivityManager.START_CANCELED:
throw new AndroidRuntimeException("Activity could not be started for "
+ intent);
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
}
}

sendActivityResult

再进入第二句mMainThread.sendActivityResult看真正的跳转动作是如何执行的:

ps:这里其实有个诀窍,既然我们的终极目标是要骗过系统的Activity Intent检测,那么跟着Intent这个变量,就不会偏离方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final void sendActivityResult(
IBinder token, String id, int requestCode,
int resultCode, Intent data) {
if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
+ " req=" + requestCode + " res=" + resultCode + " data=" + data);
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(id, requestCode, resultCode, data)); //将Intent对象进行了封装,最后形成了一个ClientTransaction
final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token);
clientTransaction.addCallback(ActivityResultItem.obtain(list));
try {
mAppThread.scheduleTransaction(clientTransaction); //最后,由AppThread来执行
} catch (RemoteException e) {
// Local scheduling
}
}

既然intent被封装到了ClientTransaction,交给了mAppThread,那么继续:

1
2
3
4
@Override
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}

前方有坑,请注意:Android Studio里并不能直接跳转,所以要手动,找到父类中的scheduleTransaction方法,这个ClientTransactionHandlerActivityThread的父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
* can perform on client.
* @hide
*/
public abstract class ClientTransactionHandler {

// Schedule phase related logic and handlers.

/** Prepare and schedule transaction for execution. */
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}

...

调用sendMessage(int, Object),在ActivityThread中找到这个方法的实现:

1
2
3
void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}

找它的最终实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
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;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}

找到另一个关键点:mH,H类的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
final H mH = new H();
class H extends Handler {
...
public static final int EXECUTE_TRANSACTION = 159;

String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
}
}
return Integer.toString(code);
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;
...
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
}

很明显,它就是一个Handler的普通子类,定义了主线程ActivityThread中可能发生的各种事件。

PS: 这里,留下case EXECUTE_TRANSACTION:分支,是因为,之前ClientTransactionHandler抽象类里面sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);,就是用的这个EXECUTE_TRANSACTION常量。

终于找到了startActivity的最终执行代码!

1
2
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);

Ok,就到这里了(事实上,本来还想往下追查,Intent被封装到ClientTransaction之后,又被得到了什么样的处理,最后发现居然查到了一个源码中都不存在的类,我表示看不懂了,就到这里吧,不影响我们hook)

hook核心代码

还记得我们的整体思路么?

    1. AMShook函数中,将真实的Intent中的信息,替换成manifest中已有的Activity信息,骗过系统的检测机制
    1. 虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到”假”的Activity,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将真实的Activity信息还原回去,跳到原本该去的Activity

说通俗一点就是:
(1)伪造一个Intent,骗过Activity Manifest检测
(2)真正要跳转之前,把原始的Intent还原回去

开始撸代码,大量反射代码即将到来,注释应该很详尽了,特别注意:看反射代码要对照源代码来看,不然很容易走神:

伪造intent,骗过Activity Manifest检测

这里,请对照:ActivityManager.javaActivityTaskManager.java

SDK 28 ~ Android 9.0: ActivityManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};

/** @hide */
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}

SDK 29 ~ Android 10.0: ActivityTaskManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
new Singleton<IActivityTaskManager>() {
@Override
protected IActivityTaskManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
return IActivityTaskManager.Stub.asInterface(b);
}
};

/** @hide */
public static IActivityTaskManager getService() {
return IActivityTaskManagerSingleton.get();
}

hook核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* 这里对AMS进行hook
* ActivityManager(ActivityManagerNative)里的IActivityManager是一个单例,用我们的代理对象替换它!
*
* @param context
*/
private static void hookAMS(Context context) {
try {
final Class<?> ActivityManagerClz;
final String getServiceMethodStr;
final String IActivityManagerSingletonFieldStr;
if (ifSdkOverIncluding29()) {//29的ams获取方式是通过ActivityTaskManager.getService()
ActivityManagerClz = Class.forName("android.app.ActivityTaskManager");
getServiceMethodStr = "getService";
IActivityManagerSingletonFieldStr = "IActivityTaskManagerSingleton";
} else if (ifSdkOverIncluding26()) {//26,27,28的ams获取方式是通过ActivityManager.getService()
ActivityManagerClz = Class.forName("android.app.ActivityManager");
getServiceMethodStr = "getService";
IActivityManagerSingletonFieldStr = "IActivityManagerSingleton";
} else {//25往下,是ActivityManagerNative.getDefault()
ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");
getServiceMethodStr = "getDefault";
IActivityManagerSingletonFieldStr = "gDefault";
}

//这个就是ActivityManager实例
Object ActivityManagerObj = ReflectUtil.invokeStaticMethod(ActivityManagerClz, getServiceMethodStr);
//这个就是这个就是ActivityManager实例中的IActivityManager单例对象
Object IActivityManagerSingleton = ReflectUtil.staticFieldValue(ActivityManagerClz,
IActivityManagerSingletonFieldStr);

// 2.现在创建我们的IActivityManager实例
// 由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建
// 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
Class<?> IActivityManagerClz;
if (ifSdkOverIncluding29()) {
IActivityManagerClz = Class.forName("android.app.IActivityTaskManager");
} else {
IActivityManagerClz = Class.forName("android.app.IActivityManager");
}

// 构建代理类需要两个东西用于创建伪装的Intent
String packageName = Util.getPMName(context);
String clz = Util.getHostClzName(context, packageName);
Object proxyIActivityManager =
Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{IActivityManagerClz},
new AMSProxyInvocation(ActivityManagerObj, packageName, clz));

//3.拿到AMS实例,然后用代理的AMS换掉真正的AMS,代理的AMS则是用 假的Intent骗过了 activity manifest检测.
//偷梁换柱
Field mInstanceField = ReflectUtil.findSingletonField("mInstance");
mInstanceField.set(IActivityManagerSingleton, proxyIActivityManager);

} catch (Exception e) {
e.printStackTrace();
}
}

private static final String ORI_INTENT_TAG = "origin_intent";

/**
* 把InvocationHandler的实现类提取出来,因为这里包含了核心技术逻辑,最好独立,方便维护
*/
private static class AMSProxyInvocation implements InvocationHandler {

Object amObj;
String packageName;//这两个String是用来构建Intent的ComponentName的
String clz;

public AMSProxyInvocation(Object amObj, String packageName, String clz) {
this.amObj = amObj;
this.packageName = packageName;
this.clz = clz;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("GlobalActivityHook", "method.getName() = " + method.getName());
//proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
if (method.getName().equals("startActivity")) {
Log.d("GlobalActivityHook", "全局hook 到了 startActivity");

Intent currentRealIntent = null;//侦测到startActivity动作之后,把intent存到这里
int intentIndex = -1;
//遍历参数,找到Intent
for (int i = 0; i < args.length; i++) {
Object temp = args[i];
if (temp instanceof Intent) {
currentRealIntent = (Intent) temp;//这是原始的Intent,存起来,后面用得着
intentIndex = i;
break;
}
}

//构造自己的Intent,这是为了绕过manifest检测
Intent proxyIntent = new Intent();
ComponentName componentName = new ComponentName(packageName, clz);//用ComponentName重新创建一个intent
proxyIntent.setComponent(componentName);
proxyIntent.putExtra(ORI_INTENT_TAG, currentRealIntent);//将真正的proxy作为参数,存放到extras中,后面会拿出来还原

args[intentIndex] = proxyIntent;//替换掉intent
//哟,已经成功绕过了manifest清单检测. 那么,我不能老让它跳到 伪装的Activity啊,我要给他还原回去,那么,去哪里还原呢?
//继续看源码。

}
return method.invoke(amObj, args);
}
}

//设备系统版本是不是大于等于29(Android 10)
private static boolean ifSdkOverIncluding29() {
int SDK_INT = Build.VERSION.SDK_INT;
return SDK_INT >= 29;
}

//设备系统版本是不是大于等于26(Android 8.0 Oreo)
private static boolean ifSdkOverIncluding26() {
int SDK_INT = Build.VERSION.SDK_INT;
return SDK_INT >= 26;
}

//设备系统版本是不是大于等于28(Android 9.0 Pie)
private static boolean ifSdkOverIncluding28() {
int SDK_INT = Build.VERSION.SDK_INT;
return SDK_INT >= 28;
}

真正要跳转之前,把原始的Intent还原回去

PS: 这里hook mH的手段,并不是针对mH本身做代理,而是对mHmCallback成员。因为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Handler {
...
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
}

handlerdispatchMessage逻辑,是先执行mCallbackhandleMessage,然后根据它的返回值决定要不要执行handler本身的handlerMessage函数
我们的目的是还原Intent,并不需要对ActivityThread原本的mH做出逻辑修改,所以,hook mCallback,加入还原Intent的逻辑,即可

本次hook,对照的源码是(源码太长,直接截取了ActivityThread里面一些关键的代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
final H mH = new H();

class H extends Handler {
...
public static final int EXECUTE_TRANSACTION = 159;
...

String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
...
case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
...
}
}
return Integer.toString(code);
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
...
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;
...
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
}

下面是Hook Mh的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//下面进行ActivityThread的mH的hook
//将真实的Intent还原回去,让系统可以跳到原本该跳的地方
private static void hookActivityThread_mH(Context context) {

try {
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");

Object sCurrentActivityThread = ReflectUtil.staticFieldValue(activityThreadClazz, "sCurrentActivityThread");

Handler mH = (Handler) ReflectUtil.fieldValue(sCurrentActivityThread, "mH");

Field mCallBackField = ReflectUtil.findField(Handler.class, "mCallback");

Handler.Callback callback;
if (ifSdkOverIncluding28()) {
//2.现在,造一个代理
// 他就是一个简单的Handler子类
callback = new ProxyHandlerCallback();//不需要重写全部mH,只需要对mH的callback进行重新定义
} else {
callback = new ActivityThreadHandlerCallBack(context);
}

//3.替换
//将Handler的mCallback成员,替换成创建出来的代理HandlerCallback
mCallBackField.set(mH, callback);


} catch (Exception e) {
e.printStackTrace();
}

}

/**
* 注意,这里有个坑
* android.os.handler 这个类有 3个 callback,按照优先级,依次是 msg的callback,自己成员变量mCallback,自己的成员方法 handleMessage()
*
* 其中,msg.callback一般很少用,但是它是最优先的,如果有一个Message.存在callback非空成员,那么它是先执行,后面两个就没戏了。
* 如果 handler自己的成员变量mCallback,非空,那么 handlerMessage()方法就没戏了
* 前面两个都执行,那么handlerMessage才会执行,
* 这个叫责任链模式?根据实际条件决定代码分支。
*/
private static class ProxyHandlerCallback implements Handler.Callback {

private int EXECUTE_TRANSACTION = 159;//这个值,是android.app.ActivityThread的内部类H 中定义的常量EXECUTE_TRANSACTION

@Override
public boolean handleMessage(Message msg) {
boolean result = false;//返回值,请看Handler的源码,dispatchMessage就会懂了
//Handler的dispatchMessage有3个callback优先级,首先是msg自带的callback,其次是Handler的成员mCallback,最后才是Handler类自身的handlerMessage方法,
//它成员mCallback.handleMessage的返回值为true,则不会继续往下执行 Handler.handlerMessage
//我们这里只是要hook,插入逻辑,所以必须返回false,让Handler原本的handlerMessage能够执行.
if (msg.what == EXECUTE_TRANSACTION) {//这是跳转的时候,要对intent进行还原
try {
//先把相关@hide的类都建好
Class<?> ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");
Class<?> LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");

Field mActivityCallbacksField = ClientTransactionClz.getDeclaredField("mActivityCallbacks");//ClientTransaction的成员
mActivityCallbacksField.setAccessible(true);
//类型判定,好习惯
if (!ClientTransactionClz.isInstance(msg.obj)) return true;
Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);//根据源码,在这个分支里面,msg.obj就是 ClientTransaction类型,所以,直接用
//拿到了ClientTransaction的List<ClientTransactionItem> mActivityCallbacks;
List list = (List) mActivityCallbacksObj;

if (list.size() == 0) return false;
Object LaunchActivityItemObj = list.get(0);//所以这里直接就拿到第一个就好了

if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;
//这里必须判定 LaunchActivityItemClz,
// 因为 最初的ActivityResultItem传进去之后都被转化成了这LaunchActivityItemClz的实例

Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);

Bundle extras = mIntent.getExtras();
if (extras != null) {
Intent oriIntent = (Intent) extras.get(ORI_INTENT_TAG);
//那么现在有了最原始的intent,应该怎么处理呢?
Log.d("1", "2");
mIntentField.set(LaunchActivityItemObj, oriIntent);
}

return result;
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
}

public static class ActivityThreadHandlerCallBack implements Handler.Callback {

private final Context mContext;

public ActivityThreadHandlerCallBack(Context context) {
mContext = context;
}

@Override
public boolean handleMessage(Message msg) {
int LAUNCH_ACTIVITY = 0;
try {
Class<?> clazz = Class.forName("android.app.ActivityThread$H");
LAUNCH_ACTIVITY = (int) ReflectUtil.staticFieldValue(clazz, "LAUNCH_ACTIVITY");
} catch (Exception e) {
}
if (msg.what == LAUNCH_ACTIVITY) {
handleLaunchActivity(mContext, msg);
}
return false;
}
}

private static void handleLaunchActivity(Context context, Message msg) {
try {
Object obj = msg.obj;

Intent proxyIntent = (Intent) ReflectUtil.fieldValue(obj, "intent");
//拿到之前真实要被启动的Intent 然后把Intent换掉
Intent originallyIntent = proxyIntent.getParcelableExtra(ORI_INTENT_TAG);
if (originallyIntent == null) {
return;
}
proxyIntent.setComponent(originallyIntent.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
}

PS:这里有个坑(请看上面if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;,为什么要加这个判断?因为,通过debug,发现从mH里面的msg.what得到的ClientTransaction,它有这么一个成员List<ClientTransactionItem> mActivityCallbacks;,注意看,从list里面拿到的ClientTransactionItem的实际类型是:LaunchActivityItem

之前我索引源码的时候,追查Intent的去向,只知道它最后被封装成了一个ClientTransaction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final void sendActivityResult(
IBinder token, String id, int requestCode,
int resultCode, Intent data) {
if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
+ " req=" + requestCode + " res=" + resultCode + " data=" + data);
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(id, requestCode, resultCode, data)); //将Intent对象进行了封装,最后形成了一个ClientTransaction
final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token);
clientTransaction.addCallback(ActivityResultItem.obtain(list)); //这里拿到的是List<ActivityResultItem>,这个list被addCallback
try {
mAppThread.scheduleTransaction(clientTransaction); //最后,由AppThread来执行
} catch (RemoteException e) {
// Local scheduling
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void handleMessage(Message msg) {
...
switch (msg.what) {
...
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj; //它的mActivityCallbacks的list<?>,?居然变成了LaunchActivityItem,塞进去的是ActivityResultItem
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;
...
}
...
}

查看ClientTransaction源码:

1
2
3
4
public class ClientTransaction implements Parcelable, ObjectPoolItem {

/** A list of individual callbacks to a client. */
private List<ClientTransactionItem> mActivityCallbacks;

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/servertransaction/ClientTransaction.java

但是,最后从mHwitch case EXECUTE_TRANSACTION分支,去debug(因为无法继续往下查源码)的时候,发现原本塞进去的ActivityResultItemlist,居然变成了LaunchActivityItemlist,查了半天,也没查到是在源码何处发生的变化

LaunchActivityItemActivityResultItem他们两个都是ClientTransaction的子类

1
2
public class LaunchActivityItem extends ClientTransactionItem 
public class ActivityResultItem extends ClientTransactionItem

不过,最后能够确定,从mHswitch case EXECUTE_TRANSACTION分支得到的transaction,就是包含了Intent的包装对象,所以只需要解析这个对象,就可以拿到intent,进行还原

OK,大功告成,安装好 Android 9.0 SDK 28的模拟器,启动起来,运行程序,看看能不能无清单跳转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
cn.appblog.activityhookdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: cn.appblog.activityhookdemo, PID: 28253
java.lang.RuntimeException: Unable to start activity ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)
at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
at cn.appblog.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}
at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:240)
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:219)
at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
at cn.appblog.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

提取关键信息:

1
2
Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)

居然找不到包?问题出在:NavUtils.getParentActivityName

进去NavUtils.getParentActivityName()去看看:

1
2
3
4
5
6
7
8
public static String getParentActivityName(@NonNull Activity sourceActivity) {
try {
return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
} catch (NameNotFoundException e) {
// Component name of supplied activity does not exist...?
throw new IllegalArgumentException(e);
}
}

看来就是这里报的错,继续:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static String getParentActivityName(@NonNull Context context,
@NonNull ComponentName componentName)
throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
int flags = PackageManager.GET_META_DATA;
// Check for disabled components to handle cases where the
// ComponentName points to a disabled activity-alias.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
flags |= PackageManager.MATCH_DISABLED_COMPONENTS;
} else {
flags |= PackageManager.GET_DISABLED_COMPONENTS;
}
ActivityInfo info = pm.getActivityInfo(componentName, flags);
if (Build.VERSION.SDK_INT >= 16) {
String result = info.parentActivityName;
if (result != null) {
return result;
}
}
if (info.metaData == null) {
return null;
}
String parentActivity = info.metaData.getString(PARENT_ACTIVITY);
if (parentActivity == null) {
return null;
}
if (parentActivity.charAt(0) == '.') {
parentActivity = context.getPackageName() + parentActivity;
}
return parentActivity;
}

找到可疑点:

1
ActivityInfo info = pm.getActivityInfo(componentName, flags);

可能就是这里抛出的异常,继续:

PackageManager.java

1
2
public abstract ActivityInfo getActivityInfo(@NonNull ComponentName component,
@ComponentInfoFlags int flags) throws NameNotFoundException;

然而,它是一个接口,那么就找它的实现类(注意,如果这个接口涉及到隐藏@hide的类,你用ctrl+T是不能找到的,不过也有办法,回到NavUtil.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String getParentActivityName(@NonNull Context context,
@NonNull ComponentName componentName)
throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
int flags = PackageManager.GET_META_DATA;
// Check for disabled components to handle cases where the
// ComponentName points to a disabled activity-alias.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
flags |= PackageManager.MATCH_DISABLED_COMPONENTS;
} else {
flags |= PackageManager.GET_DISABLED_COMPONENTS;
}
ActivityInfo info = pm.getActivityInfo(componentName, flags);

原来pm对象是来自context,既然提到了context这个抽象类,它的很多抽象方法的实现都在ContextImpl,手动进入ContextImpl找这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}

IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}

return null;
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref//frameworks/base/core/java/android/app/ContextImpl.java

这个pm对象原来是来自ActivityThread然后进行了一次封装,最后返回出去的是一个ApplicationPackageManager对象,那就进入主线程咯:

1
2
3
4
5
6
7
8
9
10
11
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/ActivityThread.java

看看IPackageManager的内容:它是AIDL动态生成的接口,用Android Studio是看不到接口内容的,只能到源码官网,查到的接口如下:

1
2
3
4
interface IPackageManager {
...
ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/content/pm/IPackageManager.aidl

Ok,看到IBinder,就知道应该无法继续往下追查了,已经跨进程了

前面提到了,从主线程拿到的pm,被封装成了ApplicationPackageManager,那么,进入它里面去找getActivityInfo方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public ActivityInfo getActivityInfo(ComponentName className, int flags)
throws NameNotFoundException {
try {
ActivityInfo ai = mPM.getActivityInfo(className, flags, mContext.getUserId());
if (ai != null) {
return ai;
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

throw new NameNotFoundException(className.toString());
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/ApplicationPackageManager.java

原来异常是这里抛出的,当mPm.getActivityInfo为空的时候,才会抛出。OK,就查到这里,得出结论:

源码,其实对Activity的合法性进行了两次检测,一次是在AMS,一次是在这里的PMS,前面的AMS,我们用一个已有的Activity伪装了一下,通过了验证,那么这里的PMS,我们也可以采用同样的方式

注:上述代码的参数ComponentName className,其实,它就是IntentComponentName成员:

1
2
3
4
5
6
7
8
9
10
11
12
public class Intent implements Parcelable, Cloneable {
....
private String mAction;
private Uri mData;
private String mType;
private String mIdentifier;
private String mPackage;
private ComponentName mComponent;
private int mFlags;
private ArraySet<String> mCategories;
@UnsupportedAppUsage
private Bundle mExtras;

这里对Intent又进行了一次检查,检查的就是这个ComponentName

接下来采用同样的方式对PMS的检测进行hook,让它不再报异常。此次hook的参照的源码是:

ActivityThread.java

1
2
3
4
5
6
7
8
9
10
11
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}

hook核心代码如下(对sPackageManager进行代理替换,让代理类检查的永远是合法的Activity):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 由于我只在SDK 28 对应的9.0设备上做过成功的试验,所以此方法命名为hookPMAfter28
*
* @param context
*/
private static void hookPMAfter28(Context context) {
try {
String pmName = Util.getPMName(context);
String hostClzName = Util.getHostClzName(context, pmName);

Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Object sCurrentActivityThread = ReflectUtil.staticFieldValue(activityThreadClazz, "sCurrentActivityThread");//PM居然是来自ActivityThread

Object iPackageManager = ReflectUtil.invokeMethod(sCurrentActivityThread, "getPackageManager");

String packageName = Util.getPMName(context);
PMSInvocationHandler handler = new PMSInvocationHandler(iPackageManager, packageName, hostClzName);
Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
Class<?>[]{iPackageManagerIntercept}, handler);
// 获取 sPackageManager 属性
Field sPackageManagerField = ReflectUtil.findField(sCurrentActivityThread, "sPackageManager");
sPackageManagerField.set(sCurrentActivityThread, proxy);
} catch (
Exception e) {
e.printStackTrace();
}
}

static class PMSInvocationHandler implements InvocationHandler {

private Object base;
private String packageName;
private String hostClzName;

public PMSInvocationHandler(Object base, String packageName, String hostClzName) {
this.packageName = packageName;
this.base = base;
this.hostClzName = hostClzName;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (method.getName().equals("getActivityInfo")) {
ComponentName componentName = new ComponentName(packageName, hostClzName);
return method.invoke(base, componentName, PackageManager.GET_META_DATA, 0); //破费,一定是这样
}

return method.invoke(base, args);
}
}

工具类

ReflectUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
import android.content.Context;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
* Created by zhangshaowen on 16/8/22.
*/
public class ReflectUtil {

/**
* Locates a given field anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the field into.
* @param name field name
* @return a field object
* @throws NoSuchFieldException if the field cannot be located
*/
public static Field findField(Object instance, String name) throws NoSuchFieldException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);

if (!field.isAccessible()) {
field.setAccessible(true);
}

return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}

throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
}

public static Object fieldValue(Object instance, String name) throws NoSuchFieldException, IllegalAccessException {
Field field = findField(instance, name);
return field.get(instance);
}

public static Object staticFieldValue(Class<?> originClazz, String name) throws NoSuchFieldException, IllegalAccessException {
Field field = findField(originClazz, name);
return field.get(null);
}

public static Field findSingletonField(String name) throws NoSuchFieldException, ClassNotFoundException {
Class<?> SingletonClz = Class.forName("android.util.Singleton");//反射创建一个Singleton的class
return findField(SingletonClz, name);
}

public static Field findField(Class<?> originClazz, String name) throws NoSuchFieldException {
for (Class<?> clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);

if (!field.isAccessible()) {
field.setAccessible(true);
}

return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}

throw new NoSuchFieldException("Field " + name + " not found in " + originClazz);
}


/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the method into.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);

if (!method.isAccessible()) {
method.setAccessible(true);
}

return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}

throw new NoSuchMethodException("Method "
+ name
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + instance.getClass());
}

/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param clazz a class to search the method into.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
public static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (; clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);

if (!method.isAccessible()) {
method.setAccessible(true);
}

return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}

throw new NoSuchMethodException("Method "
+ name
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + clazz);
}

public static Object invokeMethod(Object instance, String name) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = findMethod(instance, name);
return method.invoke(instance);
}

public static Object invokeStaticMethod(Class<?> clazz, String name) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = findMethod(clazz, name);
return method.invoke(null);
}

/**
* Locates a given constructor anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the constructor into.
* @param parameterTypes constructor parameter types
* @return a constructor object
* @throws NoSuchMethodException if the constructor cannot be located
*/
public static Constructor<?> findConstructor(Object instance, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Constructor<?> ctor = clazz.getDeclaredConstructor(parameterTypes);

if (!ctor.isAccessible()) {
ctor.setAccessible(true);
}

return ctor;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}

throw new NoSuchMethodException("Constructor"
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + instance.getClass());
}

/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
* @param extraElements elements to append at the end of the array.
*/
public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field jlrField = findField(instance, fieldName);

Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);

// NOTE: changed to copy extraElements first, for patch load first

System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);

jlrField.set(instance, combined);
}

/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
*/
public static void reduceFieldArray(Object instance, String fieldName, int reduceSize)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (reduceSize <= 0) {
return;
}

Field jlrField = findField(instance, fieldName);

Object[] original = (Object[]) jlrField.get(instance);
int finalLength = original.length - reduceSize;

if (finalLength <= 0) {
return;
}

Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), finalLength);

System.arraycopy(original, reduceSize, combined, 0, finalLength);

jlrField.set(instance, combined);
}

public static Object getActivityThread(Context context,
Class<?> activityThread) {
try {
if (activityThread == null) {
activityThread = Class.forName("android.app.ActivityThread");
}
Method m = activityThread.getMethod("currentActivityThread");
m.setAccessible(true);
Object currentActivityThread = m.invoke(null);
if (currentActivityThread == null && context != null) {
// In older versions of Android (prior to frameworks/base 66a017b63461a22842)
// the currentActivityThread was built on thread locals, so we'll need to try
// even harder
Field mLoadedApk = context.getClass().getField("mLoadedApk");
mLoadedApk.setAccessible(true);
Object apk = mLoadedApk.get(context);
Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
mActivityThreadField.setAccessible(true);
currentActivityThread = mActivityThreadField.get(apk);
}
return currentActivityThread;
} catch (Throwable ignore) {
return null;
}
}

/**
* Handy method for fetching hidden integer constant value in system classes.
*
* @param clazz
* @param fieldName
* @return
*/
public static int getValueOfStaticIntField(Class<?> clazz, String fieldName, int defVal) {
try {
final Field field = findField(clazz, fieldName);
return field.getInt(null);
} catch (Throwable thr) {
return defVal;
}
}
}

Util.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

public class Util {
/**
* 获取当前应用的第一个Activity的name
*
* @param context
* @param pmName
* @return
*/
public static String getHostClzName(Context context, String pmName) {
PackageInfo packageInfo = null;
try {
packageInfo = context.getPackageManager().getPackageInfo(pmName, PackageManager
.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return "";
}
ActivityInfo[] activities = packageInfo.activities;
if (activities == null || activities.length == 0) {
return "";
}
ActivityInfo activityInfo = activities[0];
return activityInfo.name;

}

/**
* 获取包名
*
* @param context
* @return
*/
public static String getPMName(Context context) {
// 获取当前进程已经注册的 activity
Context applicationContext = context.getApplicationContext();
return applicationContext.getPackageName();
}
}

最终效果

Application中启用hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import android.app.Application;
import android.content.Context;

import cn.appblog.androidnomenifestdemo.GlobalActivityHookHelper;
import me.weishu.reflection.Reflection;

public class AppMain extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Reflection.unseal(base);
}

@Override
public void onCreate() {
super.onCreate();
GlobalActivityHookHelper.hook(this);
}
}

添加3个Activity间跳转,但是清单文件只有一个launchActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<application
android:name=".app.AppMain"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivityByActivity();
}
});
Log.d("LifeCircle", "onCreate()");
}

private void startActivityByActivity() {
Intent i = new Intent(MainActivity.this, Main2Activity.class);
startActivity(i);
}

@Override
protected void onRestart() {
super.onRestart();
Log.d("LifeCircle", "onRestart()");
}

@Override
protected void onStart() {
super.onStart();
Log.d("LifeCircle", "onStart()");
}

@Override
protected void onResume() {
super.onResume();
Log.d("LifeCircle", "onResume()");
}

@Override
protected void onStop() {
super.onStop();
Log.d("LifeCircle", "onStop()");
}

@Override
protected void onPause() {
super.onPause();
Log.d("LifeCircle", "onPause()");
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.d("LifeCircle", "onDestroy()");
}
}

MainActivity2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main2Activity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);

findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivityByActivity();
}
});
}

private void startActivityByActivity() {
Intent i = new Intent(Main2Activity.this, Main3Activity.class);
startActivity(i);
}
}

MainActivity3.java

1
2
3
4
5
6
7
8
public class Main3Activity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
}
}

结语

无清单注册,启动Activity也完成。所谓hook,套路是简单的,就是

(1)找到hook点(比如上面,3次hook,一次是系统的AMS,一次是ActivityThreadmH,还有一次是ActivityThreadsPackageManager,注意,这里sPackageManagerhook,只针对了SDK28-9.0设备进行了hook,在其他版本的设备上运行可能会出现其他问题,比如,Intent中的参数传递不正常等)

(2)用合适的方式创建代理对象,通常要hook一个系统类,就用继承的方式,重写某方法。hook一个系统接口的实现类,那就用JDK的Proxy动态代理

(3)最后用代理对象,反射set,替换被hook的对象

套路并不难,掌握好反射,以及代理模式,即可。真正难的是哪里?是源码的阅读能力,还有写出兼容性Hook核心代码的能力!Android SDK 有很多版本迭代,现在最新的是SDK 28,我们要保证我们的hook代码能够兼容所有的系统版本,就需要大量阅读源码,确保万无一失。比如上面,如果不是在SDK 28-Android9.0的模拟器上运行发现报异常,也根本就不会去做最后一次的hook

(4)在 SDK 28之前手机上我发现了一个bug,无法隐式启动Activity。修改handleLaunchActivity方法的一行代码即可:

1
proxyIntent.setComponent(originallyIntent.getComponent());

改为

1
2
3
4
5
ComponentName cn = originallyIntent.resolveActivity(context.getPackageManager());
if (cn == null) {
return;
}
proxyIntent.setComponent(cn);

本文转载至:https://www.jianshu.com/p/eb772e50c690

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :