进击的Android注入术《四》
2016-05-18 11:12
288 查看
转:http://blog.csdn.net/l173864930/article/details/38467497
众所周知,Android的应用进程,都是由Zygote孵化的子进程,每个进程都运行在独立的JVM中。通过ptrace的注入方式,我们得到了在目标进程执行代码的机会,但距离修改JVM的内容,还差那么一点点。我们重新看一下《二》中被注入SO的关键代码:
[cpp] view
plain copy
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就无从说起了。
[java] view
plain copy
JNIEnv *jni_env = NULL;
JavaVM *jvm = AndroidRuntime::getJavaVM();
jvm-AttachCurrentThread(&jni_env, NULL);
//TODO 使用JNIEnv
jvm->DetachCurrentThread();
至此,我们就拿到了至关重要的JNIEnv对象了。接下来,我们通过DexClassLoader加载我们的dex文件,关键代码如下所示:
先找到SystemClassLoader
[cpp] view
plain copy
//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对象
[cpp] view
plain copy
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,找到自定义方法的入口,并调用
至此我们的dex逻辑开始执行了。我让com.demo.inject2.EntryClass.invoke作为的入口函数,从invoke里用上《三》示例中的com.demo.inject的代码,对com.demo.host打印的数据再进行修改(同一个进程被连续注入两次,应该是比较痛苦的)。下面看看inject2中invoke的代码:
代码跟《三》的示例非常相似,只是入口点不一样罢了。注意,这里同样有双亲委派的限制。
./poison /data/local/tmp/libimportdex.so 738
看看示例三的输出
[plain] view
plain copy
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的应用进程,都是由Zygote孵化的子进程,每个进程都运行在独立的JVM中。通过ptrace的注入方式,我们得到了在目标进程执行代码的机会,但距离修改JVM的内容,还差那么一点点。我们重新看一下《二》中被注入SO的关键代码:
[cpp] view
plain copy
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的获取,示例代码如下:[java] view
plain copy
JNIEnv *jni_env = NULL;
JavaVM *jvm = AndroidRuntime::getJavaVM();
jvm-AttachCurrentThread(&jni_env, NULL);
//TODO 使用JNIEnv
jvm->DetachCurrentThread();
至此,我们就拿到了至关重要的JNIEnv对象了。接下来,我们通过DexClassLoader加载我们的dex文件,关键代码如下所示:
先找到SystemClassLoader
[cpp] view
plain copy
//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对象
[cpp] view
plain copy
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
看看示例三的输出
[plain] view
plain copy
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的方法。
相关文章推荐
- 通过settings.db自定义Android系统默认设置:SHOW_IME_WITH_HARD_KEYBOARD 默认物理键盘与软键盘同时使用
- Android技术积累:开发规范
- Android Gradle配置Debug和release参数的方法
- Android-Volley详解
- Android Studio Error:Execution failed for task
- 进击的Android注入术《三》
- Android中MediaButtonReceiver广播监听器的机制分析
- 跳转Acitity时,同时finish掉多个Activity
- 进击的Android注入术《二》
- android 流行框架
- 如何在Android Studio中添加RecyclerView-v7支持包
- meterial design 5.0 新增控件介绍及使用方法
- Android 界面切换动画控制overridePendingTransition
- 五步搞定Android开发环境部署——非常详细的Android开发环境搭建教程
- 解决常见布局Viewpager+Fragment多页面切换销毁问题
- Android Studio 官方最新版下载地址(支持国内下载)
- 当打开studio没有android项目时
- Android Studio 1.2版安装设置图文教程
- Android ViewGroup与View里的onMeasure解析
- Android_RecyclerView&SwipeRefreshLayout_swiperefreshlayout不显示_解决