基于Android9的非root环境下frida持久化
参考:
小肩膀安卓系统沙箱课程
1. 为什么?
为什么要使用基于Android源码修改的frida-gadget持久化方案?
FridaGadget是一种免ROOT的注入方式,通过修改程序加载动态库而实现HOOK。
- 过root检测
- 免修改目标app
- 脱离PC
- 持久化
2.怎么做?
1. 注入frida-gadget方案
想要使用frida-gadget
,就需要把其注入到目标应用中. 按照一般流程. 我们应该是以下步骤:
存在so的情况下,我们可以修改其导入的so文件
pip3 install lief
for soname in injectsolist: #遍历apk中指定SO有哪几种架构,并添加gadget.so为依赖库。
if soname.find("x86") != -1:
continue
so = lief.parse(os.getcwd()+"\\"+soname)
so.add_library("libfrida-gadget.so")
so.write(soname+"gadget.so")
apk中没有so的情况下,修改入口文件
在没有so的情况下,我们通常是修改apk的入口文件,也就是修改smali代码。通过调用System.loadLibrary
来加载so
工具详细代码:https://github.com/nszdhd1/UtilScript/blob/main/SmaliInjectFrida.py
def injectso(self):
target_activity = self.get_launchable_activity_aapt()
print(target_activity)
for dex in self.dexList:
print(dex)
if self.dexDecompile(dex):
smali_path = os.path.join(self.decompileDir,target_activity.replace('.','\\'))+".smali"
print(smali_path)
with open(smali_path, 'r') as fp:
lines = fp.readlines()
has_clinit = False
start = 0
for i in range(len(lines)):
if lines[i].find(".source") != -1:
start = i
if lines[i].find(".method static constructor <clinit>()V") != -1:
if lines[i + 3].find(".line") != -1:
code_line = lines[i + 3][-3:]
lines.insert(i + 3, "%s%s\r" % (lines[i + 3][0:-3], str(int(code_line) - 2)))
print("%s%s" % (lines[i + 3][0:-3], str(int(code_line) - 2)))
lines.insert(i + 4, "const-string v0, \"frida-gadget\"\r")
lines.insert(i + 5,
"invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\r")
has_clinit = True
break
if not has_clinit:
lines.insert(start + 1, ".method static constructor <clinit>()V\r")
lines.insert(start + 2, ".registers 1\r")
lines.insert(start + 3, ".line 10\r")
lines.insert(start + 4, "const-string v0, \"frida-gadget\"\r")
lines.insert(start + 5,
"invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\r")
lines.insert(start + 6, "return-void\r")
lines.insert(start + 7, ".end method\r")
with open(smali_path, "w") as fp:
fp.writelines(lines)
self.dexCompile(dex)
但是,在这种方法的情况下,我们就需要考虑 重打包检测(签名检测,文件完整性检测)的情况了。
因此,我们可以使用一种更加舒服的姿势来实现frida持久化,也就是修改aosp的源码啦。
2. 修改Android启动流程
修改位置frameworks/base/core/java/android/app/ActivityThread.java
的handleBindApplication
中,添加了如下代码,进行控制:
String curPkgName = data.appInfo.packageName;
int curUid = Process.myUid();
if (curUid > 10000) {
Persist.LOGD("curPkgName: " + curPkgName + " curUid: " + curUid);
Boolean isPersist = Persist.isEnablePersist(curPkgName);
Persist.LOGD("isPersist: " + isPersist);
if (isPersist) {
if(Persist.doXiaojianbangPersist(appContext, curPkgName)){
Persist.LOGD("doXiaojianbangPersist is ok");
}else {
Persist.LOGD("doXiaojianbangPersist failed");
};
}
}
3. 添加自定义包
其中Persist
是自定义类
,该类封装了以下方法:
- saveFile 文件存储
- copyFile 文件复制
- isEnablePersist 判断app是否打开自动注入脚本功能 (通过判断某个标志文件是否存在来确定是否打开)
- getConfigJSPath 获取js配置文件
- copyJSFile 复制js文件到私有目录
- genGadgetConfig 生成gadget的配置文件
- copySoFile 复制gadget文件到app私有目录
- doXiaojianbangPersist 载入gadget的so文件
package com.xiaojianbang;
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;
}
}
此时添加了自定义的包和类,还不能够使用,因为Android编译的时候不会搭理它,需要添加入白名单~
/build/make/core/tasks/check_boot_jars/package_whitelist.txt
在该文件最后面添加后,才可以编译通过。
// add
com\.xiaojianbang
# // add
4. 内置frida-gadget到系统
#### 1. 将 frida-gadget 放到源码目录,比如如下文件夹中
在cmds
文件夹中创建libxiaojianbang
/frameworks/base/cmds/libxiaojianbang
2.修改源码以下文件,将 frida-gadget 拷贝到编译以后的系统中
android10:
/build/make/target/product/handheld_system.mk
添加以下数据,自动拷贝文件。
# // add
PRODUCT_COPY_FILES += \
frameworks/base/cmds/xiaojianbang/frida-gadget-14.2.18-android-arm.so:$(TARGET_COPY_OUT_SYSTEM)/lib/libxiaojianbang.so \
frameworks/base/cmds/xiaojianbang/frida-gadget-14.2.18-android-arm64.so:$(TARGET_COPY_OUT_SYSTEM)/lib64/libxiaojianbang.so
# // add
android9:
在Android9中不存在handheld_system.mk
文件,于是乎我选择了/build/make/target/product/sdk_base.mk
在include $(SRC_TARGET_DIR)/product/emulator.mk
上添加就阔以了。
5. 自定义目录设计
/data/system/xsettings/xiaojianbang/persist/pkgName/xiaojianbang_persist
/data/system/xsettings/xiaojianbang/jscfg/pkgName/config.jspersist_xiaojianbang
文件存在,则表示开启持久化config.js
表示用于注入的hook代码
但是xsettings
和xiaojianbang
都不是自带的路径.
所以,我们需要开机创建一下自定义目录.
/system/core/rootdir/init.rc
在chown root radio /proc/cmdline
下面添加代码
# // add
# /data/system/xsettings/xiaojianbang/persist
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
# // add
3. frida持久化管理APP
- 管理app的功能
显示已安装app列表
可以对每个app指定需要注入的JS
可以设置是否启用持久化 相应功能实现原理
- 创建表示启用的文件
/data/system/xsettings/xiaojianbang/persist/pkgName/xiaojianbang_persist - 指定的JS文件复制到以下目录
/data/system/xsettings/xiaojianbang/jscfg/pkgName/config.js - 剩下的复制so、JS文件和加载so的操作,由魔改的doXiaojianbangPersist函数完成
- 创建表示启用的文件
- system权限的app
方式一:单独编译
- 在 manifest 中加入 android:sharedUserId="android.uid.system"
- 将编译出来的app放入 /packages/apps/xiaojianbangPersist
- 编写Android.mk,也放入该文件夹
- 单独编译指定模块 mmm packages/apps/xiaojianbangPersist
- 编译后的模块在 /out/target/product/sailfish/system/app/ControlAPP
- 使用 make snod 将编译出来的文件打包成镜像,刷入system.img即可
方式二:整体编译
如果要在编译整个系统时,一起编译这个模块,需要将模块 ControlAPP 加入源码编译链
- 增加的内置模块,如果为APP,加入到如下文件中
/build/make/target/product/handheld_product.mk
- 增加的内置模块,如果为可执行程序,加入到如下文件中
/build/make/target/product/base_system.mk
大佬,我按着这个想实现,app加载so,但是编译遇到一些问题,可以帮帮我吗?
这个好啊 深入一下再弄一个定制app 就可以直接控制frida的持久化了 可以外连server控制 而且系统级也不用担心重打包被检测 点赞!
在App中搞个websocket客户端, 直接群控搞起。