作 者: malokch
链 接: http://bbs.pediy.com/showthread.php?t=186054
说明:
安卓系统的可执行对象有两个世界,一个是Linux Native世界,一个是Java世界.两个世界能够通过jvm产生交互,具体来说就是通过jni技术进行互相干涉.但是在正常情况下,只能在Java世界通过jni调用native方法,二native不能在没有任何java上的支持下干涉java世界.
在一些应用中,我们需要对一个app的java世界进行干涉.再说到linux上的进程注入技术,已不用我多讲,但是传统的linux进程注入技术在安卓上只能进入目标进程的native世界.
于是本教程是要注入别的进程,并hook java世界的java 方法!
文章长,详情见附件
注入安卓进程,并hook java世界的方法
说明:
安卓系统的可执行对象有两个世界,一个是Linux Native世界,一个是Java世界.两个世界能够通过jvm产生交互,具体来说就是通过jni技术进行互相干涉.但是在正常情况下,只能在Java世界通过jni调用native方法,二native不能在没有任何java上的支持下干涉java世界.
在一些应用中,我们需要对一个app的java世界进行干涉.再说到linux上的进程注入技术,已不用我多讲,但是传统的linux进程注入技术在安卓上只能进入目标进程的native世界.
于是本教程是要注入别的进程,并hook java世界的java 方法!
条件:
1) 手机已root
2) 布置好了的ndk环境
3) 网友贡献的inject代码
由于安卓上的进程注入网上已经有很多方案了,这里就不列举了,这里就假设读者已经能够将so注入到别的进程并顺利运行了.
首先贴一下这次的目标
代码:
package com.example.testar;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import dalvik.system.DexClassLoader;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.text.GetChars;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
private final Map<String, ClassLoader> mLoaders = new HashMap<String, ClassLoader>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = (Button) findViewById(R.id.button1);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiInfo info = wifi.getConnectionInfo();
System.out.println("Wifi mac :" + info.getMacAddress());
System.out.println("return " + test());
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private String test() {
return "real";
}
}
我们的目标是上面的test()方法,我们要改变其返回值.
接下来看看我们要注入到目标进程的so.cpp, MethodHooker.cpp
代码:
so.cpp:
#include "jni.h"
#include "android_runtime/AndroidRuntime.h"
#include "android/log.h"
#include "stdio.h"
#include "stdlib.h"
#include "MethodHooker.h"
#include <utils/CallStack.h>
#include "art.h"
#define log(a,b) __android_log_write(ANDROID_LOG_INFO,a,b); // LOG Ѝ:info
#define log_(b) __android_log_write(ANDROID_LOG_INFO,"JNI_LOG_INFO",b); // LOG Ѝ:info
extern "C" void InjectInterface(char*arg){
log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
log_("*-*-*-*-*-* Injected so *-*-*-*-*-*-*-*");
log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
Hook();
log_("*-*-*-*-*-*-*- End -*-*-*-*-*-*-*-*-*-*");
}
extern "C" JNIEXPORT jstring JNICALL Java_com_example_testar_InjectApplication_test(JNIEnv *env, jclass clazz)
{
Abort_();
return env->NewStringUTF("haha ");;
}
MethodHooker.cpp:
#include "MethodHooker.h"
#include "jni.h"
#include "android_runtime/AndroidRuntime.h"
#include "android/log.h"
#include "stdio.h"
#include "stdlib.h"
#include "native.h"
#include <dlfcn.h>
#define ANDROID_SMP 0
#include "Dalvik.h"
#include "alloc/Alloc.h"
#include "art.h"
#define ALOG(...) __android_log_print(ANDROID_LOG_VERBOSE, __VA_ARGS__)
static bool g_bAttatedT;
static JavaVM *g_JavaVM;
void init()
{
g_bAttatedT = false;
g_JavaVM = android::AndroidRuntime::getJavaVM();
}
static JNIEnv *GetEnv()
{
int status;
JNIEnv *envnow = NULL;
status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4);
if(status < 0)
{
status = g_JavaVM->AttachCurrentThread(&envnow, NULL);
if(status < 0)
{
return NULL;
}
g_bAttatedT = true;
}
return envnow;
}
static void DetachCurrent()
{
if(g_bAttatedT)
{
g_JavaVM->DetachCurrentThread();
}
}
static int computeJniArgInfo(const DexProto* proto)
{
const char* sig = dexProtoGetShorty(proto);
int returnType, jniArgInfo;
u4 hints;
/* The first shorty character is the return type. */
switch (*(sig++)) {
case 'V':
returnType = DALVIK_JNI_RETURN_VOID;
break;
case 'F':
returnType = DALVIK_JNI_RETURN_FLOAT;
break;
case 'D':
returnType = DALVIK_JNI_RETURN_DOUBLE;
break;
case 'J':
returnType = DALVIK_JNI_RETURN_S8;
break;
case 'Z':
case 'B':
returnType = DALVIK_JNI_RETURN_S1;
break;
case 'C':
returnType = DALVIK_JNI_RETURN_U2;
break;
case 'S':
returnType = DALVIK_JNI_RETURN_S2;
break;
default:
returnType = DALVIK_JNI_RETURN_S4;
break;
}
jniArgInfo = returnType << DALVIK_JNI_RETURN_SHIFT;
hints = dvmPlatformInvokeHints(proto);
if (hints & DALVIK_JNI_NO_ARG_INFO) {
jniArgInfo |= DALVIK_JNI_NO_ARG_INFO;
} else {
assert((hints & DALVIK_JNI_RETURN_MASK) == 0);
jniArgInfo |= hints;
}
return jniArgInfo;
}
int ClearException(JNIEnv *jenv){
jthrowable exception = jenv->ExceptionOccurred();
if (exception != NULL) {
jenv->ExceptionDescribe();
jenv->ExceptionClear();
return true;
}
return false;
}
bool isArt(){
return true;
}
static jclass findAppClass(JNIEnv *jenv,const char *apn){
//������oaders
jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders");
jthrowable exception = jenv->ExceptionOccurred();
if (ClearException(jenv)) {
ALOG("Exception","No class : %s", "android/app/ApplicationLoaders");
return NULL;
}
jfieldID fieldApplicationLoaders = jenv->GetStaticFieldID(clazzApplicationLoaders,"gApplicationLoaders","Landroid/app/ApplicationLoaders;");
if (ClearException(jenv)) {
ALOG("Exception","No Static Field :%s","gApplicationLoaders");
return NULL;
}
jobject objApplicationLoaders = jenv->GetStaticObjectField(clazzApplicationLoaders,fieldApplicationLoaders);
if (ClearException(jenv)) {
ALOG("Exception","GetStaticObjectField is failed [%s","gApplicationLoaders");
return NULL;
}
jfieldID fieldLoaders = jenv->GetFieldID(clazzApplicationLoaders,"mLoaders","Ljava/util/Map;");
if (ClearException(jenv)) {
ALOG("Exception","No Field :%s","mLoaders");
return NULL;
}
jobject objLoaders = jenv->GetObjectField(objApplicationLoaders,fieldLoaders);
if (ClearException(jenv)) {
ALOG("Exception","No object :%s","mLoaders");
return NULL;
}
//̡ȡmapĶalues
jclass clazzHashMap = jenv->GetObjectClass(objLoaders);
jmethodID methodValues = jenv->GetMethodID(clazzHashMap,"values","()Ljava/util/Collection;");
jobject values = jenv->CallObjectMethod(objLoaders,methodValues);
jclass clazzValues = jenv->GetObjectClass(values);
jmethodID methodToArray = jenv->GetMethodID(clazzValues,"toArray","()[Ljava/lang/Object;");
if (ClearException(jenv)) {
ALOG("Exception","No Method:%s","toArray");
return NULL;
}
jobjectArray classLoaders = (jobjectArray)jenv->CallObjectMethod(values,methodToArray);
if (ClearException(jenv)) {
ALOG("Exception","CallObjectMethod failed :%s","toArray");
return NULL;
}
int size = jenv->GetArrayLength(classLoaders);
for(int i = 0 ; i < size ; i ++){
jobject classLoader = jenv->GetObjectArrayElement(classLoaders,i);
jclass clazzCL = jenv->GetObjectClass(classLoader);
jmethodID loadClass = jenv->GetMethodID(clazzCL,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;");
jstring param = jenv->NewStringUTF(apn);
jclass tClazz = (jclass)jenv->CallObjectMethod(classLoader,loadClass,param);
if (ClearException(jenv)) {
ALOG("Exception","No");
continue;
}
return tClazz;
}
ALOG("Exception","No");
return NULL;
}
bool HookDalvikMethod(jmethodID jmethod){
Method *method = (Method*)jmethod;
//ؼ!!Ŀ귽ОĎnative
SET_METHOD_FLAG(method, ACC_NATIVE);
int argsSize = dvmComputeMethodArgsSize(method);
if (!dvmIsStaticMethod(method))
argsSize++;
method->registersSize = method->insSize = argsSize;
if (dvmIsNativeMethod(method)) {
method->nativeFunc = dvmResolveNativeMethod;
method->jniArgInfo = computeJniArgInfo(&method->prototype);
}
}
bool ClassMethodHook(HookInfo info){
JNIEnv *jenv = GetEnv();
jclass clazzTarget = jenv->FindClass(info.tClazz);
if (ClearException(jenv)) {
ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader",info.tClazz);
clazzTarget = findAppClass(jenv,info.tClazz);
if(clazzTarget == NULL){
ALOG("Exception","%s","Error in findAppClass");
return false;
}
}
jmethodID method = jenv->GetMethodID(clazzTarget,info.tMethod,info.tMeihodSig);
if(method==NULL){
ALOG("Exception","ClassMethodHook[Can't find method:%s",info.tMethod);
return false;
}
if(isArt()){
HookArtMethod(jenv,method);
}else{
HookDalvikMethod(method);
}
JNINativeMethod gMethod[] = {
{info.tMethod, info.tMeihodSig, info.handleFunc},
};
//funcΪNULLʱהА������������չɍ
if(info.handleFunc != NULL){
//ؼ!!Ŀ귽הҥĮative
if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) {
ALOG("RegisterNatives","err");
return false;
}
}
DetachCurrent();
return true;
}
int Hook(){
init();
void* handle = dlopen("/data/local/libTest.so",RTLD_NOW);
const char *dlopen_error = dlerror();
if(!handle){
ALOG("Error","cannt load plugin :%s",dlopen_error);
return -1;
}
SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo");
const char *dlsym_error = dlerror();
if (dlsym_error) {
ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error);
dlclose(handle);
return 1;
}
HookInfo *hookInfo;
setup(&hookInfo);
ALOG("LOG","Target Class:%s",hookInfo[0].tClazz);
ALOG("LOG","Target Method:%s",hookInfo[0].tMethod);
ClassMethodHook(hookInfo[0]);
}
以下是我们想要的目标进程java世界执行的我们自定义的代码
代码:
libTest.so
#include "native.h"
#include <android/log.h>
#include "stdio.h"
#include "stdlib.h"
#include "MethodHooker.h"
#define log(a,b) __android_log_print(ANDROID_LOG_VERBOSE,a,b); // LOG Ѝ:info
#define log_(b) __android_log_print(ANDROID_LOG_VERBOSE,"JNI_LOG_INFO",b); // LOG Ѝ:info
int getpHookInfo(HookInfo** pInfo);
JNIEXPORT void JNICALL Java_com_example_testar_InjectClassloader_hookMethodNative
(JNIEnv * jenv, jobject jboj, jobject jobj, jclass jclazz, jint slot)
{
//log("TestAE","start Inject other process");
}
JNIEXPORT jstring JNICALL test(JNIEnv *env, jclass clazz)
{
//__android_log_print(ANDROID_LOG_VERBOSE, "tag", "call <native_printf> in java");
return (*env)->NewStringUTF(env,"haha ");;
}
HookInfo hookInfos[] = {
{"android/net/wifi/WifiInfo","getMacAddress","()Ljava/lang/String;",(void*)test},
//{"com/example/testar/MainActivity","test","()Ljava/lang/String;",(void*)test},
//{"android/app/ApplicationLoaders","getText","()Ljava/lang/CharSequence;",(void*)test},
};
int getpHookInfo(HookInfo** pInfo){
*pInfo = hookInfos;
return sizeof(hookInfos) / sizeof(hookInfos[0]);
}
程序大致的流程是这样的,首先将so.so注入到目标进程,执行里面的Hook()函数,然后Hook()加载libTest.so,获取里面定义的Hook信息.接着用ClassMethodHook挂钩java世界的方法.
关键一:
从native世界进入java世界.熟悉jni编程的都知道,java到native的桥梁是JNIEnv,我们只要获得一个JNIEnv就能进入到java世界了.突破点就在AndroidRuntime,android::AndroidRuntime::getJavaVM();这个静态方法能够获取一个JavaVM, JavaVM->GetEnv方法能够获得一个JNIEnv了.JNIEnv是和线程相关的,使用前一定记得将其附加到当前进程,也要在适当的时候将其销毁.
关键二:
怎么影响内存里的java代码,这个情况替换内存是不现实的,但是可以取巧.我们知道java代码里将一个方法声明为native方法时,对此函数的调用就会到native世界里找.我们何不在运行时将一个不是native的方法修改成native方法呢?这是可以做到的,看着段代码
代码:
bool HookDalvikMethod(jmethodID jmethod){
Method *method = (Method*)jmethod;
SET_METHOD_FLAG(method, ACC_NATIVE);
int argsSize = dvmComputeMethodArgsSize(method);
if (!dvmIsStaticMethod(method))
argsSize++;
method->registersSize = method->insSize = argsSize;
if (dvmIsNativeMethod(method)) {
method->nativeFunc = dvmResolveNativeMethod;
method->jniArgInfo = computeJniArgInfo(&method->prototype);
}
}
Jni反射调用java方法时要用到一个jmethodID指针,这个指针在Dalvik里其实就是Method类,通过修改这个类的一些属性就可以实现在运行时将一个方法修改成native方法.
SET_METHOD_FLAG(method, ACC_NATIVE);
就是这么做的,其后面的代码就是设定native函数的参数占用内存大小统计.
也许你发现了,虽然将其修改成一个native方法了,但是这个方法对应的native代码在那里呢?这样做
代码:
if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) {
ALOG("RegisterNatives","err");
return false;
}
可以将一个native函数绑定到一个java的native方法
这样就能够实现开始的目标了!
我这里讲得是有点粗略了,但是这个技术牵涉到的知识太多了,主要是给老鸟参考的,小菜们拿来用用就好,要是要讲得小菜们都能明白,就不知要讲到何年何月了.还有就是上面的art环境的代码是跑不起来的,等我后面有空完善了再发一次吧!
本教程仅供学习交流用途,请勿用于非法用途!
测试代码猛击这里:http://pan.baidu.com/s/1nt9GBsX
//############### 14.16.17更新 ##########################
之前没有提到调用原来的方法的方法,方法是这样,直接内存拷贝存起来
代码:
uint mlen = sizeof(Method);
Method *oldMeth = (Method*)malloc(mlen);
memcpy(oldMeth,method,mlen);
info->odlMethod = oldMeth;
info->curMethod = method;
调用原来的方法就把内存拷回去,调用后再hook一次
代码:
memcpy(hi->curMethod,hi->odlMethod,mlen);
jmethodID om = (jmethodID)hi->curMethod;
jenv->CallVoidMethod(me,om,gDevice_Sensors);
ClassMethodHook(jenv,&baiduhookInfos[0]);