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_libartdump 脚本修复加密so
:https://github.com/lasting-yang/frida_dumpfrida 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/120033269hook 常见算法: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 中的概念:
- Blocks: 以基本块为单位进行动态编译
- Call: 对于 BL* 保存 LR,栈等信息
- Transformer: 基本块如何被动态编译。提供用户接口,可读取,修改,插入指令
- Callout: 设置一个当此位置被执行到时的 callback,可获取当前 cpu 信息
- Probes: 在 trace 中使用类似 Interceptor 的 hook
- Relocator: 重定位模块,例如:ADR Xd, label
- Trust Threshold: 基本块在被 transform 次数达到可信阈值后,内容未曾变化,下次将不再重新编译
每当执行到一个基本块,Stalker 都会做以下几件事:
- 对于方法调用,保存 lr 等必要信息
- 重定位位置相关指令,例如:ADR Xd, label
- 建立此块的索引,如果此块在达到可信阈值后,内容未曾变化,下次将不再重新编译(为了加快速度)
- 根据 transform 函数,编译生成一个新的基本块 GumExecBlock ,保存到 GumSlab 。void transform(GumStalkerIterator iterator, GumStalkerOutput output, gpointer user_data) 可以控制读取,改动,写入指令。
- 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 信息。
- 执行一个基本快 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 fdef 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
类\方法\构造函数 Wallbreaker
在frida14
上还是一直崩- 比
Wallbreaker
增加hook
看instance
的fields
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/453303913Android上利用 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