入行两年了,从刚开始的丝毫不懂。到现在的蹒跚学步,可谓是一步一个脚印走过来的。当然现在依然还是菜鸟一只。但是比起两年前茫然不知学习方向来说,现在有了明确前进的目标,只要不停下脚本,终有一天能飞上枝头成为合格的老鸟。下面整理了一些我的学习方式。希望能够帮到和曾经的我一样茫然的萌新。
最核心的基础大多是在书中有详细的介绍。看书一般不是只读一遍。我个人的方式是先大致的过一遍,每页都粗略的翻一遍,过完一遍就能大概知道哪些内容以自己目前的知识量,很难看明白,哪些是自己熟悉但是却理解不深的。然后第二遍才开始细翻,暂时跳过很难读懂的,先做个标记,以后知识面广了。再回过头来看。简单推荐几本《android软件安全权威指南》、《android应用安全防护和逆向分析》、《深入理解android java虚拟机ART》、《深入理解计算机系统》
很多大佬都会发些实战的帖子。还有一些工具的使用。还有各种经验之谈。看书相当于是闭门造车,单靠自己的摸索的路是非常艰难的。站在巨人的肩膀上才能前进的更快。我最早先啥也不懂。就是看着大佬们的帖子渐渐入门。然后慢慢的才能看的懂书籍。
我经历过很多次培训,2011年培训前我本职是修电脑的。在北京培训了asp.net然后入行做网站。2016年我在广州培训c++然后转行做游戏。2018年自学后转行做安全,然后发现基础特烂,有些问题稍微变一点就很难自己解决。2020年又报了看雪的2w和3w班。关于培训很多人有各种说法,只能说是智者见智,仁者见仁。不能以一概全。至少我觉得物有所值,如果你缺乏自制力,或者自学很久也不见成效。可以找合适的线上课程试试。
github说它是公认最大的学习网站,应该不过分把?很多时候,伟大的前人,早就踩过了数不清的弯路,然后他们为我们铺平了道路。感谢开源精神,让知识遍地开花结果。当使用一些优秀的工具时,我们可以阅读源码,看看是如何实现的,熟悉代码后,完全根据自己的使用场景来修改bug,或者做一些优化。如果对底层非常熟悉,甚至可以看穿作者的核心思路,知道核心实现的原理。
学习这么久,最吃亏的就是好多看过的东西,久久不用,基本都忘干净了。但是如果在当时,有详细的记录整个思路的话,翻一翻还是可以捡的回来。个人博客的存在我觉得其实很像是一个线上的笔记簿。可以随时在任意地方翻看。而且现在markdown的风格也很漂亮,我是用hexo搭建的个人博客,空间是github的免费空间。写笔记的时候记住一个要点,这个记录的第一目标客户是未来的自己。为了确保自己肯定看的懂,要尽量的详细。
学习了新的知识点后,一般会自己做个正向的apk练手。或者是拿别人的crashMe之类的来练手。当我们前置知识准备的差不多了,就可以拿一些自己常用的软件来进行练手。比如说某色流app。或者是某小说软件去广告。不论最后能否成功达到目的,主要是在实战逆向中的一些见识,总结碰到的问题,最后如何解决的。如果解决不了,那又是因为什么因素。结果不重要,重要的是过程中的收获。
整合工具开发,我觉得是一种比较高效的学习方式。将别人优秀的项目魔改,并且进行一定的拼合,整理成一个成套的工具。在整理的过程中,必然要熟悉对方的代码,并且对部分代码进行调整。在这个过程中,就能快速汲取到他人的经验。这种行为。就是所谓的整合怪/缝合怪了。
fridaUiTools主要是把一些常用的frida的hook脚本简单统一输出方式后,整合进来。并且将自己觉得常用的功能做成界面调用的。并且在附加进程成功时获取一些信息默认的直接展示。后续会根据自己实战的经验。不断完善这个工具。
一直想做一个frida的脚本整理工具,有很多化腐朽为神奇的脚本由于常年不使用,自己都忘记了,觉得应该有一个工具把这些东西统一起来调用,因为常年使用win系统。导致我倾向于界面化的工具。我个人感觉。界面化的至少不用再记命令。操作也方便。然后我直接参考ZenTracer,在他的原理上,重新对整体的流程以及界面和功能做的更加完善一些。
我对这个工具整体功能划分为三块。
fridaUiTools
重点并不是开发整合工具的流程,而是学习并利用别人的项目的过程,所以下面主要是分析我整合用到的项目。
ZenTracer
界面化的批量hook多个类,可以通过拉黑过滤掉一些调用率特别高的类
在js脚本中使用占位符{MATCHREGEX}和{BLACKREGEX}在后续替换,来传递参数需要批量trace的类名以及黑名单。遍历所有类匹配出符合要求的类名。并且不在黑名单中。则进行批量hook。批量hook时会将函数所有重载都hook上。最后是三种输出方式,正常的log输出、函数进入时的参数输出、函数结束时的返回值输出。
优化界面显示,优化类名的输入环节。每次附加进程后。都将所有类名都保存下来。这里就可以选择之前缓存下来的所有类数据。然后根据输入智能过滤。可以快捷方便的找到自己想要hook的类。快捷添加操作。将经常要hook的类放在里面。就可以迅速的hook了。
r0capture
安卓应用抓包的通杀脚本。并且可以过证书校验解绑定ssl pining。可以导出客户端ssl证书。可以导出pcap文件
通过分析http https tcp udp ssl的系统框架或者第三方框架的调用流程。找到底层调用的地方进行hook。就可以在一定程度上通杀了。关于调用流程的分析的详细过程可以看看我之前整理的另一篇文章android抓包学习的整理和归纳。
证书的导出是选择一个证书会调用的时机,函数getPrivateKey和函数getCertificateChain。hook后。取出私钥和证书内容。重新设置密码导出新的证书。
定位sslpinning证书绑定的位置,是通过hook类型File的构造函数,并且打印出调用堆栈。然后在里面匹配证书绑定函数是否在调用链。这是一种技巧。在其他场合同样可以使用类似的技巧来找到关键代码的位置。
可以将抓包结果保存为pcap数据。便捷于一些擅长使用网卡抓包工具的人导入分析(例如wireshark)。这个关键是需要熟悉网络数据包的组成结构,然后按照格式写入文件。可以参考他的这个顺便学习一下数据包的组成。
下面是保存到pcap的代码
我去掉了pcap的保存,直接调用脚本。把输出方式统一起来(去掉所有js的console.log打印。统一格式send到py进行输出)。其他功能都保持原有的。
jnitrace/JNI-Frida-Hook
对所有jni的函数进行hook。比如vmp中大量使用到了jni的函数来模拟java的实现。对所有的jni进行hook就可以获得一些线索。
这里我列了两个项目,是因为这两个我都分析了一下。jnitrace的使用和输出都非常的方便,可以打印jni函数的结构,以及所有参数和返回值,并且代码结构优美,全部用ts实现的,可以说是非常完美的hook脚本开发模式,虽然很香,但是想要整合进来并不容易。我需要的是逻辑清晰易读的js文件来方便的嵌入,并且可以简单的修改。太过复杂庞大的js不利于我整合进来,所以最终选择了简单的JNI-Frida-Hook,这个项目只是简单的hook了jni函数,打印了一下函数名,并没有详细的参数和返回值。我们可以后续再进行优化
hook的js开发比较麻烦的问题是多文件的调用会很难处理。所以jnitrace使用了ts写脚本再生成js来解决。
而JNI-Frida-Hook直接使用的require("./utils/jni_struct.js")
。然后再通过frida-compile agent.js -o _agent.js
来将多个文件合并。
这里我就只讲JNI-Frida-Hook的实现了。首先需要设置hook的目标模块library_name以及要监控的目标函数function_name。
这里他对android_dlopen_ext进行hook。判断目标模块加载完成了,再进行目标函数的hook。如果不这样做,在spwan的附加的时候,就会找不到模块,因为模块还未加载。
遍历所有export符号,如果有找到设定的目标函数,就进行hook所有jni函数,并且在函数结束时,关掉所有jni函数的hook。
所有jni函数的hook实现就是准备所有jni函数的名称列表,然后遍历所有,然后hook的时候将jnienv的指针传进来,再根据jni函数名和jnienv的指针进行偏移,找到对应的函数地址。直接hook即可。最后FindClass可能比较特殊,就单独hook了。
这个项目的关键就是计算偏移,这里只要熟悉类对象结构的存储,再看看jnienv这个类的结构,就看的很明白了,贴一篇我以前写的笔记博文:类对象的内存布局
他只针对了spawn的附加情况进行hook。我调整了下,判断是哪种附加,再进行不同方式的调用。最后统一下输出的方式。
DroidSSLUnpinning
主要是处理防抓包的双向验证的,客户端验证服务端的证书。这个项目可以解掉证书绑定,让中间人抓包正常运行。效果和JustTrustMe差不多。他厉害的地方在于支持各种库的解绑定。市面上大多数的绑定方式他都有处理到。下面列一下大佬的支持的库
其实实现不难。但是关键是你要熟悉各种库的正向解绑定,知道是哪个函数来绑定的,然后将绑定函数给替换掉,直接改成空函数。所以像他这种支持这么多的,就比较厉害了。
sktrace
这个项目主要是用stalker来实现trace汇编代码,打印每一句汇编指令执行后寄存器的变化。一般用于辅助分析算法还原。但是由于frida的stalker本身对arm32的支持不太好。所以这个项目目前还不支持arm32。目前还未支持spawn附加。对于c的打印方式还未完善,没有打印出寄存器的具体数值
首先CModule声明了一段c的代码。然后transform设置使用c的函数。我想,他可能是为了方便打印数据。工作流程比较简单,就是设置了目标模块,设置了符号或地址(一般是函数开始的地址,会一直执行到这个函数完,所以不用设置终止位置),他设置了两种打印方式stalkerTraceRangeC和stalkerTraceRange。用c的打印方式结果展示的比较好。但是缺少寄存器数值变化。另一种则是直接发送到py。让py部分来处理结果。但是我看他py部分也是没有解析输出。自己动手改良了一下。
优化py结果打印,增加spawn支持。
frida_hook_libart
(ps:yang大神的三件套。向大佬学习。给大佬递茶。)
hook_RegisterNatives.js,hook打印动态注册的函数,分析so时静态注册的函数我们一般直接搜索Java开头的符号名就基本都是了,但是动态注册的我们静态分析没法找到对应的native函数。
hook_artmethod.js,java的函数打印最终都是调用的ArtMethod的Invoke。对这里进行hook。就可以打印所有java函数的调用了。
hook_art.js,hook art中的jni函数并且有打印参数和返回值,在aosp10上面测试了一下。一个都没hook成功。发现是判断_ZN3art3JNIILb0的问题。用ida打开libart.so。然后搜索一个里面想要hook的函数GetStringUTFChars。找到他的符号名是_ZN3art3JNI12NewStringUTFEP7_JNIEnvPKc
。所以修改下过滤的判断。改成_ZN3art3JNI
。然后正常输出结果。不过这个打印数据相当之多。另外这个也将上面的hook_RegisterNative.js的部分给包含了。
另外在测试的时候发现。hook_artmethod.js和hook_art.js里面用到的class StdString好像在frida12的版本会出错。升到frida14就正常了。
hook_RegisterNatives.js 遍历libart.so的所有符号,找到RegisterNative函数的地址。然后hook打印
hook_artmethod.js 遍历libart.so的所有符号,找到_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc符号的地址,也就是ArtMethod的Invoke,然后hook了打印堆栈和函数名
hook_art.js 遍历libart.so的所有符号,找到一些常用的jni函数,取出函数地址。然后hook函数,用对应的方式打印
没啥好改的。调整下日志打印方式。直接淦就完了。
(ps:另外说一下。虽然这个hook_art.js也是对jni的hook。但是和我之前封装的Jni-Frida-Hook是有一定区别的。这个是直接hook系统底层的。所有触发都会调用。而那个是指定某个函数触发时,hook所有jni函数。然后函数结束后,清掉所有hook。用哪种就看自己的需求拉。)
frida_dump
dump_module.js 从内存中dump so模块保存到文件(以前叫dump_so.js)。有时候用unicorn模拟执行片段指令的时候,直接使用apk中的so是不行的。因为缺少上下文数据,如果有对外部数据的使用,就无法正常执行。但是从内存中dump出来的so是在执行过程中的,所以自带了上下文数据。
dump_dex.js 就是脱壳,在DefineClass函数调用的时机进行dump dex保存到文件
dump_dex_class.js 和上面的差不多,多了个步骤load_all_class,这个函数主要是用来遍历所有classloader。加载所有类的。
前面两个比较简单,就只说dump_dex_class.js了。
首先是找到DefineClass的函数地址。然后从参数中取出dexFile。根据dexFile的结构,偏移指针的距离得到begin的值和大小。那么就可以把这个dexfile保存出来了。可以先把所有classloader里面的所有dexfile的class全部都加载了一遍,最后在保存的。下面是遍历loadClass的流程
遍历所有classloader。然后转换成BaseDexClassLoader,获取到DexPathList,获取到dexElements,再遍历所有的dexfile,最后通过entries枚举所有类名,最后loader.loadClass来加载这个类。可能是有的壳loadClass之后才会生成解密的dex。所以就先全部loadClass一遍,然后再进行dump
(不贴了,文章太长了。感兴趣的大家自己翻翻看吧)
测试了下libart.so不需要spawn判断。都可以获取到。所以去掉spawn判断部分。然后在dex保存的时候,碰到了权限问题。不知道是不是和安卓10有关。总之修改成py来负责创建目录,赋值权限。增加功能从手机直接把脱壳好的文件下载到项目内。dump_dex_class的dump_dex抽成功能单独调用
FRIDA-DEXDump
也是脱壳,不过和上面的方式不一样,是在内存中检索dex的特征的,再dump出来进行脱壳。同时支持objection
首先是设置了三个rpc功能。然后py根据这三个功能去根据dex特征检索内存,找到后验证数据,然后读取这段数据出来,再保存到文件。
1、scandex:枚举内存中所有只读的数据,检索只读数据中所有出现64 65 78 0a 30 ?? ?? 00
数据的段。这个是dex的二进制数据的头部特征。如下图。版本有的是035,有的是037.所以他把版本部分没有匹配。匹配到结果后,就开始验证这段数据是不是一个正常的dex。验证通过后就返回这段数据的地址和大小。
2、memorydump:用来读取指定地址,指定大小的数据,并返回。代码很简单。
3、switchmode:设置是否深度搜索。这里是有一个比较模糊的特征70 00 00 00
来进行搜索。上面的图也有一个正常dex中的该特征。
(不贴了,文章太长了。感兴趣的大家自己翻翻看吧)
统一下日志输出
FART
也是脱壳用的。基于主动调用的脱壳,可以过掉大多数函数抽取壳。这是fart的frida版本。免去了编译rom。
fart的原理比较长,详细的可以直接看作者的详细文章
1、FART:ART环境下基于主动调用的自动化脱壳方案
2、FART正餐前甜点:ART下几个通用简单高效的dump内存中dex方法
3、拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点
另外有我以前自己整理的一片文章
fart的理解和分析过程
将fart主动调用和dumpclass主动调用设置到rpc中。在功能里面来调用触发。增加将libart.so快捷push到手机并chmod权限的功能,另外测试发现LoadMethod地址的获取处,在安卓10无法获取到函数地址。修改成支持安卓10的。代码如下
Wallbreaker
主要是内存漫游,搜索内存中的java类和对象,并且可以打印类的结构体,以及对象的数据。
首先找到几个关键的文件如下。
Wallbreaker/__init__.py
功能调用的入口,四个功能classsearch、classdump、objectsearch、objectdump
Wallbreaker/wallbreaker/agent/command/__init__.py
功能实现的关键代码,这里使用rpc调用js的函数来获取相关数据出来加工处理。
Wallbreaker/agent/_agent.js
核心的js。这里提供了一系列查询内存的rpc接口。searchHandles、getRealClassNameByHandle、getObjectFieldValue、instanceOf、mapDump、collectionDump。
这个项目主要是使用rpc交互达到py调用js访问frida函数并封装各种便利功能。从这个项目延伸的话,我们用这个模式可以打造各种强大的frida交互工具。
结果输出方式调整。rpc.exports修改初始化方式,以免覆盖到其他js的rpc函数。将需要使用的所有脚本添加后,最后默认追加这个脚本。由于我默认做了类列表和过滤功能,所以classsearch可能有点鸡肋。
最后整理完成,目前功能还不是很多,而且bug估计还挺多的。希望大佬们多多指点,有什么比较好的想法也可以给点建议。
/
/
批量hook
function traceClass(clsname) {
try
{
var target
=
Java.use(clsname);
/
/
获取本类所有函数(注意getMethods这个是获取本类和父类中的函数。)
var methods
=
target.
class
.getDeclaredMethods();
methods.forEach(function (method) {
var methodName
=
method.getName();
/
/
获取所有重载
var overloads
=
target[methodName].overloads;
overloads.forEach(function (overload) {
/
/
参数的类型
var proto
=
"("
;
overload.argumentTypes.forEach(function (
type
) {
proto
+
=
type
.className
+
", "
;
});
if
(proto.length >
1
) {
proto
=
proto.substr(
0
, proto.length
-
2
);
}
proto
+
=
")"
;
log(
"hooking: "
+
clsname
+
"."
+
methodName
+
proto);
/
/
hook 函数
overload.implementation
=
function () {
var args
=
[];
var tid
=
getTid();
var tName
=
getTName();
for
(var j
=
0
; j < arguments.length; j
+
+
) {
args[j]
=
arguments[j]
+
""
}
/
/
函数进入时的参数啥的在里面通过send传给py
enter(tid, tName, clsname, methodName
+
proto, args);
var retval
=
this[methodName].
apply
(this, arguments);
/
/
函数结束时的返回值在里面通过send传给py
exit(tid, ""
+
retval);
return
retval;
}
});
});
} catch (e) {
log(
);
break
;
}
}
if
(is_black) {
break
;
}
log(aClass
+
"' match by '"
+
matchRegEx[index]
+
"'"
);
traceClass(aClass);
}
}
},
onComplete: function () {
log(
"Complete."
);
}
});
});
}
/
/
批量hook
function traceClass(clsname) {
try
{
var target
=
Java.use(clsname);
/
/
获取本类所有函数(注意getMethods这个是获取本类和父类中的函数。)
var methods
=
target.
class
.getDeclaredMethods();
methods.forEach(function (method) {
var methodName
=
method.getName();
/
/
获取所有重载
var overloads
=
target[methodName].overloads;
overloads.forEach(function (overload) {
/
/
参数的类型
var proto
=
"("
;
overload.argumentTypes.forEach(function (
type
) {
proto
+
=
type
.className
+
", "
;
});
if
(proto.length >
1
) {
proto
=
proto.substr(
0
, proto.length
-
2
);
}
proto
+
=
")"
;
log(
"hooking: "
+
clsname
+
"."
+
methodName
+
proto);
/
/
hook 函数
overload.implementation
=
function () {
var args
=
[];
var tid
=
getTid();
var tName
=
getTName();
for
(var j
=
0
; j < arguments.length; j
+
+
) {
args[j]
=
arguments[j]
+
""
}
/
/
函数进入时的参数啥的在里面通过send传给py
enter(tid, tName, clsname, methodName
+
proto, args);
var retval
=
this[methodName].
apply
(this, arguments);
/
/
函数结束时的返回值在里面通过send传给py
exit(tid, ""
+
retval);
return
retval;
}
});
});
} catch (e) {
log(
);
break
;
}
}
if
(is_black) {
break
;
}
log(aClass
+
"' match by '"
+
matchRegEx[index]
+
"'"
);
traceClass(aClass);
}
}
},
onComplete: function () {
log(
"Complete."
);
}
});
});
}
/
/
导出证书到指定路径,并使用新密码
function storeP12(pri, p7, p12Path, p12Password) {
var X509Certificate
=
Java.use(
"java.security.cert.X509Certificate"
)
var p7X509
=
Java.cast(p7, X509Certificate);
var chain
=
Java.array(
"java.security.cert.X509Certificate"
, [p7X509])
var ks
=
Java.use(
"java.security.KeyStore"
).getInstance(
"PKCS12"
,
"BC"
);
ks.load(null, null);
ks.setKeyEntry(
"client"
, pri, Java.use(
'java.lang.String'
).$new(p12Password).toCharArray(), chain);
try
{
var out
=
Java.use(
"java.io.FileOutputStream"
).$new(p12Path);
ks.store(out, Java.use(
'java.lang.String'
).$new(p12Password).toCharArray())
} catch (exp) {
console.log(exp)
}
}
/
/
在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue
Java.use(
"java.security.KeyStore$PrivateKeyEntry"
).getPrivateKey.implementation
=
function () {
var result
=
this.getPrivateKey()
var packageName
=
Java.use(
"android.app.ActivityThread"
).currentApplication().getApplicationContext().getPackageName();
storeP12(this.getPrivateKey(), this.getCertificate(),
'/sdcard/Download/'
+
packageName
+
uuid(
10
,
16
)
+
'.p12'
,
'r0ysue'
);
var message
=
{};
message[
"function"
]
=
"dumpClinetCertificate=>"
+
'/sdcard/Download/'
+
packageName
+
uuid(
10
,
16
)
+
'.p12'
+
' pwd: r0ysue'
;
message[
"stack"
]
=
Java.use(
"android.util.Log"
).getStackTraceString(Java.use(
"java.lang.Throwable"
).$new());
var data
=
Memory.alloc(
1
);
send(message, Memory.readByteArray(data,
1
))
return
result;
}
/
/
SSLpinning helper 帮助定位证书绑定的关键代码
Java.use(
"java.io.File"
).$init.overload(
'java.io.File'
,
'java.lang.String'
).implementation
=
function (
file
, cert) {
var result
=
this.$init(
file
, cert)
/
/
打印堆栈
var stack
=
Java.use(
"android.util.Log"
).getStackTraceString(Java.use(
"java.lang.Throwable"
).$new());
/
/
匹配证书绑定的函数是否在调用链中
if
(
file
.getPath().indexOf(
"cacert"
) >
=
0
&& stack.indexOf(
"X509TrustManagerExtensions.checkServerTrusted"
) >
=
0
) {
var message
=
{};
message[
"function"
]
=
"SSLpinning position locator => "
+
file
.getPath()
+
" "
+
cert;
message[
"stack"
]
=
stack;
var data
=
Memory.alloc(
1
);
send(message, Memory.readByteArray(data,
1
))
}
return
result;
}
/
/
导出证书到指定路径,并使用新密码
function storeP12(pri, p7, p12Path, p12Password) {
var X509Certificate
=
Java.use(
"java.security.cert.X509Certificate"
)
var p7X509
=
Java.cast(p7, X509Certificate);
var chain
=
Java.array(
"java.security.cert.X509Certificate"
, [p7X509])
var ks
=
Java.use(
"java.security.KeyStore"
).getInstance(
"PKCS12"
,
"BC"
);
ks.load(null, null);
ks.setKeyEntry(
"client"
, pri, Java.use(
'java.lang.String'
).$new(p12Password).toCharArray(), chain);
try
{
var out
=
Java.use(
"java.io.FileOutputStream"
).$new(p12Path);
ks.store(out, Java.use(
'java.lang.String'
).$new(p12Password).toCharArray())
} catch (exp) {
console.log(exp)
}
}
/
/
在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue
Java.use(
"java.security.KeyStore$PrivateKeyEntry"
).getPrivateKey.implementation
=
function () {
var result
=
this.getPrivateKey()
var packageName
=
Java.use(
"android.app.ActivityThread"
).currentApplication().getApplicationContext().getPackageName();
storeP12(this.getPrivateKey(), this.getCertificate(),
'/sdcard/Download/'
+
packageName
+
uuid(
10
,
16
)
+
'.p12'
,
'r0ysue'
);
var message
=
{};
message[
"function"
]
=
"dumpClinetCertificate=>"
+
'/sdcard/Download/'
+
packageName
+
uuid(
10
,
16
)
+
'.p12'
+
' pwd: r0ysue'
;
message[
"stack"
]
=
Java.use(
"android.util.Log"
).getStackTraceString(Java.use(
"java.lang.Throwable"
).$new());
var data
=
Memory.alloc(
1
);
send(message, Memory.readByteArray(data,
1
))
return
result;
}
/
/
SSLpinning helper 帮助定位证书绑定的关键代码
Java.use(
"java.io.File"
).$init.overload(
'java.io.File'
,
'java.lang.String'
).implementation
=
function (
file
, cert) {
var result
=
this.$init(
file
, cert)
/
/
打印堆栈
var stack
=
Java.use(
"android.util.Log"
).getStackTraceString(Java.use(
"java.lang.Throwable"
).$new());
/
/
匹配证书绑定的函数是否在调用链中
if
(
file
.getPath().indexOf(
"cacert"
) >
=
0
&& stack.indexOf(
"X509TrustManagerExtensions.checkServerTrusted"
) >
=
0
) {
var message
=
{};
message[
"function"
]
=
"SSLpinning position locator => "
+
file
.getPath()
+
" "
+
cert;
message[
"stack"
]
=
stack;
var data
=
Memory.alloc(
1
);
send(message, Memory.readByteArray(data,
1
))
}
return
result;
}
def
log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port,
dst_addr, dst_port, data):
t
=
time.time()
if
ssl_session_id
not
in
ssl_sessions:
ssl_sessions[ssl_session_id]
=
(random.randint(
0
,
0xFFFFFFFF
),
random.randint(
0
,
0xFFFFFFFF
))
client_sent, server_sent
=
ssl_sessions[ssl_session_id]
if
function
=
=
"SSL_read"
:
seq, ack
=
(server_sent, client_sent)
else
:
seq, ack
=
(client_sent, server_sent)
for
writes
in
(
(
"=I"
,
int
(t)),
(
"=I"
,
int
((t
*
1000000
)
%
1000000
)),
(
"=I"
,
40
+
len
(data)),
(
"=i"
,
40
+
len
(data)),
(
">B"
,
0x45
),
(
">B"
,
0
),
(
">H"
,
40
+
len
(data)),
(
">H"
,
0
),
(
">H"
,
0x4000
),
(
">B"
,
0xFF
),
(
">B"
,
6
),
(
">H"
,
0
),
(
">I"
, src_addr),
(
">I"
, dst_addr),
(
">H"
, src_port),
(
">H"
, dst_port),
(
">I"
, seq),
(
">I"
, ack),
(
">H"
,
0x5018
),
(
">H"
,
0xFFFF
),
(
">H"
,
0
),
(
">H"
,
0
)):
pcap_file.write(struct.pack(writes[
0
], writes[
1
]))
pcap_file.write(data)
if
function
=
=
"SSL_read"
:
server_sent
+
=
len
(data)
else
:
client_sent
+
=
len
(data)
ssl_sessions[ssl_session_id]
=
(client_sent, server_sent)
def
log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port,
dst_addr, dst_port, data):
t
=
time.time()
if
ssl_session_id
not
in
ssl_sessions:
ssl_sessions[ssl_session_id]
=
(random.randint(
0
,
0xFFFFFFFF
),
random.randint(
0
,
0xFFFFFFFF
))
client_sent, server_sent
=
ssl_sessions[ssl_session_id]
if
function
=
=
"SSL_read"
:
seq, ack
=
(server_sent, client_sent)
else
:
seq, ack
=
(client_sent, server_sent)
for
writes
in
(
(
"=I"
,
int
(t)),
(
"=I"
,
int
((t
*
1000000
)
%
1000000
)),
(
"=I"
,
40
+
len
(data)),
(
"=i"
,
40
+
len
(data)),
(
">B"
,
0x45
),
(
">B"
,
0
),
(
">H"
,
40
+
len
(data)),
(
">H"
,
0
),
(
">H"
,
0x4000
),
(
">B"
,
0xFF
),
(
">B"
,
6
),
(
">H"
,
0
),
(
">I"
, src_addr),
(
">I"
, dst_addr),
(
">H"
, src_port),
(
">H"
, dst_port),
(
">I"
, seq),
(
">I"
, ack),
(
">H"
,
0x5018
),
(
">H"
,
0xFFFF
),
(
">H"
,
0
),
(
">H"
,
0
)):
pcap_file.write(struct.pack(writes[
0
], writes[
1
]))
pcap_file.write(data)
if
function
=
=
"SSL_read"
:
server_sent
+
=
len
(data)
else
:
client_sent
+
=
len
(data)
ssl_sessions[ssl_session_id]
=
(client_sent, server_sent)
/
/
在要hook的模块加载完后,才调用hook代码
Interceptor.attach(Module.findExportByName(null,
'android_dlopen_ext'
),{
onEnter: function(args){
/
/
first arg
is
the path to the library loaded
var library_path
=
Memory.readCString(args[
0
])
/
/
判断当前加载的模块是否是目标模块
if
( library_path.includes(library_name)){
console.log(
"[...] Loading library : "
+
library_path)
library_loaded
=
1
}
},
onLeave: function(args){
/
/
if
it's the library we want to hook, hooking it
if
(library_loaded
=
=
1
){
console.log(
"[+] Loaded"
)
/
/
hook目标函数
hook_jni(library_name, function_name)
library_loaded
=
0
}
}
})
/
*
Calculate the given funcName address
from
the JNIEnv pointer
/
/
计算出jni函数的地址
*
/
function getJNIFunctionAdress(jnienv_addr,func_name){
/
/
最关键的起始就是这里,根据jnienv的地址和函数名,计算出偏移,其实就是拿函数的当前索引。这个了解类对象的结构就很清楚了。
var offset
=
jni_struct_array.indexOf(func_name)
*
Process.pointerSize
/
/
console.log(
"offset : 0x"
+
offset.toString(
16
))
return
Memory.readPointer(jnienv_addr.add(offset))
}
/
/
Hook
all
function to have an overview of the function called
/
/
hook全部jni函数
function hook_all(jnienv_addr){
jni_struct_array.forEach(function(func_name){
/
/
Calculating the address of the function
if
(!func_name.includes(
"reserved"
))
{
var func_addr
=
getJNIFunctionAdress(jnienv_addr,func_name)
Interceptor.attach(func_addr,{
onEnter: function(args){
console.log(
"[+] Entered : "
+
func_name)
}
})
}
})
}
/
/
在要hook的模块加载完后,才调用hook代码
Interceptor.attach(Module.findExportByName(null,
'android_dlopen_ext'
),{
onEnter: function(args){
/
/
first arg
is
the path to the library loaded
var library_path
=
Memory.readCString(args[
0
])
/
/
判断当前加载的模块是否是目标模块
if
( library_path.includes(library_name)){
console.log(
"[...] Loading library : "
+
library_path)
library_loaded
=
1
}
},
onLeave: function(args){
/
/
if
it's the library we want to hook, hooking it
if
(library_loaded
=
=
1
){
console.log(
"[+] Loaded"
)
/
/
hook目标函数
hook_jni(library_name, function_name)
library_loaded
=
0
}
}
})
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-6-25 11:09
被misskings编辑
,原因: 修改细节