致谢
感谢看雪论坛中的这位大神,分享了这个技术:http://bbs.pediy.com/showthread.php?t=186054,从这篇文章中学习到了很多内容,如果没有这篇好文章,我在研究的过程中会遇到很多困难,说不定我就放弃了~~在此感谢他。
前言
之前的几篇文章都是在介绍了OC的相关知识,之前的半个月也都是在搞IOS的相关东西,白天上班做Android工作,晚上回家还有弄IOS,感觉真的很伤了。不过OC的知识也学习了差不多了。不过在这段时间遗留了很多Android方面的问题都没有进行解决和总结。所以从这段时间开始,需要将解决的问题都总结一下吧。
问题
Android中通过注入技术修改系统返回的Mac地址
技术准备
下面来看一下这个技术需要哪些知识点
1、如何将非native方法变成native方法
2、如何将native方法直接注册(不需要jni这样的头文件了)
3、Android中的类加载器相关知识
4、如何编译Android系统引用系统头文件的NDK项目
虽然这里有这四个知识点,但是其中有两个我在之前的blog中已经介绍了:
Android中的类加载器:http://blog.csdn.net/jiangwei0910410003/article/details/41384667
如何编译Android系统引用系统头文件的NDK项目:http://blog.csdn.net/jiangwei0910410003/article/details/40949475
不过在这篇文章中,我们在介绍一种新的编译方式
项目测试
第一、Android项目
package com.example.testar;import android.net.wifi.WifiInfo;import android.net.wifi.WifiManager;import android.os.Bundle;import android.app.Activity;import android.content.Context;import android.util.Log;import android.view.Menu;import android.view.View;import android.widget.Button;public class MainActivity extends Activity { @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) { WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE); WifiInfo info = wifi.getConnectionInfo(); System.out.println("Wifi mac :" + info.getMacAddress()); Log.d("DEMO", "Wifi mac:"+info.getMacAddress()); } }); }}
我们看到,这里的代码很简单,就是打印一下设备的Mac地址,现在我们要做的就是:注入这个Demo进程,然后修改Mac的值。
第二、底层的实现
首先来看一下inject.c
这个是注入进程的核心文件,由于代码比较多,这里只看核心的部分:
int main(int argc, char** argv) { char *pn = "com.example.testar"; char *is = "/data/local/libso.so"; printf("%s\n",pn); printf("%s\n",is); pid_t target_pid; target_pid = find_pid_of(pn); printf("pid: %d\n",target_pid); int ret = inject_remote_process(target_pid, is, "InjectInterface", (void*)"I'm parameter!", strlen("I'm parameter!") ); printf("result: %d\n",ret);}
就是他的主函数代码
我们看到有一个重要的函数:
inject_remote_process
第一个参数:注入进程的id
第二个参数:需要注入到目标进程的so文件
第三个参数:so文件中需要执行的函数名
第四个参数:执行函数的参数
第五个参数:执行函数的参数的长度
主要还是前面三个参数。
这里我们通过find_pid_of(pn)函数来获取进程id
传递进去的是进程名
Android中应用的进程名就是包名
char *pn = "com.example.testar";
看到上面注入到目标进程的so文件
char *is = "/data/local/libso.so";
下面再来看一下这个so文件的源代码
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>#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ÀàÐÍ:infoextern "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){ return env->NewStringUTF("haha ");}
在这个文件中,我们看到了函数
InjectInterface了,因为so是C++程序,但是inject是C程序,为了兼容,就有这种方式了
extern "C" 函数{
//do something
}
这个代码没什么难度和复杂性
这个函数中调用了Hook函数,下面在来看一下Hook函数的定义
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"#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 = tr