如果您还没有阅读第一部分的内容,这篇文章不需往下读,在阅读第一部分后才能继续下面的内容:Hook动态代理
基于上面的一篇博客,我们学习了代理的概念,以及如何寻找Hook点。本篇博客将继续拓展前文,不过这次内容要深入很多,这些都是继续学习插件化的基础,为了避免长篇的介绍代理这些枯燥的概念,我特意把它分开来讲,难度一次提升,希望读者能够耐心阅读。
之前我们解释代理设计模式的时候,用的是小明打官司的例子,通过这个例子我们发现,如果一个对象需要代理,我们就需要定义很多个代理类,这对于大型项目的话,显然有点不太现实(程序员也是人啊),所以,这里又萌生动态代理的概念。
这种机制可以理解为jvm运行时动态为我们生成一个代理类,我们只需简单的实现InvocationHandler接口即可
我们看下demo:
//定义我们自己的接口
interface MyInterface {
void foo();
}
interface MyInterface2 {
}
//实现类
static class MyObject implements MyInterface , MyInterface2{
@Override
public void foo() {
System.out.print("proxy foo");
}
}
//动态代理
static class MyInvocation implements InvocationHandler {
public MyInvocation() {
}
Object m_object;
public MyInvocation(Object object) {
m_object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//当调用foo函数之前我们打印一段haha字符串
if("foo".equals(method.getName())) {
System.out.println("haha");
}
return method.invoke(m_object, args);
}
}
public static void main(String[] args) {
MyObject myObject = new MyObject();
System.out.println(myObject);
//第一个参数是指定类加载器,第二个参数指定返回的代理对象要实现的接口
//第三个参数用于处理分发的函数调用,我们可以如本例一样,在执行foo函数之前打印haha字符串
MyInterface myInterface = (MyInterface) Proxy.newProxyInstance(myObject.getClass().getClassLoader(),
new Class[]{MyInterface.class, MyInterface2.class}, new MyInvocation(myObject));
myInterface.foo();
}
还是很好理解的,所以我们可以开始本文主题了。
我们使用插件的目的,就是希望在无需重新编译app的情况下,升级添加某些功能,但是,如果我们使用插件来达到我们的目的,我们并不能正常使用一些系统服务,比如:粘贴板,网络管理器。这是不能忍的,既然我们使用插件,就应该和平时coding一样,没有任何差异——我们能够调用任何我们能够获取的系统服务。所以,这一节我们就要解决hook系统服务的问题,保证我们插件能够正常使用。
回想一下,如果我们平时需要调用系统服务,我们如何实现呢?
like this:
ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
我们本节就是打算hook粘贴板服务,试着让粘贴板里面一直有我们自定义的内容
在开始之前,我们还是和第一节一样,切进去看下,一步步的寻找hook点,还记得我们第一节寻找hook点的原则吗——静态或单例对象
我们切换到activity的getSystemService中:
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
这里调用了Activity的函数,显然,我们如果使用粘贴板服务的话,还需调用父类的getSystemService方法。切换到Activity的父类——ContextThemeWrapper去看
显然我们又要到getBaseContext这个函数返回的对象去看。
不过可惜的是,它返回的只是一个base对象,从ContextThemeWrapper这个类名就不难看出,它只是一个包装器,真正实现功能的肯定在另一个类中:
它的CTOR:
从我截的图可以看出,初始化或者赋值mBase的话,只有通过构造函数或者attachBaseContext方法,不过,从第一节我们介绍ActivityThread的时候就提出了,activity只会在activity thread这个类中被实例化,所以,我们如果要找mBase被修改的位置,肯定要找到那个类(ActivityThread)并从中找线索
切换到ActivityThread.java
在这个类中有这样一个函数:private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent)
他是用来转载activity的,显然,这里是最有可能找到Activity产生实例的地方。
哦?这里有个newActivity哦,切进去看下
确实,这里是实例化Activity的,不过它并没有调用有参数的构造函数,所以它的父类ContextThemeWrapper也就不会凭空赋一个值
那么线索就只剩下attachBaseContext了,检索下,我们又找到了这里:
在Activity的attach这个函数中,调用了attachBaseContext,而这个显然在ActivityThread这个类中出现过,我们再次切换到ActivityThread:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
...
return activity;
}
createBaseContextForActivity的返回值,被作为参数传入到了Activity的attach。
我们在切入到createBaseContextForActivity中去查看:
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
int displayId = Display.DEFAULT_DISPLAY;
try {
displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
} catch (RemoteException e) {
}
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getDisplayAdjustments(id));
baseContext = appContext.createDisplayContext(display);
break;
}
}
}
return baseContext;
}
函数生成了一个ContextImpl对象,并且赋给baseContext,然后返回,哈哈,终于找到了Context这个接口真实的实现类!ContextImpl。
我们先总结一下刚刚到底发生了什么:
我们从Activity的getSystemService一直跟踪到了它的父类ContextThemeWrapper,想再往下找的时候,发现ContextThemeWrapper只是一个包装器,真实的实现不在这里,所以我们又要找到真实的实现位置。一路跟踪,最后找到了ContextImpl
我们现在切换到ContextImpl中:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
看来又要看看SystemServiceRegistry了,不过还好,在SystemServiceRegistry里面我们找到了想要的内容:
哇哦,这里都是调用了ServiceManager的Binder,然后 I*Manager.*.asInterface作为接口返回,最后再生成具体的java平台类!
我们看下ServiceManager.java:
public static IBinder getService(String name) {
50 try {
51 IBinder service = sCache.get(name);//静态变量哦!
52 if (service != null) {
53 return service;
54 } else {
55 return getIServiceManager().getService(name);
56 }
57 } catch (RemoteException e) {
58 Log.e(TAG, "error in getService", e);
59 }
60 return null;
61 }
哇塞,看到没sCache,从命名就可以看出它是一个静态变量,回忆之前我们寻找hook点时候指出,最好的hook点就是静态或者单例对象,毕竟他们只有一个,且相对不会变化。那么思路来了,如果我们能够生成一自定义的ibinder对象,然后存放到sCache中,不久能够Hook系统服务了吗?简直完美啊!
所以我们还得看下本地服务代理的代码:
101行获得了远程服务的binder,之后,通过IClipboard.Stub.asInterface函数,我们获得了远程服务的接口。之后就能像调用本地代码一样调用远程服务了。
我们再到102行函数里查看:
26 public static android.content.IClipboard asInterface(android.os.IBinder obj)
27 {
28 if ((obj==null)) {
29 return null;
30 }
//调用IBinder的queryLocalInterface函数,获得服务操作的接口
31 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//如果不为空 那么就返回
32 if (((iin!=null)&&(iin instanceof android.content.IClipboard))) {
33 return ((android.content.IClipboard)iin);
34 }
//否则创建一个服务代理对象
35 return new android.content.IClipboard.Stub.Proxy(obj);
36 }
显然我们一定不能让31行的iin为空,因为为空它会返回系统自己定义的服务代理对象。那个对象的行为我们无法修改。我们只能是的31永远不为空,而返回的对象正好可以是我们定制过的对象!
思路来了,首先我们hook ServiceManger中的sCache所关联ClipboardService的IBinder对象,使之在调用queryLocalInterface的时候返回一个永远不为空的android.os.IInterface对象,我们修改那个返回对象的行为就达到hook ClipboardService的目的!
代码:
public class HookApplication extends Application {
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
//获得ServiceManger的class
Class<?> serviceManager = Class.forName("android.os.ServiceManager", false, getClassLoader());
//找到getService静态方法
Method getService = serviceManager.getDeclaredMethod("getService", String.class);
//获得原始的binder对象
IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);
//定制我们自己的IBinder对象
ProxyBinder proxyBinder = new ProxyBinder(rawBinder, this);
IBinder proxy = (IBinder) Proxy.newProxyInstance(getClassLoader(), new Class[]{IBinder.class},
proxyBinder);
//注入到ServiceManager的sCache中
Field field = serviceManager.getDeclaredField("sCache");
field.setAccessible(true);
Map<String, IBinder> map = (Map<String, IBinder>) field.get(null);
map.put(CLIPBOARD_SERVICE, proxy);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException
| InvocationTargetException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
ProxyBinder的源码:
/** * Created by chan on 16/4/10. */
public class ProxyBinder implements InvocationHandler {
private Class<?> m_iClipboardInterfaceClz;
private Object m_iClipboardInterface;
private Object m_base;
private Context m_context;
public ProxyBinder(Object base, Context context)
throws ClassNotFoundException, NoSuchMethodException,
InvocationTargetException,
IllegalAccessException {
m_base = base;
m_context = context;
//获得IClipboard的class
m_iClipboardInterfaceClz = Class.forName("android.content.IClipboard",
false, context.getClassLoader());
//获得服务的Stub 这是aidl内容 需读者熟悉aidl
Class<?> clipboardServiceStub = Class.forName("android.content.IClipboard$Stub",
false, context.getClassLoader());
//获取stub的asInterface静态方法
Method asInterface = clipboardServiceStub.getDeclaredMethod("asInterface", IBinder.class);
//获得android.content.IClipboard实例 这是系统默认的实例
m_iClipboardInterface = asInterface.invoke(null, base);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//修改queryLocalInterface的行为
//我们动态的修改为我们自定义的IBinder对象
if ("queryLocalInterface".equals(method.getName())) {
return Proxy.newProxyInstance(m_context.getClassLoader(),
new Class[]{IBinder.class, m_iClipboardInterfaceClz, IInterface.class},
new BinderHook(m_iClipboardInterface));
}
//其他的操作都是调用跟原来一样的操作
return method.invoke(m_base, args);
}
}
BinderHook.java:
/** * Created by chan on 16/4/10. */
public class BinderHook implements InvocationHandler {
private Object m_base;
public BinderHook(Object iClipboardInterface) {
m_base = iClipboardInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//当调用getPrimaryClip的时候 我们始终返回我们自己定义的一段字符串
if ("getPrimaryClip".equals(method.getName())) {
return ClipData.newPlainText(null, "I am crazy");
}
//使得客户端一直默认粘贴板中有东西
if ("getPrimaryClip".equals(method.getName())) {
return true;
}
//其它的操作都还是一样调用远程的服务接口
return method.invoke(m_base, args);
}
}
demo代码:
findViewById(R.id.id_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ClipData.Item item = m_clipboardManager.getPrimaryClip().getItemAt(0);
Toast.makeText(MainActivity.this, item.getText(), Toast.LENGTH_SHORT).show();
}
});