进击的Android Hook 注入术《四》

继续

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

注入之后

完成了注入,那只是万里长征的第一步。
众所周知,Android的应用进程,都是由Zygote孵化的子进程,每个进程都运行在独立的JVM中。通过ptrace的注入方式,我们得到了在目标进程执行代码的机会,但距离修改JVM的内容,还差那么一点点。我们重新看一下《二》中被注入SO的关键代码:
  1. void Main();    
  2.     
  3. static void* _main(void*){    
  4.     Main();    
  5.     return NULL;    
  6. }    
  7.     
  8. class EntryClass {    
  9. public:    
  10.     
  11.     EntryClass() {    
  12.         pthread_t tid;    
  13.         pthread_create(&tid, NULL, _main, NULL);    
  14.         pthread_detach(tid);    
  15.     }    
  16.     
  17. } 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的获取,示例代码如下:
  1. JNIEnv *jni_env = NULL;  
  2. JavaVM *jvm = AndroidRuntime::getJavaVM();  
  3. jvm-AttachCurrentThread(&jni_env, NULL);  
  4. //TODO 使用JNIEnv  
  5.   
  6. jvm->DetachCurrentThread();  
至此,我们就拿到了至关重要的JNIEnv对象了。接下来,我们通过DexClassLoader加载我们的dex文件,关键代码如下所示:
先找到SystemClassLoader
  1. //ClassLoader.getSystemClassLoader()  
  2. static jobject getSystemClassLoader(){  
  3.  jclass class_loader_claxx = jni_env->FindClass("java/lang/ClassLoader");  
  4.  snprintf(sig_buffer, 512, "()%s", JCLASS_LOADER);  
  5.  jmethodID getSystemClassLoader_method = jni_env->GetStaticMethodID(class_loader_claxx, "getSystemClassLoader", sig_buffer);  
  6.  return jni_env->CallStaticObjectMethod(class_loader_claxx, getSystemClassLoader_method);  
  7. }  
然后通过SystemClassLoader,生成DexClassLoader对象
  1. snprintf(sig_buffer, 512, "(%s%s%s%s)V", JSTRING, JSTRING, JSTRING, JCLASS_LOADER);  
  2. jmethodID dexloader_init_method = jni_env->GetMethodID(dexloader_claxx, "<init>", sig_buffer);  
  3.   
  4. snprintf(sig_buffer, 512, "(%s)%s", JSTRING, JCLASS);  
  5. jmethodID loadClass_method = jni_env->GetMethodID(dexloader_claxx, "loadClass", sig_buffer);  
  6.   
  7. jobject class_loader = getSystemClassLoader();  
  8. check_value(class_loader);  
  9.   
  10. jobject dex_loader_obj = jni_env->NewObject(dexloader_claxx, dexloader_init_method, apk_path, dex_out_path, NULL, class_loader);  
最后再通过dex_loader_obj加载dex,找到自定义方法的入口,并调用
  1. jstring class_name = jni_env->NewStringUTF("com.demo.inject2.EntryClass");  
  2. jclass entry_class = static_cast<jclass>(jni_env->CallObjectMethod(dex_loader_obj, loadClass_method, class_name));  
  3.   
  4. jmethodID invoke_method = jni_env->GetStaticMethodID(entry_class, "invoke""(I)[Ljava/lang/Object;");  
  5. check_value(invoke_method);  
  6.   
  7. 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的代码:
  1. package com.demo.inject2;  
  2.   
  3. import java.lang.reflect.Method;  
  4.   
  5. import android.content.Context;  
  6. import android.util.Log;  
  7.   
  8. /** 
  9.  *  
  10.  * @author boyliang 
  11.  *  
  12.  */  
  13. public final class EntryClass {  
  14.   
  15.     public static Object[] invoke(int i) {  
  16.   
  17.         try {  
  18.             Log.i("TTT"">>>>>>>>>>>>>I am in, I am a bad boy 2!!!!<<<<<<<<<<<<<<");  
  19.             Context context = ContexHunter.getContext();  
  20.             Class<?> MainActivity_class = context.getClassLoader().loadClass("com.demo.host.MainActivity");  
  21.             Method setA_method = MainActivity_class.getDeclaredMethod("setA"int.class);  
  22.             setA_method.invoke(null1);  
  23.         } catch (Exception e) {  
  24.             e.printStackTrace();  
  25.         }  
  26.   
  27.         return null;  
  28.     }  
  29. }  
代码跟《三》的示例非常相似,只是入口点不一样罢了。注意,这里同样有双亲委派的限制。

输出

am start com.demo.host/.MainActivity
./poison /data/local/tmp/libimportdex.so 738

看看示例三的输出
  1. com.demo.inject starts.  
  2. I/TTT     (  738): com.demo.host starts  
  3. I/TTT     (  738): 1  
  4. I/TTT     (  738): 2  
  5. I/TTT     (  738): 3  
  6. I/TTT     (  738): 4  
  7. I/TTT     (  738): 5  
  8. I/TTT     (  738): >>>>>>>>>>>>>I am in, I am a bad boy!!!!<<<<<<<<<<<<<<  
  9. I/TTT     (  738): 998  
  10. I/TTT     (  738): 999  
  11. I/TTT     (  738): 1000  
  12. I/TTT     (  738): 1001  
  13. I/TTT     (  738): 1002  
  14. I/TTT     (  738): 1003  
  15. I/TTT     (  738): >>>>>>>>>>>>>I am in, I am a bad boy 2!!!!<<<<<<<<<<<<<<  
  16. I/TTT     (  738): 1  
  17. I/TTT     (  738): 2  
  18. I/TTT     (  738): 3  
  19. I/TTT     (  738): 4  
  20. I/TTT     (  738): 5  
  21. I/TTT     (  738): 6  
  22. I/TTT     (  738): 7  
从两次的字符串输出,证明这次的注入修改已经成功了。
示例中的所有代码,都已经上传到https://github.com/boyliang/Java_Injection

最后

到目前为止,我们已经实现如下功能:
  • 注入目标进程
  • 获取JNIEnv地址;
  • 另目标进程加载Dex,并执行指定的方法;

距离我们的目标,还差一步——截获broadcastIntent方法,在《五》里我会再介绍一种叫BinderProxy的技术,通过这种技术,我们可以截获任意的BinderService的方法。



原文地址: http://blog.csdn.net/l173864930/article/details/38467497

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值