type
status
date
slug
summary
tags
category
icon
password
0、前言
创建一个简单的app,里面就是一个数据验证和一句话,然后360免费加固学习参考来自SWDD大佬和oacia大佬
PKID看确实是360加固
app里的代码:

mt查看如下:


内容如上,输入66才有反应
1、初探
放入jadx,把learn.so放入ida

java层是可以发现一些蛛丝马迹的,比如这里的libjiagu字符,这是360加固的标志性字符
native层倒是不能直接确定加壳方式
2. java层初步分析
我们在资源目录下找到AndroidManifest.xml
,从里面可以得知360加固的入口是com.stub.StubApp

因为
com.stub.StubApp
是第一个实例化的application,且StubApp
的attachBaseContext
方法是加固逻辑的第一个执行入口我们在这个类中,不仅能看见
attachBaseContext
还能找到onCreate
Application 的 onCreate 和 attachBaseContext 是 Application 的两个回调方法,通常我们会在其中做一些初始化操作, attachBaseContext 在 onCreate 之前执行

分析attachBaseContext,中间的内容明显是被加密了的,我们查看a方法

可以发现其实就是异或16的操作,写脚本解密过后注释上去

- 在 Android 9.0 (API 28) 中,Google 限制了非 SDK 接口的访问。这段代码通过反射绕过该限制,确保加固框架能够正常调用系统隐藏 API
mHiddenApiWarningShown
字段控制隐藏 API 警告的显示,设置为true
可避免触发警告

下面这些内容就是它会判断手机的架构,针对不同的架构加载不同的Native文件

再往下可以找到DtcLoader初始化的操作,jadx貌似反编译不出来,我们使用jeb

可以发现它调用了native层的
jgdtc.so
,当DtcLoader被加载到jvm时,会调用这个so文件,但是我们跟着路径去寻找是找不到这个文件的(看其他文章都略过了,应该不重要。。。)我们要分析的是
libjiagu.so
,这个 so 在 assets
目录下3. 壳elf导入导出表修复
选取
libjiagu_a64.so
分析,可以发现它导入表导出表都没有内容的,我们需要去修复先去看看dlopen调用了哪些so文件

这也是我们要分析的so,需要把
libjiagu_64.so
dump下来然后使用Sofixer工具去修复elf
命令如下
./SoFixer-macOS-64 -s /Users/lanzhiqiang/Desktop/360test/protect/assets/libjiagu_64.so_Dump -o /Users/lanzhiqiang/Desktop/360test/protect/assets/libjiagu_fix.so -m 0x7250470000 -d


4. 壳elf分析
虽然有了导入表和导出表,但是我们还是需要想办法跟踪逻辑找关键函数,那我们就可以hook open函数来看看打开了哪些

这里可以注意到反复调用了/proc/self/maps,这就是典型的内存映射检测反frida,因为frida使用时会在内存中注入
frida-agent.so
文件绕过方式呢也不难,即然它检测的这个maps,那我们就手动调用open函数,在其调用maps时重定向至其他自定义maps即可
比如我在
data/data/com.example.learn/
下新建了一个maps空文件然后用如下脚本去看看是否成功 and 调用了哪些dex

通过返回结果,即可以确定是调用maps去隐藏内存映射,也发现打开了三个dex文件
那么我们就要去追踪这些dex文件的调用情况,即追踪调用栈
我们在上一份代码中加一些内容即可

这里不管是精确还是模糊,都能看出三个dex文件的调用栈基本一致
根据偏移,我们去ida里寻找

填充的一堆数据,目前也不知道有啥用,往下继续翻到

发现被调用了,查看是sub_8510函数

这里的内容像是so的加载器,这时候是得猜测是自定义linker实现加固so
此时可以hook
dlopen
验证猜想- 明显重复多次调用libjiagu_64.so,标准调用一般是不会重复调用的
- 每次加载时解密不同部分的代码,防止一次性获取完整逻辑
- 加固后的库被存储在应用私有目录的隐藏文件夹(
.jiagu
)中
这些信息可以确定它是自定义linker
我们在010中对libjiagu_64.so(壳elf)分析可以找到一个新的elf文件,这里大概率就是上面在ida中找到的自定义linker实现加固so后的so文件

我们需要想办法把它dump下来
然后我们这里dump出来的
main.so
就是主elf了5. 主elf分析

其实是可以看出elf的program header table
是被加密了的,ida也无法分析

那我们就需要想办法解密主elf了
壳 elf 在代码中自己实现了解析 ELF 文件的函数,并将解析结果赋值到
soinfo
结构体中,随后调用 dlopen 进行手动加载soinfo
是 Android 系统中用于表示和管理动态链接库 (Shared Object, SO) 的核心结构体,全称为 "Shared Object Information"。它位于 Bionic C 库 (bionic/libc/include/bits/soinfo.h
) 中,是动态链接器 (Linker) 的核心数据结构之一
在ida中对dlopen进行交叉引用,查看调用


这个函数的内容和AOSP里的linker.cpp源码很像,如下


这里基本就是自定义linker实现的预链接函数了,我们需要导入soinfo结构体
在 ida 中依次点击
View->Open subviews->Local Types
, 然后按下键盘上的 Insert
将下面的结构体添加到对话框中导入后,在ida中
sub_8510
函数的a1进行类型声明soinfo* a1

但是我们观察一下可以发现,其实360加固里的soinfo是被魔改了的,比如上图中框起来的部分,没有完全修复
向上找调用关系,去看怎么魔改的

在源码中也可以找到类似的地方

所以可以直接说sub_4C7C是register_soinfo_tls函数,往里面找到

这里的0x38,我们在010里看对应关系

恰好程序头大小也是0x38,那么这个方法肯定就是在加载程序头了
其他的比较乱,我们去找程序执行流,再通过上面发现的内容去推
- 推荐oacia大佬的工具https://github.com/oacia/stalker_trace_so
prelink_image
<- sub_4D48
<- sub_4EB0
这是我们在ida里交叉引用的结果,再接下来
sub_4EB0
可能被 sub_8510
或 sub_918C
调用,我们看上面的程序流程,可以确定是sub_8510
这个函数并不陌生,我们在分析壳elf时就曾找到过这个函数(世界线收束)
跟着函数调用链一处一处的在 IDA 中跳转到相应的地址进行查看,只用关注这两个函数之间的内容
然后我们找到了rc4的函数sub_6234 # rc4_enc

找到了rc4的加密部分,还要找初始化部分,对其交叉引用


这里没有被识别,按P创建函数

果然是rc4的init函数,基于对rc4加密的理解,可以确定result就是key,也就是args[0],把它hook出来
刚刚对rc4_enc还原中也可以发现rc4是魔改了的,也hook
rc4_enc
的传入传出参数看看


我们发现传入的第一个参数是
sub_8510
的v3[0],也就是qword_3E260数组,第二个参数是sub_8510
的v3[1]而这这个数组的数据就是壳elf填充的,我们解密这一段即可
刚刚分析过程中,知道rc4_enc是魔改过的,且sbox本来是256长度,但他用了257和258,所以我们需要吧sbox hook出来看看(顺便规整这个rc4调用的脚本)
我们就得到了完整的sbox[258],直接解密的结果很混乱,继续看调用链

我们能找到
sub_380C
,且它调用了下面inflateInit_
、inflate
、inflateEnd
三个zlib标准库里面的函数,说明这个函数是解压缩函数,即uncompress
然后我们来尝试解密

但是解密内容肯定是不对的,不过往下翻还是能找到一个elf

还需要找其他关键东西
继续看调用链
能看见在
sub_55BC
里也出现了像样的内容,也有0x38,且这个函数是被sub_4D48
所调用的(这个函数同时也调用了preLinker_image
,之前分析过)。所以大概率得从这个函数入手分析,从调用链和函数逻辑都合理最后的逻辑范围被缩小到了sub_5C4C、sub_5794、sub_5950之中,我们挨个找,能发现在sub_5C4C中有很奇怪的逻辑

这里是ARM64 NEON 的两个指令,用于向量操作,参考:
vdupq_n_s8
用于将单个 8 位有符号整数复制到 64 位向量的所有元素
veorq_s8
用于对两个 64 位向量的 8 位有符号整数元素进行按位异或运算

相当于上图,
0xBD
是要异或的值,后面是长度0x150,依次分组。知道了逻辑就可以继续往下推,在上面解密脚本中添加如下:
得到四段,前面我们找过
program_header_table
有6段,每一段0x38,刚好是0x150,这个a正好满足程序头的大小然后wrap_elf是由两大部分组成的,wrap_elf中分离出来的4大段数据只是第一部分,长度0x25709,但是我们在wrap_elf找到这个位置

这后面还有一个elf,也就是wrap_elf的第二部分内容,我们把两段分开
然后我们继续观察,
.rela.plt
,.rela.dyn
储存的内容是要远远大于dynamic
的,所以我们可以锁定dynamic是d再加上上面我们分析的
一块一块来搞,我们修复的是wrap_elf_part2(主elf)
修复 program header table
复制
libjiagu.so_0x150_a
的所有字节,然后来到 wrap_elf_part2
中选中 struct program_header_table
粘贴Mac: command+shift+c/v全复制/粘贴
修复 .dynamic

program header table的(RW_) Dynamic Segment
的p_offset指向.dynamic段的位置

然后跳转到该位置去修复,同修复program_header_table

这里来解析一下.dynamic结构
修复重定位表
我们需要通过
.dynamic
段的 d_tag
字段来直到重定位表的位置对于我们修复主 ELF 比较重要的
tag
有d_tag | 值 | 含义 |
DT_JMPREL | 0x17 | .rela.plt 在文件中的偏移 |
DT_PLTRELSZ | 0x2 | .rela.plt 的大小 |
DT_RELA | 0x7 | .rela.dyn 在文件中的偏移 |
DT_RELASZ | 0x8 | .rela.dyn 的大小 |
我们可以在.dynamic中发现这些tag以及对应的值

每一个Elf64_Dyn结构大小刚好是16字节,010里面看是一整行,前四个字节代表表项(tag),依次找0x2、0x17、0x7、0x8
就可以知道
.rela.plt
在文件中的偏移为0x2C070,大小为0x1818;.rela.dyn
在文件中的偏移0x8490,大小为0x23BE0。这里的值和我们之前分离出的b和c的大小一样,也证明b是.rela.plt
,c是.rela.dyn
修复.rela.plt
和.rela.dyn
同之前的修复方式,根据找到的偏移和大小去复制粘贴

再把文件基地址设置为0xe7000

至此,主elf就修复完了
6. dex释放分析
思路跳回到我们hook open函数的结果,同时ida分析刚刚修复好的主elf


跳转到0x19F558,很明显的open逻辑,那么这里就是open dex的,继续看下一个0x135d7c

再往下就是调用libart.so里的内容,所以解密一定在0x135d7c附近
原因:当应用需要访问某个类(如通过反射或直接调用)时,ART 会通过FindClass在已加载的类定义中查找。若类定义未被正确解密,ART 将无法解析其结构,导致加载失败。
在这个地址所在函数上下去尝试hook,之前我们hook的是Android_dlopen_ext,但是由于这个是主elf不是使用上面的加载的了,所以我们得改用对dlopen做hook,最后找到sub_193D78


这个函数的第二个参数居然就是我们的dex文件,那么我们想办法dump下来就行
然后我们把这三个dex pull到电脑上

0.dex即可看到我们最开始自己写的逻辑
至此分析完毕
- 作者:Sh4d0w
- 链接:https://sh4d0w.blog//article/21250fad-5ffd-806b-a58b-cccb8cb1989c
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。