抛砖引玉。
前言
本文仅供学习探讨之用,如果侵犯了您的权益请联系我删除。
工具
简要介绍
Frida
官方解释:
It’s Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code instrumentation toolkit. It lets you inject snippets of JavaScript or your own library into native apps on Windows, macOS, GNU/Linux, iOS, Android, and QNX. Frida also provides you with some simple tools built on top of the Frida API. These can be used as-is, tweaked to your needs, or serve as examples of how to use the API.
简单来说最主要的特色就是一个跨平台的可以使用JavaScript
代码片段来进行程序安全测试
的工具集,并提供较多的易用API与各种强大的特性。
SSLPinning
我们都知道通过安装伪造的证书与中间人攻击可以轻松的抓取HTTPS的加密流量信息,如mitmproxy、Charles、Fiddler等工具都是基于这个原理。
而SSLPinning就是对于HTTPS协议的反中间人攻击的一种手段。将服务器返回的证书与本地客户端的证书进行校验,如不一致则中断连接。
Frida检测
顾名思义,就是APP中包含了有专门用来检测Frida是否附加了或正在调试自身进程的代码。如果有检测到的话轻则APP闪退,重则封号。如一些游戏的反作弊引擎就会检测Frida,运气不好就会被封,但非游戏的大部分APP只做闪退处理。
禁用SSLPinning
通过Hook之类的方式在证书获取环节返回一个空的证书管理器,从而实现不让客户端校验或者通过Hook关键校验函数并返回true
来实现校验成功。
需求分析
现如今的APP只要是正经一点的基本上都会使用SSLPinning来防止用户轻松的抓取HTTPS流量信息,所以我们如果要抓包的话就必须得把SSLPinning干掉。
对于如何干掉SSLPinning其实已经有很多的开源项目了。有基于Xposed
的、LSPosed
的,甚至Magisk
的,但这些都存在一定的兼容性问题,手机变砖那真的是常态了。
而Frida的话则只需要有root权限即可,这也是选择Frida的主要原因之一。但Frida又属于检测的重点照顾对象之一,因此我们还需要进行Anti Anti-Frida
。
防止SSLPinning
/**
* 修改默认临时文件名前缀
* @see https://github.com/frida/frida-java-bridge/blob/main/lib/class-factory.js#L103
*/
if ('frida' === Java.classFactory.tempFileNaming.prefix)
Java.classFactory.tempFileNaming.prefix = 'gc';
Java.perform(() => {
const X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
const SSLContext = Java.use("javax.net.ssl.SSLContext");
// 注册个假的TrustManager类
const TrustManager = Java.registerClass({
implements: [X509TrustManager],
methods: {
checkClientTrusted(chain, authType) { },
checkServerTrusted(chain, authType) { },
getAcceptedIssuers() {
return [];
},
},
name: "com.network.TrustManager",
});
// 创建用来替换的TrustManager对象数组
const trustManagers = [TrustManager.$new()];
const SSLContextInit = SSLContext.init.overload(
"[Ljavax.net.ssl.KeyManager;",
"[Ljavax.net.ssl.TrustManager;",
"java.security.SecureRandom"
);
// Hook SSLContext.init 方法
SSLContextInit.implementation = function (keyManager, trustManager, secureRandom) {
console.log('[=] Intercepted SSLContext.init()');
// 替换假的TrustManagers
SSLContextInit.call(this, keyManager, trustManagers, secureRandom);
};
});
这里只做了一个最基本的防止SSLPinning校验的演示,如果你想了解更多的话推荐查看objection的实现。
反Frida检测
let i = 0;
Interceptor.attach(Module.findExportByName('libc.so', 'strstr'), {
onEnter(args) {
this.fridaDetection = false;
const haystack = Memory.readCString(args[0]).toLowerCase();
const needle = Memory.readCString(args[1]).toLowerCase();
if (-1 !== haystack.indexOf(needle) && (
-1 !== haystack.indexOf('frida') || // frida相关的字符串
-1 !== haystack.indexOf('gdbus') || // dbus线程名
-1 !== haystack.indexOf('gum-js-loop') || // gumjs线程名
-1 !== haystack.indexOf('gmain') || // vala线程名
-1 !== haystack.indexOf('linjector') || // 命名管道相关
-1 !== needle.indexOf('/data/local/tmp')) // 目录检测
) {
this.fridaDetection = true;
if (100 > i) {
console.log(
`[=] 检测你🐎呢\n` +
` haystack: ${haystack}\n` +
` needle: ${needle}\n` +
` i: ${i++}\n`
);
} else {
const detectionModule = Process.findModuleByAddress(this.returnAddress);
console.log(
`[=] 检测你🐎呢\n` +
` haystack: ${haystack}\n` +
` moduleName: ${detectionModule ? detectionModule.name : 'null'}\n` +
` modulePath: ${detectionModule ? detectionModule.path : 'null'}\n` +
` moduleBase: ${detectionModule ? detectionModule.base : 'null'} moduleSize: ${detectionModule ? detectionModule.size : 'null'}\n` +
` detectionPointOffset: ${detectionModule ? this.returnAddress.sub(detectionModule.base) : this.returnAddress}\n`
);
}
}
},
onLeave(retval) {
if (this.fridaDetection)
retval.replace(0);
}
});
在这个地方我们通过Hooklibc
中的strstr函数来实现简易的过检测,究其原因是因为不管你用啥方法,基于字符串查找的检测最终还是得调用这个方法。
但需要注意的是这种方法不是肯定能过,只是适用部分情况。很多用心点做的检测是可以轻松绕过这份代码的,当然也能Anti Anti就是了,这部分放在后面再说,先来看看代码效果。
未加载脚本时对进程附加Frida
加载反Frida检测脚本后对进程附加Frida
可以看到,在未加载脚本之前我们附加到进程之后是会直接被卸载掉的,而加载之后则可以正常附加,并且Frida API的调用也正常。
思考
上面说到举例的这种方法只适用于基于字符串匹配的Frida特征检测的情况,那么接下来就举出一些不适用的情况以及一些反检测的思路。列举的一些情况仅为我所知道的,如果你知道更多的请告诉我。
- 自己编写执行
syscall
的汇编代码来代替对于libc中函数的调用。 - 使用自己实现的模式匹配算法。
- 扫描可执行文件所属内存块,并暴力匹配frida相关特征。
- 对指定lib进行监控,循环计算
checksum
并校验,如果对不上说明被修改或者Hook了,这种方式比较暴力无差别。 - 对指定函数头部几个字节进行扫描,检测是否被Hook以及特征是否为Frida所属。
对于其中2
、4
、5
三种情况目前我并没有思路,比较好的方法就是找到检测点直接把检测做掉。
所以我们来看一下1
、3
情况的处理方式,对于这两种情况有一个通用的做法。即通过ptrace(PTRACE_SYSCALL)
的方式来监听APP的syscall
调用,并拦截函数序号__NR_openat
(对应为libc中的openat函数)。
相关函数与宏在linux/ptrace.h
与fcntl.h
中有定义。在拦截到相关调用的时候检查调用参数pathname
是否包含/proc/self/
,如果包含则取消调用并将返回值也即fd
改为我们提前准备好的去掉frida特征的相关临时文件的fd
。
这样当程序在对打开的信息流进行扫描的时候就发现不了frida了。
结语
到这里文章就结束了,迷迷糊糊的也不知道有没有说清楚,又水了一篇文章。
那么有缘再见~