基于Android9的非root环境下frida持久化

参考:

小肩膀安卓系统沙箱课程

https://bbs.pediy.com/thread-268175.htm

1. 为什么?

为什么要使用基于Android源码修改的frida-gadget持久化方案?

FridaGadget是一种免ROOT的注入方式,通过修改程序加载动态库而实现HOOK。

  1. 过root检测
  2. 免修改目标app
  3. 脱离PC
  4. 持久化

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)

但是,在这种方法的情况下,我们就需要考虑 重打包检测(签名检测,文件完整性检测)的情况了。

https://bbs.pediy.com/thread-268256.htm

因此,我们可以使用一种更加舒服的姿势来实现frida持久化,也就是修改aosp的源码啦。

>2. 修改Android启动流程

修改位置frameworks/base/core/java/android/app/ActivityThread.javahandleBindApplication中,添加了如下代码,进行控制:

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自定义类,该类封装了以下方法:

  1. saveFile 文件存储
  2. copyFile 文件复制
  3. isEnablePersist 判断app是否打开自动注入脚本功能 (通过判断某个标志文件是否存在来确定是否打开)
  4. getConfigJSPath 获取js配置文件
  5. copyJSFile 复制js文件到私有目录
  6. genGadgetConfig 生成gadget的配置文件
  7. copySoFile 复制gadget文件到app私有目录
  8. 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.js
persist_xiaojianbang文件存在,则表示开启持久化
config.js表示用于注入的hook代码

但是xsettingsxiaojianbang都不是自带的路径.

所以,我们需要开机创建一下自定义目录.

/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

  1. 管理app的功能
    显示已安装app列表
    可以对每个app指定需要注入的JS
    可以设置是否启用持久化
  2. 相应功能实现原理

    1. 创建表示启用的文件
      /data/system/xsettings/xiaojianbang/persist/pkgName/xiaojianbang_persist
    2. 指定的JS文件复制到以下目录
      /data/system/xsettings/xiaojianbang/jscfg/pkgName/config.js
    3. 剩下的复制so、JS文件和加载so的操作,由魔改的doXiaojianbangPersist函数完成
  1. system权限的app

方式一:单独编译

  1. 在 manifest 中加入 android:sharedUserId="android.uid.system"
  2. 将编译出来的app放入 /packages/apps/xiaojianbangPersist
  3. 编写Android.mk,也放入该文件夹
  4. 单独编译指定模块 mmm packages/apps/xiaojianbangPersist
  5. 编译后的模块在 /out/target/product/sailfish/system/app/ControlAPP
  6. 使用 make snod 将编译出来的文件打包成镜像,刷入system.img即可

方式二:整体编译

如果要在编译整个系统时,一起编译这个模块,需要将模块 ControlAPP 加入源码编译链

  1. 增加的内置模块,如果为APP,加入到如下文件中
    /build/make/target/product/handheld_product.mk
  2. 增加的内置模块,如果为可执行程序,加入到如下文件中
    /build/make/target/product/base_system.mk