学习了frida检测原理后,打算找个样本练手。起初是仿照着东方玻璃师傅的文章复现,但是在找最后一个检测点遇到了一些阻碍,导致最后一个执行流没找出来所以暂且先放放。后来在误打误撞下发现了一个新的样本,该样本有不少检测,故借此样本分析一下。最终完成检测的绕过,以及其中各类检测方法实现的分析还原。
阅读这篇文章,可能能让你掌握这些知识点:
首先就是检测点定位,这里直接使用两种方式进行frida注入的测试。这里直接使用frida的纯交互模式进行测试:
进入frida之后,发现frida马上就退出了,而程序正常运行: 
说明可能在某些so,或者子线程当中会有检测,接下来在app成功运行的情况下,使用attach进行附加,发现等待几秒钟后app直接退出, 退出之后app又重新启动了: 
那么已经可以推测出来,这个app在子线程当中一定有对frida的检测。接下来需要定位一下这个这个检测点到底在哪里,这里对dlopen进行hook,脚本如下:
在onEnter和 onLeave的位置输出一下提示,由于这个frida检测是通过退出自己的进程来打断frida注入的话,那么这个过程中检测的so可能会有下面的行为特征:
经过hook发现,进行检测的地方在这个libmsaoaidsec.so的so当中。而且可以看到的是在dlopen onLeave之前,这个进程就已经退出了,可以初步判断这个检测的位置大致是在init_proc,init_array执行过程当中,因为JNI_Onload的调用时机是在dlopen之后,所以需要对这个so当中的init_proc和init_array进行主要分析
确定了目标,就可以开始着手分析了。这里将so从apk当中取出来后,丢到IDA,接着想要通过段来找到init_array,这个结构,但是这个so有点不同,只有这三个段
这里直接在IDA View窗口搜索:能够搜到 DT_INIT,DT_INIT_ARRAY,对应的就是.init_proc,.init_array了。

这里为了快速定位到究竟是.init_proc还是.init_array当中的某个函数检测并退出进程的,就直接对call_function进行hook,将对应的函数偏移答应出来,如果遇到只进不出的就说明是在这个函数当中进行了检测行为了。对应的hook代码如下:
运行之后,发现是在这个0x14400函数退出进程的,因为并没有从这个函数onLeave,也就是检测在这个.init_proc函数当中。 
如果后续想要更进一步的了解这个so的检测的话就需要这个.init_proc进行深度分析了。这里先不深入进行分析,上面定位检测点的时候猜测是有通过子线程进行检测的,这里可以先尝试对pthread_create,进行hook,然后替换掉线程函数,看看能否绕过再说。
结合上述的分析结果可以判断,也是有子线程检测的。还有一种可能是,在.init_proc当中启动的子线程检测。这里就先尝试一下,通过替换子线程的线程函数来看看能否绕过检测。而想要对子线程的线程函数进行替换,就需要先找到到底是使用了哪一个函数,而想要做到这一步,就需要对pthread_create这个函数进行hook。既然提及到了这个pthread_create,那么顺带的也来整理一下这个函数的调用链,以及是怎么调用线程函数的。
Android分析有一点好处就是可以直接对照着源码,再和IDA的反编译进行比对分析。那么首先就先进行源码的分析。(源码可以使用在线网站,这里使用的是XRefAndroid - Support Android 16.0 & OpenHarmony 6.0 (AndroidXRef/AospXRef))
pthread_internal_t
在这个pthread_internal_t当中,对于找线程函数来说比较重要的就是这几个成员,他们的名称以及结构体偏移大致如下:
有了上面的了解之后就能够来分析pthread_create的调用链了。这个函数可以在源码当中的/bionic/libc/bionic/pthread_create.cpp当中找到: 
pthread_create首先会调用__allocate_thread将新线程所需要的内存空间申请好,并且划分不同的功能区。然后返回tcb指针,后续会通过tcb来获取pthread_internal_t*指针。接着就是对thread进行初始化,包括线程回调函数,以及函数参数的初始化。
完成上述准备之后,就进入到了最关键的一步,调用clone:以当前进程为模板,克隆出来一个新的执行流。这里需要注意的这几个参数:参数一(__pthread_start),参数四(thread)。 
跟进函数,继续分析: 
可以看到函数声明:fn就是__pthread_start,arg就是thread,接着往下分析:下面会判断fn是否为空,如果有值则调用__bionic_clone,来进行系统调用进行创建线程。 
到这里会发现无法跟进__bionic_clone,这里需要配合ida来分析libc.so代码。首先在libc.so的clone当中找到调用__bionic_clone的位置,标记一下参数传递所使用的寄存器。这里标记了几个比较重要的参数: 
这里是对照着源码的参数进行标记的。完成后进入__bionic_clone进行分析,汇编代码分析如下
这里系统调用之后如果返回的是子线程的空间的话,sp已经变成了子线程的栈帧。然后回进入__start_thread这个函数当中。此时x0是pthread_start的函数地址,x1是结构体地址。
接着继续看看__start_thread这个函数作用是什么。在这个函数当中,我们主要关注的是thread和phread_start相关的寄存器即可。 
最后会调用pthread_start这个函数。这个函数可以在源码当中看到是什么情况: 
可以看到传入的arg参数就是thread,接着会在后面调用thread当中的回调函数,也就是pthread_create传入的回调函数地址。综上所述,pthread_create的调用链大致如下(在其他不同版本的设备中可能会不太一样):
而且在这个分析过程中可以发现,有非常多的点可以通过pthread_internal_t结构体拿到对应的线程函数,例如clone,__bionic_clone,__start_thread等函数当中。有了上面的知识就可以来试试找出通过创建线程来检测frida的函数在哪里了。
有了上面的知识就可以通过frida来写脚本,获取线程函数地址了:
以Spawn方式注入,输出如下,可以发现能够找到对应so当中相关的线程函数了。 
接着就来试一下将这三个函数给替换掉
只不过替换线程函数之后就进程就直接崩溃了,命令行当中也打印了崩溃转储信息(这个可能需要多重复操作几次才会打印): 
通过转储信息可以看到一个是fault addr = 0x000b0202000015b2,另一个是backtrace,当中的调用堆栈。现在就需要去IDA当中通过调用堆栈回溯一下发生这种问题的点可能在哪里。
在IDA当中直接跳转到0x20d10的位置: 
可以看到这里是将W8,这个寄存器当中的值存放到x20+0x188的位置,通过转储信息可以看到此时的 x20 = 000b02020000142a而加上0x188之后就是这个fault addr了,那么此时需要找到这个x20,到底是从哪里来的。 
这里将x20高亮一下,然后再函数开头发现是从这个 [X8,#off_4B728@PAGEOFF] 中取出来的,接着找这个标签: 
最后发现是调用了sub_2082C这个函数的返回值。现在得分析一下这个sub_2082C这个函数到底是做什么的: 
进来看到好几个带有linker64的字符串,接着就是 .symtab,.strtab,solist

根据这些特征猜测这个函数是解析elf,根据solist这个符号获取solist的结构,用ai分析了一下确实是:通过解析 linker64 的 ELF 符号表,定位 Android linker 内部的 solist 全局变量,从而拿到当前进程加载模块链表的头指针。
那么接下来hook一下这个函数,看看返回值是什么:

通过对代码流程分析可以得出:如果这个函数返回值为0的话,就不会执行到0x20D10的位置,这里将返回值改为 0,验证 solist/soinfo 路径是否参与检测,顺便hook一下调用sub_2082C的函数,有没有正常退出(onLeave)。 
这个sub_20bdc就是上层的函数,可以看到能够正常退出了,而且很惊喜的发现这个dlopen也成功的退出了,则就说明剩下的init_array没有检测函数了。可惜这里的进程还是退出了,这里hook了一下JNI_OnLoad,发现JNI_Onload能够正常退出,但是进程仍然退出,并且也没有新的so加载,那么可能就是在java层调用了检测函数了: 
到这里就不再细究java层了。经过资料查找发现,有不少app都有使用了这个so来进行检测,这里找到最新版的某艺进行测试,依旧是使用一模一样的frida脚本,然后进行hook,发现是可以完成Frida的注入的: 
可以发现是能够注入了。
在以前旧版本的检测当中,只需要将启动线程的函数replace即可,但是在新版当中由于线程相关逻辑缺失,导致某个后续流程使用了未初始化/无效状态。在进行了nop这个线程函数,或者是替换线程函数的情况下,需要再另外对目标函数进行处理。满足以上两点才能够进行frida的检测绕过
这个so当中主要的混淆手段就是ollvm,还有就是字符串的加密,以及ShellCode的加密及使用。ollvm就不需要说了,这里就来讲讲后面两种:
这里找到一段反编译后的代码看看: 
大致的特征就是这种:先计算长度,然后异或解密这种。这种特征貌似不只是在这个so当中出现过,之前分析壳的时候好像也见过类似的。解决办法: 目前针对这种解密字符串没想到有什么比较好的方法,只能一个个写脚本解密。有比较好的想法的师傅可以指点一下
可能会有的师傅会想通过hook exit这类函数来定位一下进程是在哪里退出的。恐怕这个想法实现不了(可以试一下,主包这里没试过)。这里拿一个检测函数当中调用的退出函数看看:

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2小时前
被x0rrrrr编辑
,原因: