首页
社区
课程
招聘
[原创]脱壳纪事——梆梆企业版(一)
2024-2-14 12:09 14456

[原创]脱壳纪事——梆梆企业版(一)

2024-2-14 12:09
14456

​ 本来这篇文章应该在年前发布了,但碍于原神和星铁的更新就拖到了现在(xxx,你害人不浅啊),其次便是近几日在站内也看到了saidyou大佬对梆梆企业版的分析,想着快过年了也不着急,然后就拖到现在,所以就当是一个DLC补充包吧!

​ 除去用于修复已加载到内存中的libDexHelper.so的ELF文件格式的init_proc函数和init_array函数中通过调用_cxa_atexit函数注册一些位于libDexHelper.so被卸载时调用的回调函数外,其重心就位于Jni_OnLoad中。

libDexHelper.so的分析如下:

其一先是对libc.so中函数进行hook,被hookd的函数和在分析样本中hook回调的偏移如下:

open、open_2、openat64、openat_2、mmap64、_close、read、write、pwrite64

0x51768、0x517D4、0x51840、0x518AC、0x5191C、0x51EDC、0x52060、0x52580、0x52B24

​ 虽说也有看到其他的如针对于日志的Z11do_hook_logi函数,但显得便没那么重要了。

其二便是我们最关心的检测分析,如下:

​ 有针对于环境的检测,位于偏移0x33E6C的函数处(该函数因无法再ida中反汇编,所以只能通过文字的方式),具体检测下方所示:

​ 调用 openat 中断访问/proc/self/attr/prev文件获取数值与字符串u:r:zygote:s0比对

​ 调用 access 访问 /sbin/.core/mirror 是否存在

​ 调用 access 访问 /data/local/su 是否存在

​ 调用 access 访问 /data/local/bin/su 是否存在

​ 调用 access 访问 /data/local/xbin/su 是否存在

​ 调用 access 访问 /sbin/su 是否存在

​ 调用 access 访问 /su/bin/su 是否存在

​ 调用 access 访问 /system/bin/su 是否存在

​ 调用 access 访问 /system/bin/.ext/su 是否存在

​ 调用 access 访问 /system/bin/fail/safe/su 是否存在

​ 调用 access 访问 /system/sd/xbin/su 是否存在

​ 调用 access 访问 /system/usr/we-need-root/su 是否存在

​ 调用 access 访问 /system/xbin/su 是否存在

​ 调用 _system_property_get("ro.build.tags", resultAddr) 返回release-keys与test-kes比较

​ 也有针对面具的检测如下所示:

​ 偏移0x34870处调用 fork 创建子进程,访问 /proc/self/maps通过_memmove_chk拷贝maps每个内存段的信息,检测每一个带有tls标志的内存段的首地址,范围:内存段大小,如果发现存在"/.magisk/"或者是".MAGISK"字样的字符串,检测maps中所有的内存段之后,调用getpid获取当前子进程的pid并通过kill结束。

​ 偏移 0x348F4 调用 fork 创建子进程,访问 /proc/self/maps通过_memmove_chk拷贝maps每个内存段的信息,检查带有[stack]标志、/memfd:标志、/system/bin/app_process标志、的内存段的首地址中是否存在字符串"/.magisk/"或者是".MAGISK",检测maps中所有的内存段之后,调用getpid获取当前子进程的pid并通过kill结束。

​ 当然so中这么热闹怎么可能少了java端的检测呢,位于偏移0x453F8处调用fork创建子进程,通过execl 启动 /system/bin/app_process(zygote方式启动), 进程为com.secneo.apkwrapper.H,参数一是:"--write-fd",参数二是:"68",姑且粗略的看了下这个main函数中也有检测/proc/self/status文件

​ 位于偏移0x3539C函数的函数检测CLASSPATH、hook框架、libart.so中的函数是否被修改,下方所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if ( *v0 ){
 
​    v1 = 0LL;
 
​    do{
 
​      v2 = (unsigned __int8)v0[v1 + 1];
 
​      v3 = ++v1;
 
​    }
 
​    while ( v2 );
 
​    if ( v3 >= 6 )
 
​      return inCaseForSaveEnvFileOrKill(128LL, 0xB6A287DELL, 4013LL);  直接br x12程序崩溃
 
  }

​ _system_property_get("dalvik.vm.dex2oat-flags", resultAddr)返回的值与 --inline-max-code-units=0比对

​ 偏移:4595C的函数(重命名为isExportFunctionExist)对目标so以绝对路径的方式通过mmap进行映射到maps中与原来已经在maps的so在内存中的导出函数进行比对前16个字节,如果比对不一致则返回1,否则返回0,如果maps没有这个so则返回0,如果maps中有这个so但没有这个要搜索的导出函数则返回-1

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art6mirror9ArtMethod14RegisterNativeEPNS_6ThreadEPKvb")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art6mirror9ArtMethod16UnregisterNativeEPNS_6ThreadE")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art9ArtMethod14RegisterNativeEPKvb")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art9ArtMethod16UnregisterNativeEv")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art9ArtMethod14RegisterNativeEPKv")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art11ClassLinker14RegisterNativeEPNS_6ThreadEPNS_9ArtMethodEPKv")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art11ClassLinker16UnregisterNativeEPNS_6ThreadEPNS_9ArtMethodE")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE")

​ isExportFunctionExist(0x21, "libart.so", "_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6mirror5ClassE")

​ 当然上述16字节只要不配对则程序崩溃

​ 位于0x365A4处的函数则是用于检测虚拟机环境,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
调用 _system_property_get("ro.bluetooth.name", resultAddr)
 
​              ro.ip.name
 
​              ro.product.manufacturer        
 
​              ro.product.model
 
这么四个属性对应的数值与"vmos""vmos"代表虚拟机)比较
 
调用 _system_property_get("vmos.browser.home", resultAddr)
 
​              vmos.camera.enable
 
​              ro.vmos.simplest.rom
 
​              persist.vmos.setting.show
 
​              persist.vmos.tool.show
 
这么四个属性对应的数值与0x30作比较(满足一定的条件resultAddr会被自动赋值为0x30)比较,此处是比较不相同
 
调用 _system_property_get("vmprop.wifissid", resultAddr)
 
调用 _system_property_get("persist.vmos.root.enable", resultAddr)
 
调用 _system_property_get("persist.vmos.root.show", resultAddr)
 
​              persist.vmos.skills.show
 
​              persist.vmos.xposed.enable
 
​              persist.vmos.xposed.show
 
​              ro.vmos.simplest.rom
 
​              vmos.camera.enable
 
上述调用_system_property_get获取属性值之后,如果_system_property_get的返回值是否大于0
 
调用 access 访问 /system/priv-app/vmos-pro-intent 是否存在
 
fopen 打开 //proc/version.osed.show 文件,调用fgets获取文件内容,是否存在字符串"titan"
 
fopen 打开 //property_contexts.show 文件,调用fgets获取文件内容,是否存在字符串"titan"
 
fopen 打开 //service_contexts 文件,调用fgets获取文件内容,是否存在字符串"titan"

​ 0x3d788 創建線程 (线程回调处==0x52EA8),该回调中为循环判断调用waitpid(pid, 0, 0)等待子进程是否被关闭(也就是偏移0x3D754处调用fork创建的子进程),如果关闭则杀死自身,如下图:

​ 0x52F98创建线程(线程回调 == 0x323F0),foepn /proc/pid/status文件,检测"State:"标志和"TracerPid:"标志,如果获取的pid不为0且跟调用getpid函数获取自身pid的不一致则调用中断 kill 关闭调试进程,否则调用sleep(1)循环检查status文件(该回调检测在0x3D754处调用的fork创建的子进程也会被调用),如下图:

​ 0x3d9bc 创建线程(线程回调:6DDF936FFC == 0x52FFC),该回调中为一直循环调用getppid如果返回值为1则说明父进程已终止,杀死自身(子进程),如下所示:

​ 0x3da1c创建线程(线程回调 == 0x53218) 该回调中有明显的反frida手段,如下所示:

​ 读取 /proc/self/task/threadID/status文件中的文件的Name属性,调用read中断每次只读一个字节与gum-js-loop、gmain、gdbus比对

​ 读取/proc/self/fd目录下的文件通过readlink函数与 linjector 比对

​ 只要上述两处判断有一处配对存在则直接调用inCaseForSaveEnvFileOrKill杀死作为子进程的自己

​ 否则死循环检测/proc/self/task/tid/status和/proc/self/fd

​ 本样本中的签名检测有两处,一处位于偏移0x3caac处通過反射getpackaeginfo獲取了當前包名的簽名经过一系列的算法求出大小为32字节的hash值再通过0x3D54C位置处调用strncmp函数比对签名。

​ 另一处则是位于偏移0x54568的函数中通过访问bask.apk定位文件偏移0x5afe058处大小0x4ac的数据进行一系列的算法求出大小为32字节的hash值以循环的方式比对签名。

​ 虽说也有看到一些其他的检测方式,但在本样本中并未启动,如:检测ro.debuggable之类,奇怪的是在本样本中并没有找到调用ptrace函数trace自身的情况

(不知道是自己看走眼了)

其三则为解密源dex文件,分析如下:

​ 跟样本在解密dex文件的步骤跟saidyou大佬分析的大差不差,在0x55FA8函数内调用malloc申请一大块内存(在分析的样本中大小为0x32C0008) ,其次便是通过反射获取DexCache类的dexFile字段,通过该字段访问壳dex定位被加密的数据,如位于偏移0x56018处的函数(被我命名为find_dexdata0_Flag)该标志在壳最大的dex中,搜索dexdata0标志,其被加密的数据也就在旁边(该函数内可以获取0x14bf000字节的被加密数据)。

其解密的函数位于偏移0x422DC处(有三个参数分别是:被加密数据的地址、存放解密后数据的位置、参与解密运算的32字节密钥),如下图所示:

但在跟踪的时候发现其实只解密了每个dex的前0x20000字节数据,且还会修正dexSize字段所记录的dex大小,所以推荐的做法是,下一次循环调用0x422DC函数进行解密前,dump上一次的解密并修复完的dex。

疑惑:

​ 此处就是我还没搞懂的问题了,希望有哪位好心人能帮我解惑,万分感谢!

​ 1、在位于偏移0x53030处,发现访问linker64并将linker64的前0x1000个字节映射到0x52c00000处,并将该0x52c00000处的0x1000的堆地址修改为可执行的内存属性,不知道有何作用。

​ 2、位于0x53218处的frida检测是在子进程中进行,检测的也是子进程的/proc/self/task/tid/status和/proc/self/fd文件,为什么不通过调用getppid获取父进程的进程号在检测/proc/self/task/tid/status和/proc/self/fd文件(当然,也不说检测子进程不行)

​ 3、这个检测CLASSPATH是啥原理

结尾:

​ 就先以获取源dex作为本篇的结尾吧,后续应该也会出针对于该样本l中ibdexjni.so中vmp部分的分析(嗯,会出的,应该会的,不知从何处传来咕咕咕的声音)


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

上传的附件:
收藏
点赞19
打赏
分享
最新回复 (14)
雪    币: 189
活跃值: (1085)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
苦瓜tim 2024-2-14 13:31
2
0
感谢大佬分享
雪    币: 3777
活跃值: (5535)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huangjw 2024-2-14 14:51
3
0

不错哦,是最新版的吗。 期待aijiami也来一个,还有易盾

最后于 2024-2-14 14:51 被huangjw编辑 ,原因:
雪    币: 1306
活跃值: (921)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
逆天而行 2024-2-14 19:38
4
0
太强啦
雪    币: 436
活跃值: (578)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
樨下 2024-2-14 22:11
5
0
huangjw 不错哦,是最新版的吗。 期待aijiami也来一个,还有易盾
应该是的
雪    币: 1412
活跃值: (4143)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
IamHuskar 4 2024-2-14 22:14
6
0
读linker头部 有一部分是查询e_machine看看是x86还是arm 判断模拟器
雪    币: 243
活跃值: (505)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
PoisonGZ 2024-2-14 22:34
7
0
求大佬写篇关于易盾的
雪    币: 13418
活跃值: (4748)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 2024-2-15 10:40
8
0
IamHuskar 读linker头部 有一部分是查询e_machine看看是x86还是arm 判断模拟器
加载dexhelp之前就判断了cpu类型
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
hack逃逸 2024-2-18 12:15
9
0
继续JCCKckckckck
雪    币: 229
活跃值: (213297)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
shinratensei 1 2024-2-18 17:37
10
0
tql
雪    币: 2097
活跃值: (3756)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
st0ne 1 2024-2-20 19:21
11
0
666
雪    币: 13418
活跃值: (4748)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 2024-2-21 08:24
12
0
把所有调用的api搞出来了?可以写一个frida脚本
雪    币: 217
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
云觞 2024-3-1 20:50
13
0
http://www.yxfzedu.com/article/7171大佬们看看这个网站的文章是不是侵权了呢
雪    币: 293
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
WhiteCloud 2024-3-3 21:48
14
0
都是强人呀
雪    币: 541
活跃值: (881)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
寻梦之璐 2024-3-8 11:27
15
0
云觞 http://www.yxfzedu.com/article/7171大佬们看看这个网站的文章是不是侵权了呢
我的文章也被搬过去了。。。。是个爬虫,好多都被爬过去了
游客
登录 | 注册 方可回帖
返回