Android Hook实现无清单启动Activity
正常开发中,所有Activity都要在AndroidManifest.xml
中进行注册,才可以正常跳转。通过hook
,可以绕过系统对activity注册的检测,即使不注册,也可以正常跳转。
整体思路
我们已经实现Activity启动流程的hook
,最终采用的方案,是Hook AMS
,实现全局startActivity
动作的劫持。现在就从这个AMS
的hook
为起点,来实现无清单启动Activity
我们既然侦测到了startActivity
这个方法的调用,那么自然就可以拿到里面的实参,比如Intent。Intent是跳转意图,从哪里来,跳到哪里去的信息,都包含在Intent里面。而manifest Activity的检测,也是要根据Intent里面的信息来的。
所以,要骗过系统,假装我们跳的Activity是已经注册过的,那么只需要将Intent里面的信息换成已经在manifest中注册的某个Activity即可(如APP启动的LauncherActivity)
确定思路:
- 在AMS的hook函数中,将真实的Intent中的信息,替换成manifest中已有的Activity信息,骗过系统的检测机制。
- 虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到"假"的Activity,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将真实的Activity信息,还原回去,跳到原本该去的Activity
对应的核心代码,其实也就两句话:
/**
* 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
,到最终跳转动作的最终执行者的全过程
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);
}
}
}
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.java
的execStartActivity
方法
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值,抛出响应的异常,或者什么都不做
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这个变量,就不会偏离方向
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
,那么继续:
@Override
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}
前方有坑,请注意:Android Studio里并不能直接跳转,所以要手动,找到父类中的scheduleTransaction
方法,这个ClientTransactionHandler
是ActivityThread
的父类
/**
* 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
中找到这个方法的实现:
void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
找它的最终实现:
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类的定义:
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
的最终执行代码!
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
Ok,就到这里了(事实上,本来还想往下追查,Intent被封装到ClientTransaction
之后,又被得到了什么样的处理,最后发现居然查到了一个源码中都不存在的类,我表示看不懂了,就到这里吧,不影响我们hook)
hook核心代码
还记得我们的整体思路么?
-
- 在
AMS
的hook
函数中,将真实的Intent
中的信息,替换成manifest
中已有的Activity信息,骗过系统的检测机制
- 在
-
- 虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到"假"的Activity,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将真实的Activity信息还原回去,跳到原本该去的Activity
说通俗一点就是:
(1)伪造一个Intent
,骗过Activity Manifest
检测
(2)真正要跳转之前,把原始的Intent
还原回去
开始撸代码,大量反射代码即将到来,注释应该很详尽了,特别注意:看反射代码要对照源代码来看,不然很容易走神:
伪造intent,骗过Activity Manifest检测
这里,请对照:ActivityManager.java
或ActivityTaskManager.java
SDK 28 ~ Android 9.0: ActivityManager.java
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
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核心代码如下
/**
* 这里对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
本身做代理,而是对mH
的mCallback
成员。因为:
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);
}
}
}
handler
的dispatchMessage
逻辑,是先执行mCallback
的handleMessage
,然后根据它的返回值决定要不要执行handler本身的handlerMessage
函数
我们的目的是还原Intent
,并不需要对ActivityThread
原本的mH
做出逻辑修改,所以,hook mCallback
,加入还原Intent
的逻辑,即可
本次hook,对照的源码是(源码太长,直接截取了ActivityThread里面一些关键的代码):
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
的完整代码:
//下面进行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
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
}
}
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
源码:
public class ClientTransaction implements Parcelable, ObjectPoolItem {
/** A list of individual callbacks to a client. */
private List<ClientTransactionItem> mActivityCallbacks;
但是,最后从mH
的witch case EXECUTE_TRANSACTION
分支,去debug(因为无法继续往下查源码)的时候,发现原本塞进去的ActivityResultItem
的list
,居然变成了LaunchActivityItem
的list
,查了半天,也没查到是在源码何处发生的变化
而LaunchActivityItem
和ActivityResultItem
他们两个都是ClientTransaction
的子类
public class LaunchActivityItem extends ClientTransactionItem
public class ActivityResultItem extends ClientTransactionItem
不过,最后能够确定,从mH
的switch case EXECUTE_TRANSACTION
分支得到的transaction
,就是包含了Intent的包装对象,所以只需要解析这个对象,就可以拿到intent,进行还原
OK,大功告成,安装好 Android 9.0 SDK 28的模拟器,启动起来,运行程序,看看能不能无清单跳转:
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)
提取关键信息:
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()
去看看:
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);
}
}
看来就是这里报的错,继续:
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;
}
找到可疑点:
ActivityInfo info = pm.getActivityInfo(componentName, flags);
可能就是这里抛出的异常,继续:
PackageManager.java
public abstract ActivityInfo getActivityInfo(@NonNull ComponentName component,
@ComponentInfoFlags int flags) throws NameNotFoundException;
然而,它是一个接口,那么就找它的实现类(注意,如果这个接口涉及到隐藏@hide
的类,你用ctrl+T是不能找到的,不过也有办法,回到NavUtil.java
):
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
找这个方法:
@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;
}
这个pm
对象原来是来自ActivityThread
然后进行了一次封装,最后返回出去的是一个ApplicationPackageManager
对象,那就进入主线程咯:
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;
}
看看IPackageManager
的内容:它是AIDL
动态生成的接口,用Android Studio是看不到接口内容的,只能到源码官网,查到的接口如下:
interface IPackageManager {
...
ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
}
Ok,看到IBinder,就知道应该无法继续往下追查了,已经跨进程了
前面提到了,从主线程拿到的pm
,被封装成了ApplicationPackageManager
,那么,进入它里面去找getActivityInfo
方法:
@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());
}
原来异常是这里抛出的,当mPm.getActivityInfo
为空的时候,才会抛出。OK,就查到这里,得出结论:
源码,其实对Activity的合法性进行了两次检测,一次是在AMS,一次是在这里的PMS,前面的AMS,我们用一个已有的Activity伪装了一下,通过了验证,那么这里的PMS,我们也可以采用同样的方式
注:上述代码的参数ComponentName className
,其实,它就是Intent
的ComponentName
成员:
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
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
):
/**
* 由于我只在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
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
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
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
<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
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
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
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
,一次是ActivityThread
的mH
,还有一次是ActivityThread
的sPackageManager
,注意,这里sPackageManager
的hook
,只针对了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
方法的一行代码即可:
proxyIntent.setComponent(originallyIntent.getComponent());
改为
ComponentName cn = originallyIntent.resolveActivity(context.getPackageManager());
if (cn == null) {
return;
}
proxyIntent.setComponent(cn);
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/29/android-hook-implement-launching-activity-without-manifests/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论