过某加固Frida检测
现象
通过jadx分析可知使用了某厂商加固
使用frida启动的时候,会退出应用
后多次尝试发现,只启动server时,也会使程序退出
推测该应用检测了Frida
定位
直接在lib目录里搜索frida
通过010逐一排查,只有红框中的so是“LIBFRIDA”,其他都是Friday。红框中正是壳so,我们只需分析它即可
Dump So
首先把libxxx.so拖入到IDA中,发现报错
继续后发现
为了防止我们的静态分析,它已经把section段的结构打乱了
ELF 两种视图:链接视图、程序视图,执行的时候只用知道Program中Segement即可。
正常情况下so加密分两种,一种是so自解密可以把解密方法放在.init_array段,另一种是先加载一个解密so A,再加载加密后的so B,让A去解密B。不管哪种为了正常运行,内存中的so都应该是解密后的。
使用GG模拟器Dump内存中的so,首先先让程序运行起来。
打开GG模拟器,选中我们要Dump的应用,选择最右侧栏,打开选项选择导出内存
选择起始地址,点击保存,导出dump下来的so
因为程序运行的时候是不需要知道section的,只用知道segement就行,但是静态分析又需要section,所以我们要通过从内存中导出的so,去修复section段,让ida能够正常反编译。
SoFixer[1]
-s 添加需要修复的so
-o 要保存的路径
-m 我们dump的起始地址,gg模拟器已经自动将开始和结束地址保存下来了,只用加开始地址就行(16进制形式)
-d 输出信息
分析
这时候再拖到IDA中分析,看到代码已经还原出来了
首先我们得先了解一下有哪些检测Frida的手段
-
1. 遍历运行的进程列表从而检查fridaserver是否在运行
-
2. 遍历data/local/tmp目录查看有无frida相关文件
-
3. 遍历映射库,检测frida相关so
-
4. 检查27042这个默认端口
-
5. 内存中扫描frida特征字符串 “LIBFRIDA”
-
6. frida使用D-Bus协议通信,我们为每个开放的端口发送D-Bus的认证消息
-
7. 检测有无frida特征的线程名,如:gum-js-loop
其实通过刚才的现象结合着这些检测点我们已经大致能推测出是哪种方式检测的了,因为还没有使用frida附加,仅启动server就会使应用退出,那么检测点1、2、3、5、7都不符合。
我们就推测它通过检测点6去检测Frida,向每个开放端口发送认证消息,认证消息里会包含“AUTH“字符串,我们直接在Strings Window里搜索“AUTH“
查找引用果然发现检测代码
void __noreturn sub_2CA40()
{
FILE *v0; // r11
int v1; // r4
__pid_t v2; // r0
unsigned int v3; // r0
char v4; // r0
char *v5; // r1
unsigned int v6; // r2
int v7; // r5
_BYTE *v8; // r6
int v9; // r3
int v10; // r4
int v11; // r6
bool v12; // zf
unsigned int v13; // r0
unsigned int v14; // r2
unsigned int v15; // r6
int v16; // r4
__pid_t v17; // r0
int v18; // [sp+0h] [bp-458h]
int v19; // [sp+4h] [bp-454h]
void *v20; // [sp+8h] [bp-450h]
int v21; // [sp+10h] [bp-448h]
unsigned int v22; // [sp+14h] [bp-444h]
void *v23; // [sp+18h] [bp-440h]
int v24; // [sp+20h] [bp-438h]
int v25; // [sp+24h] [bp-434h]
void *v26; // [sp+28h] [bp-430h]
char v27; // [sp+30h] [bp-428h]
int buf; // [sp+430h] [bp-28h]
__int16 v29; // [sp+434h] [bp-24h]
char v30; // [sp+436h] [bp-22h]
struct sockaddr addr; // [sp+438h] [bp-20h]
*(_DWORD *)&addr.sa_data[6] = 0;
*(_DWORD *)&addr.sa_data[10] = 0;
*(_DWORD *)&addr.sa_family = 0;
*(_DWORD *)&addr.sa_data[2] = 0;
addr.sa_family = 2;
inet_aton("127.0.0.1", (struct in_addr *)&addr.sa_data[2]);
while ( 1 )
{
v26 = 0;
v24 = 0;
v25 = 0;
v0 = fopen("/proc/net/tcp", "r");
if ( v0 )
{
while ( fgets(&v27, 1024, v0) )
{
v23 = 0;
v21 = 0;
v22 = 0;
v3 = strlen(&v27);
sub_138AC((unsigned int *)&v21, (int)&v27, v3);
v4 = v21;
v5 = (char *)v23;
v6 = v22;
if ( !(v21 & 1) )
{
v6 = (unsigned int)(unsigned __int8)v21 >> 1;
v5 = (char *)&v21 + 1;
}
if ( v6 >= 0xA )
{
v7 = (int)&v5[v6];
v8 = v5 + 9;
v9 = (int)&v5[v6];
if ( (signed int)(v6 - 9) >= 1 )
{
v10 = v6 - 9;
do
{
v9 = (int)v8;
if ( *v8 == 58 )
break;
--v10;
++v8;
v9 = (int)&v5[v6];
}
while ( v10 );
}
v11 = v9 - (_DWORD)v5;
v12 = v9 == v7;
if ( v9 != v7 )
v12 = v11 == -1;
if ( !v12 )
{
v20 = 0;
v13 = v6 - (v11 + 1);
v14 = v11 + 5;
v18 = 0;
v19 = 0;
if ( v13 < v11 + 5 )
v14 = v13;
sub_138AC((unsigned int *)&v18, v9 + 1, v14);
if ( v24 & 1 )
{
*(_BYTE *)v26 = 0;
v25 = 0;
}
else
{
LOWORD(v24) = 0;
}
sub_1E4F8(&v24, 0);
v24 = v18;
v25 = v19;
v26 = v20;
v15 = std::__ndk1::stoi(&v24, 0, 16);
v16 = socket(2, 1, 0);
*(_WORD *)addr.sa_data = bswap32(v15) >> 16;
if ( connect(v16, &addr, 0x10u) != -1 )
{
v30 = 0;
v29 = 0;
buf = 0;
send(v16, byte_72C60, 1u, 0);
send(v16, "AUTHrn", 6u, 0);
usleep(0xBB8u);
if ( recv(v16, &buf, 6u, 64) != -1 && !strcmp((const char *)&buf, "REJECT") )
{
close(v16);
v17 = getpid();
kill(v17, 9);
}
}
close(v16);
v4 = v21;
}
}
if ( v4 & 1 )
operator delete(v23);
}
fclose(v0);
}
else
{
v1 = socket(2, 1, 0);
*(_WORD *)addr.sa_data = -23959;
if ( connect(v1, &addr, 0x10u) != -1 )
{
v30 = 0;
v29 = 0;
buf = 0;
send(v1, byte_72C60, 1u, 0);
send(v1, "AUTHrn", 6u, 0);
usleep(0xBB8u);
if ( recv(v1, &buf, 6u, 64) != -1 && !strcmp((const char *)&buf, "REJECT") )
{
close(v1);
v2 = getpid();
kill(v2, 9);
}
}
close(v1);
}
sleep(4u);
if ( v24 & 1 )
operator delete(v26);
}
}
建立socket链接,然后通过端口去发送认证消息,recv函数去发送这个消息,如果不等与-1表示能发送成功,比较返回结果中是否包含“REJECT”,如果包含则说明存在fridaserver
过检测
我们可以直接Hook recv函数,这个是libc的函数,让它返回-1
Interceptor.attach(Module.getExportByName("libc.so", "recv"), {
onEnter: function(args) {
// console.log(Memory.readCString(args[1]))
},
onLeave: function(retval) {
retval.replace(-1)
console.log("bypass")
}
});
这时我们再次启动应用,发现已经可以正常使用frida了
引用链接
[1]
SoFixer: https://github.com/F8LEFT/SoFixer
随手分享、点赞、在看是对我们最大的支持
原文始发于微信公众号(移动安全星球):过某加固Frida检测