jnitrace、frida-trace、Stalker、sktrace、Frida Native Trace、r0tracer、strace、IDA trace、Unidbg Trace

news/2024/12/2 11:27:00/

JNI 提示:https://developer.android.google.cn/training/articles/perf-jni?hl=zh-cn

JNI 文档:https://docs.oracle.com/en/java/javase/19/docs/specs/jni/index.html

trace 大盘点:https://blog.csdn.net/fenfei331/article/details/123523182

JavaVM 和 JNIEnv

JNI 定义了两个关键数据结构,即“JavaVM”和“JNIEnv”。两者本质上都是指向函数表的二级指针。(在 C++ 版本中,它们是一些类,这些类具有指向函数表的指针,并具有每个通过该函数表间接调用的 JNI 函数的成员函数。)JavaVM 提供“调用接口”函数,您可以利用此类来函数创建和销毁 JavaVM。理论上,每个进程可以有多个 JavaVM,但 Android 只允许有一个。

JNIEnv 提供了大部分 JNI 函数 ( :https://docs.oracle.com/en/java/javase/19/docs/specs/jni/functions.html )。您的原生函数都会收到 JNIEnv 作为第一个参数。该 JNIEnv 将用于线程本地存储。因此,您无法在线程之间共享 JNIEnv如果一段代码无法通过其他方法获取自己的 JNIEnv,您应该共享相应 JavaVM,然后使用 GetEnv 发现线程的 JNIEnv。(假设该线程包含一个 JNIEnv;请参阅下面的 AttachCurrentThread。)

JNIEnv 和 JavaVM 的 C 声明与 C++ 声明不同。"jni.h" include 文件会提供不同的类型定义符,具体取决于该文件是包含在 C 还是 C++ 中。因此,我们不建议在这两种语言包含的头文件中添加 NIEnv 参数。(换个说法:如果您的头文件需要 #ifdef __cplusplus,且该标头中有任何内容引用 JNIEnv,您可能都必须进行一些额外操作。)

ZenTracer

在逆向分析的工作中,因为经常要面对海量的代码,往往都会尝试很多小技巧来高效准确的定位关键点,trace便是其中之一。tracer顾名思义就是用来追踪代码(执行)的工具,当然,根据需求的不同,tracer也分几类,比如trace指令trace函数trace文件操作Android Java方法的Tracer 等。frida官网有一个 frida-trace 可以用来 trace native code 的,并不支持 Java方法。

用 FRIDA 打造 MethodTracer:https://www.secpulse.com/archives/119314.html

ZenTracer:https://github.com/hluwa/ZenTracer

1、jnitrace

jnitrace 是跟踪 JNI API 调用的工具,可以指定跟踪哪个so的JNI调用。在 Android 下混饭吃,首推的就是 jnitrace,缺点就是,hook 的太全,欺负欺负小厂的还行,大厂的反调试一上,分分钟教你做人。这里还有个优化的文章 https://blog.seeflower.dev/archives/82/

github:https://github.com/chame1eon/jnitrace

一个基于 frida 的工具,用来追踪安卓应用的 JNI API。jnitrace 是一个动态分析追踪工具,类似与 frida-trace 和 strace,但是 jnitrace 只针对 JNI 函数。( JNI 函数:https://docs.oracle.com/en/java/javase/19/docs/specs/jni/functions.html )

安装:pip install jnitrace

jnitrace --help

D:\> jnitrace --help
用法:jnitrace [选项] -l libname target

参数:
        target                要追踪的 "应用名"

选项:

  •   -h, --help            帮助
  •   -m {spawn,attach}     指定注入进程的方式,默认是 spawn
  •   -R [REMOTE], --remote 通过 IP:PORT 连接到远程 frida-server
  •   -b {fuzzy,accurate,none}, --backtrace {fuzzy,accurate,none}   打印每个JNI调用的追踪
  •   -i --include / -e --exclude  指定一个正则表达式来 过滤 / 排除 方法
  •   -I --include-export / -E --exclude-export  :trace 导出的方法,jnitrace 认为导出的函数应该是从 Java 端能够直接调用的函数,所以可以包括使用 RegisterNatives 来注册的函数,例如 -I stringFromJNI -I nativeMethod([B)V,就包括导出名里有 stringFromJNI,以及使用 RegisterNames 来注册,并带有 nativeMethod([B)V 签名的函数。
  •   --hide-data           打印参数的内容。
  •   --ignore-env          不追踪 JNIEnv calls.
  •   --ignore-vm           不追踪 JavaVM calls.
  • -p PREPEND, --prepend PREPEND  在jnitrace运行之前预先运行Frida脚本。这可以用于在 jnitrace 启动之前对抗反调试。
  • -a APPEND, --append APPEND     在jnitrace启动后运行。附加一个Frida脚本。
  •   -o OUTPUT, --output OUTPUT    将跟踪数据输出到JSON格式的文件。
  •  -v, --version         显示版本

  -l LIBRARIES, --libraries LIBRARIES
                        指定跟踪JNI调用的so库。输入*,跟踪全部库
                        或多次使用该参数指定一组库。
  --aux name=(string|bool|int)value    当使用 spawning 方式时,设置 aux 选项
D:\>

使用方法:
        jnitrace -l libnative-lib.so com.example.myapplication
或者,以 spawn 或 attach 方式启动 app
        jnitrace -l libnative-lib.so -m spawn com.example.myapplication
        jnitrace -l libnative-lib.so -m attach com.example.myapplication

jnitrace -l xxx.so 包名 --ignore-vm

Spawn 模式及 jnitrace 都是需要关闭面具隐藏( Magisk Hide )

其他

加密函数定位:

jni | unicorn | androidemu | frida_hook
:https://codeooo.blog.csdn.net/article/details/127105204
:https://github.com/lasting-yang/frida_hook_libart

dump 脚本修复加密so
:https://github.com/lasting-yang/frida_dump

frida hook模板:
so:https://codeooo.blog.csdn.net/article/details/122124012
java:https://codeooo.blog.csdn.net/article/details/120025814

遍历so方法:
https://codeooo.blog.csdn.net/article/details/120033269

hook 常见算法:https://codeooo.blog.csdn.net/article/details/120025814

jnitrace-engine

jnitrace-engien是基于jnitrace裁剪出来的库,你可以取其所需,只hook你关心的函数。

https://github.com/chame1eon/jnitrace-engine

编译的时候需要nodejs,然后有一段时间没有更新了,导致某一个frida的库版本比较老,

jtrace

方便定制的 Trace 才是好工具,jtrace 感觉就比较帅了,信息齐全,直接在 _agent.js 或者_agent_stable.js 里面加自己的逻辑就行。

https://github.com/SeeFlowerX/jtrace

比较推荐的玩法就是:不要一上来就 hook_all_jni();, 而是指定分析某段函数

Interceptor.attach(targetSo.add(0x56582+1),{
    onEnter:function(args){

        console.log(" ================ jni Onload");
                hook_all_jni();
    }
});

需要它的时候,就 hook,不需要的时候注释掉。

2、frida-trace

frida-trace:https://frida.re/docs/frida-trace/

函数追踪工具 Frida-Trace:https://www.bilibili.com/video/BV1rz4y1f7h9

frida-trace 可以一次性监控一堆函数地址。还能打印出比较漂亮的树状图,不仅可以显示调用流程,还能显示调用层次。并且贴心的把不同线程调用结果用不同的颜色区分开了。

用法:frida-trace [options] target

示例:frida-trace -U -i "strcmp" -f com.gdufs.xman

解释:在 com.gdufs.xman 的所有 module 中的所有 function 中 追踪调用 strcmp 的函数

关于 -F、-f

frida-trace 默认是 attache 附加到进程。示例:frida-trace -U -i "Java_*" "crackme"

可以追踪到:frida-trace -U -F -I "libJniTest.so" -i "Java_"
无法追踪到:frida-trace -U -f "demo2.jni.com.myapplication" -I "libJniTest.so" -i "Java_"
可以追踪到 libc.so 中的 open 函数:frida-trace -U -f "demo2.jni.com.myapplication" -i "open"

-F 方式可以成功追踪到,但是 -f 方法就无法追踪到,Native 方法如下,

-f 是通过 spwan 方式启动的,libJniTest.so 还没有加载到 内存中,所以无法追踪到

所以,对于 so 库函数的追踪,推荐 -F 参数

帮助

D:\> frida-trace.exe --help
用法: frida-trace [options] target

位置参数:
        args                  extra arguments and/or target

选项:
  -h, --help               显示帮助
  -D ID, --device ID       通过 ID 连接设备
  -U, --usb                通过 USB 连接设备
  -R, --remote             连接到远程 frida-server
  -H HOST, --host HOST     连接到远程 host 上的 frida-server
  --certificate 证书       设置证书,通过 TSL 与 host 交互                        
  --origin ORIGIN          设置连接到远程服务的 "Origin" 头部
  --token TOKEN            设置 与host 认证
  --keepalive-interval     时间间隔。0表示禁用,-1表示基于传输自动选择                        
  --p2p                    建立一个点对点的连接
  --stun-server ADDRESS    设置--p2p 的 STUN 服务地址
  --relay address,username,password,turn-{udp,tcp,tls}  添加--p2p 延迟
  -f TARGET, --file TARGET       spawn 模式
  -F, --attach-frontmost         附加到最前端的 application
  -n NAME, --attach-name NAME    附加到一个名字                        
  -N IDENTIFIER, --attach-identifier IDENTIFIER   附加到标识符                        
  -p PID, --attach-pid PID        附加到 pid                        
  -W PATTERN, --await PATTERN
                        await spawn matching PATTERN
  --stdio {inherit,pipe}
                        stdio behavior when spawning (defaults to “inherit”)
  --aux option          set aux option when spawning, such as “uid=(int)42” (supported types are:
                        string, bool, int)
  --realm {native,emulated}            附件的范围
  --runtime {qjs,v8}                   使用的脚本运行环境
  --debug                              启用 Node.js 兼容的脚本调试器
  --squelch-crash                      如果启用,将不会将崩溃报告转储到控制台
  -O FILE, --options-file FILE         包含其他命令行选项的文本文件
  --version                            显示版本号

// -i 和 -a 都是对 C 函数进行追踪   ( 或者 so 库中的函数 )

    -I --include-module  /  -X --exclude-module:包含 / 排除 的module
    -i  --include  /  -x  --exclude                包含 / 排除 的函数( 基于全局变量)

   -a MODULE!OFFSET, --add MODULE!OFFSET           添加 MODULE!OFFSET

  -T INCLUDE_IMPORTS, --include-imports INCLUDE_IMPORTS  包括 program's imports
  -t MODULE, --include-module-imports MODULE             包括 MODULE imports

// -m 对 Object-C 的方法进行追踪(即 Hook),后面可以跟各种模糊匹配等等

    -m OBJC_METHOD, --include-objc-method OBJC_METHOD      包括 OBJC_METHOD
    -M OBJC_METHOD, --exclude-objc-method OBJC_METHOD      排除 OBJC_METHOD

// -j 和 -J 都是对 Java 方法 进行追踪

  -j JAVA_METHOD, --include-java-method JAVA_METHOD      包括 JAVA_METHOD
  -J JAVA_METHOD, --exclude-java-method JAVA_METHOD      排除 JAVA_METHOD

  -s DEBUG_SYMBOL, --include-debug-symbol DEBUG_SYMBOL    包括 DEBUG_SYMBOL
  -q, --quiet              不格式化输出信息
  -d, --decorate        添加模块名到生成的onEnter日志语句
  -S PATH, --init-session PATH    用于初始化会话的JavaScript文件的路径
  -P PARAMETERS_JSON, --parameters PARAMETERS_JSON
                         参数格式为JSON,公开为全局命名的 "parameters"
  -o OUTPUT, --output OUTPUT  保存信息到文件中
D:\>

操作之后,会在当前命令行所在的目录生成一个handle目录,里面就存放了一系列的 js 文件,每个 js 文件就是一个方法函数的对应,如果我们需要对某个方法函数进行参数打印、参数改变等,直接修改对应的这个 js 文件保存,然后重新 frida-trace 启动应用,当执行到该方法函数时就能在命令行窗口中打印出相关信息。

IOS、Android 追踪

​Objective-C,通常写作 ObjC 或 OC 和较少用的 Objective C 或 Obj-C,是扩充C的面向对象编程语言。它主要使用于Mac OS X和GNUstep这两个使用OpenStep标准的系统,而在NeXTSTEP和OpenStep中它更是基本语言。

github:https://github.com/0xdea/frida-scripts/

iOS

  • raptor_frida_ios_trace.js. Full-featured ObjC and Module tracer for iOS.
  • raptor_frida_ios_enum.js. Collection of functions to enumerate ObjC classes and methods.
  • ios-snippets/raptor_frida_ios_*.js. Miscellaneous script snippets for iOS.

Android

  • raptor_frida_android_trace.js. Full-featured Java and Module tracer for Android.
  • raptor_frida_android_enum.js. Collection of functions to enumerate Java classes and methods.
  • android-snippets/raptor_frida_android_*.js. Miscellaneous script snippets for Android.

设备相关

-D    连接到指定的设备,多个设备时使用。示例:frida-trace -D xxxxx -F 
-U    连接到 USB 设备,只有一个设备时使用。示例:fria-trace -U -F

应用程序相关

-f    目标应用包名。spawn 模式。示例:frida-trace -U -f com.apple.www
-F    当前正在运行的程序。attach 模式示例。示例:frida-trace -U -F或frida-trae -UF
-n    正在运行的程序的名字。attach 模式。示例:frida-trace -U -n QQ
-N    正在运行的程序的包名。attach 模式。示例:frida-trace -U -N com.apple.www
-p    正在运行的程序的pid。attach 模式。示例:frida-trace -U -p 2302  

方法相关

以下参数在一条跟踪命令中可重复使用

-I    包含模块。示例:frida-trace -UF -I "libcommonCrypto*"
-X    不包含模块。示例:frida-trace -UF -X "libcommonCrypto*"
-i     包含 c 函数。示例:frida-trace -UF -i "CC_MD5"
-x     不包名c函数。示例:frida-trace -UF -i "*MD5" -x "CC_MD5"
-a     包含模块+偏移跟踪。示例:frida-trace -UF -a 模块名\!0x7B7D48
-m     包含某个 oc 方法。示例:frida-trace -UF -m "+[NSURL URLWithString:]"
-M     不包含某个 oc 方法。示例:frida-trace -UF -M "+[NSURL URLWithString:]"

日志相关

-o    日志输出到文件。示例:frida-trace -UF -m "*[* URL*]" -o run.log

-i, -x: include / exclude function ( 基于 全局)

可用的表达式

MODULE ! FUNCTION
FUNCTION
!FUNCTION
MODULE !

示例

-i "msvcrt.dll!cpy"      在 msvcrt.dll 模块中匹配名字中带有 cpy 的所有函数
-i "free"           在所有模块中的所有函数中匹配名字带有 free 的函数
-i "!free"           与 -i "free" 相同
-i "gdi32.dll!"       在 gdi32.dll模块中追踪所有函数。( 与 -I "gdi32.dll" 相同 )

注意:顺序不同,则搜索结果不同

-i "str*" -i "mem*" -X "msvcrt.dll"

  • '-i "str*"'

    假设在 3 个模块中匹配80个函数,那么 结果集合中就有 80 个函数
  • '-i "mem*"'

    假设在 3 个模块中匹配18个函数,那么 结果集合中就有 80+18 = 98 个函数
  • '-X "msvcrt.dll"'

    假设在 msvcrt.dll 中有 28个 "str" 和 6个 "mem" 函数,则最终的结果集中有 98 - 28 - 6 = 64 个函数

-i "str*" -X "msvcrt.dll" -i "mem*"

  • '-i "str*"'

    假设在 3 个模块中匹配80个函数,那么 结果集合中就有 80 个函数
  • '-X "msvcrt.dll"'

    假设在 msvcrt.dll 中有28个 "str",则结果集中有 80 - 28 = 52 个函数
  • '-i "mem*"'

    假设在 msvcrt.dll 中有 18个"mem" 函数,则最终结果集中有 52+18=70个函数

-X "msvcrt.dll" -i "str*" -i "mem*"

  • '-X "msvcrt.dll"'

    假设 msvcrt.dll 有 28 "str" and 6 "mem" 需要排除,但是结果集这时为0,所以此时还是0。
  • '-i "str*"'

    假设在 3 个模块中匹配80个函数,那么 结果集合中就有 80 个函数
  • '-i "mem*"'

    假设有 18个"mem" 函数,则最终结果集中有 80+18=98个函数.

-a: include function ( 基于 offset(偏移) )

示例:-a "libjpeg.so!0x4793c"

示 例:( -i、-m、-j )

  • -i 针对系统中的 C 函数。如果 针对 so库函数进行追踪,必须用 -I 参数指定 so库
  • -m 针对 Object-C 的方法进行追踪(即 Hook),后面可以跟各种模糊匹配等等
  • -j 针对 Java 函数进行追踪

-i 示例

# 在 Safari 中追踪 recv* 和 send* 函数, 同时在 log中插入库名
$ frida-trace --decorate -i "recv*" -i "send*" Safari

frida-trace -UF -I "libJniTest.so" -i "Java_"

# 打开手机上的 SnapChat,追踪 crypto API calls
$ frida-trace \
    -U \
    -f com.toyopagroup.picaboo \
    -I "libcommonCrypto*"

# 追踪所有 JNI 方法
$ frida-trace -U -i "Java_*"  "app名字"

# 追踪进程1372调用msvcrt.dll模块中函数名包含 mem 的函数
$ frida-trace -p 1372 -i "msvcrt.dll!*mem*"
$ frida-trace -p 1372 -i "*open*" -x "msvcrt.dll!*open*"

跟踪 libjpeg.so 中未导出的函数

$ frida-trace -p 1372 -a "libjpeg.so!0x4793c"

-m 示例

# 在 Safari 中追踪 ObjC method calls。
$ frida-trace -m "-[NSView drawRect:]" Safari

-j 示例

# 在 Android 设备上打开YouTube,追踪 Java methods
# with “certificate” in their signature (s), ignoring case (i)
# and only searching in user-defined classes (u)
$ frida-trace \
    -U \
    -f com.google.android.youtube \
    --runtime=v8 \
    -j '*!*certificate*/isu'

-j / -J   对 Java 函数进行追踪

常用 ​frida-trace 命令

// -m 对 Object-C 的方法进行追踪(即 Hook),后面可以跟各种模糊匹配等等

- 号开头,表示 "实例对象"

+ 号开头,表示 "类"

* 号开头,表示 所有 类和实例

用法: "通配符[类 方法名]"

​frida-trace 中的方法匹配命令支持模糊匹配,星号匹配0个或多个字符,问号匹配1个字符:

-m "-[NSURL *]"    // 匹配 NSURL 类的所有实例方法
-m "+[NSURL *]"    // 匹配 NSURL 类的所有 类方法 ( 即 "静态方法" )
-m "*[NSURL *]"    // 匹配 NSURL 类的所有方法
-m "*[*URL *]"       // 匹配以 URL 结尾类的所有方法
-m "*[URL* *]"      // 匹配以 URL 开头类的所有方法
-m "*[*URL* *]"     // 匹配包含 URL 的类的所有方法
-m "*[*URL* *login*]"    // 匹配包含 URL 的类的带login的所有方法
-m "*[????? *]"            // 匹配类名只有五个字符的类的所有方法

使用示例

跟踪单个方法:frida-trace -UF -m "-[DetailViewController obj]"

跟踪多个方法:frida-trace -UF -m "-[DetailViewController obj]" -m "+[NSURL URLWithString:]"

跟踪某个类的所有方法:frida-trace -UF -m "*[DetailViewController *]"

跟踪某个类的所有方法并排除viewDidLoad方法:frida-trace -UF -m "*[DetailViewController *]" -M "-[DetailViewController viewDidLoad]"

跟踪整个App中包含sendMsg关键词的所有方法:frida-trace -UF -m "*[* *sendMsg*]"

需要忽略某个字母的大小写,请使用?代替sendMsg中的M:frida-trace -UF -m "*[* *send?sg*]"

日志过多时,可保存到文件:frida-trace -UF -m "*[* *sendMsg*]" -o run.log

跟踪某个动态库:frida-trace -UF -I "libcommonCrypto*"

跟踪某个c函数:frida-trace -UF -i "CC_MD5"

跟踪sub_1007B7D48函数:frida-trace -UF -a xxxxx\!0x7B7D48

简而言之:

当你不确定你要跟踪的方法是类方法还是实例方法时,用星号代替

当你只知道部分类名时,不确定的地方用星号代替

当你只知道部分方法名时,不确定的地方用星号代替

当你不知道方法名时,直接用星号代替

当你不知道某个字母是大小写时,用问号代替

修改生成的 js

frida-trace 命令会在当前目录生成 ./__handlers__/ 文件夹内生成对应函数的 js 代码。当你需要打印入参,返回值。或修改入参,返回值时,可编辑对应的 js 文件。

打印或修改 OC 方法的入参

$ frida-trace -UF -m "-[DetailViewController setObj:]"
Instrumenting...
-[DetailViewController setObj:]: Auto-generated handler at "/Users/witchan/__handlers__/DetailViewController/setObj_.js"
Started tracing 1 function. Press Ctrl+C to stop.

{
  onEnter(log, args, state) {
    var self = new ObjC.Object(args[0]);  // 当前对象
    var method = args[1].readUtf8String();  // 当前方法名
    log(`[${self.$className} ${method}]`);

    var isData = false;

    // 字符串
    // var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    // args[2] = str  // 修改入参

    // array
    // var 

    // 数组
    // var array = ObjC.classes.NSMutableArray.array();  // 对应的oc语法:NSMutableArray array = [NSMutablearray array];
    // array.addObject_("item1");  // 对应的oc语法:[array addObject:@"item1"];
    // array.addObject_("item2");  // 对应的oc语法:[array addObject:@"item2"];
    // args[2] = array; // 修改入参

    // 字典
    // var dictionary = ObjC.classes.NSMutableDictionary.dictionary(); // 对应的oc语法:NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    // dictionary.setObject_forKey_("value1", "key1"); // 对应的oc语法:[dictionary setObject:@"value1" forKey:@"key1"]
    // dictionary.setObject_forKey_("value2", "key2"); // 对应的oc语法:[dictionary setObject:@"value2" forKey:@"key2"]
    // args[2] = dictionary; // 修改入参

    // 字节
    var data = ObjC.classes.NSMutableData.data(); // 对应的oc语法:NSMutableData *data = [NSMutableData data];
    var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 获取一个字符串。 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    var subData = str.dataUsingEncoding_(4);  // 将str转换为data,编码为utf-8。对应的oc语法:NSData *subData = [str dataUsingEncoding:NSUTF8StringEncoding];
    data.appendData_(subData);  // 将subData添加到data。对应的oc语法:[data appendData:subData];
    args[2] = data; // 修改入参
    isData = true;

    // 更多数据类型:https://developer.apple.com/documentation/foundation

    var before = args[2];

    // 注意,日志输出请直接使用log函数。不要使用console.log()
    if (isData) {
        // 打印byte对象
      var after = new ObjC.Object(args[2]); // 打印NSData
      var outValue = after.bytes().readUtf8String(after.length()) // 将data转换为string
      log(`before:=${before}=`);
      log(`after:=${outValue}=`);
    } else {
        // 打印字符串、数组、字段
      var after = new ObjC.Object(args[2]); // 打印出来是个指针时,请用该方式转换后再打印
      log(`before:=${before}=`);
      log(`after:=${after}=`);
    }

    // 如果是自定义对象时,使用以上方法无法打印时,请使用以下方法:
    // var customObj = new ObjC.Object(args[0]); // 自定义对象
    // // 打印该对象所有属性
    // var ivarList = customObj.$ivars;
    // for (key in ivarList) {
    //   log(`key${key}=${ivarList[key]}=`);
    // }

    // // 打印该对象所有方法
    // var methodList = customObj.$methods;
    // for (var i=0; i<methodList.length; i++) {
    //   log(`method=${methodList[i]}=`);
    // }
  },
  onLeave(log, retval, state) {

  }
}

修改OC方法的返回值

$ frida-trace -UF -m "-[DetailViewController obj]"
Instrumenting...
-[DetailViewController obj]: Loaded handler at "/Users/witchan/__handlers__/DetailViewController/obj.js"
Started tracing 1 function. Press Ctrl+C to stop.

{
  onEnter(log, args, state) {

  },
  onLeave(log, retval, state) {
    // 字符串
    var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    retval.replace(str)  // 修改返回值
    var after = new ObjC.Object(retval); // 打印出来是个指针时,请用该方式转换后再打印
    log(`before:=${retval}=`);
    log(`after:=${after}=`);

    // 其他数据类型,请往上看
  }
}

打印 C函数 的入参和返回值

$ frida-trace -UF -i "CC_MD5"
Instrumenting...
CC_MD5: Loaded handler at "/Users/witchan/__handlers__/libcommonCrypto.dylib/CC_MD5.js"
Started tracing 1 function. Press Ctrl+C to stop.

{
  onEnter(log, args, state) {
    // 注意。c方法里的参数直接从下标0开始
    this.args0 = args[0];    
    this.args2 = args[2];
    this.backtrace = 'CC_MD5 called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n';
  },
  onLeave(log, retval, state) {
    
    var ByteArray = Memory.readByteArray(this.args2, 16);
    var uint8Array = new Uint8Array(ByteArray);

    var str = "";
    for(var i = 0; i < uint8Array.length; i++) {
        var hextemp = (uint8Array[i].toString(16))
        if(hextemp.length == 1){
            hextemp = "0" + hextemp
        }
        str += hextemp;
    }
    log(`CC_MD5(${this.args0.readUtf8String()})`);   
    log(`CC_MD5()=${str}=`);
    log(this.backtrace);    // 打印函数调用栈
  }
}

frida-trace 命令大全

Frida-trace 常用命令

1、spawn - 冷启动
        $ frida-trace -U -f 包名 -m "+[NSURL URLWithString:]"
2、attach - 热启动
        $ frida-trace -UF 包名 -m "+[NSURL URLWithString:]"
3、Hook类方法
        $ frida-trace -UF 包名 -m "+[NSURL URLWithString:]"
4、Hook实例方法
        $ frida-trace -UF 包名 -m "-[NSURL host]"
5、Hook类的所有方法
        $ frida-trace -UF 包名 -m "*[NSURL *]"
6、模糊Hook类的所有方法
        $ frida-trace -UF 包名 -m "*[*service* *]"
7、模糊Hook所有类的特定方法
        $ frida-trace -UF 包名 -m "*[* *sign*]"
8、模糊Hook所有类的特定方法并忽略大小写
        假设我们要hook所有类中包含getSign或getsign关键词的方法

        $ frida-trace -UF 包名 -m "*[* get?ign]"
9、模糊Hook所有类的特定方法并排除viewDidLoad方法
        $ frida-trace -UF 包名 -m "*[DetailViewController *]" -M "-[DetailViewController viewDidLoad]"
10、Hook某个动态库
        $ frida-trace -UF 包名 -I "libcommonCrypto*"
11、Hook get或post的接口地址
        $ frida-trace -UF 包名 -m "+[NSURL URLWithString:]"
js代码如下:

{
  onEnter(log, args, state) {
    var args2 = new ObjC.Object(args[2]);
    log(`-[NSURL URLWithString:${args2}]`);
  },
  onLeave(log, retval, state) {
  }
}
12、Hook post的body
        $ frida-trace -UF 包名 -m "-[NSMutableURLRequest setHTTPBody:]"
js代码如下:

{
  onEnter(log, args, state) {
    var args2 = new ObjC.Object(args[2]);
    log(`-[NSMutableURLRequest setHTTPBody:${args2.bytes().readUtf8String(args2.length())}]`);
  },
  onLeave(log, retval, state) {
  }
}
13、Hook即将显示页面
        $ frida-trace -UF 包名 -m "-[UINavigationController pushViewController:animated:]" -m "-[UIViewController presentViewController:animated:completion:]"
pushViewController:animated:方法的js代码如下:

{
  onEnter(log, args, state) {
    var args2 = new ObjC.Object(args[2]);
    log(`-[UINavigationController pushViewController:${args2.$className} animated:${args[3]}]`);
  },
  onLeave(log, retval, state) {
  }
}
presentViewController:animated:completion:方法对应的js代码如下:

{
  onEnter(log, args, state) {
    var args2 = new ObjC.Object(args[2]);
    log(`-[UIViewController presentViewController:${args2.$className} animated:${args[3]} completion:${args[4]}]`);
  },
  onLeave(log, retval, state) {
  }
}
14、Hook MD5函数
        $ frida-trace -UF 包名 -i "CC_MD5"
js代码如下:

{
  onEnter(log, args, state) {
    this.args0 = args[0];    // 入参
    this.args2 = args[2];    // 返回值指针
  },
  onLeave(log, retval, state) {
    var ByteArray = Memory.readByteArray(this.args2, 16);
    var uint8Array = new Uint8Array(ByteArray);

    var str = "";
    for(var i = 0; i < uint8Array.length; i++) {
        var hextemp = (uint8Array[i].toString(16))
        if(hextemp.length == 1){
            hextemp = "0" + hextemp
        }
        str += hextemp;
    }
    log(`CC_MD5(${this.args0.readUtf8String()})`);       // 入参
    log(`CC_MD5()=${str}=`);                                                    // 返回值
  }
}
15、Hook Base64编码方法
        $ frida-trace -UF 包名 -m "-[NSData base64EncodedStringWithOptions:]"
js代码如下:

{
  onEnter(log, args, state) {
    this.self = args[0];
  },
  onLeave(log, retval, state) {
    var before = ObjC.classes.NSString.alloc().initWithData_encoding_(this.self, 4);
    var after = new ObjC.Object(retval);
    log(`-[NSData base64EncodedStringWithOptions:]before=${before}=`);
    log(`-[NSData base64EncodedStringWithOptions:]after=${after}=`);
  }
}
16、Hook Base64解码方法
        $ frida-trace -UF 包名 -m "-[NSData initWithBase64EncodedData:options:]" -m "-[NSData initWithBase64EncodedString:options:]"
initWithBase64EncodedData:options:方法对应的js代码如下:

{
  onEnter(log, args, state) {
    this.arg2 = args[2];
  },
  onLeave(log, retval, state) {
    var before = ObjC.classes.NSString.alloc().initWithData_encoding_(this.arg2, 4);
    var after = ObjC.classes.NSString.alloc().initWithData_encoding_(retval, 4);
    log(`-[NSData initWithBase64EncodedData:]before=${before}=`);
    log(`-[NSData initWithBase64EncodedData:]after=${after}=`);
  }
}

initWithBase64EncodedString:options:方法对应的js代码如下:

{
  onEnter(log, args, state) {
    this.arg2 = args[2];
  },
  onLeave(log, retval, state) {
    var before = new ObjC.Object(this.arg2);
    var after = ObjC.classes.NSString.alloc().initWithData_encoding_(retval, 4);
    log(`-[NSData initWithBase64EncodedString:]before=${before}=`);
    log(`-[NSData initWithBase64EncodedString:]after=${after}=`);
  }
}

17、Hook加密函数AES、DES、3DES
        frida-trace -UF 包名 -i CCCrypt
js代码如下:

{
    onEnter: function(log, args, state) {
        this.op = args[0]
        this.alg = args[1]
        this.options = args[2]
        this.key = args[3]
        this.keyLength = args[4]
        this.iv = args[5]
        this.dataIn = args[6]
        this.dataInLength = args[7]
        this.dataOut = args[8]
        this.dataOutAvailable = args[9]
        this.dataOutMoved = args[10]

        log('CCCrypt(' +
            'op: ' + this.op + '[0:加密,1:解密]' + ', ' +
            'alg: ' + this.alg + '[0:AES128,1:DES,2:3DES]' + ', ' +
            'options: ' + this.options + '[1:ECB,2:CBC,3:CFB]' + ', ' +
            'key: ' + this.key + ', ' +
            'keyLength: ' + this.keyLength + ', ' +
            'iv: ' + this.iv + ', ' +
            'dataIn: ' + this.dataIn + ', ' +
            'inLength: ' + this.inLength + ', ' +
            'dataOut: ' + this.dataOut + ', ' +
            'dataOutAvailable: ' + this.dataOutAvailable + ', ' +
            'dataOutMoved: ' + this.dataOutMoved + ')')

        if (this.op == 0) {
            log("dataIn:")
            log(hexdump(ptr(this.dataIn), {
                length: this.dataInLength.toInt32(),
                header: true,
                ansi: true
            }))
            log("key: ")
            log(hexdump(ptr(this.key), {
                length: this.keyLength.toInt32(),
                header: true,
                ansi: true
            }))
            log("iv: ")
            log(hexdump(ptr(this.iv), {
                length: this.keyLength.toInt32(),
                header: true,
                ansi: true
            }))
        }
    },
    onLeave: function(log, retval, state) {
        if (this.op == 1) {
            log("dataOut:")
            log(hexdump(ptr(this.dataOut), {
                length: Memory.readUInt(this.dataOutMoved),
                header: true,
                ansi: true
            }))
            log("key: ")
            log(hexdump(ptr(this.key), {
                length: this.keyLength.toInt32(),
                header: true,
                ansi: true
            }))
            log("iv: ")
            log(hexdump(ptr(this.iv), {
                length: this.keyLength.toInt32(),
                header: true,
                ansi: true
            }))
        } else {
            log("dataOut:")
            log(hexdump(ptr(this.dataOut), {
                length: Memory.readUInt(this.dataOutMoved),
                header: true,
                ansi: true
            }))
        }
        log("CCCrypt did finish")
    }
}
18、Hook加密函数RSA
        rsa 加密有公钥加密和私钥加密两种方式

        $ frida-trace -UF 包名 -i "SecKeyEncrypt" -i "SecKeyRawSign"
SecKeyEncrypt公钥加密函数对应的js代码如下:

{
  onEnter(log, args, state) {
    // 由于同一条加密信息可能会多次调用该函数,故在这输出该函数的调用栈。可根据栈信息去分析上层函数
    log(`SecKeyEncrypt()=${args[2].readCString()}=`);
    log('SecKeyEncrypt called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
  },
  onLeave(log, retval, state) {
  }
}
SecKeyRawSign私钥加密函数对应的js代码如下:

{
  onEnter(log, args, state) {
    log(`SecKeyRawSign()=${args[2].readCString()}=`);
    log('SecKeyRawSign called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
  },
  onLeave(log, retval, state) {
  }
}
19、修改方法的入参
        $ frida-trace -UF 包名 -m "-[DetailViewController setObj:]"
js代码如下:

/*
 * Auto-generated by Frida. Please modify to match the signature of -[DetailViewController setObj:].
 * This stub is currently auto-generated from manpages when available.
 *
 * For full API reference, see: https://frida.re/docs/javascript-api/
 */

{
  /**
   * Called synchronously when about to call -[DetailViewController setObj:].
   *
   * @this {object} - Object allowing you to store state for use in onLeave.
   * @param {function} log - Call this function with a string to be presented to the user.
   * @param {array} args - Function arguments represented as an array of NativePointer objects.
   * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8.
   * It is also possible to modify arguments by assigning a NativePointer object to an element of this array.
   * @param {object} state - Object allowing you to keep state across function calls.
   * Only one JavaScript function will execute at a time, so do not worry about race-conditions.
   * However, do not use this to store function arguments across onEnter/onLeave, but instead
   * use "this" which is an object for keeping state local to an invocation.
   */
  onEnter(log, args, state) {
    var self = new ObjC.Object(args[0]);  // 当前对象
    var method = args[1].readUtf8String();  // 当前方法名
    log(`[${self.$className} ${method}]`);

    // 字符串
    // var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    // args[2] = str  // 修改入参为字符串

    // 数组
    // var array = ObjC.classes.NSMutableArray.array();  // 对应的oc语法:NSMutableArray array = [NSMutablearray array];
    // array.addObject_("item1");  // 对应的oc语法:[array addObject:@"item1"];
    // array.addObject_("item2");  // 对应的oc语法:[array addObject:@"item2"];
    // args[2] = array; // 修改入参为数组

    // 字典
    // var dictionary = ObjC.classes.NSMutableDictionary.dictionary(); // 对应的oc语法:NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    // dictionary.setObject_forKey_("value1", "key1"); // 对应的oc语法:[dictionary setObject:@"value1" forKey:@"key1"]
    // dictionary.setObject_forKey_("value2", "key2"); // 对应的oc语法:[dictionary setObject:@"value2" forKey:@"key2"]
    // args[2] = dictionary; // 修改入参为字典

    // 字节
    var data = ObjC.classes.NSMutableData.data(); // 对应的oc语法:NSMutableData *data = [NSMutableData data];
    var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 获取一个字符串。 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    var subData = str.dataUsingEncoding_(4);  // 将str转换为data,编码为utf-8。对应的oc语法:NSData *subData = [str dataUsingEncoding:NSUTF8StringEncoding];
    data.appendData_(subData);  // 将subData添加到data。对应的oc语法:[data appendData:subData];
    args[2] = data; // 修改入参字段

    // 更多数据类型:https://developer.apple.com/documentation/foundation
  },

  onLeave(log, retval, state) {

  }
}
20、修改方法的返回值
        $ frida-trace -UF 包名 -m "-[DetailViewController Obj]"
js代码如下:

{
  onEnter(log, args, state) {

  },
  onLeave(log, retval, state) {
    // 字符串
    var str = ObjC.classes.NSString.stringWithString_("hi wit!")  // 对应的oc语法:NSString *str = [NSString stringWithString:@"hi with!"];
    retval.replace(str)  // 修改返回值
    var after = new ObjC.Object(retval); // 打印出来是个指针时,请用该方式转换后再打印
    log(`before:=${retval}=`);
    log(`after:=${after}=`);
  }
}
21、打印字符串、数组、字典
        $ frida-trace -UF 包名 -m "-[DetailViewController setObj:]"
js代码如下:

{
  onEnter(log, args, state) {
    var self = new ObjC.Object(args[0]);  // 当前对象
    var method = args[1].readUtf8String();  // 当前方法名
    log(`[${self.$className} ${method}]`);

    var before = args[2];
    // 注意,日志输出请直接使用log函数。不要使用console.log()
    var after = new ObjC.Object(args[2]); // 打印出来是个指针时,请用该方式转换后再打印
    log(`before:=${before}=`);
    log(`after:=${after}=`);
  },
  onLeave(log, retval, state) {

  }
}
22、打印NSData
        $ frida-trace -UF 包名 -m "-[DetailViewController setObj:]"
js代码如下:

{
  onEnter(log, args, state) {
    var self = new ObjC.Object(args[0]);  // 当前对象
    var method = args[1].readUtf8String();  // 当前方法名
    log(`[${self.$className} ${method}]`);

    var before = args[2];

    // 注意,日志输出请直接使用log函数。不要使用console.log()
   
    var after = new ObjC.Object(args[2]); // 打印NSData
    var outValue = after.bytes().readUtf8String(after.length()) // 将data转换为string
    log(`before:=${before}=`);
    log(`after:=${outValue}=`);
  },
  onLeave(log, retval, state) {

  }
}
23、打印对象的所有属性和方法
        $ frida-trace -UF 包名 -m "-[DetailViewController setObj:]"
js代码如下:

{
  onEnter(log, args, state) {
    var self = new ObjC.Object(args[0]);  // 当前对象
    var method = args[1].readUtf8String();  // 当前方法名
    log(`[${self.$className} ${method}]`);

    var customObj = new ObjC.Object(args[2]); // 自定义对象
    // 打印该对象所有属性
    var ivarList = customObj.$ivars;
    for (key in ivarList) {
       log(`key${key}=${ivarList[key]}=`);
    }

    // 打印该对象所有方法
    var methodList = customObj.$methods;
    for (var i=0; i<methodList.length; i++) {
       log(`method=${methodList[i]}=`);
    }
  },
  onLeave(log, retval, state) {

  }
}
24、打印调用栈
        $ frida-trace -UF 包名 -m "+[NSURL URLWithString:]"
js代码如下:

{
  onEnter(log, args, state) {
    var url = new ObjC.Object(args[2]);
    log(`+[NSURL URLWithString:${url}]`);
    log('NSURL URLWithString: called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
  },
  onLeave(log, retval, state) {
  }
}
25、日志输出到文件
        $ frida-trace -UF 包名 -m "+[NSURL URLWithString:]" -o run.log
26、更多数据类型
/**
 * Converts to a signed 32-bit integer.
 */
  toInt32(): number;

  /**
  * Converts to an unsigned 32-bit integer.
  */
  toUInt32(): number;

  /**
  * Converts to a “0x”-prefixed hexadecimal string, unless a `radix`
  * is specified.
  */
  toString(radix?: number): string;

  /**
  * Converts to a JSON-serializable value. Same as `toString()`.
  */
  toJSON(): string;

  /**
  * Returns a string containing a `Memory#scan()`-compatible match pattern for this pointer’s raw value.
  */
  toMatchPattern(): string;

  readPointer(): NativePointer;
  readS8(): number;
  readU8(): number;
  readS16(): number;
  readU16(): number;
  readS32(): number;
  readU32(): number;
  readS64(): Int64;
  readU64(): UInt64;
  readShort(): number;
  readUShort(): number;
  readInt(): number;
  readUInt(): number;
  readLong(): number | Int64;
  readULong(): number | UInt64;
  readFloat(): number;
  readDouble(): number;
  readByteArray(length: number): ArrayBuffer | null;
  readCString(size?: number): string | null;
  readUtf8String(size?: number): string | null;
  readUtf16String(length?: number): string | null;
以上就是关于frida-trace在iOS端的常用命令,希望能帮助到大家。同时也建议大家阅读官方文档:https://frida.re/docs/frida-trace/ 1

frida-trace 指令追踪&监控寄存器变化 反Ollvm 及SVC HOOK

( 注意看帖子评论 ):https://bbs.kanxue.com/thread-273501-1.htm

原版的 frida-trace 主要是对函数的追踪、 对于寄存器的变化 没有显示, 这个版本是完善了寄存器的变化。后续有时间的话可能会完善流程cfg图

github 地址:https://github.com/IIIImmmyyy/frida-trace

3、Stalker

:https://frida.re/docs/stalker/

Stalker 是 Frida 的代码追踪引擎。可以在Native层的 方法级别,块级别,指令级别实现代码修改,代码跟踪。它允许跟踪线程, 捕获每个函数,每个块,甚至执行的每个指令。 这里提供了对潜行者引擎的一个很好的概述,建议先仔细阅读。显然,实现是 有点特定于架构,尽管它们之间有很多共同点。 Stalker目前支持移动设备上常见的 AArch64 架构运行Android或iOS的手机和平板电脑,以及Intel 64和IA-32 台式机和笔记本电脑上常见的体系结构。

实现原理:动态编译

Stalker 的目标是实现一个快速的,不被反调发现的,指令级别的代码追踪引擎,怎么能同时实现这些,答案是:动态编译。
官方的文档讲的很清楚,有兴趣的自己可以自己看看,会了解的比较透彻。
下面通俗的说意思自己的理解,有不对的地方还请大佬们指出哈。
Stalker 不像 Interceptor 一样直接在指令执行的地方做修改,Stalker 会重新开辟一块空间用于执行动态编译的代码,并且暴漏接口给用户来操纵动态编译的指令。Stalker 以基本块为基本的执行单元,从使用者的角度来说,需要了解下面几个 Stalker 中的概念:

  1. Blocks: 以基本块为单位进行动态编译
  2. Call: 对于 BL* 保存 LR,栈等信息
  3. Transformer: 基本块如何被动态编译。提供用户接口,可读取,修改,插入指令
  4. Callout: 设置一个当此位置被执行到时的 callback,可获取当前 cpu 信息
  5. Probes: 在 trace 中使用类似 Interceptor 的 hook
  6. Relocator: 重定位模块,例如:ADR Xd, label
  7. Trust Threshold: 基本块在被 transform 次数达到可信阈值后,内容未曾变化,下次将不再重新编译

每当执行到一个基本块,Stalker 都会做以下几件事:

  1. 对于方法调用,保存 lr 等必要信息
  2. 重定位位置相关指令,例如:ADR Xd, label
  3. 建立此块的索引,如果此块在达到可信阈值后,内容未曾变化,下次将不再重新编译(为了加快速度)
  4. 根据 transform 函数,编译生成一个新的基本块 GumExecBlock ,保存到 GumSlab 。void transform(GumStalkerIterator iterator, GumStalkerOutput output, gpointer user_data) 可以控制读取,改动,写入指令。
  5. transform 过程中还可通过 void gum_stalker_iterator_put_callout (GumStalkerIterator self,GumStalkerCallout callout, gpointer data, GDestroyNotify data_destroy) 来设置一个当此位置被执行到时的 callout。通过此 void callout(GumCpuContext cpu_context, gpointer user_data) 获取 cpu 信息。
  6. 执行一个基本快 GumExecBlock,开始下一个基本快

Stalker 使用 示例

Frida Stalker 是什么?:https://blog.csdn.net/fenfei331/article/details/125326417

Stalker是基于动态重新编译的代码跟踪器。 它将代码指令复制到内存中的另一个位置,在该位置对其进行调整以适应新位置并包括其他跟踪指令。 如果应用程序在原始位置检查其代码,则会发现该代码是完整无缺的,因为它是被篡改的代码的副本。

某小说 App来演示 Stalker 的用法, http://91fans.com.cn/post/readbookone/

​Stalker 在 ARM32 下貌似不大好使,这里我们在 ARM64 下做的测试

function trace_entry(baseAddr,tatgetAddr){Interceptor.attach(tatgetAddr, {onEnter: function(args){console.log("enter tatgetAddr===================================");this.pid = Process.getCurrentThreadId();Stalker.follow(this.pid,{events:{// 暂时不需要这些 eventscall:false,ret:false,exec:false,block:false,compile:false},onReceive:function(events){},transform: function (iterator) {var instruction = iterator.next();const startAddress = instruction.address;// 从ida里面 找到 Java_com_baidu_searchbox_NativeBds_dae1 函数// 的 代码 在 0xE84 和 0x126C 之间var isModule = startAddress.compare(baseAddr.add(0xE84)) >= 0 && startAddress.compare(baseAddr.add(0x126C)) < 0;do{if (isModule){console.log(instruction.address.sub(baseAddr) + "\t:\t" + instruction);}iterator.keep();} while ((instruction = iterator.next()) !== null);},onCallSummary:function(summary){}});},onLeave: function(retval){Stalker.unfollow(this.pid);console.log("retval:"+retval);console.log("leave tatgetAddr===================================");}});
}

需要解释的是 transform 函数, 其他的 events、 onReceive 和 onCallSummary 目前我们还用不到,它们可以做block 和 call之类的跟踪分析。

transform 遍历执行了当前的每一行汇编指令,默认显示的地址是实际内存地址,我们 instruction.address.sub(baseAddr) 减去了一个so的基地址,得到的就和ida中显示的地址一致了

加 Hook

光显示指令没啥意思,指令看上去还没有ida好看呢。

但是这个是活的执行,我们可以加个hook,通过ida分析,感觉差不多 0xFB8的时候是计算key的长度。

所以我们在这里加个打印

do{
    if (isModule){
        console.log(instruction.address.sub(baseAddr) + "\t:\t" + instruction);

        if(instruction.address.sub(baseAddr) == 0xfb8){
            iterator.putCallout((context) => {
            var string = Memory.readCString(context["x21"]);
            console.log("####  key = " + string)
            })
        }
    }
    iterator.keep();
} while ((instruction = iterator.next()) !== null);
 

这样就可以顺利的打印出本次aes算法中用的key了

0xfb4        :        add x21, x21, x0, lsr #1
0xfb8        :        mov x0, x21
0xfbc        :        ldr x22, [x8, #0x580]
0xfc0        :        bl #0x7a751c55f0
####  key = D0CD8B760CE07BC3
0xfc4        :        mov x1, x0
0xfc8        :        mov x0, x20
0xfcc        :        blr x22
0xfd0        :        ldr x8, [x20]

对于某些不知道怎么分析的,可以 hook 一些基本的公共操作,例如:解密之后的数据大概率是要赋值给字符串的。我们先把字符串赋值来捞一遍。

var strCls = Java.use("java.lang.StringBuilder");
strCls.toString.implementation = function(){var result = this.toString();console.log(result.toString());return result;
}

frida stalker 使用方式

:https://www.cnblogs.com/c-x-a/p/17076881.html

Frida Javascript api #Stalker (中文版):https://www.jianshu.com/p/9fc8ebc6465b/

frida 整合怪:fridaUiTools

:https://bbs.kanxue.com/thread-268219-1.htm

:https://github.com/dqzg12300/fridaUiTools

Hook脚本如下( 附加进程前使用

  • 整合r0capture
  • 整合jnitrace
  • java层的加解密相关自吐
  • ssl pining(整合DroidSSLUnpinning)
  • 模糊匹配函数进行批量hook(整合ZenTracer)
  • native的sub函数批量hook(参数统一方式打印。所以输出只能做参考)
  • stalker的trace(整合sktrace)
  • 整合frida_hook_libart
  • 脱壳相关(整合frida_dump、FRIDA-DEXDump、fart)
  • 自定义脚本添加
  • patch汇编代码

调用功能如下( 附加进程后使用

  • fart主动调用
  • DUMPDex主动调用
  • dump打印指定地址
  • dump指定模块
  • wallBreak整合

4、sktrace

sktrace 是基于 Frida Stalker 的 trace 工具。

功能:类似 ida 指令 trace 功能,统计寄存器变化,辅助分析,并且可能会有字符串产生,

github 地址:https://github.com/bmax121/sktrace

基于 Frida Stalker 的 trace 工具:https://bbs.kanxue.com/thread-264680.htm

  • Stalker 的 transform 函数配合 put_callout 使得我们可以对每一个基本块做任何想做事,对于运行过程中进行解密的代码,也可以通过修改 trustThreshold 来监视解密过程。
  • sktrace 是对 Stalker 最为简单的应用,实现了一个类似 IDA 指令 trace 的功能。另外对于每个寄存器的连续变化做了个统计,会更利于分析,如果出现连续四个可打印字符,会以字符串的形式打印出来。

示例 1:sktrace 的使用

基于 Frida Stalker 的 trace 工具:https://bbs.kanxue.com/thread-264680.htm

示例 2:sktrace 的使用

我们现在掌握了打印指令和寄存器值的方法。不过这样打出来太不帅了,IDA 的 Trace 只会打印被修改的寄存器的值。还好有大佬已经写好了:https://github.com/bmax121/sktrace

先把我们代码中 Stalker 部分注释掉,因为要干掉登录和 vip,所以先启动我们的 js

frida -U -l bqgst.js -f com.bqg.ddnoverl --no-pause

然后启动 sktrace

python sktrace.py -m attach -l libTxtFormatter.so -i Java_com_baidu_searchbox_NativeBds_dae1 com.bqg.ddnoverl

-l  指定so名称
-i  指定要Trace的函数名

最后跟的是包名或者app名称

0x7a2e289e9c        add        x29, sp, #0x50        ; x29=0x7a7bdb9d48->0x7a7bdb9c50
0x7a2e289ea0        mov        x20, x0        ; x20=0x0->0x7c02192b70
0x7a2e289ea4        ldr        x8, [x20]        ; x8=0x79a1c49061b6ff43->0x7b3fe0e198
0x7a2e289ea8        mov        x1, x2        ; x1=0x9e30a688->0x7a7bdb9d18
0x7a2e289eac        mov        x2, xzr        ; x2=0x7a7bdb9d18->0x0
0x7a2e289eb0        mov        x19, x3        ; x19=0x7cb23102a0->0x7a7bdb9d1c
0x7a2e289eb4        ldr        x8, [x8, #0x548]        ; x8=0x7b3fe0e198->0x7b3fbe20e0
0x7a2e289ebc        ldr        x8, [x20]        ; x0=0x7c02192b70->0x7b522130f0, x1=0x7a7bdb9d18->0x7dee5ef7cc, x3=0x7a7bdb9d1c->0x10, x4=0x7a7bdb9d58->0x0, x8=0x7b3fbe20e0->0x7b3fe0e198, x11=0x40->0x522130f7, x12=0xb->0xffff00000eff, x13=0x380->0xafba2fb8, x16=0x7a2e289e94->0x7dd27ad7f8, x17=0x7cb23102a0->0x7dee554c78, x30=0x7deaefdc0c->0x7a2e289ebc
0x7a2e289ec0        mov        x21, x0        ; x21=0x0->0x7b522130f0
0x7a2e289ec4        ldr        x22, [x8, #0x580]        ; x22=0x7abbab01b8->0x7b3fbe5504
0x7a2e2895f0        adrp        x16, #0x7a2e29a000        ; x16=0x7dd27ad7f8->0x7a2e29a000, x30=0x7a2e289ebc->0x7a2e289ecc
0x7a2e2895f4        ldr        x17, [x16, #0xfe8]        ; x17=0x7dee554c78->0x7dee561980
0x7a2e2895f8        add        x16, x16, #0xfe8        ; x16=0x7a2e29a000->0x7a2e29afe8

现在打出来的,就有 ida Trace 内味了。

同学们还可以模仿下 sktrace 的代码,搞个更趁手的兵刃出来。

矛与盾的战斗是永不停息的,无痛hook,硬件断点应该是趋势。

一个趁手的调试器非常难得,一个能被Debug的App是藏不住秘密的。

5、Frida Native Trace (IDA 插件 )

:https://blog.csdn.net/fenfei331/article/details/118357441

IDA识别出所有函数,然后导出来给OD,给这些函数下断点,触发之后先打日志,再自动取消断点。这样程序运行的流程不就出来了?

实际跑起来发现,有些函数会频繁被调用,这样导致程序容易假死或者崩溃,所以还需要有个方便的过滤措施,过滤掉频繁调用的函数。

搞出了 PEStalker 之后,程序的运行流程就无所遁形了。

不过时代变了,移动互联网时代的 AppStalker 怎么搞?

frida-trace 可以一次性监控一堆函数地址。然后还能打印出比较漂亮的树状图,不仅可以显示调用流程,还能显示调用层次。并且贴心的把不同线程调用结果用不同的颜色区分开了。

现在就缺个数据源,把 IDA 的识别结果导出来了。大佬已经写好了,我们又可以白嫖了。

trace_natives:https://github.com/Pr0214/trace_natives

下载 traceNatives.py 放到 ida 的 plugins 路径下, 我是 mac 放在了 /Applications/IDA Pro 7.0/idabin/plugins 目录
Pr0214可能用的是 IDA 7.x+ 和 python 3.x 的环境, 在我的 IDA7.0和python2下需要微调下代码

# search_result = [f"-a '{so_name}!{offset}'" for offset in search_result]
search_result = ["-a '{}!{}'".format(so_name,offset) for offset in search_result]

# with open(save_path, "w", encoding="utf-8")as F:
with open(save_path, "w")as F:

# print(f"frida-trace -UF -O {save_path}")
print("frida-trace -UF -O {} !".format(save_path))

IDA 打开之前 http://91fans.com.cn/post/ldqsignone/ 里的 libxxbitmapkit.so, Edit -> Plugins -> traceNatives 。 然后在分析目录下面会生成 libxxbitmapkit_16250177xx.txt 这就是frida-trace要导入的数据了

先把某电商程序的App跑起来,然后命令:frida-trace -UF -O /Users/fenfei/Desktop/xx/armeabi-v7a_9_4_6/libjdbitmapkit_1625017920.txt

跑起来即可,请注意 这里不要出现中文路径,

最后 随便点个商品详情页

结果还是很漂亮的。

 Android 的so Trace 没问题,ios的App咋不行?

Android so 的代码段name是 .text, ios的代码段name是 __text ,所以我们在 getSegAddr 里加个判断

...
if (idc.get_segm_name(seg)).lower() == '.text' or (
        idc.get_segm_name(seg)).lower() == 'text' or (
        idc.get_segm_name(seg)).lower() == '__text' :
...

有一函数,第一个入参必定是2,但是不知道是哪个?可以Trace出来吗?

可以修改 handlers/libxxbitmapkit.so/xxx.js下面的脚本来完成一些特别的操作,比如在Trace的时候把第一个参数是2的函数挑出来。

  onEnter(log, args, state) {
    log('sub_10d71()');
  },

// 改成

  onEnter(log, args, state) {
    if(args[0] == 2){
        log(" ======== I am here! ========");
    }
    log('sub_10d71()');
  },

当然成千上万个函数,手工去改。。。


def alter(file,old_str,new_str):
    """
    替换文件中的字符串
    :param file:文件名
    :param old_str:就字符串
    :param new_str:新字符串
    :return:
    
    """
    file_data = ""
    with open(file, "r", encoding="utf-8") as f:
        for line in f:
            if old_str in line:
                line = line.replace(old_str,new_str)
            file_data += line
    with open(file,"w",encoding="utf-8") as f:
        f.write(file_data)

def findAllFile(base):
    for root, ds, fs in os.walk(base):
        for f in fs:
            yield f

def main():
    strPath = '/Users/fenfei/Desktop/work/blogCode/trace/__handlers__/libxxbitmapkit.so'
    
    for i in findAllFile(strPath):
        print(i)
        alter(strPath +"/" + i, "onEnter(log, args, state) {", 'onEnter(log, args, state) { if(args[0] == 2){ log(" ======== I am here! ========");}')

和 PEStalker 的遇到的问题是一样的,hook 的函数太多,App 很容易崩溃,必须有个方便的过滤措施,把一些频繁调用的,不重要的函数过滤掉。 

还有个工具叫 https://github.com/oleavr/art-tracer 据说比较牛叉,可以研究下。

6、r0tracer

安卓 Java 层多功能追踪脚本。精简版 objection + Wallbreaker

r0tracer:https://github.com/r0ysue/r0tracer

功能

  • 根据黑白名单批量追踪类的所有方法:hook("javax.crypto.Cipher", "$");
  • 在命中方法后打印出该类或对象的所有域值、参数、调用栈和返回值
  • 极简的文本保存日志机制、易于搜索关键参数
  • 针对加壳应用找不到类时可以切换Classloader

使用方法

  • 修改 r0tracer.js 文件最底部处的代码,开启某一个 Hook 模式。
  • 推荐使用 Frida14 版本,并且将日志使用 -o 参数进行输出保存。
    $ frida -U -f com.r0ysue.example -l r0tracer.js  --no-pause -o saveLog5.txt
    "-f"为Spawn模式,去掉"-f"为Attach模式
  • Frida版本=<12时,要加上--runtime=v8 选项。
    $ frida -U com.r0ysue.example -l r0tracer.js  --runtime=v8 --no-pause -o saveLog6.txt

优势

  • objection增加延时spawn
  • objection增加批量hook类\方法\构造函数
  • Wallbreakerfrida14上还是一直崩
  • Wallbreaker增加hookinstancefields
  • inspectObject函数可以单独拿出去使用

注意点:

  • Frida的崩溃有时候真的是玄学,大项目一崩溃根本不知道是哪里出的问题,这也是小而专的项目也有一丝机会的原因
  • Frida自身即会经常崩溃,建议多更换Frida(客/服要配套)版本/安卓版本,ROOT采用Magisk Root
  • 我自己常用的组合是两部手机,Frida12.8.0全家桶+Google Factoty Image Android 8.1.0,和Frida14.2.2全家桶+Google Factoty Image Android 10

使用 示例

某酒店App sign、appcode签名解析(一) 带壳分析 r0tracer:http://www.manongjc.com/detail/59-wufvbxfshklybfx.html

frida工具Jnitrace | Objection | r0tracer:https://blog.csdn.net/weixin_38927522/article/details/127120012

7、strace : 追踪进程的系统调用

官网:https://strace.io/
strace 跟踪进程中的系统调用:https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/strace.html
Linux 内核监控在 Android 攻防中的应用:https://zhuanlan.zhihu.com/p/453303913

Android上利用 strace 跟踪系统调用:https://mabin004.github.io/2019/06/27/Android%E4%B8%8A%E5%88%A9%E7%94%A8Strace%E8%B7%9F%E8%B8%AA%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8/
使用 strace:https://source.android.google.cn/docs/core/tests/debug/strace?hl=zh-cn
安卓 strace 下载:https://github.com/ipduh/strace/tree/master/binaries/arm64

strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。

-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出strace关于标准错误的调试信息.
-f 跟踪由fork调用所产生的子进程.
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column
设置返回值的输出位置.默认 为40.
-e expr
指定一个表达式,用来控制如何跟踪.格式如下:
[qualifier=][!]value1[,value2]...
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的 qualifier是 trace.感叹号是否定符号.例如:
-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none.
注意有些shell使用!来执行历史记录里的命令,所以要使用\\.
-e trace=set
只跟踪指定的系统 调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
-e trace=file
只跟踪有关文件操作的系统调用.
-e trace=process
只跟踪有关进程控制的系统调用.
-e trace=network
跟踪与网络有关的所有系统调用.
-e strace=signal
跟踪所有与系统信号有关的 系统调用
-e trace=ipc
跟踪所有与进程通讯有关的系统调用
-e abbrev=set
设定 strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.
-e raw=set
将指 定的系统调用的参数以十六进制显示.
-e signal=set
指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
-e read=set
输出从指定文件中读出 的数据.例如:
-e read=3,5
-e write=set
输出写入到指定文件中的数据.
-o filename
将strace的输出写入文件filename
-p pid
跟踪指定的进程pid.
-s strsize
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username
以username 的UID和GID执行被跟踪的命令

strace -o output.txt -T -tt -e trace=all -p 28979
跟踪 28979 进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。

Systrace Android 工具在内部调用一个名为 atrace 的工具,它是 ftrace 或 strace (Linux工具)的扩展。

frida_hook_libart

frida_hook_libart:https://github.com/lasting-yang/frida_hook_libart

frida_hook_libart 是 一个 hook native so 的工具,运行显示的结果类似于jnitrace

(1) hook native so:frida -U --no-pause -f package_name -l hook_RegisterNatives.js

(2) hook_art:frida -U --no-pause -f package_name -l hook_art.js

hook_RegisterNatives.js:trace 动态注册 jni 函数

hook_art.js

yang神 hook_art.js 依然是可以提供jni trace的。可以灵活的增加你需要hook的函数

var addrCallStaticObjectMethodV = null;
...

} else if (symbol.name.indexOf("CallStaticObjectMethodV") >= 0) {
        addrCallStaticObjectMethodV = symbol.address;
    console.log("CallStaticObjectMethodV is at ", symbol.address, symbol.name);
}

...

if (addrCallStaticObjectMethodV != null) {
    Interceptor.attach(addrCallStaticObjectMethodV, {
        onEnter: function (args) {
            if (args[3] != null) {
                    // var argsInt = parseInt(args[4]);
                    console.log("[CallStaticObjectMethodV] >>> args[3] = " + args[3]);
                            }else if (args[4] != null) {
                                    console.log("[CallStaticObjectMethodV] >>> args[4] = " + args[4]);
                            }else if (args[2] != null) {
                                    console.log("[CallStaticObjectMethodV] >>> args[2] = " + args[2]);
                            }else{
                        console.log("[CallStaticObjectMethodV]");
                          }


        },
        onLeave: function (retval) {
                            if(retval != null){
                            console.log("[CallStaticObjectMethodV] retval= " + JSON.stringify(retval));
                            }
                    }
    });
}

hook_artmethod.js:trace java 函数调用

修改AOSP源码打印

改 aosp 源码 trace 信息:https://bbs.pediy.com/thread-255653-1.htm

JNI-Frida-Hook

JNI-Frida-Hook 也是个不错的选择,这个哥哥把函数名都给你定义好了,哪里想看,Hook哪里。

:https://github.com/Areizen/JNI-Frida-Hook

art-tracer

:https://github.com/oleavr/art-tracer

jnitrace 能跑通的,首推。然后就是 jtrace ,定制方便,信息全。都崩溃的你怀疑人生的时候,老老实实用 hook_art.js ,能 hook 就行。

trace_natives

做技术,要相信一见钟情,第一次Native Trace用的就是他,层次分明,信息全,结合frida-trace使用,很奇妙的想法。

so_path, so_name = getSoPathAndName()
# search_result = [f"-a '{so_name}!{offset}'" for offset in search_result]
search_result = ["-a '{}!{}'".format(so_name,offset) for offset in search_result]
search_result = " ".join(search_result)

script_name = so_name.split(".")[0] + "_" + str(int(time.time())) +".txt"

save_path = os.path.join(so_path, script_name)
# with open(save_path, "w", encoding="utf-8")as F:
with open(save_path, "w")as F:
    F.write(search_result)

print("使用方法如下:")
# print(f"frida-trace -UF -O {save_path}")
print("frida-trace -UF -O {} !".format(save_path))

和jnitrace一样,hook的太多,很容易崩掉。大厂 so木办法玩。

findhash

findhash也是ida插件,他本来是为了检测加解密函数用的,批量hook一大堆可疑的加解密函数,但是它提供了一个超帅的批量hook模板,我们只要按照格式生成 hook_suspected_function 函数中的 const funcs 数组,就可以把他当成一个Native Trace库来用了。

:https://github.com/Pr0214/findhash

那const funcs 数组如何生成呢? 这个 改造下之前的 trace_natives.py 是可以的,不过我还没搞。
我改了下龙哥的例子,搞了个Unidbg的

public static Map<Integer, Integer> subTraceMap = new HashMap<Integer, Integer>();
public static Map<Integer, Integer> calcMap = new HashMap<Integer, Integer>();public static void PrintHookSubInfo(){//*System.out.println("subTrace len = " + subTraceMap.size());String strOut = "";for (Map.Entry<Integer, Integer> entry : subTraceMap.entrySet()) {int iShow = entry.getKey();// 为了和unidbg显示一致这里处理下if(iShow % 2 != 0){iShow = iShow -1;}strOut = strOut + "  ,['sub_" + Integer.toHexString(iShow ) + "','0x" + Integer.toHexString((int)entry.getKey() ) + "']";}System.out.println(strOut);// */
}private void traceFn(final long baseAddr, final long starAddr, final long endAddr) {// 这个代码是没法trace 导入函数的PrintStream traceStream = null;try {// 保存文件String traceFile = "/Users/fenfei/Desktop/money/ffFunctions.txt";traceStream = new PrintStream(new FileOutputStream(traceFile), true);} catch (FileNotFoundException e) {e.printStackTrace();}final PrintStream finalTraceStream = traceStream;emulator.getBackend().hook_add_new(new BlockHook() {@Overridepublic void hookBlock(Backend backend, long address, int size, Object user){// 块太小 影响 ida的 hook , 这里也可以改成 8  10 之类的if (size > 20){Instruction[] insns = emulator.disassemble(address, 4, 0);// 只搞函数 就开启这个// if (insns[0].getMnemonic().equals("push")){int level = emulator.getUnwinder().depth();assert finalTraceStream != null;for (int i = 0; i < level; i++) {finalTraceStream.print("    |    ");}// 为了和 frida-trace 对应,所以加 1//  Thumb 模式切换导致 hook失败// Frida-trace显示函数地址的方式是“sub_Hook地址”,因为Thumb模式下要+1的缘故,所以Frida trace中“sub_123C”在IDA中显示是“sub_123B”,对照ida分析时要注意一下。// finalTraceStream.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr) +1 ) + " ");// 给 traceNative 函数用的, 显示的时候就不加 1// finalTraceStream.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");int iSize = insns[0].getSize();int iUseAddr = 0;if( iSize == 4){// ARM模式 4字节iUseAddr = (int) (address - baseAddr);}else {// THUMB模式 2 字节 ,hook的时候 + 1 ,iUseAddr = (int) (address - baseAddr) + 1;}if(calcMap.containsKey(iUseAddr)){int iValue = calcMap.get(iUseAddr);calcMap.put(iUseAddr,iValue + 1);// 4次以上的调用就不显示了, 也不用Native Trace了if(iValue > 3){subTraceMap.remove(iUseAddr);}else{System.out.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");finalTraceStream.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");}}else{calcMap.put(iUseAddr,1);subTraceMap.put(iUseAddr,1);System.out.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");finalTraceStream.println("  " + "sub_" + Integer.toHexString((int) (address - baseAddr)  ) + " ");}}}}@Overridepublic void onAttach(UnHook unHook){}@Overridepublic void detach(){}}, starAddr, endAddr, 0);
}// unidbg/unidbg-api/src/main/java/com/github/unidbg/unwind/Unwinder.java
public final int depth(){int count = 0;Frame frame = null;while((frame = unw_step(emulator, frame)) != null) {if(frame.isFinish()){return count;}count++;}return count;
}

Native Trace有这两个就差不多了,崩溃的话就少hook点,把引发崩溃的hook点删除。

fridaMemoryAccessTrace

:https://github.com/asmjmp0/fridaMemoryAccessTrace

IDA trace

关于IDA trace 跟踪笔记:https://blog.csdn.net/qq_34905587/article/details/99984276

Unidbg Trace


http://www.ppmy.cn/news/331759.html

相关文章

ROS(indigo) 安装和使用更新版本的Gazebo----3,4,5,6,7 附:中国机器人大赛中型组仿真比赛说明

ROS(indigo) 安装和使用更新版本的Gazebo&#xff0c;本文以7为例。 Gazebo7支持更多新的功能&#xff0c;如果使用下面命令安装ROS&#xff08;indigo&#xff09;&#xff1a; ~$ sudo apt-get install ros-indigo-desktop-full 那么配套安装的是Gazebo2&#xff0c;如何在R…

2、Android Market简介注册过程

Android Market是个开放的平台&#xff0c;开发者可以上传和销售自己的应用&#xff0c;用户可以随时随地的下载、安装和评价应用。本文将对Android Market做相关介绍&#xff0c;并演示其注册过程&#xff0c;通过本文学习&#xff0c;你将了解Market并掌握如何开通自己的mark…

使用 PotPlayer 搭配 SVP 4 播放60帧电影

环境 系统&#xff1a;Windows 10 Pro 64播放器&#xff1a;PotPlayer插件&#xff1a;SVP 4 PotPlayer PotPlayer 是一款强大的视频播放器&#xff0c;而如果你在百度直接搜索会出现一个国内山寨 PotPlayer 的首页&#xff0c;所以请记住正确的官网地址&#xff1a;https:/…

Mediawiki页面权限设置 禁止游客编辑 禁止注册

分享一下我老师大神的人工智能教程&#xff01;零基础&#xff0c;通俗易懂&#xff01;http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章。分享知识&#xff0c;造福人民&#xff0c;实现我们中华民族伟大复兴&#xff01; 原贴:http://www.kankanblog.com/read.php…

Yolov5环境配置 配不好来打我

Yolov5环境安装及配置详细教程 文件准备Pycharm下载链接Anaconda下载链接Yolov5源码下载地址链接CUDA下载地址CUDNN下载地址 环境配置Pycharm安装Anaconda安装CUDA安装CUDNN安装使用Anaconda配置环境 第一次目标检测 文件准备 可以将这些文件都下载好放置在桌面上 再进行环境配…

服务发现注册对比

服务发现&#xff1a;Zookeeper vs etcd vs Consul 【编者的话】本文对比了Zookeeper、etcd和Consul三种服务发现工具&#xff0c;探讨了最佳的服务发现解决方案&#xff0c;仅供参考。 如果使用预定义的端口&#xff0c;服务越多&#xff0c;发生冲突的可能性越大&#xff0c;…

python - kubernetes中grpc服务健康检查实现

概述 kubernetes本身不支持gRPC健康检查&#xff0c;本文记录使用 ‘grpc-health-probe’ 实现grpc服务的健康检查 ‘grpc-health-probe’&#xff0c;这是 Kubernetes 原生的健康检查 gRPC 应用程序的方法 官方参考文档&#xff1a;https://kubernetes.io/zh-cn/blog/2018/1…

伦茨小知识-什么是耳机阻抗

耳机阻抗是耳机交流阻抗的简称&#xff0c;单位为欧姆(Ω)。不同阻抗的耳机主要用于不同的场合&#xff0c;在台式机或功放等设备上&#xff0c;通常会使用高阻抗耳机&#xff0c;有些专业耳机阻抗甚至会在300欧姆以上&#xff0c;这是为了与专业机上的耳机插口匹配。对于各种便…