1、什么是Hook

Hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序就先捕获该音讯,钩子函数先得到控制权,这时钩子函数既能够加工处理(改动)该函数的履行行为,还能够强制完毕音讯的传递。简略来说,便是把系统的程序拉出来变成咱们自己履行代码片段。

2、对App的so进行Hook的一种思路

咱们知道现在JNI在Android开发中是特别重要的,运用JNI有什么好处呢?

  1. Preference,C/C++在运转功能上面甩Java几条GAI
  2. Security,更多的加密解密还是放在Native上。

优点不止这两点,比如在Native里边拓荒空间并不受JVM管理,JVM怎样运用native memory。这儿不再赘述。

本文供给一种对Android上so库进行Hook的一种思路,不触及ELF的检查修正,不改动对方的调用办法。 思路便是一招移花接木,用自己的so替换App的so,让对象调用自己的so的时候调用咱们自己写的so,咱们再调用本来的so,这样就能够获得对方so办法的输入输出。

能够运用在想获取对方App的数据传递格局或许无法破解对方的加解密,可是能够经过hook获取对方的数据格局再调用对方的加解密办法得到自己想要的成果。

3、一个最基本的JNI sample程序代表方针宿主Apk

这儿咱们自己预备一个宿主Apk,直接用AndroidStudio新建一个支撑JNI的工程,勾选Include C++ support,默许生成一个Android工程。

Android对so进行简单hook
默许的MainActivity.java和native-lib.cpp分别长这姿态:

//MainActivity
public class MainActivity extends Activity {
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
    public native String stringFromJNI(String param);
}
//native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_hook_yocn_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

咱们为了逻辑明晰略微做一点修正,逻辑是希望传入一个字符串,然后在C++里边临字符串的每个字符都+1操作,也便是 java -> kbwb。改完之后代码长这个姿态:

//MainActivity.java
public class MainActivity extends Activity {
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI("java"));
    }
    public native String stringFromJNI(String param);
}
//native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOG_TAG "hook"
#define LOGV(...)  __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
Java_com_hook_yocn_MainActivity_stringFromJNI(JNIEnv *env, jobject obj, jstring param) {
    int length = (env)->GetStringLength(param);
    const char *nativeString = (env)->GetStringUTFChars(param, 0);
    char *resultChars = new char[length + 1];
    for (int i = 0; i < length; ++i) {
        resultChars[i] = nativeString[i] + 1;
    }
    resultChars[length] = '\0';
    std::string par = resultChars;
    LOGE("输入参数->%s,长度:%d", nativeString, length);
    LOGE("输出成果->%s", resultChars);
    return env->NewStringUTF(resultChars);
}

咱们跑一下,假如正常的话,输出应该是:

Android对so进行简单hook

现在预备工作就做完了,咱们有了一个方针宿主Apk,下面开端着手进行Hook。

4、开端Hook

咱们需求一台root了的手机或许一个Android模拟器,这儿我用模拟器演示。
先理一下思路,按照咱们的了解,需求以下几步:

  • 4.1、 编写Hook的代码,并打包成so
  • 4.2、 找到方针app咱们要替换的so的寄存目录,把对方的so仿制一份,用咱们自己写的so替换掉对方的so。
  • 4.3、 从头运转app检查调用成果

咱们按照思路一步步往下走:

4.1、编写Hook代码

由于上面的宿主Apk是咱们自己写的,所以咱们知道调用的办法姓名,而面临一个咱们并不了解的陌生Apk的时候,Apk尤其是so对咱们来说完全是一个黑盒,这个时候咱们怎样知道要怎样编写Hook的代码呢?

  1. 运用IDA检查对方的so文件,这个我也不了解,大牛随意用。
  2. 换个思路,so咱们看不了,可是咱们能够检查java代码,能够从java代码中找思路。
  3. 什么都不必,直接运转,哪个办法报错咱们就预备神呢么办法。咱们用这个办法解说。

所以咱们先编一个空的so出来,命名为libhook.so,或许随意找个so直接到第4.2步,找到app的so寄存目录。这个目录一般在data/data/packageName/lib/xxx.so,比如我现在宿主包名是com.ahook.yocn,需求hook的so叫做libnative-lib.so,所以目录应该是在/data/data/com.ahook.yocn/lib/native-lib.so
咱们直接在terminal里边履行:

// 咱们自己的libhook.so push到内存卡而且重命名为libnative-lib.so
adb push libhook.so /mnt/sdcard/libnative-lib.so
// 进到手机目录
adb shell
// 获取root权限
su
// 到so寄存目录
cd /data/data/com.ahook.yocn/lib
// 宿主的so拷贝一份,由于咱们还要调用,起个别号,咱们还要用
cp libnative-lib.so libnative-lib-src.so
// 将咱们push进来的so拷贝到当前目录并覆盖宿主apk的so
cp /mnt/sdcard/libnative-lib.so .

su是为了获取root权限,由于data/data/目录需求root权限才能够进。咱们退出app(假如没有退出的话),从头翻开app,不出预料会报错,假如没有报错或许是上面的so拷贝没有生效,需求double check。

2019-07-06 21:49:45.531 12052-12052/com.ahook.yocn E/com.ahook.yocn: No implementation found for java.lang.String com.hook.yocn.MainActivity.stringFromJNI(java.lang.String) (tried Java_com_hook_yocn_MainActivity_stringFromJNI and Java_com_hook_yocn_MainActivity_stringFromJNI__Ljava_lang_String_2)
2019-07-06 21:49:45.533 12052-12052/com.ahook.yocn E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.ahook.yocn, PID: 12052
    java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.hook.yocn.MainActivity.stringFromJNI(java.lang.String) (tried Java_com_hook_yocn_MainActivity_stringFromJNI and Java_com_hook_yocn_MainActivity_stringFromJNI__Ljava_lang_String_2)
        at com.hook.yocn.MainActivity.stringFromJNI(Native Method)
        at com.hook.yocn.MainActivity.onCreate(MainActivity.java:21)
 ......

报错告知咱们有一个全限制名为com.hook.yocn.MainActivity.stringFromJNI的办法没有找到,这个办法承受一个String参数,而且有一个String回来值~
So,咱们找到了宿主Apk调用的第一个办法的姓名,而且知道它的参数和回来值,咱们能够开端干活了。

// hook.cpp
#include <jni.h>
#include <string.h>
#include <vector>
#include <stdio.h>
#include <android/log.h>
#include <android/bitmap.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string>
#define LOG_TAG "hook"
#define LOGV(...)  __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
using namespace std;
extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
//宿主动态链接库途径
#define LIB_CACULATE_PATH "/data/data/com.ahook.yocn/lib/libnative-lib-src.so"
//函数指针
typedef jstring (*CAC_FUNC)(JNIEnv *env, jobject thiz, jstring param);
jstring callFunc(JNIEnv *env, jobject thiz, jstring param) {
    void *handle;
    char *error;
    CAC_FUNC cac_func = NULL;
    //翻开动态链接库
    handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY);
    if (!handle) {
        LOGV("dlopen: %s\n", dlerror());
    }
    //清除之前存在的错误
    dlerror();
    //获取一个函数
    *(void **) (&cac_func) = dlsym(handle, "Java_com_hook_yocn_MainActivity_stringFromJNI");
    if ((error = const_cast<char *>(dlerror())) != NULL) {
        LOGV("dlsym: %s\n", error);
    }
    jstring ret = (*cac_func)(env, thiz, param);
//    printfJstring(env, thiz, ret);
    //关闭动态链接库
//    dlclose(handle);
    return ret;
}
JNIEXPORT jstring JNICALL
Java_com_hook_yocn_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz, jstring param) {
    LOGE("Java_com_hook_yocn_MainActivity_stringFromJNI");
    string hookPre = "Hook_Head ";
    string paramString = (env)->GetStringUTFChars(param, 0) + hookPre;
    string modifyString = hookPre + paramString;
    jstring modifyParam = env->NewStringUTF(modifyString.c_str());
    jstring ss = callFunc(env, thiz, modifyParam);
    string rawResult = (env)->GetStringUTFChars(ss, 0);
    string hookEndString = rawResult + " Hook_End";
    return env->NewStringUTF(hookEndString.c_str());
}
} //extern "C"
//Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS    := -llog -ljnigraphics
LOCAL_MODULE    := hook
APP_PROJECT_PATH:= LOCAL_PATH
LOCAL_SRC_FILES := hook.cpp
LOCAL_CFLAGS    =  -ffast-math -O3 -funroll-loops
include $(BUILD_SHARED_LIBRARY)
//Application.mk
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := x86
APP_PLATFORM := android-28

代码很简略

  1. 实现一个Java_com_hook_yocn_MainActivity_stringFromJNI的JNI办法承受一个jstring,回来一个jstring。
  2. 找到宿主本来的so,存到LIB_CACULATE_PATH里,运用dlopen办法翻开而且调用它自己的stringFromJNI办法而且得到一个jstring回来值。这儿咱们能够对办法的输入输出恣意修正

我本身更了解ndk-build,假如了解makefile,也能够用makefile。由于我用的是模拟器所以Application.mk里边APP_ABI数x86,假如用真机的是arm架构的能够修正成armeabi-v7a,编写完之后目录结构差不多这姿态的,然后进到jni目录履行ndk-build能够得到libhook.so。

Android对so进行简单hook

得到了so之后咱们履行

adb push libhook.so /mnt/sdcard/libnative-lib.so
...

然后重复上面的代码替换掉宿主的so。履行完之后从头翻开app,应该能看到下面这样的输出。

Android对so进行简单hook

假如能够得到成果就阐明咱们hook成功了。或许宿主apk的办法不止一个,咱们成功模拟了第一个办法后后边的还会报错,所以咱们需求一直重复上面的过程直到运转正常或许得到咱们想要的数据为止。

咱们整理一下思路:

  1. 找到方针app咱们要替换的so的寄存目录,把对方的so仿制一份。
  2. 打个空的so包后者随意找个so,用咱们自己的so替换掉对方的so。
  3. 从头运转app检查调用成果,这时候肯定会犯错,依据犯错的全限制办法名编写咱们的hook代码
  4. 依据全限制名和输入输出编写宿主需求的代码,用输入调用宿主的so得到输出,对输出加工后回来回去,相当于一个代理模式
  5. 重复1234步直到宿主app运转正常或许得到咱们想要的数据。

假如有问题能够去看源码,本文源码:github.com/yocn/HookSo…