Android Hook(2) Java2java

原文地址:http://www.zhaoxiaodan.com/android/Android-Hook(2)-java2java.html

之前学习了如何做一个简单android的函数勾子, 而这个勾子是用native 的函数去hook java函数, 现在来学习如何封装让他可以实现java hook java

不过不管怎么说, 这里已经不算是原理了, 因为原理就是改accessFlags并设置nativeFunc, 实际的hook 函数还是个native函数, 所以说这个是用这个原理来封装

我们一般要hook一个方法, 有可能希望在三个时间点进行处理:

  • 原方法执行前
  • 替换原方法
  • 原方法执行后

先来做个最简单的, 在原方法执行之前 执行, 那么就需要如下的Handler 用来hook 原函数, 并且这个java层的hook 可以获得原函数所在类的对象实例和所有参数值

public interface Handler
{
    /**
     * 在原方法执行之前执行
     * @param params 原方法参数
     */
    public void before(Object instance, Object[] params);
}

所以在jni 函数hook 里将原函数的nativeFunc 修改为 forwardToHander, 然后forwardToHander里 解析原函数的参数值等信息之后, 再调用 Handler的before方法, 将原函数所在类实例和参数值传递给handler的before回调方法

具体代码:

#include <jni.h>
#include "log.h"
#include "Dalvik.h"

/**
 * dvmCallMethod 需要的是 ArrayObject*, 而 nativeFunction 传入的参数是 const u4*
 * 做个转换
 */
ArrayObject* argsToArrayObject(const char* argsTypeDesc, const u4* args)
{
    //全部参数都转成Object型
    ClassObject* objectArrayClass = dvmFindArrayClass("[Ljava/lang/Object;", NULL);

    // 参数个数, 类型描述符都是一个字符, 所以 argsTypeDesc 字符串长度就是参数个数
    int argsSize = strlen(argsTypeDesc);

    // 分配空间
    ArrayObject* argArray = dvmAllocArrayByClass(objectArrayClass,argsSize ,ALLOC_DEFAULT);

    // 挨个赋值
    // 因为 args 并不是一个真正意义上的"数组", 当long 或者 double 形, 会占用 args[i]和args[i+1]
    // 所以需要多一个下标来指向args内存
    int pArgs = 0;
    for(int i=0;i<argsSize;i++)
    {
        // 当前参数类型描述符
        const char descChar = argsTypeDesc[i];

        JValue value;
        Object* obj;

        switch(descChar)
        {

            case 'Z':
            case 'C':
            case 'F':
            case 'B':
            case 'S':
            case 'I':

                //如果是基本数据类型, 则使用java的"自动装配", 也就是 int 转 Integer, long 转 Long, 这样就都转成Object了
                value.i = args[pArgs++];
                obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));
                dvmReleaseTrackedAlloc(obj, dvmThreadSelf());
                break;
            case 'D':
            case 'J':

                // 基本数据类型中, double 和 long 占用两个字节, 所以 pArgs 需要 +2
                value.j = dvmGetArgLong(args, pArgs++);
                pArgs++;
                obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));
                dvmReleaseTrackedAlloc(obj, dvmThreadSelf());
                break;
            case '[':
            case 'L':
                obj  = (Object*) args[pArgs++];
                break;
            default:
                LOGE("Unknown method signature description character: %c\n", descChar);
                obj = NULL;
        }

        // 塞到ArrayObject 中
        dvmSetObjectArrayElement(argArray,i,obj);
    }

    return argArray;
}

/**
 * 被hook的函数统一调用这个native方法, 由他来装配之后跳转到Handler
 */
static void forwardToHander(const u4* args, JValue* pResult,
                          const Method* method, struct Thread* self) {

    // 之前放进去的 "附加参数"
    Object* handlerObject = const_cast<Object*>(reinterpret_cast<const Object*>(method->insns));

    // 在原方法执行之前, 我们是要调用 Handler 的 before 这个hook
    Method* handlerBeforeMethod = dvmFindInterfaceMethodHierByDescriptor(handlerObject->clazz,"before","(Ljava/lang/Object;[Ljava/lang/Object;)V");

    if(nullptr != handlerBeforeMethod)
    {
        LOGD("handlerBeforeMethod name:%s,desc:%s",handlerBeforeMethod->name,handlerBeforeMethod->shorty);
        JValue* result = new JValue();

        // method->shorty[0] 表示返回值类型, 比如"ILJ" 说明原函数有一个Object型参数和一个long型参数, 返回值为 整形
        // 注意自动装配类型, int long double, 如果是Integer, Long, Double, 则描述符为Ljava/lang/Integer,Ljava/lang/Long和Ljava/lang/Double
        const char returnTypeDesc = method->shorty[0];
        const char* argsTypeDesc = &(method->shorty[1]);

        // 转一下参数格式
        ArrayObject* argsArray ;
        Object* originalInstance;

        // 同样的, 如果原函数不是static 的, args[0] 函数是所在类的实例
        if(dvmIsStaticMethod(method))
        {
            argsArray = argsToArrayObject(argsTypeDesc,&(args[0]));
            originalInstance = nullptr;
        }else{
            argsArray = argsToArrayObject(argsTypeDesc,&(args[1]));
            originalInstance = (Object*)(args[0]);
        }


        dvmCallMethod(self,handlerBeforeMethod, handlerObject, result, originalInstance ,argsArray);
    }else{
        LOGE("method no found....");
    }

    // before 前置调用先不修改原返回值了
    pResult->l = nullptr;


}


/**
 * 设置hook, 将原函数调用转发给nativeFunc forwardToHander 进行处理
 */
extern "C" JNIEXPORT void JNICALL
Java_com_zhaoxiaodan_hookdemo_Demo2_hook(JNIEnv *env, jobject instance, jobject clazzToHook,
                                                 jstring methodName_, jstring methodSig_,
                                                 jobject handler) {
    const char *methodName = env->GetStringUTFChars(methodName_, 0);
    const char *methodSig = env->GetStringUTFChars(methodSig_, 0);

    jmethodID methodIDToHook = env->GetMethodID((jclass) clazzToHook,methodName,methodSig);

    // 找不到有可能是个static
    if(nullptr == methodIDToHook){
        env->ExceptionClear();
        methodIDToHook = env->GetStaticMethodID((jclass) clazzToHook,methodName,methodSig);
    }


    if(methodIDToHook != nullptr)
    {
        Method* method = reinterpret_cast<Method*>(methodIDToHook);

        LOGD("hook function %s, args desc:%s",method->name,method->shorty);

        SET_METHOD_FLAG(method, ACC_NATIVE);
        //转发
        method->nativeFunc = &forwardToHander;
        method->registersSize=method->insSize;
        method->outsSize=0;

        // "附加参数" 功能
        // 在hook 的时候传进来的 handler 实现实例, 我们放到Method 结构体的insns 属性中
        // 到时候dvm 调用 nativeFunc 的时候会带回到 nativeFunc 中, 我们就能取到了
        method->insns = reinterpret_cast<const u2*>(dvmDecodeIndirectRef(dvmThreadSelf(),handler));
    }

    env->ReleaseStringUTFChars(methodName_, methodName);
    env->ReleaseStringUTFChars(methodSig_, methodSig);
}

使用代码为:

public class Demo2
{
    String TAG = "===[hookdemo]===";

    public interface Handler
    {
        /**
         * 在原方法执行之前执行
         * @param params 原方法参数
         */
        public void before(Object instance, Object[] params);
    }

    public String test(String param1, long param2, int[] param3)
    {
        return param1;
    }

    public static String staticTest(String param1, Long param2, int[] param3)
    {
        return param1;
    }
    public void demo()
    {
        String param1 = "param1";

        /**
         * 设置hook
         * hook 住Demo2 这个类的 test 方法
         * 实现一个Handler类来实现 回调函数 before
         */
        hook(Demo2.class, "test", "(Ljava/lang/String;J[I)Ljava/lang/String;", new Handler()
        {
            @Override
            public void before(Object instance, Object[] params)
            {
                Log.d(TAG, "===========Handler before,param len: "+params.length+",instance:"+instance);
                for (int i = 0; i < params.length; i++)
                {
                    Log.d(TAG, "p" + i + "=" + params[i]);
                }
            }
        });
        Log.d(TAG, "===========call test" + this.test(param1,999L,new int[]{1,2,3}));


        /**
         * 测试静态函数的hook, 则回调函数参数中 instance 为 null
         */
        hook(Demo2.class, "staticTest", "(Ljava/lang/String;Ljava/lang/Long;[I)Ljava/lang/String;", new Handler()
        {
            @Override
            public void before(Object instance, Object[] params)
            {
                Log.d(TAG, "===========Handler before,param len: "+params.length+",instance:"+instance);
                for (int i = 0; i < params.length; i++)
                {
                    Log.d(TAG, "p" + i + "=" + params[i]);
                }
            }
        });
        Log.d(TAG, "===========call test" + this.staticTest(param1, 999L, new int[]{1, 2, 3}));
    }

    /**
     * hook 函数, 是个jni 函数
     * @param clazzToHook
     * @param methodName
     * @param methodSig
     * @param handler
     */
    private native void hook(Class<?> clazzToHook, String methodName, String methodSig, Handler handler);
}

但是这个封装其实也不好, 只是学习如何在nativeFunc 通过dvm 调用回java层代码而已

它并没有真正的转发处理, 还是native来调用具体的方法; 也就是说如果hooker 需要做其他逻辑的话, 比如, 我是不是设置了before是不是有replace之类的, native代码就多了

最好的就是, 我把原函数改掉, 然后获取信息, 然后转发给一个java层处理器, 告诉它, 被hook的是这个函数, 对象是这个, 参数是这些, 然后勾子是这个, 就可以了, Handler 勾子的调用都由这个java层处理器来做, 而不是natviceFunc里, native里不需要做太多逻辑

具体实现就不在做了, 因为alibaba/dexposed项目已经做的很好了, 具体可以看它的实现

我自己也做了些注释:

pangliang/dexposed

内容概要:文章以“智能网页数据标注工具”为例,深入探讨了谷歌浏览器扩展在毕业设计中的实战应用。通过开发具备实体识别、情感分类等功能的浏览器扩展,学生能够融合前端开发、自然语言处理(NLP)、本地存储与模型推理等技术,实现高效的网页数据标注系统。文中详细解析了扩展的技术架构,涵盖Manifest V3配置、内容脚本与Service Worker协作、TensorFlow.js模型在浏览器端的轻量化部署与推理流程,并提供了核心代码实现,包括文本选择、标注工具栏动态生成、高亮显示及模型预测功能。同时展望了多模态标注、主动学习与边缘计算协同等未来发展方向。; 适合人群:具备前端开发基础、熟悉JavaScript和浏览器机制,有一定AI模型应用经验的计算机相关专业本科生或研究生,尤其适合将浏览器扩展与人工智能结合进行毕业设计的学生。; 使用场景及目标:①掌握浏览器扩展开发全流程,理解内容脚本、Service Worker与弹出页的通信机制;②实现在浏览器端运行轻量级AI模型(如NER、情感分析)的技术方案;③构建可用于真实场景的数据标注工具,提升标注效率并探索主动学习、协同标注等智能化功能。; 阅读建议:建议结合代码实例搭建开发环境,逐步实现标注功能并集成本地模型推理。重点关注模型轻量化、内存管理与DOM操作的稳定性,在实践中理解浏览器扩展的安全机制与性能优化策略。
基于Gin+GORM+Casbin+Vue.js的权限管理系统是一个采用前后端分离架构的企业级权限管理解决方案,专为软件工程和计算机科学专业的毕业设计项目开发。该系统基于Go语言构建后端服务,结合Vue.js前端框架,实现了完整的权限控制和管理功能,适用于各类需要精细化权限管理的应用场景。 系统后端采用Gin作为Web框架,提供高性能的HTTP服务;使用GORM作为ORM框架,简化数据库操作;集成Casbin实现灵活的权限控制模型。前端基于vue-element-admin模板开发,提供现代化的用户界面和交互体验。系统采用分层架构和模块化设计,确保代码的可维护性和可扩展性。 主要功能包括用户管理、角色管理、权限管理、菜单管理、操作日志等核心模块。用户管理模块支持用户信息的增删改查和状态管理;角色管理模块允许定义不同角色并分配相应权限;权限管理模块基于Casbin实现细粒度的访问控制;菜单管理模块动态生成前端导航菜单;操作日志模块记录系统关键操作,便于审计和追踪。 技术栈方面,后端使用Go语言开发,结合Gin、GORM、Casbin等成熟框架;前端使用Vue.js、Element UI等现代前端技术;数据库支持MySQL、PostgreSQL等主流关系型数据库;采用RESTful API设计规范,确保前后端通信的标准化。系统还应用了单例模式、工厂模式、依赖注入等设计模式,提升代码质量和可测试性。 该权限管理系统适用于企业管理系统、内部办公平台、多租户SaaS应用等需要复杂权限控制的场景。作为毕业设计项目,它提供了完整的源码和论文文档,帮助学生深入理解前后端分离架构、权限控制原理、现代Web开发技术等关键知识点。系统设计规范,代码结构清晰,注释完整,非常适合作为计算机相关专业的毕业设计参考或实际项目开发的基础框架。 资源包含完整的系统源码、数据库设计文档、部署说明和毕
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值