0%

Android_Hook框架

Android Hook

相对于Windows下面的hook操作,Android的hook主要是以java层以及native层为主。

native层hook

通过一个注入器,将我们的so动态链接库注入到我们想要注入的进程中去。
注入的方式有间接注入(zytoge注入)和直接注入(ptrace注入)为主,我这里用的是ADBI框架,比较简单,还不需要root。
从GitHub下载下来之后,我们可以得到两个主要的关键文件夹。

  • instruments
  • hijack

第一个使我们要编写的so文件,也就是想要被注入的动态链接库,第二个是我们的注入器,作者已经写好了,我们只需要用NDK进行编译就好,来到jni的目录,使用ndk-build就可以编译(环境变量需要设置好),然后他就会在hijack\obj\local\armeabi目录下生成一个hijack文件,这个就是我们的注入器。
注入器得到之后,我们就需要编写我们的动态链接库,在这之前我们需要先获取到我们需要hook的函数,示例是之前那个TestJNI,分析java层之后,我们要hook的是encodestr这个函数。
输出函数
Java_com_example_testjni_NativeClass_encodestr我们就是要hook这个函数,新建一个文件夹,拷贝一下example文件家中的内容:
hook文件
首先打开后缀是_arm的那个c文件,里面的内容:

1
2
3
4
5
6
7
8
9
10
#include <jni.h>
#include <sys/types.h>
#include <sys/epoll.h>

extern jstring my_Java_com_example_testjni_NativeClass_encodestr(JNIEnv * env, jobject jstr, jstring username, jstring password);

jstring my_Java_com_example_testjni_NativeClass_encodestr_arm(JNIEnv * env, jobject jstr, jstring username, jstring password)
{
return my_Java_com_example_testjni_NativeClass_encodestr(env, jstr, username, password);
}

声明的外部函数是我们的hook函数,然后又实现了一个后面是arm的函数,这两个函数的函数调用约定是要和我们的定义的一样的。
函数声明
由于使用了JNI的函数,所以我们要导入标准的jni.h,这个文件就是这么简单,我们来到另外一个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <string.h>
#include <termios.h>
#include <pthread.h>
#include <sys/epoll.h>

#include <jni.h>
#include <stdlib.h>

#include "../base/hook.h"
#include "../base/base.h"

#undef log

#define log(...) \
{FILE *fp = fopen("/data/local/tmp/adbi_example.log", "a+"); if (fp) {\
fprintf(fp, __VA_ARGS__);\
fclose(fp);}}


// this file is going to be compiled into a thumb mode binary

void __attribute__ ((constructor)) my_init(void);

static struct hook_t eph;

// for demo code only
static int counter;

// arm version of hook
extern jstring my_Java_com_example_testjni_NativeClass_encodestr_arm(JNIEnv * env, jobject jstr, jstring username, jstring password);

/*
* log function to pass to the hooking library to implement central loggin
*
* see: set_logfunction() in base.h
*/
static void my_log(char *msg)
{
log("%s", msg)
}

jstring my_Java_com_example_testjni_NativeClass_encodestr(JNIEnv * env, jobject jstr, jstring username, jstring password)
{
jstring (*orig_testjni_NativeClass_encodestr)(JNIEnv *, jobject , jstring , jstring );
orig_testjni_NativeClass_encodestr = (void*)eph.orig;

hook_precall(&eph);
jstring res = orig_testjni_NativeClass_encodestr(env, jstr, (*env)->NewStringUTF(env, "a"), (*env)->NewStringUTF(env, "b"));
if (counter) {
hook_postcall(&eph);
log("encodestr() called\n");
counter--;
if (!counter)
log("removing hook for encodestr()\n");
}
//char fail[] = "wkertest";
return (*env)->NewStringUTF(env, "Hello Test NDK !");
//return res;
}

void my_init(void)
{
counter = 3;

log("%s started\n", __FILE__)

set_logfunction(my_log);

hook(&eph, getpid(), "libTestJNI.", "Java_com_example_testjni_NativeClass_encodestr", my_Java_com_example_testjni_NativeClass_encodestr_arm, my_Java_com_example_testjni_NativeClass_encodestr);
}

首先定义了一个log函数,在/data/local/tmp/adbi_example.log这个目录下面我们保存我们的日志。
然后下面的一些内容照葫芦画瓢就好。
比较关键的是my_Java_com_example_testjni_NativeClass_encodestr这个函数和my_init函数,这两个函数是关键,前一个就是我们的hook函数,里面的内容编写我们要hook写入的方法,这里我们直接返回随意一个字符串就好。
他接受了原先的函数,使用了hook_precall这个样子的一个函数,具体这个函数是做什么的,没分析,反正他写上去了,咱就跟着写上去,然后下面的操作就根据需求来。
my_init函数,是一个入口函数,关键的是:

1
hook(&eph, getpid(), "libTestJNI.", "Java_com_example_testjni_NativeClass_encodestr", my_Java_com_example_testjni_NativeClass_encodestr_arm, my_Java_com_example_testjni_NativeClass_encodestr);

这个函数,第一个参数是哪个结构体,第二个是当前的pid,第三个是你要hook的so,第四个是导出函数,第五个使我们在之前那个arm文件中实现的函数,最后一个是我们的hook函数。
最后我们还是用ndk-build编译那个Android.mk文件,最后会在lib文件加下面生成我们的so文件,当然我们要在之前生成我们的base文件,这个base文件夹是在instruments文件加下面的,同样使用NDK编译。
最后我们将我们的这个注入器和so文件导入我们的Android模拟器中。
导入文件
我们在adb中启动我们的注入器,注入器使用规则:
./hijack -d -p 21846 -l /data/local/tmp/libexample.so
21846使我们进程的PID,这里我们通过ddms获取,最后一个使我们的so文件,必须绝对路径(最好都给权限,还有那个log文件我们要线程手动生成一个,有没有内容不重要)。
可以看到结果:

1
2
3
4
5
6
7
8
9
mprotect: 0x400421ec
dlopen: 0x40000ef9
pc=400433dc lr=400b1fb5 sp=bec14488 fp=bec1461c
r0=fffffffc r1=bec144a8
r2=10 r3=1f0
stack: 0xbec00000-0xbec15000 leng = 86016
executing injection code at 0xbec14438
calling mprotect
library injection completed!

看到这个之后我们就算是成功了。
成功
使用IDA附加进程,来到我们的encodestr函数:
跳转1
跳转2
可以看到确实实现了hook。

java层hook

这里使用到的框架式Xposed这个框架,这个框架使用起来很简单,首先我们需要先安装我们的客户端:
Xposed
安装完毕之后就可以了,当然要给root了,但是这个是可以在X86架构上的,因为是java层的。
安装完毕之后我们就需要导入我们的jar包:
jar包
导入这个包之后我们需要配置一下AndroidManifest.xml,需要在application这个节点下面添加:

1
2
3
4
5
6
7
8
9
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="测试服务端" />
<meta-data
android:name="xposedminversion"
android:value="54" />

第一个是表明是xposed的模块,第二个是插件的名称,第三个是你使用的jar包的版本号。
声明好这三个节点之后,我们就需要对APK程序进行分析,这里为了方便我就直接用了源代码级别的,我们是要hook用户输入的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.example.passtest;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity implements OnClickListener{

public EditText et_username;
public EditText et_password;
public Button btn;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_username = (EditText) findViewById(R.id.username);
et_password = (EditText) findViewById(R.id.password);

btn = (Button) findViewById(R.id.soLogin);
btn.setOnClickListener(this);
}

public void onClick(View v)
{
switch (v.getId()) {
case R.id.soLogin:
//Toast.makeText(MainActivity.this, "ttttttt", Toast.LENGTH_SHORT).show();
Log.d("wker", "aaa");
break;

default:
break;
}
}

}

我们的目的就是要获取到用户输入的内容,其实也就是et_password中的内容

分析完毕之后,接下来我们就需要新建一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.example.androidtestxposed;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import java.lang.reflect.Field;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

public class TestMain implements IXposedHookLoadPackage {
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
Log.v("packageName", lpparam.packageName);
Log.d("wker1",lpparam.packageName);
if (!lpparam.packageName.equals("com.example.passtest"))
return;
Log.d("wker","into");
findAndHookMethod("com.example.passtest.MainActivity", lpparam.classLoader, "onClick", View.class,new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
/*Class clazz = param.thisObject.getClass();
Log.v("wker", clazz.getName());*/
}

@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
/*
TextView tv = (TextView) param.thisObject;
String text = tv.getText().toString();
tv.setText(text + " :)");
tv.setTextColor(Color.RED);
Log.v("redcolock","redcolock");*/
Class clazz = param.thisObject.getClass();
Log.v("wker", clazz.getName());
Field field = clazz.getField("et_password");
EditText password = (EditText) field.get(param.thisObject);
Log.v("wker", password.getText().toString());
//Toast.makeText((Activity)param.thisObject, password.getText().toString(), Toast.LENGTH_SHORT).show();
password.setText("ceshi");
}
});
}
}

首先这个类要继承IXposedHookLoadPackage这个接口,并且实现handleLoadPackage方法,这个方法传进来一个LoadPackageParam这个类的一个参数,这个我们是可以获取到包名的,通过packageName这个成员,我们就通过包名来判断是不是我们想要hook的一个程序,不是的话呢返回。
判断是我们想要hook的程序之后,我们使用findAndHookMethod这个样子的一个静态方法设置我们想要hook的类:

  1. 第一个参数是完整的类名
  2. 第二个参数是类的加载器。
  3. 第三个参数是类中想要hook的方法
  4. 第四个参数是hook的方法的参数
  5. 最后一个参数是我们new的一个内部类
    • 内部类可以实现hook的方法。

第五个参数:
beforeHookedMethod实现这个方法是在执行原先函数之前我们需要做的操作,我们这里通过反射获取他的et_password,前两句我们的对象是否正确,发现没问题,第三个是通过我们获取到的这个对象中的成员,然后通过反射获取成员变量,然后最终就可以截获我们输入的密码。
afterHookedMethod这个方法是hook之后做的一些操作,但是我在测试的过程中发现,我在截取密码的时候,after如果有获取对象操作的时候,我们的操作就会失败befor也会出现错误,我觉的很是奇怪,之后具体分析
XC_MethodReplacement这个类是可以完全替换我们的函数方法,这里没有测试,之后具体分析,他也是XC_MethodHook(第五个参数的一个子类)。
hook成功
这里发现就可以hook成功了。
log信息
其实我们也可以看到,Class clazz = param.thisObject.getClass();这个获取到的类也就是我们MainActivity这个类!

java层hook的实现原理

native层的hook和我们在Windows下面的hook道理其实差不多,但是java层的一个hook是通过反射基址进行hook的。

首先我们要确定我们要hook的类和相对应的方法。
这里就hook我们的点击事件就好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button button = (Button) findViewById(R.id.hookClick);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("test", "点击事件继续");
}
});

TextView tv = (TextView) findViewById(R.id.textView1);
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "test", Toast.LENGTH_SHORT).show();;
}
});

简单的一个点击。
想要hook OnclickListener这个方法。
首先我们先要分析这个内部类的一个实现:

1
2
3
4
5
6
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}

其实这个setOnClickListerner这个方法就是将getListenerInfo()获取到的对象返回回去:

1
2
3
4
5
6
7
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}

获取到他的对象之后,然后将其中的mOnClickListener变量设置为我们的一个变量,分析到这里就明白了,那么我们就来hook。

1
2
3
Method method = View.class.getDeclaredMethod("getListenerInfo");
method.setAccessible(true);
Log.e("test", "method=" + method.getName());

这一步我们先获取到getListenerInfo这个方法,然后设置这个方法为可更改的。

1
2
// 获取Button的ListenerInfo对象mListenerInfo
Object mListenerInfo = method.invoke(button);

这个就是通过执行getListenerInfo方法来获取到我们原先的一个对象(点击事件的),穿进去的参数是我们的按钮对象。

1
2
3
4
// 内部类需要使用$分隔
Class<?> classListenerInfo = Class.forName("android.view.View$ListenerInfo");
// 获取内部Field mOnClickListener
Field field = classListenerInfo.getDeclaredField("mOnClickListener");

获取到我们的对象之后我们就需要来获取我们的内部类,我们是需要通过这个内部类来获取到mOnClickListener这个字段的。

1
2
3
// 然后获取Button的ListenerInfo对象mListenerInfo的mOnClickListener变量
// --这就是真正的拿到了Button的监听回调View.OnClickListener的实例对象
final View.OnClickListener onClickListener = (View.OnClickListener) field.get(mListenerInfo);

我们获取到内部类中的mOnClickListener字段之后,我们就通过这个字段获取我们真正的对象(也就是我们那个按钮点击监听器的一个对象,匿名内部类),然后将这个对象保存起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 然后准备替换为我们自己的点击事件
// 1. 创建代理点击对象,然后替换 (这里继承接口实现一个类也可以)
Object proxyOnClickListener = Proxy.newProxyInstance(button.getClass().getClassLoader(),
new Class[]{View.OnClickListener.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Toast.makeText(MainActivity.this,
"你点击我嘛,我很烦的!",
Toast.LENGTH_SHORT).show();
// 为了保证其点击逻辑,除了插入我们的操作,我们还是要处理正常的调用逻辑
return method.invoke(onClickListener, args);
}
});

我们通过动态代理获取一下相对应的一个点击事件,可以看到和我们的xposed框架很相似:

  1. 第一个参数是我们hook的对象的一个类加载器
  2. 第二个是这个方法一个参数
  3. 第三个方法就是当执行到这个参数的时候我们想要执行的命令

最后一个参数实现invoke方法,这个方法穿进去的第二三个参数比较重要,第二个参数是我们可以通过他获取原先的执行方法,感觉就是反射得到的,传进去我们之前获取到的对象和第三个参数的参数就可以了,然后我们就弹出一个Toast。
获取完毕之鸥,我们就替换掉之前的button事件:

1
2
// 2. 然后替换掉Button的点击事件
field.set(mListenerInfo, proxyOnClickListener);

这样我们就实现了hook点击事件,以此类推,我们也可以hookTextView的一个点击事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
try {
Method method = View.class.getDeclaredMethod("getListenerInfo");
method.setAccessible(true);
Object mLObject = method.invoke(tv);
Class<?> cli = Class.forName("android.view.View$ListenerInfo");
Field fi = cli.getDeclaredField("mOnClickListener");
final View.OnClickListener oc = (OnClickListener) fi.get(mLObject);

Object newOnClickListern = Proxy.newProxyInstance(tv.getClass().getClassLoader(),new Class[]{View.OnClickListener.class}, new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Toast.makeText(MainActivity.this,
"HOOK",
Toast.LENGTH_SHORT).show();

return method.invoke(oc,args);
}
});


fi.set(mLObject, newOnClickListern);

} catch (Exception e) {
e.printStackTrace();
}

这个的话呢和上面的大同小异。
hook实现

这就是比较简单的Android HOOk操作,一些具体的等待分析!