Hook框架配置备忘录-copy

Java层Hook工具,应用较多的有Xposed和Friday;so层Hook工具常用Substrate。

Frida:

动态插桩工具,使用Python、JavaScript共同完成。主要hook逻辑在JavaScript中完成,Python脚本负责将JS脚本传递给Android系统中的frida-server。

frida需要在root环境下执行,在移动端安装frida-server服务,并将设备连接至服务端。服务端需配置Linux环境和SDK PlatformTools,配置adb和fastboot环境变量。

与Xposed相比,Frida更改hook程序后无需重启系统,可直接重写方法体。Frida还可以hookNative层函数。

Xposed:

主要用于监控APP行为,改变原程序逻辑,沙箱定制(主要关注反环境检测);

hook重载方法时,可忽略参数,使用hookAllMethods(),对于多个构造方法的hook,可使用hookAllConstructors()方法。

由于dalvik环境下xposed对multidex的支持没有很好的通用解决方案,寻找目标函数会发生ClassNotFoundError,所以处理multidex需要一些技巧(Tips): 此问题因为classloader出错引起的,所以要寻找attachBaseContext 的classloader,而非lpparam.classLoader(此思路来自非虫前辈)。

Frida配置

这里介绍Android中Frida的环境配置。分为服务端和客户端两个部分。

一、环境准备

1.1 客户端配置

客户端的配置是在PC端,需要首先安装Python环境,配置pip模块。然后,执行命令:

1
2
pip install frida 
pip install frida-tools

然后执行pip list查看是否安装成功。

1.2 服务端配置

服务端配置是在Android端。

首先,查看测试机的CPU架构:

1
adb shell cat /proc/cpuinfo

结果显示:

即为ARM64.

然后,下载与客户端frida对应版本、与测试机架构相同的frida-server压缩包。

地址: https://github.com/frida/frida/releases

注意:服务端frida-server版本与客户端frida版本必须相同才能正常运行。

下载之后,解压,并将解压得到的文件传入测试机/data/local/tmp目录下。

1
2
3
adb push frida-server /data/local/tmp *# adb push 参数1 参数2*
adb shell cd data/local/tmp
chmod 777 frida-server

注意:最好使用真机,模拟器会因为架构兼容模式从而使得程序报错。

执行frida-servcer: ./frida-server

另起一个终端,配置端口转发: adb forward tcp:27043 tcp:27043

执行frida-ps -U测试是否启动成功,如图:

至此,frida环境配置和启动完成。

二、模块编写

可以使用JS或Python编写hook脚本,示例可以参考官方文档。具体编写示例会在后续博客中详述。

若使用JS hook,只需执行: frida -U -f hookpackagename -l test.js --no-pause

若使用Python,则直接执行python3 test.py即可。

Xposed Hook配置

一、Xposed安装

1.1 模拟器

模拟器采用网易MuMu,Android版本号6.0.1

1.2 Xposed安装包下载

下载地址:https://forum.xda-developers.com/showthread.php?t=3034811

下载如下图所示安装包:

1.3 安装

直接把安装包拖入到MuMu模拟器中即可。

1.4 激活Xposed框架

1.4.1 关闭应用兼容性

Xposed激活需要关闭MuMu设置中的应用兼容性,操作步骤如下:

打开设置,找到应用兼容性,关闭按钮,使其处于灰色状态

1.4.2 激活Xposed框架

打开“Xposed Installer”应用,点击下图表识先进行下载。

下载完成会跳出如下界面,如没有跳出,请再点击一下

1.4.3 重新开启应用兼容性

重新开启“应用兼容性”,使模拟器能同时运行x86软件和arm软件。

步骤一:打开设置,找到应用兼容性

步骤二:点击按钮,使其处于绿色状态,即开启状态

二、Xposed模块编写

2.1 配置AndroidManifest.xml

  1. 使用Android Studio新建一个Android工程
  2. 然后在AndroidManifest.xml中插入如下代码
1
2
3
4
5
6
7
8
9
<meta-data 
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="这是一个Xposed例程" />
<meta-data
android:name="xposedminversion"
android:value="54" />

2.2 配置build.gradle

在build.gradle中添加如下代码:

1
repositories { jcenter()}

以及

1
compileOnly 'de.robv.android.xposed:api:82'compileOnly 'de.robv.android.xposed:api:82:sources'

这句代码是告诉AndroidStuido使用jcenter作为代码仓库,从这个仓库里远程寻找 de.robv.android.xposed:api:82 这个API。这个网上很少有Xposed教程介绍它的!(我们不用自己找XposedBridgeApi.jar了。注意!此处要用compileOnly这个修饰符!网上有些写的是provide ,现在已经停用了!)

插入位置如下图,

2.3 Hook 实践

2.3.1 创建Button

创建一个Button控件,用于测试Hook

在MainActivity中写入如下代码

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
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
private String TAG = "Tag";
private Button button;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

button = findViewById(R.id.button);

button.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
Toast.makeText(MainActivity.this, toastMessage(), Toast.LENGTH_SHORT).show();
//getContacts();
}
});
}

public String toastMessage() {
return "我未被劫持";
}
}

2.3.2 测试为hook代码

测试代码,运行后点击Button控件

2.3.3 hook代码编写

在MainActivity的同级路径下新建一个类“HookTest.java”,代码如下:

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
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

/**
* Created by fanqiliang on 2018/12/25.
*/

public class HookTest implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
// "com.wanbang.xposedemo"为自己代码的包名
if (lpparam.packageName.equals("com.wanbang.xposedemo")) {
XposedBridge.log(" has Hooked!");
// "com.wanbang.xposedemo.MainActivity"为自己代码的类名
Class clazz = lpparam.classLoader.loadClass("com.wanbang.xposedemo.MainActivity");

// "toastMessage"为需要Hook的方法名
XposedHelpers.findAndHookMethod(clazz, "toastMessage", new XC_MethodHook() {
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
XposedBridge.log("BBBB has Hooked!");
}

protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("你已被劫持");
}
});
}
}
}

2.3.4 添加入口点

  1. 新建assets 文件夹

右键点击 “main ” 文件夹 , 选择new –> Folder –>Assets Folder,输入文件夹名为assets,如下图所示:

新建后结构如下图:

  1. 新建xposed_init文件,在assets文件夹下新建xposed_init文件,如下入所示

  1. 配置xposed_init

在xposed_init文件中输入hook代码的类名,类名需要写全路径(即包名+类名)。如下图是我编写的HookTest类,全路径为com..wanbang.xposedome.HookTest,

因此在xposed_init写入如下全路径

2.3.5 禁用 Instant Run

单击File -> Settings -> Build, Execution, Deployment -> Instant Run,把勾全部去掉(该路径为Mac系统的路径,其他系统可查找相应路径),具体如下图所示:

注意:每次修改代码都需重启Xposed

Cydia Hook 配置

一、环境准备

1.1 模拟器

经测试Substrate的apk包在很多模拟器中都无法正常运行,此处使用Genymotion的Google Nexys4 机型。

1.2 相关包下载

apk包:http://www.cydiasubstrate.com/

sdk下载:http://asdk.cydiasubstrate.com/zips/cydia_substrate-r2.zip

apk包下载后,安装至模拟器中,授予root权限。打开应用,如图:

img

二、模块编写

2.1 java层hook

2.1.1 配置AndroidManifest.xml

(1)增加权限:

1
<uses-permission android:name="cydia.permission.SUBSTRATE"/>

(2)Application标签中增加:

1
<meta-data android:name="com.saurik.substrate.main" android:value=".Main"/>

注:此处.Main文件是模块的入口文件,完成路径为:’packageName’.Main

img

2.1.2 导入jar包

将下载的sdk包解压缩,取出substrate-api.jar,放在libs目录下,并Add As Library

2.1.3 编写hook代码

在Main类中添加方法:

1
static void initialize() { // ... code to run when extension is loaded }

具体的hook代码可以写在这个方法中。

如,hook TelePhonyManager中的getDeviceId()方法:

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
public class Main {
static void initialize() {

MS.hookClassLoad("android.telephony.TelephonyManager",
new MS.ClassLoadHook() {
@SuppressWarnings("unchecked")
public void classLoaded(Class<?> arg0) {
Method hookimei;
try {
hookimei = arg0.getMethod("getDeviceId", null);
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
hookimei = null;
}
if (hookimei != null) {
final MS.MethodPointer old1 = new MS.MethodPointer();
MS.hookMethod(arg0, hookimei, new MS.MethodHook() {
@Override
public Object invoked(Object arg0,
Object... arg1) throws Throwable {
// TODO Auto-generated method stub
String imei = (String) old1.invoke(arg0,
arg1);
//此处可对imei进行修改
return imei;
}
}, old1);
}
}
});


}
}

2.2 Native层hook

2.2.1 相关文件导入和新建

(1)在src/main目录下新建目录jni

img

(2)向目录中添加Cydia sdk包中的.h头文件和两个.so库;(由于测试环境为模拟器,因此仅放了x86类型的so文件)

(3) 在jni目录下新建.cpp文件,

本例中为module.cpp:

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
#include "substrate.h"
#include <android/log.h>


//配置,这里是hook可运行程序(即NDK小程序)的写法,下面那个就是hook dvm的写法
MSConfig(MSFilterExecutable,"/system/bin/app_process")
//MSConfig(MSFilterLibrary, "libdvm.so");

//旧的函数地址,目的为了保留指向原来函数的入口,在新的函数执行
//完后,一般会再调用该函数,以确保程序的正常运行
char* (* stringFromJNI)(void);

//新的函数,替代hook的函数,返回修改后的值
char* newHello(void)
{
//直接返回新的字符串
return "cydia hook native success!";
//执行原函数,确保程序运行正常,但这里代码简单,可以直接返回字符串即可
//return hello();
}

//通过so库的绝对路径和函数名,找到其函数的映射地址
void* lookup_symbol(char* libraryname,char* symbolname)
{
//获取so库的句柄
void *handle = dlopen(libraryname, RTLD_GLOBAL | RTLD_NOW);
if (handle != NULL){
//根据so库句柄和符号名(即函数名)获取函数地址
void * symbol = dlsym(handle, symbolname);
if (symbol != NULL){
return symbol;
}else{

return NULL;
}
}else{
return NULL;
}
}

MSInitialize
{
//获取hook函数的地址,最好不要用下面MS提供的方法
void * symbol = lookup_symbol("/data/data/com.example.cydiatestdemo/lib/cydiatest-jni.so","stringFromJNI");
// MSImageRef image=MSGetImageByName("/data/data/com.samuelzhan.hello/lib/libhello.so");
// void *symbol=MSFindSymbol(image, "createHello");

//这里将旧函数的入口(参数一)指向hello(参数三),然后执行新函数(参数二)
MSHookFunction(symbol, (void*)&newHello, (void**)&stringFromJNI);
}

(4)生成Android.mk:Build -> Make Project ,会报错,但不用管 。进入:app/build/intermediates/ndk/debug目录,取出Android.mk,放在src/main/jni目录下。

2.2.2 相关配置

(1)build.gradle

img

defaultConfig内:

1
2
3
4
5
6
7
externalNativeBuild {
cmake {
cppFlags ""
//生成多个版本的so文件
abiFilters 'x86','x86_64'
}
}

android内:

1
2
3
4
5
externalNativeBuild {
cmake {
path "CMakeLists.txt" // 设置所要编写的c源码位置,以及编译后so文件的名字
}
}

(2)拷入CMakeLists.txt,放在与build.gradle同级目录下(即app目录下)

注意:生成的module必须以.cy为后缀

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
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.
#CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)


set(lib_src_DIR ${CMAKE_SOURCE_DIR}/src/main/jni)
include_directories(
${CMAKE_SOURCE_DIR}/src/main/jni
)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
# 设置so文件名称.
module.cy

# Sets the library as a shared library.
SHARED
# 设置这个so文件为共享.

# Provides a relative path to your source file(s).
# 设置这个so文件为共享.
src/main/jni/module.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log )



add_library(
substrate
SHARED
IMPORTED
)
set_target_properties(
substrate
PROPERTIES IMPORTED_LOCATION
${lib_src_DIR}/libsubstrate.so
)

add_library(
substrate-dvm
SHARED
IMPORTED
)
set_target_properties(
substrate-dvm
PROPERTIES IMPORTED_LOCATION
${lib_src_DIR}/libsubstrate-dvm.so
)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
# 制定目标库.
module.cy
substrate
substrate-dvm

# Links the target library to the log library
# included in the NDK.
${log-lib} )

引入第三方的头文件,需要指定其路径:

img

引入的第三方库,需要在CMakeLists中指定名字和路径:

img

3)AndroidManifest.xml配置:

添加权限:

<uses-permission android:name="cydia.permission.SUBSTRATE" />

Application标签的属性:android:hasCode="false"