前言
我们以前介绍过Xposed,这个只可以Hook java层,如果要hook native层就要使用InlineHook了,以前的文章都有提到。今天介绍一个既可以Hook java层又可以Hook native层的框架,就是Cydia Substrate。
环境
Android4.4.4
Nexus5手机(ARM)
Eclipse
apk下载地址:http://www.cydiasubstrate.com
sdk下载地址:http://asdk.cydiasubstrate.com/zips/cydia_substrate-r2.zip
安装Cydia Substrate apk
使用adb install安装后,进入到app
1、点击Link Substrate Files
2、获取root权限
3、重启设备
如下图说明安装成功了

java层Hook
创建项目后把so文件和jar文件拷贝到指定目录,jar添加进来

新建一个Hook的入口类HookTest.java
HookTest.java
package com.example.cydiasubstratehook;
import java.lang.reflect.Method;
import android.util.Log;
import com.saurik.substrate.MS;
public class HookTest {
public static final String SHARK = "Shark";
/**
* substrate 初始化后的入口
*/
static void initialize() {
// Hook System Color
//先hook 类加载时
//参数一是类名 参数二是实现MS.ClassLoadHook的类
//在classLoaded中完成业务逻辑
MS.hookClassLoad("android.content.res.Resources",
new MS.ClassLoadHook() {
@SuppressWarnings({ "unchecked", "rawtypes" })
public void classLoaded(Class<?> resources) {
Method getColor;
try {
//从这个类获取getColor方法对象
getColor = resources.getMethod("getColor",
Integer.TYPE);
} catch (NoSuchMethodException e) {
getColor = null;
}
if (getColor != null) {
//保存原来的方法
final MS.MethodPointer old = new MS.MethodPointer();
//hook方法
MS.hookMethod(resources, getColor,
new MS.MethodHook() {
//hook方法的业务逻辑
public Object invoked(Object resources,
Object... args) {
try {
//调用原来的方法
int color = (Integer) old
.invoke(resources, args);
//修改返回值
return color & ~0x0000ff00
| 0x00ff0000;
} catch (Throwable e) {
Log.i(SHARK,
"hook color err:"
+ Log.getStackTraceString(e));
}
return 0xFFFFFFFF;
}
}, old);
} else {
Log.i(SHARK, "getColor == null");
}
}
});
}
}
这里我们拿Hook系统的字体颜色做例子
initialize方法是substrate 初始化后的入口,注释都很明白了。Hook也无非是哪几样东西,这里先hook加载类在hook方法。最后修改返回值就可以了。
配置AndroidManifest.xml
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cydiasubstratehook"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="19" />
<!-- 加入substrate权限 -->
<uses-permission android:name="cydia.permission.SUBSTRATE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 声明substrate的入口类 -->
<meta-data
android:name="com.saurik.substrate.main"
android:value="com.example.cydiasubstratehook.HookTest" />
</application>
</manifest>
一个是要加入substrate权限
一个是在meta-data中声明substrate的入口类
编译安装后手机会有信息提示

点击进入,重启设备

重启后可以看到效果

Hook Native层
重点在这里
hook Native选择以前我们做过的一个案例,Android源码分析 之 分析Dalvik下Dex加载流程寻找脱壳点,以前我们是通过修改源码进行内存dump的,现在我们使用Hook DexFileParse函数这个脱壳点进行内存dump
添加文件

这里我们在jni目录中加了三个文件
DexFile.h: 因为可能会用到一些函数的定义和结构所以我们从源码处拷贝过来并引用进代码中
Android.mk:ndk的Makefile文件
hookdvm.cpp:hook的代码
Android.mk
LOCAL_PATH := $(call my-dir)
#加入substrate的so
include $(CLEAR_VARS)
LOCAL_MODULE := substrate
LOCAL_SRC_FILES := libsubstrate.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := substrate-dvm
LOCAL_SRC_FILES := libsubstrate-dvm.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
#这里一定要注意编译的文件名必须是cy结尾的
LOCAL_MODULE := hookdvm.cy
LOCAL_SRC_FILES := hookdvm.cpp
LOCAL_LDLIBS := -llog
LOCAL_ARM_MODE := arm
LOCAL_LDLIBS += -L$(LOCAL_PATH) -lsubstrate-dvm -lsubstrate
include $(BUILD_SHARED_LIBRARY)
这个应该不用说写过makefile的都应该看得懂。主要注意hookdvm的LOCAL_MODULE必须是cy结尾的
#include <jni.h>
#include "substrate.h"
#include <android/log.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <stdlib.h>
#include "DexFile.h"
#define TAG "Shark"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
const char* ex0 = "re-initialized>";
const char* ex1 = "zygote";
const char* ex2 = "app_process";
const char* ex3 = "/system/bin/dexopt";
const char* ex4 = "com.google.android.gms";
const char* ex5 = "com.google.android.gms.persistent";
const char* ex6 = "com.google.process.gapps";
const char* ex7 = "com.google.android.gms.wearable";
const char* ex8 = "com.android.phone";
const char* ex9 = "com.android.systemui";
const char* ex10 = "com.google.android.gms.unstable";
const char* ex11 = "android.process.acore";
const char* ex12 = "android.process.media";
const char* ex13 = "dexopt";
#define BUF_SIZE 1024
MSConfig(MSFilterLibrary, "/system/lib/libdvm.so");
const char* workDir = "/sdcard/hookdex/";
//得到进程的名称
void getNameByPid(pid_t pid, char *task_name) {
char proc_pid_path[BUF_SIZE];
char buf[BUF_SIZE];
sprintf(proc_pid_path, "/proc/%d/status", pid);
FILE* fp = fopen(proc_pid_path, "r");
if(NULL != fp){
if(fgets(buf, BUF_SIZE-1, fp) == NULL){
fclose(fp);
}
fclose(fp);
sscanf(buf, "%*s %s", task_name);
}
}
//是否需要过滤系统进程
int exclude(char* s){
int i = !strcmp(s,ex0)||!strcmp(s,ex1)||!strcmp(s,ex2)||!strcmp(s,ex3)||
!strcmp(s,ex4)||!strcmp(s,ex5)||!strcmp(s,ex6)||!strcmp(s,ex7)||
!strcmp(s,ex8)||!strcmp(s,ex9)||!strcmp(s,ex10)||!strcmp(s,ex11)||
!strcmp(s,ex12)||!strcmp(s,ex13);
return i;
}
//检测目录是否存在
int checkDir()
{
mode_t myMode = 777 ;
if(0 == access(workDir,0)) {//目录存在
return 0;
} else{
if(0 == mkdir(workDir,myMode)) {
return 0;
}
else {
return 1;
}
}
}
//老DexFileParse的保存
DexFile* (*oldDexFileParse)(const u1* data, size_t length, int flags);
DexFile* newDexFileParse(const u1* addr, size_t len, int dvmdex)
{
char buf[200];
char pname[50];
pid_t pid = getpid();
//得到进程名称
getNameByPid(pid, pname);
//判断是否需要Hook
if(exclude(pname)){
LOGI("exclude process:%s", pname);
return oldDexFileParse(addr, len, dvmdex);
}
LOGD("call dvm dex pid:%d,pname:%s", pid, pname);
sprintf(buf,"/sdcard/hookdex/%s_%d.dex", pname, pid);
FILE* file = fopen(buf, "wb");
if(!file){
LOGD("error open sdcard file to write");
}else{
//内存dump
fwrite(addr, 1, len, file);
fclose(file);
LOGD("write dex:%s len=%d succ!", buf, (int)len);
}
//进行原来的调用,不影响程序运行
return oldDexFileParse(addr,len,dvmdex);
}
MSInitialize{
LOGD("Substrate initialized.");
//创建目录
if(checkDir()){
LOGD("create dir err...");
return;
}else{
LOGD("create dir succ...");
}
//打开libdvm.so得到MSImageRef
MSImageRef image = MSGetImageByName("/system/lib/libdvm.so");
if (image != NULL){
//得到dexFileParse函数的地址,_Z12dexFileParsePKhji为导出名称,使用ida查看得到
void * dexload = MSFindSymbol(image, "_Z12dexFileParsePKhji");
if(dexload == NULL){
LOGD("error find _Z12dexFileParsePKhji");
}else{
//进行hook
MSHookFunction(dexload, (void*)&newDexFileParse, (void **)&oldDexFileParse);
}
}else{
LOGD("ERROR FIND LIBDVM");
}
}
Cydia Substrate已经封装了MSGetImageByName和MSFindSymbol供我们得到函数的内存地址。最后调用MSHookFunction进行hook,原来的函数地址保存在oldDexFileParse中。
我们要Hook系统的dexFileParse函数,这个函数在/system/lib/libdvm.so中。导出它使用ida打开搜索得到他的导出名称。

_Z12dexFileParsePKhji这个就是我们要Hook的名称。
在Hook时我使用int exclude(char* s)进行了过滤,并不是所有的进程都是我们想要hook的,而且这些进程未必有dex文件,比如鼻祖进程zygote,而这些进程过滤规则,需要我们自己打印看结果。然后构造。
注意
编译流程可以看看NDK JNI开发 之 Eclipse中静态注册(二)
这里编译使用ndk10版本,如果不是的话可能会有版本问题
ndk10下载地址:https://link.jianshu.com/?t=http://dl.google.com/android/ndk/android-ndk32-r10b-windows-x86_64.zip
运行
按照上面的方法编译安装重启手机
运行软件后,查看/sdcard/hooktest目录

引用
Android逆向之旅—Native层的Hook神器Cydia Substrate使用详解
利用Cydia Substrate进行Android HOOK
Cydia Substrate之hook native代码
网友评论