首页
社区
课程
招聘
[原创]使用Frida打印Java类函数调用关系
发表于: 2020-11-3 09:56 17780

[原创]使用Frida打印Java类函数调用关系

2020-11-3 09:56
17780

本题来自于3W班8月的第二题:请定制ART,完成对APP运行流程中所有的java类函数(包含java函数和jni函数)调用关系的trace。

对于Java层的单纯调用关系,可以比较方便的打印。参数的打印还是比较麻烦,Java对象的地址如何获取属性值等信息还是需要进一步深入研究。

根据之前的课程,可以得知有4种调用方式。其中java->java,java->jni在switch解释器下会通过DoCall方法。而jni->jni,jni->java则会通过反射相关的InvokeWithArgArray最后调用ArtMethod的Invoke方法。

这里考虑先使用frida验证运行逻辑,再修改源码刷机

(这里编译源码将解释器改成Switch方便查看源码)

DoCall只能打印java层发起的调用,Jni反射调用的并不走这个流程。

这里也可以HOOK ArtMethod的Invoke,参数基本上一样,而且ArtMethod的调用会多很多。

InvokeWithArgArray方法有个问题就是,我没有找到调用方的方法,虽然java->jni可以确定是哪里调用,但是jni->jni的可能会导致不知道上方是哪里调用的。

根据源码中这些方法都有Thread参数,所以尝试直接打印出tid,这样就算没有调用方,单一线程也只能同时运行一个方法。

源码中Thread可以直接调用GetTid获取线程ID,frida则可以根据内存分布,得知+0x10就是线程ID

InvokeWithArgArray主要是va_list的处理,我这里先写Demo使用AS确定内存分布。之后读取参数就比较方便了。

这里打印参数就统一打印U32值,也可以根据方法名,判断参数类型,然后自定义打印。

第一个参数是this指针,后续才是参数传递的值。

DoCall函数虽然可以打印调用关系,但是还没有为被调用方法初始化ShadowFrame,所以往下找了一些,最后HOOK了PerformCall里面的ArtInterpreterToInterpreterBridge和ArtInterpreterToCompiledCodeBridge。因为在DoCallCommon函数中初始化了ShadowFrame。

接下来就可以读取参数了,这里是通过修改参数,直接找到偏移进行读取,没有仔细理解ShadowFrame。

关于ArtInterpreterToCompiledCodeBridge的参数个数,不像ArtInterpreterToInterpreterBridge可以直接通过DexFile::CodeItem对象获取参数数量。所以直接读取的ShadowFrame的number_ofvregs打印所有寄存器值

这里就偷懒了,刷机比较慢,只写调用,不读取参数了,只加了2行日志

这里打印反而有问题,刚调用函数打印调用和被调用方法名字一样?没理解什么原因,应该是哪里还要设置ShadowFrame,Frida那边正常,这边也能看到上一步调用,倒也不影响流程查看。

为了测试方便,这里加了过滤,直接hook strstr即可修改过滤字符。

 
 
 
public int JavaCallJava(int i){
    Log.i("javajnitrace","step1->JavaCallJava onEnter");
    return JavaCallJni(0x1111);
}
 
public int JniCallJava(int i){
    int javajnitrace = Log.i("javajnitrace", "step4->JniCallJava onEnter " + i);
    return 1;
}
 
public String JniCallJavaISS(int i, String s1, String s2){
    int javajnitrace = Log.i("javajnitrace", "stepX->JniCallJavaISS onEnter " + i+" "+s1+" "+s2);
    JavaCallJavaIII(0x1111,0x2222,0x3333);
    return "JniCallJavaISS Called";
}
 
public int JavaCallJavaIII(int i, int j, int k){
    Log.i("javajnitrace","stepX->JavaCallJavaIII onEnter");
    //JavaCallJavaI17(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);
    return i+j+k;
}
public int JavaCallJava(int i){
    Log.i("javajnitrace","step1->JavaCallJava onEnter");
    return JavaCallJni(0x1111);
}
 
public int JniCallJava(int i){
    int javajnitrace = Log.i("javajnitrace", "step4->JniCallJava onEnter " + i);
    return 1;
}
 
public String JniCallJavaISS(int i, String s1, String s2){
    int javajnitrace = Log.i("javajnitrace", "stepX->JniCallJavaISS onEnter " + i+" "+s1+" "+s2);
    JavaCallJavaIII(0x1111,0x2222,0x3333);
    return "JniCallJavaISS Called";
}
 
public int JavaCallJavaIII(int i, int j, int k){
    Log.i("javajnitrace","stepX->JavaCallJavaIII onEnter");
    //JavaCallJavaI17(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);
    return i+j+k;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_cwuzao_javajnitrace_MainActivity_JavaCallJni(JNIEnv *env, jobject thiz, jint i) {
    __android_log_print(4, "javajnitrace", "step2->JavaCallJni onEnter %0x", i);
    jclass class_MainActivity = env->FindClass("com/cwuzao/javajnitrace/MainActivity");
    jmethodID  method_JniCallJni = env->GetMethodID(class_MainActivity, "JniCallJni", "(I)I");
 
    int callResult = env->CallIntMethod(thiz, method_JniCallJni, 0x2222);
    return  callResult + 1;
}
 
extern "C"
JNIEXPORT jint JNICALL
Java_com_cwuzao_javajnitrace_MainActivity_JniCallJni(JNIEnv *env, jobject thiz, jint i) {
    __android_log_print(4, "javajnitrace", "step3->JniCallJni onEnter %0x",i);
    jclass class_MainActivity = env->FindClass("com/cwuzao/javajnitrace/MainActivity");
    jmethodID  method_JniCallJava = env->GetMethodID(class_MainActivity, "JniCallJava", "(I)I");
    int callResult = env->CallIntMethod(thiz, method_JniCallJava, 0x3333);
    return  callResult + 1;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_cwuzao_javajnitrace_MainActivity_JavaCallJni(JNIEnv *env, jobject thiz, jint i) {
    __android_log_print(4, "javajnitrace", "step2->JavaCallJni onEnter %0x", i);
    jclass class_MainActivity = env->FindClass("com/cwuzao/javajnitrace/MainActivity");
    jmethodID  method_JniCallJni = env->GetMethodID(class_MainActivity, "JniCallJni", "(I)I");
 
    int callResult = env->CallIntMethod(thiz, method_JniCallJni, 0x2222);
    return  callResult + 1;
}
 
extern "C"
JNIEXPORT jint JNICALL
Java_com_cwuzao_javajnitrace_MainActivity_JniCallJni(JNIEnv *env, jobject thiz, jint i) {
    __android_log_print(4, "javajnitrace", "step3->JniCallJni onEnter %0x",i);
    jclass class_MainActivity = env->FindClass("com/cwuzao/javajnitrace/MainActivity");
    jmethodID  method_JniCallJava = env->GetMethodID(class_MainActivity, "JniCallJava", "(I)I");
    int callResult = env->CallIntMethod(thiz, method_JniCallJava, 0x3333);
    return  callResult + 1;
}
function DoCall_onEnter(args) {
    var addr_ArtMethod = args[2].add(8).readPointer();
    allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
    PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
    var methodName = allocPrettyMethod.readCString();
    if(methodName.indexOf(searchName) > -1){
        var addr_Call_ArtMethod = args[0];
        allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
        PrettyMethod(addr_ArtMethod_PrettyMethod, addr_Call_ArtMethod, allocPrettyMethod, 0x100);
        var call_methodName = allocPrettyMethod.readCString();
        console.log("DoCall:",methodName,"->", call_methodName);
    }
}
function DoCall_onEnter(args) {
    var addr_ArtMethod = args[2].add(8).readPointer();
    allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
    PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
    var methodName = allocPrettyMethod.readCString();
    if(methodName.indexOf(searchName) > -1){
        var addr_Call_ArtMethod = args[0];
        allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
        PrettyMethod(addr_ArtMethod_PrettyMethod, addr_Call_ArtMethod, allocPrettyMethod, 0x100);
        var call_methodName = allocPrettyMethod.readCString();
        console.log("DoCall:",methodName,"->", call_methodName);
    }
}
DoCall: void com.cwuzao.javajnitrace.MainActivity$1.onClick(android.view.View) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int)
DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJni(int)
addr_InvokeWithArgArray onEnter-> int com.cwuzao.javajnitrace.MainActivity.JniCallJni(int)
addr_InvokeWithArgArray onEnter-> int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int)
DoCall: int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
addr_InvokeWithArgArray onLeave
addr_InvokeWithArgArray onLeave
DoCall: void com.cwuzao.javajnitrace.MainActivity$1.onClick(android.view.View) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int)
DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJni(int)
addr_InvokeWithArgArray onEnter-> int com.cwuzao.javajnitrace.MainActivity.JniCallJni(int)
addr_InvokeWithArgArray onEnter-> int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int)
DoCall: int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
addr_InvokeWithArgArray onLeave
addr_InvokeWithArgArray onLeave
Interceptor.attach(addr_InvokeWithArgArray, {
       onEnter: function(args){
           var addr_ArtMethod = args[1];
           allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
           PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
           var methodName = allocPrettyMethod.readCString();
 
           if(methodName.indexOf(searchName) > -1 ){
               this.methodName = methodName;
               console.log("InvokeWithArgArray->",methodName,  args[1],  args[2],  args[3],  args[4]);
           }
       },
       onLeave: function(retval){
       }
   });
Interceptor.attach(addr_InvokeWithArgArray, {
       onEnter: function(args){
           var addr_ArtMethod = args[1];
           allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
           PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
           var methodName = allocPrettyMethod.readCString();
 
           if(methodName.indexOf(searchName) > -1 ){
               this.methodName = methodName;
               console.log("InvokeWithArgArray->",methodName,  args[1],  args[2],  args[3],  args[4]);
           }
       },
       onLeave: function(retval){
       }
   });
DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJni(int)
addr_ArtMethod6Invoke onEnter
addr_InvokeWithArgArray onEnter-> int com.cwuzao.javajnitrace.MainActivity.JniCallJni(int)
addr_ArtMethod6Invoke onEnter
addr_InvokeWithArgArray onEnter-> int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int)
addr_ArtMethod6Invoke onEnter
DoCall: int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJni(int)
addr_ArtMethod6Invoke onEnter
addr_InvokeWithArgArray onEnter-> int com.cwuzao.javajnitrace.MainActivity.JniCallJni(int)
addr_ArtMethod6Invoke onEnter
addr_InvokeWithArgArray onEnter-> int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int)
addr_ArtMethod6Invoke onEnter
DoCall: int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
 
[3741]DoCall: void com.cwuzao.javajnitrace.MainActivity$1.onClick(android.view.View) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int)
[3741]DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
[3741]DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJni(int)
[3741]addr_ArtMethod6Invoke onEnter->int com.cwuzao.javajnitrace.MainActivity.JavaCallJni(int)
[3741]addr_ArtMethod6Invoke onEnter->int com.cwuzao.javajnitrace.MainActivity.JniCallJni(int)
[3741]addr_ArtMethod6Invoke onEnter->int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int)
[3741]DoCall: int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
[3741]DoCall: void com.cwuzao.javajnitrace.MainActivity$1.onClick(android.view.View) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int)
[3741]DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
[3741]DoCall: int com.cwuzao.javajnitrace.MainActivity.JavaCallJava(int) -> int com.cwuzao.javajnitrace.MainActivity.JavaCallJni(int)
[3741]addr_ArtMethod6Invoke onEnter->int com.cwuzao.javajnitrace.MainActivity.JavaCallJni(int)
[3741]addr_ArtMethod6Invoke onEnter->int com.cwuzao.javajnitrace.MainActivity.JniCallJni(int)
[3741]addr_ArtMethod6Invoke onEnter->int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int)
[3741]DoCall: int com.cwuzao.javajnitrace.MainActivity.JniCallJava(int) -> int android.util.Log.i(java.lang.String, java.lang.String)
 
Interceptor.attach(addr_InvokeWithArgArray, {
       onEnter: function(args){
           var addr_ArtMethod = args[1];
           allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
           PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
           var methodName = allocPrettyMethod.readCString();
 
           if(methodName.indexOf("com.cwuzao.javajnitrace") > -1 ){
               this.showResult = true;
               this.methodName = methodName;
 
               this.tid = args[0].readPointer().add(0x10).readU32();
               var argscount =  args[2].add(0x8).readU32();
 
               console.log("["+this.tid+"]InvokeWithArgArray->",methodName,  args[1],  args[2],  args[3],  args[4]);
               // console.log("["+this.tid+"]args count:", argscount, "NumBytes:", args[2].add(0xC).readU32());
               // console.log("addr_InvokeWithArgArray args[0]->", hexdump(args[2].add(0x10).readPointer()));
               var argspointer = args[2].add(0x10).readPointer();
               for(var i=0; i< argscount; i++){
                   console.log("["+this.tid+"]  args_", i+1,"->0x"+argspointer.add(i*4).readU32().toString(16));
               }
           }
       },
       onLeave: function(retval){
           if(this.showResult){
               console.log("["+this.tid+"]InvokeWithArgArray onLeave->",this.methodName, "\n  result->0x"+retval.toString(16));
           }
       }
   });
Interceptor.attach(addr_InvokeWithArgArray, {
       onEnter: function(args){
           var addr_ArtMethod = args[1];
           allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
           PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
           var methodName = allocPrettyMethod.readCString();
 
           if(methodName.indexOf("com.cwuzao.javajnitrace") > -1 ){
               this.showResult = true;
               this.methodName = methodName;
 
               this.tid = args[0].readPointer().add(0x10).readU32();
               var argscount =  args[2].add(0x8).readU32();
 
               console.log("["+this.tid+"]InvokeWithArgArray->",methodName,  args[1],  args[2],  args[3],  args[4]);
               // console.log("["+this.tid+"]args count:", argscount, "NumBytes:", args[2].add(0xC).readU32());
               // console.log("addr_InvokeWithArgArray args[0]->", hexdump(args[2].add(0x10).readPointer()));
               var argspointer = args[2].add(0x10).readPointer();
               for(var i=0; i< argscount; i++){
                   console.log("["+this.tid+"]  args_", i+1,"->0x"+argspointer.add(i*4).readU32().toString(16));
               }
           }
       },
       onLeave: function(retval){
           if(this.showResult){
               console.log("["+this.tid+"]InvokeWithArgArray onLeave->",this.methodName, "\n  result->0x"+retval.toString(16));
           }
       }
   });
 
Interceptor.attach(addr_ArtInterpreterToInterpreterBridge, {
    onEnter: function(args){
 
        var addr_ArtMethod = args[2].add(8).readPointer();
        allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
        PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
        this.methodName = allocPrettyMethod.readCString();
        if(this.methodName.indexOf(searchName) > -1){
            this.showReuslt = true;
            // this.result = args[4];
            this.result = args[2].add(0x3C)
            this.tid = args[0].add(0x10).readU32();
            console.log("["+this.tid+"]ArtInterpreterToInterpreterBridge onEnter->",this.methodName);
 
            // var regcount = args[2].add(0x30).readU32(); //
            var registers_size_ = args[1].readU16();
            var ins_size_ = args[1].add(0x2).readU16();
            var outs_size_ = args[1].add(0x4).readU16();
            // console.log("registers_size_->",registers_size_, "ins_size_->",ins_size_,"outs_size_->",outs_size_);
 
            //读取参数
            var argsPointer = args[2].add(0x3C + 4*outs_size_);
            // console.log(hexdump(argsPointer));
            for(var i=0; i< ins_size_; i++){
                console.log("["+this.tid+"]  args_"+i+"->0x"+argsPointer.add(i*4).readU32().toString(16));
            }
        }
    },
    onLeave: function(retval){
        if(this.showReuslt){
            console.log("["+this.tid+"]ArtInterpreterToInterpreterBridge onLeave->",this.methodName,"\n  result->0x"+this.result.readU32().toString(16));
        }
    }
});
 
Interceptor.attach(addr_ArtInterpreterToCompiledCodeBridge, {
    onEnter: function(args){
 
        var addr_ArtMethod = args[3].add(8).readPointer();
        allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
        PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
        this.methodName = allocPrettyMethod.readCString();
        if(this.methodName.indexOf(searchName) > -1){
            this.showReuslt = true;
            this.result = args[4];
            this.tid = args[0].add(0x10).readU32();
            console.log("["+this.tid+"]ArtInterpreterToCompiledCodeBridge onEnter->",this.methodName, args[1],args[2],args[3]);
            var registers_size_ = args[3].add(0x30).readU32();
 
            var argsPointer = args[3].add(0x3C);//这里并不是参数数量
            for(var i=0; i< registers_size_; i++){
                console.log("["+this.tid+"]  args_"+i+"->0x"+argsPointer.add(i*4).readU32().toString(16));
            }
        }
    },
    onLeave: function(retval){
        if(this.showReuslt){
            console.log("["+this.tid+"]ArtInterpreterToCompiledCodeBridge onLeave->",this.methodName,"\n  result->0x"+this.result.readU16().toString(16));
        }
    }
});
Interceptor.attach(addr_ArtInterpreterToInterpreterBridge, {
    onEnter: function(args){
 
        var addr_ArtMethod = args[2].add(8).readPointer();
        allocPrettyMethod.writeByteArray(allocPrettyMethodInit);
        PrettyMethod(addr_ArtMethod_PrettyMethod, addr_ArtMethod, allocPrettyMethod, 0x100);
        this.methodName = allocPrettyMethod.readCString();
        if(this.methodName.indexOf(searchName) > -1){
            this.showReuslt = true;
            // this.result = args[4];
            this.result = args[2].add(0x3C)
            this.tid = args[0].add(0x10).readU32();
            console.log("["+this.tid+"]ArtInterpreterToInterpreterBridge onEnter->",this.methodName);
 
            // var regcount = args[2].add(0x30).readU32(); //
            var registers_size_ = args[1].readU16();
            var ins_size_ = args[1].add(0x2).readU16();
            var outs_size_ = args[1].add(0x4).readU16();
            // console.log("registers_size_->",registers_size_, "ins_size_->",ins_size_,"outs_size_->",outs_size_);

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 5
支持
分享
最新回复 (8)
雪    币: 10944
活跃值: (7329)
能力值: ( LV12,RANK:219 )
在线值:
发帖
回帖
粉丝
2
漂亮
2020-11-4 11:14
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
真棒,
2020-11-4 11:34
0
雪    币: 8285
活跃值: (4710)
能力值: ( LV8,RANK:134 )
在线值:
发帖
回帖
粉丝
4
感谢Google的开源精神。通过对Android源码进行阅读,可以很快发现ART下类函数的四种调用:java调用java,java调用jni,jni调用java,jni调用jni;而java函数又有两种运行模式:interpreter模式和quick模式。ART对不用的调用过程有着不同的处理逻辑。文章使用frida对ART下函数调用流程中的关键逻辑进行hook,同时结合对ART的定制,便可以得到APP运行过程中的执行轨迹,当然,对于函数调用过程中发生的参数传递以及返回结果等,事实上都可以通过对ART定制得到,进而能够构建一个动态分析沙箱。
2020-11-4 12:53
1
雪    币: 408
活跃值: (1107)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
5

我在art/runtime/interpreter/interpreter_common.cc中修改bool DoCall函数,但是打印不出日志信息,使用frida  Hook strstr函数,我是在版本为8.1.0_r1版本修改的

文件头有引入android::base::StringPrintf

最后于 2020-11-9 09:55 被灯-等灯等灯~编辑 ,原因:
2020-11-8 23:13
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
大侠,里面好多参数找不到在哪定义的,可以给发个demo吗
2021-5-7 16:03
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
大侠,你好,请问这里的“DoCall”方法是怎么Hook的
2021-5-19 10:24
0
雪    币: 245
活跃值: (272)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
没头没尾。
2021-10-23 22:30
1
雪    币: 143
活跃值: (473)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9

请问有什么方法,可以扫描出APP调用了哪些系统API呢?

最后于 2022-1-10 17:27 被lvcoffee编辑 ,原因:
2022-1-10 17:26
0
游客
登录 | 注册 方可回帖
返回
//