package me.ele.amigo.hook; import android.app.Activity; import android.app.IServiceConnection; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.res.TypedArray; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import java.lang.reflect.Method; import java.util.Map; import java.util.Random; import me.ele.amigo.AmigoInstrumentation; import me.ele.amigo.compat.ActivityThreadCompat; import me.ele.amigo.compat.RCompat; import me.ele.amigo.reflect.MethodUtils; import me.ele.amigo.stub.ServiceStub; import me.ele.amigo.utils.component.ServiceFinder; import static android.R.attr.activityCloseEnterAnimation; import static android.R.attr.activityCloseExitAnimation; import static android.R.attr.activityOpenEnterAnimation; import static android.R.attr.activityOpenExitAnimation; import static me.ele.amigo.reflect.FieldUtils.readField; public class IActivityManagerHookHandle extends BaseHookHandle { private static final String TAG = IActivityManagerHookHandle.class.getSimpleName(); private static ServiceInfo proxyServiceInfo; public IActivityManagerHookHandle(Context context) { super(context); } @Override protected void init() { hookedMethodHandlers.put("startService", new startService(context)); hookedMethodHandlers.put("stopService", new stopService(context)); hookedMethodHandlers.put("stopServiceToken", new stopServiceToken(context)); hookedMethodHandlers.put("bindService", new bindService(context)); hookedMethodHandlers.put("unbindService", new unbindService(context)); hookedMethodHandlers.put("unbindFinished", new unbindFinished(context)); hookedMethodHandlers.put("peekService", new peekService(context)); hookedMethodHandlers.put("startActivity", new startActivity(context)); hookedMethodHandlers.put("startActivityAsUser", new startActivityAsUser(context)); hookedMethodHandlers.put("startActivityAsCaller", new startActivityAsCaller(context)); hookedMethodHandlers.put("finishActivity", new finishActivity(context)); hookedMethodHandlers.put("overridePendingTransition", new overridePendingTransition (context)); } private static class startService extends HookedMethodHandler { private ServiceInfo info = null; public startService(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { info = replaceFirstServiceIntentOfArgs(args); return super.beforeInvoke(receiver, method, args); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { if (invokeResult instanceof ComponentName) { if (info != null) { setFakedResult(new ComponentName(info.packageName, info.name)); } } info = null; super.afterInvoke(receiver, method, args, invokeResult); } } private static class stopService extends HookedMethodHandler { public stopService(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 1; if (args != null && args.length > index && args[index] instanceof Intent) { Intent intent = (Intent) args[index]; int re = ServiceManager.getDefault().stopService(context, intent); if (re == 1) { setFakedResult(1); return true; } } return super.beforeInvoke(receiver, method, args); } } private static class stopServiceToken extends HookedMethodHandler { public stopServiceToken(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (args != null && args.length > 2) { ComponentName componentName = (ComponentName) args[0]; if (isComponentNameInNewApp(context, componentName)) { IBinder token = (IBinder) args[1]; Integer startId = (Integer) args[2]; boolean re = ServiceManager.getDefault().stopServiceToken(context, componentName, token, startId); setFakedResult(re); return true; } } return super.beforeInvoke(receiver, method, args); } } private static class bindService extends HookedMethodHandler { private ServiceInfo info = null; public bindService(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { info = replaceFirstServiceIntentOfArgs(args); int index = findIServiceConnectionIndex(method); if (info != null && index >= 0) { final Object oldIServiceConnection = args[index]; MyIServiceConnection proxyConnection = new MyIServiceConnection(info) { public void connected(ComponentName name, IBinder service) { try { MethodUtils.invokeMethod(oldIServiceConnection, "connected", new ComponentName(mInfo.packageName, mInfo.name), service); } catch (Exception e) { Log.e(TAG, "invokeMethod connected", e); } } }; args[index] = proxyConnection; ServiceManager.getDefault().addBindServiceRecord(oldIServiceConnection, (Intent) args[findFirstIntentIndexInArgs(args)], proxyConnection); } return super.beforeInvoke(receiver, method, args); } private int findIServiceConnectionIndex(Method method) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes != null && parameterTypes.length > 0) { for (int index = 0; index < parameterTypes.length; index++) { if (parameterTypes[index] != null && TextUtils.equals(parameterTypes[index] .getSimpleName(), "IServiceConnection")) { return index; } } } return -1; } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { if (invokeResult instanceof ComponentName) { if (info != null) { setFakedResult(new ComponentName(info.packageName, info.name)); } } info = null; super.afterInvoke(receiver, method, args, invokeResult); } private abstract static class MyIServiceConnection extends IServiceConnection.Stub { protected final ServiceInfo mInfo; private MyIServiceConnection(ServiceInfo info) { mInfo = info; } } } private static class unbindService extends HookedMethodHandler { public unbindService(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = findIServiceConnectionIndex(method); if (index != -1) { Object connection = args[index]; Object proxyConnection = ServiceManager.getDefault().getProxyConnection(connection); setFakedResult(ServiceManager.getDefault().unbind(context, connection)); if (proxyConnection != null) { args[index] = proxyConnection; } } return super.beforeInvoke(receiver, method, args); } private int findIServiceConnectionIndex(Method method) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes != null && parameterTypes.length > 0) { for (int index = 0; index < parameterTypes.length; index++) { if (parameterTypes[index] != null && TextUtils.equals(parameterTypes[index] .getSimpleName(), "IServiceConnection")) { return index; } } } return -1; } } private static class peekService extends HookedMethodHandler { public peekService(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { replaceFirstServiceIntentOfArgs(args); return super.beforeInvoke(receiver, method, args); } } private static class unbindFinished extends HookedMethodHandler { public unbindFinished(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { replaceFirstServiceIntentOfArgs(args); return super.beforeInvoke(receiver, method, args); } } private static ServiceInfo replaceFirstServiceIntentOfArgs(Object[] args) throws RemoteException { int intentOfArgIndex = findFirstIntentIndexInArgs(args); if (args != null && args.length > 1 && intentOfArgIndex >= 0) { Intent intent = (Intent) args[intentOfArgIndex]; ServiceInfo serviceInfo = ServiceFinder.resolveNewServiceInfo(context, intent); if (serviceInfo != null) { ServiceInfo proxyService = selectProxyService(serviceInfo); if (proxyService != null) { Intent newIntent = new Intent(); newIntent.setAction(proxyService.name + new Random().nextInt()); newIntent.setClassName(proxyService.packageName, proxyService.name); newIntent.putExtra(AmigoInstrumentation.EXTRA_TARGET_INTENT, intent); newIntent.setFlags(intent.getFlags()); args[intentOfArgIndex] = newIntent; return proxyService; } } } return null; } //// TODO: we may need to support new added services in multi-process app private static ServiceInfo selectProxyService(ServiceInfo serviceInfo) { if (proxyServiceInfo == null) { try { proxyServiceInfo = context.getPackageManager().getServiceInfo(new ComponentName (context.getPackageName(), ServiceStub.class.getName()), 0); } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException(e); } } return proxyServiceInfo; } private static int findFirstIntentIndexInArgs(Object[] args) { if (args != null && args.length > 0) { int i = 0; for (Object arg : args) { if (arg != null && arg instanceof Intent) { return i; } i++; } } return -1; } private static boolean isComponentNameInNewApp(Context context, ComponentName componentName) { return ServiceFinder.resolveNewServiceInfo(context, new Intent().setComponent(componentName) ) != null; } private class startActivity extends HookedMethodHandler { public startActivity(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { overridePendingTransition(args, activityOpenEnterAnimation, activityOpenExitAnimation); super.afterInvoke(receiver, method, args, invokeResult); } } private class startActivityAsUser extends HookedMethodHandler { public startActivityAsUser(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { overridePendingTransition(args, activityOpenEnterAnimation, activityOpenExitAnimation); super.afterInvoke(receiver, method, args, invokeResult); } } private class startActivityAsCaller extends HookedMethodHandler { public startActivityAsCaller(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { overridePendingTransition(args, activityOpenEnterAnimation, activityOpenExitAnimation); super.afterInvoke(receiver, method, args, invokeResult); } } private void overridePendingTransition(Object[] args, int enterAnimFlag, int exitAnimFlag) { try { IBinder token = getIBinderToken(args); if (token == null) { return; } Activity activity = (Activity) MethodUtils.invokeMethod(ActivityThreadCompat.instance (), "getActivity", token); int windowAnimations = activity.getWindow().getAttributes().windowAnimations; int[] attrs = {enterAnimFlag, exitAnimFlag}; TypedArray ta = activity.obtainStyledAttributes(windowAnimations, attrs); int enterAnimation = ta.getResourceId(0, 0); int exitAnimation = ta.getResourceId(1, 0); ta.recycle(); activity.overridePendingTransition(enterAnimation, exitAnimation); } catch (Exception e) { //ignore } } public IBinder getIBinderToken(Object[] args) { if (args != null && args.length > 0) { for (Object arg : args) { if (arg != null && arg instanceof IBinder) { return (IBinder) arg; } } } return null; } private class finishActivity extends HookedMethodHandler { public finishActivity(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { try { Map mActivities = (Map) readField(ActivityThreadCompat.instance(), "mActivities", true); if (mActivities != null && mActivities.size() == 1) { return; } } catch (Exception e) { //ignore } overridePendingTransition(args, activityCloseEnterAnimation, activityCloseExitAnimation); super.afterInvoke(receiver, method, args, invokeResult); } } private class overridePendingTransition extends HookedMethodHandler { public overridePendingTransition(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { for (int i = 0; i < args.length; i++) { Object arg = args[i]; if ((arg.getClass() == int.class || arg.getClass() == Integer.class)) { int anim = RCompat.getHostIdentifier(context, (int) arg); args[i] = anim; } } return super.beforeInvoke(receiver, method, args); } } }