目录
1.1、app加固的本质
代码安全只是表面,核心是帮助客户满足业务不被阻断、关键数据资产不被窃取的安全需求。因为加固自身不创造价值,加固的价值必须和公司业务挂钩,来间接体现。通过安全体系建立为业务服务保障,增加收益与减少了资损率。
1.2、不同视角看加固
切换立场、改变视角来看APP加固,因为对一件事的不同面,每一面都能看到的不同的东西。
用户视角:更多的是关注功能使用与交互体验,看到的是表现层的功能和交互。所以对于C端用户而言,很难感知其存在价值。
产品视角:关注产品本身体验与价值而考量,看到的是需求、方案、价值。
技术视角:关注技术成本和可扩展性与安全性,看到的是架构、实现、可扩展、安全性。能感知到安全加固的重要性,但是更多的是从技术实现角度出发。
业务视角:保障业务正常运营,看到的是成本和收益。
所以,视角不同,观点不同,加固决策不同,安全程度也不同。
有了上面这些铺垫,接下来你看完文章后大概不会觉得这样的方案很奇怪,这是计划的一部分。
2.1、加固架构
2.2、解壳过程
壳加载运行起来后是解密原始dex与启动加载APP Application过程,完成一系列的工作:
解密原apk的dex集合
使用加密过程中对应的算法进行解密每个dex文件。
将解密之后的dex集合添加到dexElements数组
通过反射将解密的dex集合添加到dexElements数组。
动态加载原apk的Application
原apk的Application在加密过程被替换成解密壳的Application,因此需要在加载壳过程中还原该操作。
3.1、attachBaseContext
so释放与加载在创建APP进程加载Application之前,完成解压缩释放到lib目录、System.loadLibrary("nesec")加载so到内存并在MyJni类中注册了如下几个jni方法:
public static native void cp();
3.2、native load 加载dex
加载完so后调用注册好的native方法load解密dex并加载到内内存,dex加载的过程大到为dex文件解密及将解密的dex集合添加到dexElements数组。
3.3、原Application
加载完dex后要从原APP的Application运行,代码如下:
public static String strAppName;
通过动态加载原app的Application并执行,具体流程如下:
(1)通过Class.newInstance()创建一个Application实例;
(2)执行Application实例的attach();
Application v0_7 = MyApplication.a(arg13);
4.1、壳入口隐藏
按照我个人惯例,定位到壳so模块后首先使用ida加载模块静态分析收集下信息(字符串,壳入口、导出方法)等,用ida打开后直接提示如图4-1所示:
图4-1
不用奇怪,这是节信息被处理过了,防止静态反编译,点击ok继续,查看导出函数时发现一堆乱码,如图4-2所示:
图4-2
应该是被加密处理了,查看节信息也找不到init_array节,如图4-3所示:
图4-3
静态不好定位就动态定位壳入口,在内存中一切的隐藏都很难跳系统机制。
5.1、壳入口定位
根据linker加载so流程中主要有两个点可以作为壳入口,init或init_array是so程序代码可以执行的最早的时机, 然后才加载Jni_onload,只要在linker执行init进下断点就可以定位壳入口。如图5-1所示:
图5-1
定位到的init_array方法中有几个关键的地方,init_array3,解密Jni_OnLoad代码。如图5-2所示:
图5-2
.note.gnu.text:000000782421DFD0 dec_sub_7D26D97FD0 ; CODE XREF: dec_sub_7D26D98040+50↓p
5.2、运行时加解密技术
运行时解密,运行后加密:
.note.gnu.text:00000071554361D4 decCode_loc_71F33B41D4
执行到代码时解密,执行后加密回去,可以防止静态分析或内存dump。
5.3、linker加载填充方法
解密字符串表:
解析elf定位到加密后字符串且,解密后如图5-3所示:
图5-3
解密算法相对简单,就是异或算法
.note.gnu.text:0000007155463288 DecSecString_sub_71F336D288
解密指令:
像是魔改后的rc4算法。
.note.gnu.text:0000007155462040 dec_sub_7D26D98040 ; CODE XREF: Dec_sub_71F336C13C+13C↓p
填充指令:
解析ELF头,获取到Load so所需要的节,mmap映射到内存中,抹掉elf头信息,mprotect修改内存的读写权限。具填充代码如下:
.note.gnu.text:0000007155462F60
这样做的一个好处就是防止从内存中直接反elf dump出来。以上流程都是在init_array中完成,接下来是执行JNI_OnLoad方法。
5.4、JNI_OnLoad定位
虽然代码己经解密并填充,但是导出函数还是不能看到,所以还是需要借肋系统机制定位Jni_OnLoad方法。
分析LoadNativeLibrary流程,执行到到_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_方法,这个方法中会调用_ZN3art13SharedLibrary29FindSymbolWithoutNativeBridgeERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE,方法直接返回JNI_OnLoad地址。
5.5、反调式
查找是否有android_server运行
.note.gnu.proc:00000071553D11B4 40 01 89 9A CSEL X0, X10, X9, EQ
5.6、RegisterNatives
注册native方法
.note.gnu.proc:00000071553E88AC ; ---------------------------------------------------------------------------
根据源码中结构可以得到方法存放位置,X2寄存器存放native方法。
static int registerNativeMethods(JNIEnv* env
5.6、解密dex
检测常见脱壳器:
.note.gnu.proc:00000071553C3680 94 04 03 94 BL getString_sub_78232118D0
创建子进程反调试:
.note.gnu.proc:00000071553D2340 CC C9 02 94 BL fork
解密dex所需信息:
c:5FD6EB80B07CB412704C5A46D899C63B
从壳dex中拷贝出密文解密,包名、Application、key等信息
解密出明文dex:
.note.gnu.proc:00000071553EB834 ; 解密dex
解析壳DEX格式,获取加密存放的DEX,循环解密完6个DEX,解密出明文,dump点1,这时dump出来的dex是原始dex。
5.7、加载dex
在android10之前使用DexClassLoader加载的dex文件系统默认会执行dex2oat进行优化。但是android 10之后系统默认不在对使用DexClassLoader加载的dex文件执行dex2oat优化。Android 运行时只接受系统生成的OAT文件。
解密dex后直接调用_ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE 加载DEX,该方法定义如下:
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,
OpenCommon这是一个比较好的脱壳点。直接hook可以拿到dex。
接下来创建PathDexList对像,通过PathDexList的成员变量 Element[] dexElements来指向DEX文件,这样DEX就被加载到内存中。
6.1、dump后dex重打包
通过上面的分析其实dump点有很多,解密后内存中dump,DexFile::OpenCommon加载dex时。将dump出来的DEX重新打包,
修改入口类就可以运行(com.xiaopeng.mycarinfo.application.tinker.CarApplication),这个入口类在上面己经解密。
加载完所有DEX后反射调用原始入口,如图6-1所示:
图6-1
亮点:
Native层so保护技术相对于整体压缩加密的方式它做了sms技术与自定义linker加载so,抹掉elf头,节信息等,能很好的防止内存dump elf。如果要完整脱壳需要根据elf格式重新组合一个完整的so文件。防脱壳的安全度还是有的。
不足点:
虽然native层做了很多反调试,反脱壳等手段,但是最终DEX在内存中出现了完整的明文,很难防止各种脱壳机。
通过本次分析学习了解到高版本系统加载dex流程以及自定义linker相关知识。
贵州饭店