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

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

博客: http://blog.androidcrack.com

参考:

小肩膀安卓系统沙箱课程

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函数完成
  3. 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
<think>我们正在讨论如何在Linux系统上使用frida-gadget动态库来加载另一个动态库(libdp2pre.so)。已知frida-gadget的版本是12.11.13,架构是linux-x86。FridaGadget通常用于将Frida运行时嵌入到目标应用中,从而支持在没有frida-server的情况下进行动态插桩。这里,我们想使用frida-gadget来加载libdp2pre.so,这实际上是通过修改目标程序的环境变量(如LD_PRELOAD)或者直接使用dlopen等方式来实现的。但是,请注意:frida-gadget本身就是一个动态库(.so)。因此,常见的方法是通过设置LD_PRELOAD环境变量来预加载frida-gadget,然后在gadget的初始化过程中,再去加载目标库(libdp2pre.so)。具体步骤如下:1.设置LD_PRELOAD环境变量指向frida-gadget-12.11.13-linux-x86.so。2.通过配置FridaGadget的配置文件(如配置文件名为config.json)来指定在gadget初始化时要加载的库(libdp2pre.so)。3.然后正常运行目标程序。另一种方法是:如果不使用配置文件,我们也可以使用Frida的API在gadget的脚本中主动加载libdp2pre.so。这通常需要编写一个JavaScript脚本来调用dlopen。这里我们主要讲解第一种方法(使用配置文件):首先,创建一个配置文件(例如config.json),内容如下:```json{"interaction":{"type":"script","path":"load_lib.js"}}```或者,如果只是想简单地加载库而不需要运行自定义脚本,我们可以使用以下配置(但这种配置方式并不是直接加载另一个so,所以可能需要通过脚本来完成):因此,我们选择在脚本中加载。创建load_lib.js文件:```javascriptJava.perform(function(){//在Android上通常这样加载,但在普通的Linux环境中,我们使用纯Node.js的API?注意:Frida在Linux上运行,通常使用Process模块//使用dlopen加载库constdlopen=newNativeFunction(Module.getExportByName(null,&#39;dlopen&#39;),&#39;int&#39;,[&#39;pointer&#39;,&#39;int&#39;]);constRTLD_NOW=2;varpath="/path/to/libdp2pre.so";//替换为实际路径varhandle=dlopen(Memory.allocUtf8String(path),RTLD_NOW);if(handle.isNull()){console.error("Failedtoloadlibrary:"+path);}else{console.log("Libraryloadedsuccessfully:"+path);}});```然后,将这两个文件(config.json和load_lib.js)与frida-gadget-12.11.13-linux-x86.so放在一起(或者指定路径)。接下来,运行目标程序:```bashLD_PRELOAD=./frida-gadget-12.11.13-linux-x86.so./your_target_program```注意:上面的配置会使得frida-gadget在启动时加载load_lib.js脚本,该脚本会使用dlopen加载libdp2pre.so。但是,需要注意的是,目标程序必须是x86架构的(与frida-gadget和libdp2pre.so的架构一致)。另外,如果目标程序是Android应用,那么情况有所不同(需要使用Android的加载方式),但问题中指定的是Linux系统。总结步骤:1.准备好frida-gadget-12.11.13-linux-x86.so文件,以及要加载的libdp2pre.so文件(注意其路径,并在脚本中写正确)。2.创建配置文件config.json,指定一个初始化脚本(load_lib.js)。3.编写load_lib.js脚本,使用dlopen加载libdp2pre.so。4.使用LD_PRELOAD加载frida-gadget,然后运行目标程序。重要:确保所有文件的路径正确,并且权限允许。另外,如果目标程序已经运行,我们也可以使用Frida注入的方式(但这里要求使用gadget预加载,所以使用LD_PRELOAD)。如果不想在每次运行程序时都写LD_PRELOAD,也可以将gadget重命名为libdl.so,然后设置环境变量LD_PRELOAD=libdl.so,但是注意这种方式可能会干扰正常的dlopen(因为libdl.so是系统的动态链接库),所以通常不推荐。替代方案:使用patchelf工具将frida-gadget添加到目标程序的DT_NEEDED列表中,然后将目标程序重新链接。但是这样需要修改目标程序的可执行文件。因此,最简便的方式还是使用LD_PRELOAD。相关问题:1.如果目标程序是64位的,而frida-gadget是32位的,会出现什么问题?如何解决?2.FridaGadget的配置文件是否支持其他交互方式(如listen)?3.除了使用dlopen,还有哪些方式加载动态库?4.在Android系统上使用frida-gadget加载so有什么不同?以上步骤即为在Linux系统上使用frida-gadget加载libdp2pre.so的方法。注意:上面的示例脚本中使用了`Java.perform`,这是因为在Frida的脚本中,如果目标进程是Java进程(比如Android),我们通常需要这样写。但在普通Linux进程中,可能没有Java环境,因此需要去掉`Java.perform`,直接写:```javascript//load_lib.js修改为适用于Linux的普通进程constdlopen=newNativeFunction(Module.getExportByName(null,&#39;dlopen&#39;),&#39;int&#39;,[&#39;pointer&#39;,&#39;int&#39;]);constRTLD_NOW=2;varpath="/path/to/libdp2pre.so";varpathPtr=Memory.allocUtf8String(path);varhandle=dlopen(pathPtr,RTLD_NOW);if(handle.isNull()){console.error("Failedtoloadlibrary:"+path);}else{console.log("Libraryloadedsuccessfully:"+path);}```同时,确保在Java进程中去掉`Java.perform`。如果目标程序是命令行程序,还可以使用frida的CLI工具(如frida)进行注入,但这里使用gadget的方式,所以采用预加载。最后,运行程序时,可能还需要注意设置库的搜索路径(如果libdp2pre.so不在默认路径下,需要设置LD_LIBRARY_PATH),或者在脚本中使用绝对路径。例如:```bashLD_PRELOAD=./frida-gadget-12.11.13-linux-x86.soLD_LIBRARY_PATH=/path/to/libs./your_target_program```或者在脚本中使用绝对路径。以上即为完整的步骤。</think> 要使用 `frida-gadget-12.11.13-linux-x86.so` 加载 `libdp2pre.so` 文件,需通过以下步骤实现 Frida 的动态注入和交互: --- ### **核心步骤** #### 1. **创建配置文件 `config.json`** ```json { "interaction": { "type": "script", "path": "./hook.js", "on_change": "reload" } } ``` > 📝 **说明**:此配置使 Frida Gadget 在加载时自动执行 `hook.js` 脚本。 --- #### 2. **编写注入脚本 `hook.js`** ```javascript // hook.js Module.load("/path/to/libdp2pre.so"); Interceptor.attach(Module.findExportByName("libdp2pre.so", "目标函数符号"), { onEnter: function(args) { console.log("[+] libdp2pre.so 已加载!"); console.log("函数参数: ", args[0].toInt32()); } }); ``` > 📝 **说明**: > - 使用 `Module.load()` 动态加载目标库。 > - `Interceptor.attach()` 用于监听库中的函数调用(替换 `目标函数符号` 为实际函数名)。 --- #### 3. **加载 Frida Gadget** 通过环境变量强制目标程序预加载 Frida Gadget: ```bash # 方式1:动态链接预加载 LD_PRELOAD=/path/to/frida-gadget-12.11.13-linux-x86.so /path/to/目标程序 # 方式2:直接启动 Gadget (调试用) ./frida-gadget-12.11.13-linux-x86.so -c ./config.json ``` --- #### 4. **远程调试连接** 若需远程控制,添加监听端口配置: ```json { "interaction": { "type": "listen", "address": "0.0.0.0", "port": 27042 } } ``` 连接命令: ```bash frida -H IP地址:27042 目标程序 ``` --- ### **关键技术原理** 1. **动态加载机制** Frida Gadget 通过 `LD_PRELOAD` 劫持程序启动流程,优先加载自身并初始化 Frida 运行时环境。 2. **脚本注入** JavaScript 脚本通过 `Module.load()` 直接调用 Linux 动态链接器(`dlopen`)加载目标 so 文件。 3. **函数拦截** 使用 `Interceptor.attach()` 修改目标函数入口点,实现参数监控或逻辑篡改。 --- ### **注意事项** - **路径问题** 确保配置文件和脚本路径正确,建议使用绝对路径(如 `/home/user/hook.js`)。 - **权限要求** 目标程序需具备执行权限,且 Gadget so 文件需与程序架构一致(此处为 x86)。 - **调试输出** 在脚本中添加 `console.log()` 实时观察输出,或通过 `frida-trace` 追踪库调用。 --- ### **应用场景示例** ```bash # 场景:监控游戏程序加载的作弊模块 LD_PRELOAD=./frida-gadget-12.11.13-linux-x86.so ./game_app ``` > 日志输出: > ``` > [+] libdp2pre.so 已加载! > 函数参数: 1337 > ``` --- ### 相关问题 1. **如何避免 Frida Gadget 被反调试机制检测?** 2. **Frida 脚本能否修改 `libdp2pre.so` 中的全局变量?** 3. **如何在 Android 设备上通过 Frida Gadget 加载 so?** 4. **如何确保多个 so 文件的加载顺序?** 5. **Frida 的 `Module.load()` 与 Linux `dlopen()` 有何区别?**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值