目录

充电学习中...

X

使用frida-gumjs将sekiro注入到任意app进程

使用frida-gumjs将sekiro注入到任意app进程

在之前的文章中,我们讨论了原生环境下,使用NPS和Sekiro在黑盒调用场景下的一些差别和优劣,其中提到Sekiro相比于NPS,对目标应用的代码有入侵性,在实际应用时,如果要向目标app进程注入sekiro,常用的途径一般是利用xposed模块,除此之外可能就需要解包添加代码。而本文重点介绍这一需求的另一种实现方式,即先将Sekiro打包成dex文件,然后通过Frida进行载入。当然,在这之前我们需要先将Frida进行持久化,实现的方式有两中,一是使用frida-inject实现脱离pc环境运行,二是借助Magisk的Riru模块将frida-gumjs引擎注入孵化器。由于第一种方案需要执行shell命令来启动app,因此我们暂不考虑。第二种实现方式见文章

1.将Sekiro打包成Dex文件

这一步骤我们直接用Android Studio实现,新建一个空白的安卓项目,然后引入Sekiro库,如下

implementation 'com.virjar.sekiro.business:sekiro-business-api:1.4'

Sekiro新版本是从其商业版Fork而来,原先免费版已经停止维护,新版本的api使用了一些注解特性,由于我没有找到如何使用frida去实现Java的注解功能,因此这部分我们使用Java去实现,如下,我们硬编码实现Sekiro需要的SekiroRequestInitializer、RequestHandler两个接口

package com.virjar.sekiro.inject;

import com.virjar.sekiro.business.api.interfaze.HandlerRegistry;
import com.virjar.sekiro.business.api.interfaze.SekiroRequest;
import com.virjar.sekiro.business.api.interfaze.SekiroRequestInitializer;

public class BaseSekiroRequestInitializer implements SekiroRequestInitializer {
    @Override
    public void onSekiroRequest(SekiroRequest sekiroRequest, HandlerRegistry handlerRegistry) {
        handlerRegistry.registerSekiroHandler(new BaseRequestHandler());
    }
}

package com.virjar.sekiro.inject;

import com.virjar.sekiro.business.api.fastjson.JSONObject;
import com.virjar.sekiro.business.api.interfaze.Action;
import com.virjar.sekiro.business.api.interfaze.AutoBind;
import com.virjar.sekiro.business.api.interfaze.RequestHandler;
import com.virjar.sekiro.business.api.interfaze.SekiroRequest;
import com.virjar.sekiro.business.api.interfaze.SekiroResponse;

@Action("handler")
public class BaseRequestHandler implements RequestHandler {
    @AutoBind
    private String data;
    //如需自定义实现  可以用frida去重写此方法
    @Override
    public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
        sekiroResponse.success("ok");
    }
}


需要注意的是,上面的BaseRequestHandler放了一个空实现,在实际使用中,我们往往需要根据请求参数,执行目标app的方法,然后返回结果,这一功能可以通过Frida去Hook handleRequest方法实现。

项目准备完毕后,执行"Build-Rebuild Project",执行完毕后在app/build/intermediates/dex目录下能找到对应的dex文件,然后我们将dex文件推送到设备目录下,这个目录可以自已定,例如/data/local/tmp下

2.编写Frida脚本

Frida脚本比较简单,加载dex,然后启动Sekiro即可,例如:

Java.perform(function(){
    Java.openClassFile("/data/local/tmp/sekiro.dex").load();
    var id = "client:"+new Date().getTime();
    var SekiroIns = Java.use('com.virjar.sekiro.business.api.SekiroClient').$new("youer-group",id , "youerhost", youerport);
    SekiroIns.setupSekiroRequestInitializer(Java.use('com.virjar.sekiro.inject.BaseSekiroRequestInitializer').$new()).start();
    log("sekiro 注入成功!");
});

如需自定义请求处理,则重载handleRequest即可,如下

            var BaseRequestHandler = Java.use('com.virjar.sekiro.inject.BaseRequestHandler');
            BaseRequestHandler.handleRequest.implementation = function(sekiroRequest,sekiroResponse)
            {
                //处理请求代码
            }

3.测试连接

调用sekiro的api,查看对应group的在线client列表,如下图

image.png

4.快速调用

在上述基础上,我对dex做了一些修改,使其能够兼容一些方法调用,前提是被调用的方法的参数类型,返回值等都是基本类型。具体的实现效果是,对于符合要求的方法,frida脚本无需做任何修改,只要在sekiro的api中传入正确的参数即可。

1)入参结构体

例子

{
    "classname": "com.xxy.test.MainActivity",
    "method": "GetText",
    "staticmethod": false,
    "instancefuncentity": {
        "classname": "com.xxy.test.MainActivity",
        "method": "$new",
        "parms": [
            {
                "value": "xxy",
                "type": "java.lang.String"
            }
        ]
    },
    "parms": [
        {
            "value": "xxy",
            "type": "java.lang.String"
        },
        {
            "value": "false",
            "type": "boolean"
        },
        {
            "value": "nb",
            "type": "[B"
        }
    ]
}

对应的Java实体类

package com.virjar.sekiro.inject;

import java.util.List;

//调用所需参数
public class CallEntity {
    //待调用类名 全路径
    public String classname;
    //方法名
    public String method;
    //是否为静态方法
    public boolean staticmethod;
    //如果为非静态方法 此字段为获取对象的方法
    public CallEntity instancefuncentity;
    //参数列表
    public List<CallParm> parms;
    public class CallParm {
        //参数 字符串值
        public   String value;
        //参数类型
        public   String type;
    }

}


2)修改代码实现

修改handle实现,将上述json内容解释并执行调用,由于传入的参数不定长,因此用到了大量反射实现

package com.virjar.sekiro.inject;

import com.virjar.sekiro.business.api.fastjson.JSONObject;
import com.virjar.sekiro.business.api.interfaze.Action;
import com.virjar.sekiro.business.api.interfaze.AutoBind;
import com.virjar.sekiro.business.api.interfaze.RequestHandler;
import com.virjar.sekiro.business.api.interfaze.SekiroRequest;
import com.virjar.sekiro.business.api.interfaze.SekiroResponse;

@Action("handler")
public class BaseRequestHandler implements RequestHandler {
    @AutoBind
    private String data;
    //自定义实现  可以用frida去重写此方法
    @Override
    public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
        try {
            CallEntity entity = JSONObject.parseObject(data,CallEntity.class);
            sekiroResponse.success(CallMethods.CallMethod(entity,true));
        } catch (Exception ex) {
            sekiroResponse.failed(405, ex);
        }
    }
}


package com.virjar.sekiro.inject;

import org.json.JSONObject;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;

public class CallMethods {
    //实例保存在map中 不重复实例化
    static HashMap<String,Object> InsMap = new HashMap<>();
    //反射调用
    public static Object CallMethod(CallEntity entity,boolean asstring) throws Exception {
        int size = entity.parms == null?0:entity.parms.size();
        Class[] parmtypes = new Class[size];
        Object[] parmvalues = new Object[size];
        //组装请求类型和参数列表 将字符串解释为java基本类型
        for (int i = 0; i < size; i++) {
            CallEntity.CallParm p = entity.parms.get(i);
            parmtypes[i] = FridaType2Java(p.type);
            parmvalues[i] = String2Target(p.value, p.type);
        }
        //目标类
        Class call_class = FridaType2Java(entity.classname);
        //反射method相关方法供后续使用
        Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod",String.class, Class[].class);
        Method getMethod = Class.class.getDeclaredMethod("getMethod",String.class, Class[].class);
        Method methodinvoke = Method.class.getDeclaredMethod("invoke",Object.class, Object[].class);

        Method targetmethod = null;
        Object result = null;
        //如果是非静态函数调用  需要用到构造函数 $new是frida写法
        if(entity.method.toLowerCase().equals("$new"))
        {
            if(!InsMap.containsKey(entity.classname))
            {
                Method con_method = Class.class.getDeclaredMethod("getConstructor", Class[].class);
                Constructor con = (Constructor) con_method.invoke(call_class,new Object[]{parmtypes});
                result = Constructor.class.getDeclaredMethod("newInstance",Object[].class).invoke(con,new Object[]{parmvalues});
                InsMap.put(entity.classname,result);
            }
            else
            {
                result = InsMap.get(entity.classname);
            }
        }
        else {
            Object temp = getDeclaredMethod.invoke(call_class, new Object[]{entity.method,parmtypes});
            if (temp == null) {
                temp = getMethod.invoke(call_class, new Object[]{entity.method,parmtypes});
            }
            if (temp == null) {
                throw new Exception("未找到调用方法或其重载");
            }
            targetmethod = (Method) temp;
            Object ins = null;
            //非静态方法需要实例化
            if (!entity.staticmethod) {
                if(entity.instancefuncentity == null||entity.instancefuncentity.classname == null||entity.instancefuncentity.classname.isEmpty())
                {
                    throw new Exception("非静态方法需要提供获取实例的函数");
                }
                ins = CallMethod(entity.instancefuncentity,false);
                if (ins == null) {
                    throw new Exception("调用所需实例对象获取失败");
                }
            }
            result = methodinvoke.invoke(targetmethod, new Object[]{ins,parmvalues});
            if(asstring)
            {
                result = Target2String(result,targetmethod.getReturnType().getName());
            }
        }

        return  result;
    }

    //frida 的类型方式转java
    public static Class FridaType2Java(String tp) throws ClassNotFoundException {
        Class type = null;
        switch (tp) {
            case "java.lang.String":
                type = String.class;
                break;
            case "[B":
                type = byte[].class;
                break;
            case "boolean":
                type = boolean.class;
                break;
            case "int":
                type = int.class;
                break;
            case "float":
                type = float.class;
                break;
            case "double":
                type = double.class;
                break;
            default:
                type = Class.forName(tp);
                break;
        }
        return type;
    }

    public static String Target2String(Object value, String type) {
        String result = null;
        System.out.println(type);
        switch (type) {
            case "[B":
                result = new String((byte[]) value);
                break;
            default:
                result = String.valueOf(value);
                break;
        }
        return result;
    }

    //类型转换 支持几种基本类型
    public static Object String2Target(String value, String type) {

        Object result = null;
        try {
            switch (type) {
                case "[B":
                    result = value.getBytes();
                    break;
                case "boolean":
                    result = Boolean.parseBoolean(value.toLowerCase());
                    break;
                case "float":
                    result = Float.parseFloat(value.toLowerCase());
                    break;
                case "int":
                    result = Integer.parseInt(value);
                    break;
                case "long":
                    result = Long.parseLong(value);
                    break;
                case "double":
                    result = Double.parseDouble(value);
                    break;
                case "java.lang.String":
                default:
                    result = value;
                    break;
            }
        } catch (Exception e) {
            result = value;
        }
        return result;
    }
}

3)测试调用

重新Rebuild Project生成dex并推送到设备目录,待测试调用的方法如下

package com.xxy.test;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {

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

    public  void  Click(View v)
    {
       TextView tv =  findViewById(R.id.tv);
       tv.setText(GetText("xxy",true,"nb".getBytes()));
    }

    public static   String  GetText(String str1,boolean flag,byte [] bts)
    {
        return str1 + new String(bts) + String.valueOf(flag);
    }
}

测试调用GetText方法,我们将传入的data参数值为:

{
    "classname": "com.xxy.test.MainActivity",
    "method": "GetText",
    "staticmethod": true,
    "instancefuncentity": {},
    "parms": [
        {
            "value": "xxy",
            "type": "java.lang.String"
        },
        {
            "value": "true",
            "type": "boolean"
        },
        {
            "value": "nb666",
            "type": "[B"
        }
    ]
}

调用结果如下:

image.png

5.资源

已封装快速调用的Sekiro Dex文件


标题:使用frida-gumjs将sekiro注入到任意app进程
作者:Cubeeeee
地址:http://blog.nps.fuguicun.com/articles/2021/07/20/1626768588189.html