从三道题目入门frida

微信扫一扫,分享到朋友圈

从三道题目入门frida

本文为看雪论坛优秀文章

看雪论坛作者
ID:小白abc


本文为 看雪安卓高研2w班(6月班)优秀学员
作品。

下面先让我们来看看讲师对学员学习成果的点评,以及学员的学习心得吧!



讲师点评
 

这三道题主要考察`Frida Java Hook`三板斧“hook、invoke、rpc”中的前两板斧,即hook分析和主动调用。
第一题是签到题掌握
基础即可回答,第二题加入动态加载的Dex需要枚举Classloader
,第三题设置了全端口检测Frida的Native层反调试,要
求用三种解发去除这个反调试:其一是反编译重打包、其二是对so进行硬编码、其
三是Frida hook native,小白abc童鞋非常优秀,三种解法全部实现。             



学员感想

题目来自2w班6月习题
,题目本身都没有加壳没有混淆,主要逻辑就是输入flag,程序本身利用hash加密后与程序已有密文进行对比,细节方面略有不同,但大体来说就是爆破,整个过程逻辑很清晰,都不难,基本上都是Frida一把梭。

题目主要考察了以下知识点:

1. frida java hook与静态函数的主动调用

2. Frida遍历ClassLoader从而hook动态加载的dex的函数

3. frida native hook去反调试

ps. 题目附件请点击“阅读原文”下载。

现在,


看雪
《安卓高级研修班(网课)》9月班


开始招生啦!点击查看详情报名吧~

程序分析

Jadx打开程序:

可知程序逻辑就是将用户名和密码进行拼接传入关键函数VVVVV.VVVV(this, str),继续看VVVV函数:

可得知信息:

1. 输入长度为5位

2. frida hook这个函数直接强制返回true就行,这个check就这样pass了,但是拿不到正确的flag

后续的函数继续去观察会发现实际上就是一个sha1 + salt的加密。

又在运行程序后发现提示只能是数字,那么思路就清晰了,爆破!

解题方法根据要求写了两种:


1. Frida

直接上frida脚本,关键代码如下:

Java.perform(function(){

Java.use('com.kanxue.pediy1.VVVVV').VVVV.implementation = function(listener,input){

console.log('input= ',input);

for (var i = '0'; i <= '9'; i++) {

//System.out.println(i);

for (var j = '0'; j <= '9'; j++) {

for (var k = '0'; k <= '9'; k++) {

for (var t = '0'; t <= '9'; t++) {

for (var y = '0'; y <= '9'; y++) {

var newInput = Java.use('java.lang.String').$new(i.toString()

+ j.toString()

+ k.toString()

+ t.toString()

+ y.toString())

console.log(newInput)

var result = this.VVVV(listener,newInput)

if(result == true){

console.log('flag is ',newInput)

return result;

}

}

}

}

}

}

}

})

最终拿到flag为66888:


2. 抠代码

将APK中函数直接全部copy到一个java工程,运行一遍就行,实际上也是爆破,没啥差别。

这里只展示了main函数代码,最终拿到flag为66888:

check一下:

拿到flag!!!

程序分析

Jadx打开程序:

可以发现实际上就是动态加载dex并调用动态加载的dex中的VVVV函数。

jadx打开需要动态加载的dex:

其实会发现算法和第一题是一样的。

直接写frida脚本,遍历ClassLoader选择正确的classLoader再对函数进行主动调用爆破得到:

function enumerateClassLoader(){

Java.perform(function(){

Java.enumerateClassLoaders({

onMatch: function(loader){

//console.log('classLoader',classLoader.toString());

if(loader.toString().indexOf('dalvik.system.DexClassLoader')>-1){

console.log('find classLoader',loader.toString());

Java.classFactory.loader = loader;

hookVVVV()

return;

}

},onComplete: function(){

console.log('search complete!');

}

})

})

}

function hookVVVV(){

Java.perform(function(){

console.log('loader',Java.classFactory.loader);

for (var i = '0'; i <= '9'; i++) {

for (var j = '0'; j <= '9'; j++) {

for (var k = '0'; k <= '9'; k++) {

for (var t = '0'; t <= '9'; t++) {

for (var y = '0'; y <= '9'; y++) {

var newInput = Java.use('java.lang.String').$new(i.toString()

+ j.toString()

+ k.toString()

+ t.toString()

+ y.toString())

//console.log(newInput)

var result = Java.use('com.kanxue.pediy1.VVVVV').VVVV(newInput)

if(result == true){

console.log('flag is ',newInput)

return;

}

}

}

}

}

}

})

}

setImmediate(enumerateClassLoader)

这里需要注意的是,脚本attach的时间,要在第一次输入进行check后再将脚本attach上去这样才能保证遍历ClassLoader能够得到DexClassLoader,即校验函数正确的ClassLoader。

最终得到爆破得到的flag为66999:

但实际测试还不对,继续往回看,会发现有一个stringFromJNI的一层调用,这个函数是native函数,使用IDA打开:


这个函数有点骚……就是将输入的数字字符串转换为int然后加了一返回了 ……

那么flag就很明显了,应该为66999-1 = 66998。

check一下:

正确!

写在前面

这一题,其实就是第二题的进阶版,在第二题的基础上加了反调试,所以在这

里只分析反调试部分,其余不管。

程序分析

这一题和第二题不同的地方在于在OnCreate函数中,调用了init函数,而这个函数是native函数。

IDA打开这个so文件:

观察JNI_Onload函数可以发现java层的init函数被动态注册到init函数上去了。

详细介绍的话就涉及JNINativeMethod结构体了,这里不再赘述。

接下来就简单了,跟踪到init函数:

观察函数名,可以清楚的了解到,这个函数实际上就是创建了一个新线程用于检测frida,跟踪这个函数, 伪代码如下:

void __fastcall __noreturn detect_frida_loop(void *a1)

{

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(i) >> 16;

if ( 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 = "AUTHrn";

v36 = -1LL;

v35 = 6LL;

v34 = 0;

v45 = v5;

v44 = "AUTHrn";

v43 = -1LL;

v42 = 6LL;

v41 = 0;

v40 = 0LL;

v39 = 0;

sendto(v5, "AUTHrn", 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 ( 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. 二次打包

使用apktool将apk转换为smali然后,修改调用init函数的smali语句。

具体来说:


1.1. apktool 命令反编译为smali。


1.2 删除调用init函数的smali语句。

对应我这,也就是删除MainActivity.smali的第397和398行。


1.3 二次打包。

这样就过了反调试了。

2. frida去hookpthread_create函数

这里就不赘述了,直接几乎照抄的r0ysue大佬星球里的,链接见


https://wx.zsxq.com/dweb2/login



,这里也贴上我抄的代码吧(这里其实还有很多种其他更简单的方法,比如hook strcmp函数等等,我这里的代码还有很多的改进之处,仅供参考

function hook_pthread_create(){

var pt_create_func = Module.findExportByName(null,'pthread_create');

var detect_frida_loop_addr = null;

console.log('pt_create_func:',pt_create_func);

Interceptor.attach(pt_create_func,{

onEnter:function(){

if(detect_frida_loop_addr == null)

{

var base_addr = Module.getBaseAddress('libnative-lib.so');

if(base_addr != null){

detect_frida_loop_addr = base_addr.add(0xe9c)

console.log('this.context.x2: ', detect_frida_loop_addr , this.context.x2);

if(this.context.x2.compare(detect_frida_loop_addr) == 0) {

hook_anti_frida_replace(this.context.x2);

}

}

}

},

onLeave : function(retval){

// console.log('retval',retval);

}

})

}

function hook_anti_frida_replace(addr){

console.log('replace anti_addr :',addr);

Interceptor.replace(addr,new NativeCallback(function(a1){

console.log('replace success');

return;

},'pointer',[]));

}

3. 硬编码so

这里我注意到,在detect_frida_loop函数的最后,有一个strcmp函数的调用:

对应汇编中:

可以看到loc_1220分支和loc_1210分支分别为没有找到frida_server和退出进程,毫无疑问我们需要的是loc_1220。

这里我选择将偏移为0x120C处的B loc_1210这条汇编改为B loc_1220,从而达到无论有没有检测到frida_server都不要退出进程的效果。

修改之后伪代码变成了:

最终get Flag,之后的过程就和第二题一致了,脚本都不用变。

最终拿到flag为99998。

写在最后

整体来说,三道题目其实都不是很难,其实就考察了一些frida的API的基本使用,希望能对刚入门frida的人有点帮助。

看雪ID:小白abc

https://bbs.pediy.com/user-715334.htm

*本文由看雪论坛 小白abc 原创,转载请注明来自看雪社区。

活动专区

在本文下方留言,

留言点赞第一名 可以获得 看雪论坛 转正邀请码一个(价值1000雪币)。

使用邀请码后,可使临时会员转正成功,升级至正式会员!

好书推荐

公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com


ps. 觉得对你有帮助的话,别忘点
分享

点赞

在看
,支持看雪哦~



“阅读原文

一起来充电吧!

Linux系统监控命令详解

上一篇

高盛上调特斯拉目标价至1300美元 并给予该股中性评级

下一篇

你也可能喜欢

从三道题目入门frida

长按储存图像,分享给朋友