Android可以通过插件来hook系统api,这个功能既能用在安全方面,也能用在其他方面,下面是通过研究360开源的插件源码来分析的。
一、 声明Application:
<application android:name="com.morgoo.droidplugin.PluginApplication"android:label="@string/app_name"
android:icon="@drawable/ic_launcher"android:theme="@style/AppTheme">
<activity
android:name="MyActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:configChanges="orientation"
>
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
因为插件肯定要在app启动前初始化的,所以要放在Application里面
二、 Application里初始化插件
@Override
public void onCreate() {
super.onCreate();
PluginHelper.getInstance().applicationOnCreate(getBaseContext());
}
@Override
protected void attachBaseContext(Context base) {
PluginHelper.getInstance().applicationAttachBaseContext(base);
super.attachBaseContext(base);
}
上面重点就是在PluginHelper.getInstance().applicationAttachBaseContext(base);这个作用就是开始hook系统api,不过这边主要是分析一下如何hook系统api,其他方面可以自己看一下源码。
三、 hook分析
下面以IActivityManagerHook为例分析:
@Override
public void onInstall(ClassLoaderclassLoader) throws Throwable {
Class cls =ActivityManagerNativeCompat.Class();
Object obj =FieldUtils.readStaticField(cls, "gDefault");
if (obj == null) {
ActivityManagerNativeCompat.getDefault();
obj =FieldUtils.readStaticField(cls, "gDefault");
}
if(IActivityManagerCompat.isIActivityManager(obj)) {
setOldObj(obj);
Class<?> objClass =mOldObj.getClass();
List<Class<?>>interfaces = Utils.getAllInterfaces(objClass);
Class[] ifs = interfaces != null&& interfaces.size() > 0 ? interfaces.toArray(newClass[interfaces.size()]) : new Class[0];
Object proxiedActivityManager =MyProxy.newProxyInstance(objClass.getClassLoader(), ifs, this);
FieldUtils.writeStaticField(cls,"gDefault", proxiedActivityManager);
Log.i(TAG, "InstallActivityManager Hook 1 old=%s,new=%s", mOldObj, proxiedActivityManager);
} else if(SingletonCompat.isSingleton(obj)) {
Object obj1 =FieldUtils.readField(obj, "mInstance");
if (obj1 == null) {
SingletonCompat.get(obj);
obj1 = FieldUtils.readField(obj,"mInstance");
}
setOldObj(obj1);
List<Class<?>>interfaces = Utils.getAllInterfaces(mOldObj.getClass());
Class[] ifs = interfaces != null&& interfaces.size() > 0 ? interfaces.toArray(newClass[interfaces.size()]) : new Class[0];
final Object object =MyProxy.newProxyInstance(mOldObj.getClass().getClassLoader(), ifs,IActivityManagerHook.this);
Object iam1 =ActivityManagerNativeCompat.getDefault();
//这里先写一次,防止后面找不到Singleton类导致的挂钩子失败的问题。
FieldUtils.writeField(obj,"mInstance", object);
//这里使用方式1,如果成功的话,会导致上面的写操作被覆盖。
FieldUtils.writeStaticField(cls,"gDefault", new android.util.Singleton<Object>() {
@Override
protected Object create() {
Log.e(TAG, "InstallActivityManager 3 Hook old=%s,new=%s", mOldObj, object);
return object;
}
});
Log.i(TAG, "InstallActivityManager Hook 2 old=%s,new=%s", mOldObj.toString(), object);
Object iam2 =ActivityManagerNativeCompat.getDefault();
// 方式2
if (iam1 == iam2) {
//这段代码是废的,没啥用,写这里只是不想改而已。
FieldUtils.writeField(obj,"mInstance", object);
}
} else {
throw newAndroidRuntimeException("Can not install IActivityManagerNativehook");
}
}
1、 首先Object obj = FieldUtils.readStaticField(cls, "gDefault");获取gDefault变量,hook通常都是找静态变量或者单例,而ActivityManagerNative就刚好提供了这么一个变量,如下:
private static final Singleton<IActivityManager> gDefault = newSingleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b =ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager","default service binder = " + b);
}
IActivityManager am =asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
}
上面就是7.0系统中ActivityManagerNative类中声明的变量,所以有了这个静态变量就能hook,只要我们将这个变量替换成我们自己的类,然后在类里判断调用的方法是否是需要hook的,如果是就调用自己的方法,否则就通过反射调用系统的,这样就达成了hook的目的。
2、如何读取静态变量呢?上面我们看到调用的是FieldUtils.readStaticField,下面来看一下它的源码:
public static Object readStaticField(finalClass<?> cls, final String fieldName) throws IllegalAccessException {
final Field field = getField(cls, fieldName, true);
Validate.isTrue(field != null, "Cannot locate field '%s' on%s", fieldName, cls);
// already forced access above, don't repeat it here:
return readStaticField(field, true);
}
private static Field getField(Class<?> cls, String fieldName, final boolean forceAccess){
Validate.isTrue(cls != null, "Theclass must not be null");
Validate.isTrue(!TextUtils.isEmpty(fieldName), "The field name mustnot be blank/empty");
String key = getKey(cls, fieldName);
Field cachedField;
synchronized (sFieldCache) {
cachedField = sFieldCache.get(key);
}
if (cachedField != null) {
if (forceAccess &&!cachedField.isAccessible()) {
cachedField.setAccessible(true);
}
return cachedField;
}
// check up the superclass hierarchy
for (Class<?> acls = cls; acls !=null; acls = acls.getSuperclass()) {
try {
final Field field =acls.getDeclaredField(fieldName);
// getDeclaredField checks fornon-public scopes as well
// and it returns accurateresults
if(!Modifier.isPublic(field.getModifiers())) {
if (forceAccess) {
field.setAccessible(true);
} else {
continue;
}
}
synchronized (sFieldCache) {
sFieldCache.put(key,field);
}
return field;
} catch (final NoSuchFieldExceptionex) { // NOPMD
// ignore
}
}
// check the public interface case.This must be manually searched for
// incase there is a publicsupersuperclass field hidden by a private/package
// superclass field.
Field match = null;
for (final Class<?> class1 :Utils.getAllInterfaces(cls)) {
try {
final Field test =class1.getField(fieldName);
Validate.isTrue(match == null,"Reference to field %s is ambiguous relative to %s"
+ "; a matchingfield exists on two or more implemented interfaces.", fieldName, cls);
match = test;
} catch (final NoSuchFieldExceptionex) { // NOPMD
// ignore
}
}
synchronized (sFieldCache) {
sFieldCache.put(key, match);
}
return match;
}
上面函数就是寻找这个属性用的,它首先判断缓存中是否已经有这个field了,没有的话接着从本类开始到它的所有基类一个个判断是否有这个属性,有的话直接返回,没有的话再次判断这个类所实现的类是否有这个属性,没有的话说明这个属性是不存在的,返回null,否则返回这个属性
3、 拿到变量后开始下一步处理,就是替换了,首先我们看到了
if (IActivityManagerCompat.isIActivityManager(obj)) 这个判断,这个是因为不同android版本造成的原因,低版本的android系统中gDefault变量就是一个IActivityManager类型,但是高版本android系统中gDefault变量又变成了Singleton类型,而且这个类型有的系统是不开放的,也就是说你可能获取不到这个类型,那应该怎么办呢,我们可以看看源码:
Objectobj1 = FieldUtils.readField(obj, "mInstance");
if (obj1 == null) {
SingletonCompat.get(obj);
obj1 = FieldUtils.readField(obj,"mInstance");
}
看到了吧,是去获取一个mInstance变量,这个变量其实就是Singleton类的一个单例属性,也就是说我们虽然获取不到Singleton类,但是我们可以通过调用getDefault()函数来获取到这个gDefault变量,它就是Singleton类的一个对象,然后我们可以改变这个对象的mInstance属性,这样的话也能达到替换gDefault的目的
4、 拿到属性后如何替换呢,我们看到一行代码
FieldUtils.writeStaticField(cls, "gDefault",proxiedActivityManager);接着来看看这个函数:
public static void writeStaticField(final Class<?> cls, final String fieldName,final Object value) throws IllegalAccessException {
final Field field = getField(cls,fieldName, true);
Validate.isTrue(field != null,"Cannot locate field %s on %s", fieldName, cls);
// already forced access above, don'trepeat it here:
writeStaticField(field, value, true);
}
public static void writeStaticField(final Field field, final Object value, finalboolean forceAccess) throws IllegalAccessException {
Validate.isTrue(field != null,"The field must not be null");
Validate.isTrue(Modifier.isStatic(field.getModifiers()),"The field %s.%s is not static", field.getDeclaringClass().getName(),
field.getName());
writeField(field, (Object) null, value,forceAccess);
}
public static void writeField(final Field field, final Object target, final Objectvalue, final boolean forceAccess)
throws IllegalAccessException {
Validate.isTrue(field != null,"The field must not be null");
if (forceAccess &&!field.isAccessible()) {
field.setAccessible(true);
} else {
MemberUtils.setAccessibleWorkaround(field);
}
field.set(target, value);
}
找到了,就是field.set(target,value);这句话了,不过也要主要执行这句话之前先要field.setAccessible(true);如果是静态属性的话target可以为null,如果不是静态属性那就是改变target对象的属性值