继续
在前 《一》、 《二》、 《三》里已经把注入的技术介绍完了,这章开始说注入之后需要做的事情。如果对注入技术已经比较熟悉了,那么可以直接看本章,否则建议先把前三章阅读一遍会比较好。

注入之后
完成了注入,那只是万里长征的第一步。
众所周知,Android的应用进程,都是由Zygote孵化的子进程,每个进程都运行在独立的JVM中。通过ptrace的注入方式,我们得到了在目标进程执行代码的机会,但距离修改JVM的内容,还差那么一点点。我们重新看一下《二》中被注入SO的关键代码:

void Main();  
   
 static void* _main(void*){  
     Main();  
     return NULL;  
 }  
   
 class EntryClass {  
 public:  
   
     EntryClass() {  
         pthread_t tid;  
         pthread_create(&tid, NULL, _main, NULL);  
         pthread_detach(tid);  
     }  
   
 } boy;  
 当so被注入后,我们的逻辑代码实际上是跑在一个Linux线程上,这样做的目的是为了不对主线程造成干扰。我们的目标是打通Java层,很自然的联想到JNI,通过JNI我们就是可以跟Java层互动了。但这里缺少了一个非常重要的元素——JNIEnv,没有这个对象,JNI就无从说起了。示例三
 我们知道,在JVM进程中,JavaVM是全局唯一的,而JNIEnv则是按线程分配。另外,Dalvik的线程跟Linux线程是一一对应的,因此我们可以把自身所在的线程Attatch到JavaVM,JavaVM就会为我们分配JNIEnv对象了。通过阅读Dalvik源码,从AndroidRuntime中我们可以得到JavaVm的地址,再通过JavaVm所提供的AttachCurrentThead和DetachCurrentThread两个函数,即可完成JNIEnv的获取,示例代码如下:
 JNIEnv *jni_env = NULL;
 JavaVM *jvm = AndroidRuntime::getJavaVM();
 jvm-AttachCurrentThread(&jni_env, NULL);
 //TODO 使用JNIEnv
  
 jvm->DetachCurrentThread();
 至此,我们就拿到了至关重要的JNIEnv对象了。接下来,我们通过DexClassLoader加载我们的dex文件,关键代码如下所示:
 先找到SystemClassLoader
 //ClassLoader.getSystemClassLoader()
 static jobject getSystemClassLoader(){
  jclass class_loader_claxx = jni_env->FindClass("java/lang/ClassLoader");
  snprintf(sig_buffer, 512, "()%s", JCLASS_LOADER);
  jmethodID getSystemClassLoader_method = jni_env->GetStaticMethodID(class_loader_claxx, "getSystemClassLoader", sig_buffer);
  return jni_env->CallStaticObjectMethod(class_loader_claxx, getSystemClassLoader_method);
 }
 然后通过SystemClassLoader,生成DexClassLoader对象
 snprintf(sig_buffer, 512, "(%s%s%s%s)V", JSTRING, JSTRING, JSTRING, JCLASS_LOADER);
 jmethodID dexloader_init_method = jni_env->GetMethodID(dexloader_claxx, "<init>", sig_buffer);
  
 snprintf(sig_buffer, 512, "(%s)%s", JSTRING, JCLASS);
 jmethodID loadClass_method = jni_env->GetMethodID(dexloader_claxx, "loadClass", sig_buffer);
  
 jobject class_loader = getSystemClassLoader();
 check_value(class_loader);
  
 jobject dex_loader_obj = jni_env->NewObject(dexloader_claxx, dexloader_init_method, apk_path, dex_out_path, NULL, class_loader);
 最后再通过dex_loader_obj加载dex,找到自定义方法的入口,并调用
 jstring class_name = jni_env->NewStringUTF("com.demo.inject2.EntryClass");
 jclass entry_class = static_cast<jclass>(jni_env->CallObjectMethod(dex_loader_obj, loadClass_method, class_name));
  
 jmethodID invoke_method = jni_env->GetStaticMethodID(entry_class, "invoke", "(I)[Ljava/lang/Object;");
 check_value(invoke_method);
  
 jobjectArray objectarray = (jobjectArray) jni_env->CallStaticObjectMethod(entry_class, invoke_method, 0);
 至此我们的dex逻辑开始执行了。我让com.demo.inject2.EntryClass.invoke作为的入口函数,从invoke里用上《三》示例中的com.demo.inject的代码,对com.demo.host打印的数据再进行修改(同一个进程被连续注入两次,应该是比较痛苦的)。下面看看inject2中invoke的代码:
 package com.demo.inject2;
  
 import java.lang.reflect.Method;
  
 import android.content.Context;
 import android.util.Log;
  
 /**
  * 
  * @author boyliang
  * 
  */
 public final class EntryClass {
  
     public static Object[] invoke(int i) {
  
         try {
             Log.i("TTT", ">>>>>>>>>>>>>I am in, I am a bad boy 2!!!!<<<<<<<<<<<<<<");
             Context context = ContexHunter.getContext();
             Class<?> MainActivity_class = context.getClassLoader().loadClass("com.demo.host.MainActivity");
             Method setA_method = MainActivity_class.getDeclaredMethod("setA", int.class);
             setA_method.invoke(null, 1);
         } catch (Exception e) {
             e.printStackTrace();
         }
  
         return null;
     }
 }
 代码跟《三》的示例非常相似,只是入口点不一样罢了。注意,这里同样有双亲委派的限制。输出
 am start com.demo.host/.MainActivity
 ./poison /data/local/tmp/libimportdex.so 738看看示例三的输出
 com.demo.inject starts.
 I/TTT     (  738): com.demo.host starts
 I/TTT     (  738): 1
 I/TTT     (  738): 2
 I/TTT     (  738): 3
 I/TTT     (  738): 4
 I/TTT     (  738): 5
 I/TTT     (  738): >>>>>>>>>>>>>I am in, I am a bad boy!!!!<<<<<<<<<<<<<<
 I/TTT     (  738): 998
 I/TTT     (  738): 999
 I/TTT     (  738): 1000
 I/TTT     (  738): 1001
 I/TTT     (  738): 1002
 I/TTT     (  738): 1003
 I/TTT     (  738): >>>>>>>>>>>>>I am in, I am a bad boy 2!!!!<<<<<<<<<<<<<<
 I/TTT     (  738): 1
 I/TTT     (  738): 2
 I/TTT     (  738): 3
 I/TTT     (  738): 4
 I/TTT     (  738): 5
 I/TTT     (  738): 6
 I/TTT     (  738): 7


从两次的字符串输出,证明这次的注入修改已经成功了。
示例中的所有代码,都已经上传到https://github.com/boyliang/Java_Injection

最后
到目前为止,我们已经实现如下功能:
注入目标进程
获取JNIEnv地址;
另目标进程加载Dex,并执行指定的方法;
距离我们的目标,还差一步——截获broadcastIntent方法,在 《五》里我会再介绍一种叫BinderProxy的技术,通过这种技术,我们可以截获任意的BinderService的方法。

继续
在Android,几乎所有的IPC通讯都是通过Binder,可以说Binder在Android中占据着非常重要的地位。IPC通讯一般涉及client和server两部分,在Android上,所有Binder的serivce部分统称为NativeService(跟平时所说的Service组件不一样),一个NativeService可以跟多个client通讯,如果想更详细地了解这方面的内容可以到 老罗的博客睢睢。
在日常开发过程中, 我们经常会使用到的ActivityManager、PackageManager就是一个client的调用,只是本身封装得比较好,让你感觉不到。而Service部分的逻辑,主要集中在system_process和com.android.phone这两个进程里头。
broadcastIntent是ActivityManagerService(AMS)的一个方法,AMS的宿主进程是system_process,毫无疑问我们需要先注入到system_process进程,至于接下来怎么做呢,正是本章的内容。

BinderProxy

原理
所有NativeService都继承到IBinder接口,BinderProxy原理很简单,就是先到找到要代理的NativeService引用,再通过自己编写的ProxyBinder对象代理NativeService,从而达到截获IPC通讯的目的。下面我们以AMS为例,做一个说明:

android中注入Java sdk android ptrace代码注入_Android


AMS跟binder进行通讯,是通过JNI实现的。AMS继承Binder(IBinder的子类,封装了IPC通讯公共部分的逻辑),Binder里保存着一个类型为int的mObject的字段,这个字段正是其C++对象JavaBBinder对象的地址,这个JavaBBinder才是AMS最终跟内核通讯的对象。代码如下:

public class Binder implements IBinder {
     //...
  
     /* mObject is used by native code, do not remove or rename */
     private int mObject; //这个对象保存的就是JavaBBinder的指针
     private IInterface mOwner;
     private String mDescriptor;
  
     //...
 }
 同样的,在JavaBBinder中,也保存着一个类型jobject的mObject,指向上层Java对象。看看JavaBBinder的代码:
 class JavaBBinder : public BBinder
 {
  
 //...
  
     jobject object() const
     {
         return mObject;
     }
  
     //...
  
     private:
         JavaVM* const   mVM;
         jobject const   mObject; //这个保存的是AMS的引用
     };
 }

Java和C++就是通过这两个字段相互连结在一起的。

其中JavaBBinder中的mObject是整个IPC关键的一节,所有的client请求,都是先到达JavaBBinder,然后JavaBBinder再通过JNI调用mObject的execTransact的方法,最终把请求发送到AMS。

因此,我们只要想办法找到AMS的对象的JavaBBinder,再把mObject替换为代理对象(记作ProxyBinder,一个Java对象的引用),即可实现BinderService代理,下面是示意图:

 

在实现这个代理,我们需要获取AMS和及对应用的JavaBBinder两个对象。

android中注入Java sdk android ptrace代码注入_Java_02


获取AMS引用

要获取AMS引用,通过ServiceManager即可,不过这类是隐藏类,通过反射才可以调用。通过ServiceManager.getService("activity")即可以拿到AMS。

 

获取JavaBBinder
通过前面的介绍,拿到AMS之后,就可以获取其mObject字段,这个对象正好就是JavaBBinder的地址。另外,也有一种比较简单的方式,那就是通过defaultServiceManager的getService方法获取到。

替换mObject对象

JavaBBinder的mObject对象并不能直接替换,因为mObject是const的,我写了一个DummyJavaBBinder的类,可以很容易地处理好这个问题,DummyJavaBBinder的实现如下:
 class DummyJavaBBinder : public BBinder{
 public:
     virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) {
         return NO_ERROR;
     }
  
     jobject object() const {
         return mObject;
     }
  
     JavaVM* javaVM() const {
         return mVM;
     }
  
     void changeObj(jobject newobj){
         const jobject* p_old_obj = &mObject;
         jobject* p_old_obj_noconst = const_cast<jobject *>(p_old_obj);
         *p_old_obj_noconst = newobj;
     }
  
 private:
     JavaVM* const   mVM;
     jobject const   mObject;
 };


这个类的作用主要添加了changeObj方法,主要功能是把mObject去掉const限制,并修改为的newobj。

示例四
示例四包含三部分代码,分别是com.demo.sms,com.demo.smstrojan,以及DemonInject3。

com.demo.sms和com.demo.smstrojan的逻辑是一样的,都是拦截短信,并打印短信内容,代码片断如下:
 public final class SmsReceiver extends BroadcastReceiver {
  
     @Override
     public void onReceive(Context context, Intent intent) {
         
         Bundle bundle = intent.getExtras();
  
         if (bundle != null) {
             this.abortBroadcast();
             
             Object[] pdus = (Object[]) bundle.get("pdus");
             SmsMessage[] messages = new SmsMessage[pdus.length];
  
             for (int i = 0; i < pdus.length; i++) {
                 messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
             }
  
             for (SmsMessage message : messages) {
                 String msg = message.getMessageBody();
                 String to = message.getOriginatingAddress();
                 
                 Log.i("TTT", context.getPackageName() + " To:" + to + " Msg:" + msg);
             }
         }
         
     }
  
 }
 DemoInject3相对复杂,包含dex和proxybinder(被注入的so)两部分。dex的逻辑是生成代理的proxybinder,并通过invoke返回给lib,lib再通过DummyJavaBBinder修改其mObject为proxybinder,关键代码如下dex代码
 package com.demo.inject3;
  
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.Log;
  
 /**
  * 
  * @author boyliang
  * 
  */
 public final class EntryClass {
  
     private static final class ProxyActivityManagerServcie extends Binder {
         private static final String CLASS_NAME = "android.app.IActivityManager";
         private static final String DESCRIPTOR = "android.app.IActivityManager";
         private static final int s_broadcastIntent_code;
  
         private SmsReceiverResorter mResorter;
  
         static {
             if (ReflecterHelper.setClass(CLASS_NAME)) {
                 s_broadcastIntent_code = ReflecterHelper.getStaticIntValue("BROADCAST_INTENT_TRANSACTION", -1);
             } else {
                 s_broadcastIntent_code = -1;
             }
         }
  
         private IBinder mBinder;
  
         public ProxyActivityManagerServcie(IBinder binder) {
             mBinder = binder;
             mResorter = new SmsReceiverResorter(binder);
         }
  
         @Override
         protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
  
             if (code == s_broadcastIntent_code) {
                 mResorter.updatePriority("com.demo.sms");
             }
  
             return mBinder.transact(code, data, reply, flags);
         }
     }
  
     public static Object[] invoke(int i) {
         IBinder activity_proxy = null;
  
         try {
             activity_proxy = new ProxyActivityManagerServcie(ServiceManager.getService("activity"));
  
             Log.i("TTT", ">>>>>>>>>>>>>I am in, I am a bad boy 3!!!!<<<<<<<<<<<<<<");
         } catch (Exception e) {
             e.printStackTrace();
         }
  
         return new Object[] { "activity", activity_proxy };
     }
 }
 看到onTransact中code的过滤处理,当code==s_broadcastIntent_code时,证明有client调用了sendBroadcast方法了,然后马上调用SmsReceiverRestorter中的updatePriority方法。
 最后invoke返回的是一个Object数组,分别是"activity"字符串和activity_proxy对象,再看看proxybinder.cpp的中调用invoke方法的处理:
 <span style="white-space:pre">    </span>jmethodID invoke_method = jni_env->GetStaticMethodID(entry_class, "invoke", "(I)[Ljava/lang/Object;");
     check_value(invoke_method);
  
     jobjectArray objectarray = (jobjectArray) jni_env->CallStaticObjectMethod(entry_class, invoke_method, 0);
     check_value(objectarray);
  
     jsize size = jni_env->GetArrayLength(objectarray);
     sp<IServiceManager> servicemanager = defaultServiceManager();
     for (jsize i = 0; i < size; i += 2) {
         jstring name = static_cast<jstring>(jni_env->GetObjectArrayElement(objectarray, i));
         jobject obj = jni_env->GetObjectArrayElement(objectarray, i + 1);
  
         const char* c_name = jni_env->GetStringUTFChars(name, NULL);
         DummyJavaBBinder* binder = (DummyJavaBBinder*) servicemanager->getService(String16(c_name)).get();
         binder->changObj(jni_env->NewGlobalRef(obj));
     }
 lproxybinder.cpp中根据invoke返回的数组进行处理。
 至此,整个BinderProxy技术的技术已经介绍完毕了,接下来看看SmsReceiverRestorter的代码,这个类主要是负责修改广播的发送顺序。跟广播发送顺序有关的变量位置ActivityManagerService.mReceiverResolver.mActionToFilter,其定义如下为private final HashMap<String, ArrayList<IntentFilter>> mActionToFilter。其中key是action,value是各个broadcast中的intentfilter描述,这个value本身是一个List,其顺序即为广播的发送顺序,调整这个顺序即可,见代码;
 final class SmsReceiverResorter {
     private static final String[] sActions = { "android.provider.Telephony.SMS_RECEIVED", "android.provider.Telephony.SMS_RECEIVED2", "android.provider.Telephony.GSM_SMS_RECEIVED" };
     private final String TAG = "SmsReceiverResorter";
     private HashMap<String, ArrayList<? extends IntentFilter>> mActionToFilter;
     private Field mPackageNameField;
  
     @SuppressWarnings("unchecked")
     public SmsReceiverResorter(IBinder am) {
         Class<?> claxx = am.getClass();
         try {
             Field field = claxx.getDeclaredField("mReceiverResolver");
             field.setAccessible(true);
             Object mReceiverResolver = field.get(am);
  
             claxx = mReceiverResolver.getClass();
             
             field = claxx.getSuperclass().getDeclaredField("mActionToFilter");
             field.setAccessible(true);
  
             mActionToFilter = (HashMap<String, ArrayList<? extends IntentFilter>>) field.get(mReceiverResolver);
             
         } catch (Exception e) {
             Log.e(TAG, e.toString());
         }
     }
  
     /**
      * 修改优先级
      */
     public void updatePriority(String target_pkg) {
         
         if (mActionToFilter != null) {
             
             for (String action : sActions) {
                 
                 @SuppressWarnings("unchecked")
                 ArrayList<IntentFilter> filters = (ArrayList<IntentFilter>) mActionToFilter.get(action);
  
                 if (filters != null) {
                     Log.i("TTT", "send sms broadcast");
                     
                     IntentFilter filter = null;
  
                     for (IntentFilter f : filters) {
                         String pkg = getPackageName(f);
                         if (target_pkg.equals(pkg)) {
                             filter = f;
                             break;
                         } 
                     }
  
                     // 调整顺序
                     if (filter != null && filters.remove(filter) ) {
                         
                         filters.add(0, filter);
                         filter = null;
                         
                         Log.i("TTT", target_pkg + " is the first now");
                     }
                 }
             }
  
         }
     }
  
     private String getPackageName(IntentFilter filter) {
         
         if (mPackageNameField == null && filter != null) {
             Class<?> claxx = filter.getClass();
             try {
                 mPackageNameField = claxx.getDeclaredField("packageName");
                 mPackageNameField.setAccessible(true);
             } catch (Exception e) {
                 Log.e(TAG, e.toString());
             }
         }
         
         String result = null;
  
         if (filter != null) {
             try {
                 result = (String) mPackageNameField.get(filter);
             } catch (Exception e) {
                 Log.e(TAG, e.toString());
             }
         }
         
         return result;
     }
 }

最后
这次的示例代码有点多,我已经上传至https://github.com/boyliang/Hijack_AMS_broadIntent。
通过上面的方法,无论com.demo.sms是怎样落后于sms.demo.smstrojan注册广播,都可以最先拦截到短信。

终于把这个方案讲解完了,累死。。。
谁能坚持看到这里,也算是一种缘分吧。
在下一章里,我会全面介绍AIM这个框架的实现细节,AIM框架对前面所提及的技术点做了一个很好的汇总。