Frida 入门小练习
本文为看雪论坛优秀文章
看雪论坛作者ID:ychangeol
本文为看雪安卓高研2w班(6月班)优秀学员作品。
1. 先hook、看参数和返回值
2. 再构造参数、主动调用
3. 最后配rpc导出结果,每一步搞完了没有任何疑问了,再进入下一步。
尤其是第三题,采用了反编译重打包修改Smali、反汇编so修改arm、以及frida hook native修改libc中的recvfrom函数的返回值这样三种方法,思路很好。
本文的3道Crackme来自Android高研2W班6月测试题。 这三题本身难度不大,flag的求解都是通过爆破长度为5位的字符串(只包含数字),求其hash1+salt的摘要与程序中硬编码的密文进行比较,非常适合Frida入门。
这三道题分别考察了:
1. Frida静态函数的主动调用。
2. Frida hook动态加载dex。
3. native层去反调试。
ps. 题目附件请点击“阅读原文”下载。
现在,看雪《安卓高级研修班(网课)》9月班正在火热招生!名额有限,点击查看详情报名吧~
第一题
静态分析
静态分析可知,输入的用户名和密码的字符串拼接作为参数,然后传入vvvv方法。
进入vvvv方法后,input参数限制长度为5,经过eeeee方法得到的结果和p一致,就可以获得flag。
简单逆向分析可以看出,主要算法在eeeee方法中。
sssss方法在获取字符串的Sha-1值。
ccccc方法将sha-1的摘要转化成了16进制字符串的形式。
因为input的内容都是数字,并且只有5位,所以爆破空间只有10的5次方,直接爆破就可以了。
复制代码重建工程
重新创建工程,可以看出eeeee的逻辑:将输入的字符串 逐字符插入到SALT的byte数组当中。然后再进行sha-1运算,获得其16进制的摘要字符串。
直接进行暴力破解,脚本以及结果如下:
public static void bruteForce(){
for (int i1 ='0'; i1<= '9'; i1++)
for (int i2 = '0';i2<='9'; i2++)
for (int i3 = '0';i3<='9'; i3++)
for (int i4 = '0';i4<='9'; i4++)
for (int i5 = '0';i5<='9'; i5++){
String flag = num2str(i1)+num2str(i2)+num2str(i3)+num2str(i4)+num2str(i5);
Log.d("test_flag",flag);
if (vvvv(flag)){
System.out.println(flag);
Log.d("test_flag is success",flag);
return ;
}
}
}
private static String num2str(int i){
return String.valueOf(i-48);
}
得到结果:
flag:66888。
Frida主动调用
对com.kanxue.pediy1.VVVV类中的方法eeeee进行主动调用。
function main(){
Java.perform(function(){
var flag = null;
var enc1 = Java.use("java.lang.String").$new("6f452303f18605510aac694b0f5736beebf110bf");
console.log(enc1);
// BruteForce
var res = null;
for (var i1 = 48;i1 < 58; i1++)
for (var i2 = 48;i2 < 58; i2++)
for (var i3 = 48;i3 < 58; i3++)
for (var i4 = 48;i4 < 58; i4++)
for (var i5 = 48;i5 < 58; i5++){
var flag = String.fromCharCode(i1,i2,i3,i4,i5);
console.log("flag",flag);
res = Java.use("java.lang.String").$new(Java.use("com.kanxue.pediy1.VVVVV").eeeee(flag));
if (enc1.equals(res)){
console.log("flag is success",flag);
return;
}
}
})
}
setImmediate(main)
最终得到flag也是66888。
输入检查一下:
第二题
本题在第一题的基础上加入了dex的动态加载以及Native函数的引入。
利用frida解决
因为loadDexClass在OnclickListener中调用,所以当点击事件发生之后,要加载的DexClassLoader才会被创建。
可以首先Java.choose主动调用loadDexClass方法;然后Java.enumerateClassLoaders枚举所有的类加载器,找到存在“com.kanxue.pediy1.VVVVV”的类加载器,通过Java.classFactory.loader=loader,对类加载器进行替换。
function main(){
Java.perform(function(){
var flag = null;
Java.choose("com.kanxue.pediy1.MainActivity",{
onMatch:function(instance){
console.log("hook instance method",instance.loadDexClass());
// console.log("hook instance stringFromJni",instance.stringFromJNI('66999').overload('java.lang.String'));
},onComplete:function(){console.log("search complete")}
})
Java.enumerateClassLoaders({
onMatch:function(loader){
console.log("Find ClassLoader",loader);
try{
if (loader.findClass("com.kanxue.pediy1.VVVVV")){
console.log("Successfully Find ClassLoader",loader);
Java.classFactory.loader = loader;
}
}catch(error){
console.log("found error",error);
}
},onComplete:function(){console.log("find complete")}
})
var res = null;
for (var i1 = 54;i1 < 55; i1++)
for (var i2 = 54;i2 < 55; i2++)
for (var i3 = 54;i3 < 58; i3++)
for (var i4 = 54;i4 < 58; i4++)
for (var i5 = 54;i5 < 58; i5++){
var flag = String.fromCharCode(i1,i2,i3,i4,i5);
console.log("flag",flag);
res = Java.use("java.lang.String").$new(Java.use("com.kanxue.pediy1.VVVVV").eeeee(flag));
console.log("res", res);
if (enc1.equals(res)){
console.log("flag is success",flag);
return;
}
}
})
}
setTimeout(main,2000)
替换掉loader之后,和题目1一样进行暴力破解,得到下面的结果。
但是输入结果并不对。所以,还需要去看一看so文件,分析一下stringFromJNI函数。
静态分析,猜测可能是将字符串转成数字,然后对数字加一。于是我把so文件拿出来,重新创建工程进行加载,如下图所示,验证结果与静态分析一致。
因此,输入结果应该是66999 -1 = 66998。
输入结果,获得flag,66998就是最终结果。check结果没有问题~
第三题
本题在第二题的基础上加入了native层对Frida的反调试。
Frida 反调试
v9 = a1;
v14 = &v6;
v13 = 16LL;
v12 = 0;
v11 = 16LL;
v10 = 16LL;
v15 = __memset_chk(&v6, 0LL, 16LL, 16LL);
v6 = 2;
inet_aton("0.0.0.0", &v8);
while ( 1 )
{
for ( i = 1; i <= 65533; ++i )
{
v5 = socket(2LL, 1LL, 0LL);
v7 = bswap32((unsigned __int16)i) >> 16;
if ( (unsigned int)connect(v5, &v6, 16LL) != -1 )
{
v20 = &v2;
v19 = 7LL;
v18 = 0;
v17 = 7LL;
v16 = 7LL;
v21 = __memset_chk(&v2, 0LL, 7LL, 7LL);
v26 = v5;
v25 = &unk_14A2;
v24 = -1LL;
v23 = 1LL;
v22 = 0;
v33 = v5;
v32 = &unk_14A2;
v31 = -1LL;
v30 = 1LL;
v29 = 0;
v28 = 0LL;
v27 = 0;
sendto(v5, &unk_14A2, 1LL, 0LL, 0LL, 0LL);
v38 = v5;
v37 = "AUTH\r\n";
v36 = -1LL;
v35 = 6LL;
v34 = 0;
v45 = v5;
v44 = "AUTH\r\n";
v43 = -1LL;
v42 = 6LL;
v41 = 0;
v40 = 0LL;
v39 = 0;
sendto(v5, "AUTH\r\n", 6LL, 0LL, 0LL, 0LL);
usleep(500LL);
v50 = v5;
v49 = &v2;
v48 = 7LL;
v47 = 6LL;
v46 = 64;
v57 = v5;
v56 = &v2;
v55 = 7LL;
v54 = 6LL;
v53 = 64;
v52 = 0LL;
v51 = 0LL;
v3 = recvfrom(v5, &v2, 6LL, 64LL, 0LL, 0LL);
if ( v3 != -1 )
{
if ( (unsigned int)strcmp(&v2, "REJECT") )
{
__android_log_print(4LL, "pediy", "not FOUND FRIDA SERVER");
}
else
{
v1 = getpid();
kill(v1, 9LL);
}
}
}
close(v5);
}
}
反调试逻辑:
通过一直循环创建Socket连接,遍历端口,检查端口是否被占用,收到“REJECT”时,说明frida-server正在运行,直接杀掉进程。
根据r0ysue老师的要求,下面从三种方法过掉反调试:
1. 反编译重打包
onCreate方法中的init方法就是具有反调试功能的native层函数。
思路:查看对应的smali代码,然后将对调用init方法的smali语句进行删除,就可以了。
使用apktool解包,删除对应的smali代码,然后重打包,最后再重签名一下。
2. so硬编码
.text:00000000000011F8
.text:00000000000011F8 loc_11F8 ; CODE XREF: detect_frida_loop(void *)+358j
.text:00000000000011F8 ADRP X1, #aReject@PAGE ; "REJECT"
.text:00000000000011FC ADD X1, X1, #aReject@PAGEOFF ; "REJECT"
.text:0000000000001200 ADD X0, SP, #0x1F0+var_19C
.text:0000000000001204 BL .strcmp
.text:0000000000001208 CBNZ W0, loc_1220
.text:000000000000120C B loc_1210
.text:0000000000001210 ; ---------------------------------------------------------------------------
.text:0000000000001210
.text:0000000000001210 loc_1210 ; CODE XREF: detect_frida_loop(void *)+370j
.text:0000000000001210 BL .getpid
.text:0000000000001214 MOV W1, #9
.text:0000000000001218 BL .kill
.text:000000000000121C B loc_123C
.text:0000000000001220 ; ---------------------------------------------------------------------------
.text:0000000000001220
.text:0000000000001220 loc_1220 ; CODE XREF: detect_frida_loop(void *)+36Cj
.text:0000000000001220 ADRP X1, #aPediy@PAGE ; "pediy"
.text:0000000000001224 ADD X1, X1, #aPediy@PAGEOFF ; "pediy"
.text:0000000000001228 ADRP X2, #aNotFoundFridaS@PAGE ; "not FOUND FRIDA SERVER"
.text:000000000000122C ADD X2, X2, #aNotFoundFridaS@PAGEOFF ; "not FOUND FRIDA SERVER"
.text:0000000000001230 MOV W0, #4
.text:0000000000001234 BL .__android_log_print
.text:0000000000001238 B loc_123C
思路:修改跳转地址,使程序无法进入kill(v1, 9LL)的代码段。
修改完之后,效果如下图:
这样进程就不会被杀掉了。
3. frida hook
思路:除了在strcmp的跳转处hook,还可以hook recvfrom方法,修改返回值为-1,从而无法kill掉进程。
代码:
function hook_native(){
var recvfrom_addr = Module.findExportByName("libc.so","recvfrom");
console.log("recvfrom_addr:",recvfrom_addr);
if (recvfrom_addr){
Java.perform(function(){
Interceptor.attach(recvfrom_addr,{
onEnter:function(args){
},onLeave:function(retval){
retval.replace(-1);
console.log("hook complete");
}
})
})
}
}
然后后面和题目二的解法完全相同。
最终flag值为:99998。
输入检查结果,没有问题。
总结
这三道题可以帮助我们熟悉Frida基本API的使用,难度不大,很适合初学
者。一步一个脚印,加油~~~
看雪ID:ychangeol
https://bbs.pediy.com/user-848457.htm
推荐文章++++