长亭百川云 - 文章详情

汽车APP产品分析-亿盾加固

矛和盾的故事

51

2024-07-13

目录

一、前言

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,因此需要在加载壳过程中还原该操作。

三、壳java层分析

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);

四、壳so静态分析

4.1、壳入口隐藏

按照我个人惯例,定位到壳so模块后首先使用ida加载模块静态分析收集下信息(字符串,壳入口、导出方法)等,用ida打开后直接提示如图4-1所示:

                                图4-1

不用奇怪,这是节信息被处理过了,防止静态反编译,点击ok继续,查看导出函数时发现一堆乱码,如图4-2所示:

                                图4-2

应该是被加密处理了,查看节信息也找不到init_array节,如图4-3所示:

                                图4-3

静态不好定位就动态定位壳入口,在内存中一切的隐藏都很难跳系统机制。

五、壳so动态分析

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相关知识。

贵州饭店

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2