背景

想做frida持久化hook,通过搜索资料发现gadget可以。

这是官方的资料:https://frida.re/docs/gadget/

这里有详细的使用方法,比如反编译apk插入so文件,如果这个app没有so库,就需要自己在application自己加代码load so,但是这些都不是我现在需要的操作。

我既然修改的是AOSP,在app加载的时候加载so就行了。

经过搜索资料发现了这个博客:http://zhuoyue360.com/crack/78.html

这里面应该学习了某个大佬的课程然后分享出来的,经过分析和实践验证了博客中的一部分内容是可行的,有一些重要的部分是没有的,但是主干思路有了之后剩下的部分有盼头了。

实践与验证

环境:

AOSP 10 r41

pixel 3

Ubuntu 18.04


修改app的启动流程,在启动期间去加载so。

frameworks/base/core/java/android/app/ActivityThread.java中的handleBindApplication

方法中,找到Application创建之前的位置,经过我的测试onCreate之后加载so也可以的!加入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//data是前面已经存在的app信息,获取到当前app的包名,用于判断某个目录下是否存在
//某个文件,文件存在做某事
String curPkgName = data.appInfo.packageName;
int curUid = Process.myUid();
//获取进程id,猜测大部分系统进程id都是10000内,应该是过滤了系统的app
if (curUid > 10000) {
//这个类后面会给出来,是复制文件,判断文件夹等等用途
Persist.LOGD("curPkgName: " + curPkgName + " curUid: " + curUid);
//判断当前app是否激活了持久化hook,就是判断包目录下是否存在某个文件
Boolean isPersist = Persist.isEnablePersist(curPkgName);
Persist.LOGD("isPersist: " + isPersist);
if (isPersist) {
//复制hook脚本到app安装目录下,然后加载gadget so
if(Persist.doXiaojianbangPersist(appContext, curPkgName)){
Persist.LOGD("doXiaojianbangPersist is ok");
}else {
Persist.LOGD("doXiaojianbangPersist failed");
};
}
}

这阶段主要是判断app是否需要持久化hook,不是所有的app都需要这样,正常流程。

下面是Persist这个类的加入了,其实这个类加入到系统编译不需要这么麻烦,直接在ActivityThread.java的同级目录下创建这个类就行了,整个流程这个类其实就是给ActivityThread.java使用的,如果按照上面博客的操作还得给这个类配置白名单。

在编译阶段复制so到system/lib下

/frameworks/base/cmds/目录下创建一个自己的目录,把GitHub上面下载的gadget的so文件放到目录中。

下载地址:https://github.com/frida/frida/releases随便下载一个版本,我习惯用12.x版本或者14.x版本。高版本有bug不考虑了。

下载好之后放进去Ubuntu解压xz文件

1
xz -d xxxx.tar.xz

这样就得到了so文件了,arm和arm64都下载回来。

添加复制so的脚本

1
2
3
4
5
6
7
//源码根目录下,在打开这个文件
/build/make/target/product/handheld_system.mk
//找到PRODUCT_COPY_FILES地方
//myfrida这个目录就是你创建的,你可以随便写自己的。
PRODUCT_COPY_FILES += \
frameworks/base/cmds/myfrida/frida-gadget-14.2.18-android-arm.so:$(TARGET_COPY_OUT_SYSTEM)/lib/myfrida.so \
frameworks/base/cmds/myfrida/frida-gadget-14.2.18-android-arm64.so:$(TARGET_COPY_OUT_SYSTEM)/lib64/myfrida.so

加入这个之后,保存文件,编译刷机之后怎么验证是否成功了呢。

adb shell进入system/lib和system/lib64目录下查看是否存在myfrida.so,这个命名不要和里面的so重名,不然就挂了。

如果你已经做到这一步了,你会发现没有什么用,app启动的时候其实没有注入so的,上面的判断是否持久化没有成立的。

1
Boolean isPersist = Persist.isEnablePersist(curPkgName);

这里是没有成立的。分析这个方法。

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import android.content.Context;
import android.util.Log;
import android.os.Process;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.json.JSONObject;


public class Persist {

public static final String SO_NAME = "libxiaojianbang.so";
public static final String SO_CONFIG_NAME = "libxiaojianbang.config.so";
public static final String LIB32_DIR = "/system/lib";
public static final String LIB64_DIR = "/system/lib64";

public static final String SETTINGS_DIR = "/data/system/xsettings/xiaojianbang/persist";
public static final String ENABLE_PERSIST_FILE_NAME = "xiaojianbang_persist";

public static final String CONFIG_JS_DIR = "/data/system/xsettings/xiaojianbang/jscfg";
public static final String CONFIG_JS_FILE_NAME = "config.js";

public static final String TAG_NAME = "xiaojianbang_persist";


public static void LOGD(String msg) {
Log.d(TAG_NAME, msg);
}

private static boolean saveFile(String filePath, String textMsg) {
try{
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write(textMsg.getBytes("utf-8"));
fileOutputStream.flush();
fileOutputStream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

private static boolean copyFile(File srcFile, File dstFile) {
try{
FileInputStream fileInputStream = new FileInputStream(srcFile);
FileOutputStream fileOutputStream = new FileOutputStream(dstFile);
byte[] data = new byte[16 * 1024];
int len = -1;
while((len = fileInputStream.read(data)) != -1) {
fileOutputStream.write(data,0, len);
fileOutputStream.flush();
}
fileInputStream.close();
fileOutputStream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
// 判断app是否打开自动注入脚本功能
public static boolean isEnablePersist(String pkgName) {
// 判断文件是否存在 /data/system/xsettings/xiaojianbang/persist/com.xiaojianbang.app/xiaojianbang_persist
File enableFile = new File(SETTINGS_DIR, pkgName + File.separator + ENABLE_PERSIST_FILE_NAME);
return enableFile.exists();
}
// 获取源JS文件路径
private static File getConfigJSPath(String pkgName) {
// /data/system/xsettings/xiaojianbang/jscfg/com.xiaojianbang.app/config.js
return new File(CONFIG_JS_DIR, pkgName + File.separator + CONFIG_JS_FILE_NAME);
}
// 拷贝源JS文件到app私有目录
private static File copyJSFile(Context context, String pkgName) {
// 判断源JS文件是否存在
File srcJSFile = getConfigJSPath(pkgName);
if(!srcJSFile.exists()) {
LOGD("srcJSFile not exists");
return null;
}
// 拷贝源JS文件到app私有目录
// /data/data/com.xiaojianbang.app/files/config.js
File dstJSFile = new File(context.getFilesDir(), CONFIG_JS_FILE_NAME);
boolean isCopyJSOk = copyFile(srcJSFile, dstJSFile);
if(!isCopyJSOk){
LOGD("copyJSFile fail: " + srcJSFile + " -> " + dstJSFile);
return null;
}
return dstJSFile;
}
// 生成Gadget配置文件
private static boolean genGadgetConfig(Context context, File dstJSFile) {
JSONObject jsonObject = new JSONObject();
JSONObject childObj = new JSONObject();
try {
childObj.put("type", "script");
childObj.put("path", dstJSFile.toString());
jsonObject.put("interaction", childObj);
}catch (Exception e){
e.printStackTrace();
return false;
}
String configFilePath = context.getFilesDir() + File.separator + SO_CONFIG_NAME;
boolean isSaveOk = saveFile(configFilePath, jsonObject.toString());
if(!isSaveOk){
LOGD("saveFile fail: " + configFilePath);
return false;
}
return true;
}
// 拷贝源so文件到app私有目录
private static File copySoFile(Context context) {
// 判断源so文件是否存在
// /system/lib/libxiaojianbang.so
// /system/lib64/libxiaojianbang.so
File srcSoFile = new File(LIB32_DIR, SO_NAME);
if(Process.is64Bit()) {
srcSoFile = new File(LIB64_DIR, SO_NAME);
}
if(!srcSoFile.exists()) {
LOGD("srcSoFile not exists");
return null;
}
// 拷贝源so文件到app私有目录
// /data/data/com.xiaojianbang.app/files/libxiaojianbang.so
File dstSoFile = new File(context.getFilesDir(), SO_NAME);
if(srcSoFile.length() != dstSoFile.length()) {
boolean isCopyFileOk = copyFile(srcSoFile, dstSoFile);
if(!isCopyFileOk){
LOGD("copySoFile fail: " + srcSoFile + " -> " + dstSoFile);
return null;
}
}
return dstSoFile;
}
// 进行Frida Gadget持久化
public static boolean doXiaojianbangPersist(Context context, String pkgName) {
File dstJSFile = copyJSFile(context, pkgName);
if(null == dstJSFile) return false;
if(!genGadgetConfig(context, dstJSFile)) return false;
File dstSoFile = copySoFile(context);
if(null == dstSoFile) return false;
System.load(dstSoFile.toString());
return true;
}

}

是否激活持久化是这样判断的:判断文件是否存在

1
/data/system/xsettings/xiaojianbang/persist/com.xiaojianbang.app/xiaojianbang_persist

这个文件是否存在。仔细发现我们的系统中并没有xsettings这个目录,需要我们创建。

持久化相关的目录

/system/core/rootdir/init.rc在手机启动的时候处理我们需要的目录。

chown root radio /proc/cmdline下面添加代码

1
2
3
4
mkdir /data/system/xsettings 0775 system system
mkdir /data/system/xsettings/xiaojianbang 0775 system system
mkdir /data/system/xsettings/xiaojianbang/persist 0775 system system
mkdir /data/system/xsettings/xiaojianbang/jscfg 0775 system system

其实不一定要在chown root radio /proc/cmdline下面添加代码,这里的操作无非是创建目录给权限。

只要mkdir /data/system目录创建之后去执行上面的代码就行了,也就是先有父目录,后面就可以创建子目录了。

OK,事情都好像很顺利,开心的编译刷机,然后进入shell中创建目录,持久化文件,app启动的时候注入so成功了。

1
/data/system/xsettings/xiaojianbang/jscfg/pkgName/config.js

在这里写入hook脚本写成功了。

注意,这里是通过shell去做的,博客也没有说,我是这样先做注入测试的。

我们需要针对某个app进行持久化hook每次都这样手动进入shell去设置是有点麻烦的,能不能开发一个app来做这件事:

1:查看当前用户安装的app列表。

2:选择某个app配置js脚本。js脚本也可能多个,做列表来显示多个脚本,可以选择其中一个来hook。

3:是否激活持久化hook。

写app嘛,老本行了,说干就干。

编写管理脚本的app

需求已经明确了,开始撸码。

根据上面Persist这个类的规则,我们想激活某个app进行持久化hook就需要在/data/system/xsettings/xiaojianbang/persist/pkgName/xiaojianbang_persist这里有一个文件,举个例子:

包名是com.demo。就需要在/data/system/xsettings/xiaojianbang/persist 下创建目录com.demo切在目录中创建一个文件,空文件就行。

最后是这样: /data/system/xsettings/xiaojianbang/persist/com.demo/xiaojianbang_persist

创建目录和创建文件都不难,这里会出问题,我们直接写app创建是没有权限的,普通用户的app无法操作系统目录。

那如果我们也是系统级app呢?这就到了AOSP内置apk的问题了。

我的哔站有视频:https://www.bilibili.com/video/BV1S14y1K7Ew

我的公众号:https://mp.weixin.qq.com/s/c9TT25UR5JCmTHFOXRVnvQ

这个管理的app是无so的,直接复制脚本就行了。签名使用platform

编译apk的时候需要在配置文件AndroidMainfest.xml中加入android:sharedUserId="android.uid.system"这样app有系统权限了。

一切都很美好~,创建目录,创建文件。。。Permission Denial

还是没有权限。。。

配置新增目录的SEPolicy

这里需要配置的内容就比较多了,下一篇给出全部配置代码。

欢迎关注公众号:黄大官AOSP