0x00 加固
一、APK加固
APK加固最简单的方法就是将原APK整体或者部分进行加密并放入某个地方,例如爱加密将原Dex字节码加密并放入如下文件:
![](https://i-blog.csdnimg.cn/blog_migrate/5f6854400db7326850f415f125f94681.png)
将该加密文件放入壳程序,壳程序运行时一般在Application类中的相关方法会取出这些文件进行解密并通过动态加载的方式启动原APK程序。
一个简单的APK加壳原理介绍:https://blog.csdn.net/jiangwei0910410003/article/details/48415225
下面是常见的一些APK壳如何识别方法:https://blog.csdn.net/wenrennaoda/article/details/86170927
APK脱壳最简单的思想就是逆向分析壳程序的运行过程,理清处理过程。但是一般都会调用SO层进行解密或者处理。在SO调用时一般都会有反调试操作以及so加固(见小节),通过设置内存值或者其他操作跳过反调试并dump so,进行静态或者动态调试分析。如果您熟知安卓虚拟机运行过程,将会了解到在该壳中,虚拟机中的哪些函数能够获取原dex文件。
二、so加固
为了加大逆向的难度,一般将重点加解密操作使用JNI编程。然而这并不能本质解决问题,依然能通过类似IDA工具进行代码静态分析。为了防止静态分析,对SO加壳是必要的。一般so加壳的原理是通过某算法将so中的.text代码段进行加密并放回原处,在.initarray段函数或者JNI_Load中实现对.text段的解密工作。这样so被加载到内存后,通过启动函数将使得内存中的so得到还原。
也有些简单的加壳就是将section段加密或者删除,这是利用了IDA需要通过该段加载SO来进行静态分析的缺陷而使得无法打开so,而so的加载只需要segment就可以了。需要根据so的elf结构反向分析推导出section结构,网络上提供了一个section的修复工具。
一般加壳都是按照第一种方式,虽然so被加密无法静态分析。但是运行在内存中so一定被还原,可以通过IDA的IDC脚本dump出对应的so,并修复section段。最终得到可静态分析的so。
https://blog.csdn.net/QQ1084283172/article/details/78818917
0x01 反编译安全
一、配置文件和资源文件格式增加无效数据
apktool是Android分析中会用到的一个重要的开源工具,但是apktool的使用者的增加,相应的对抗apktool的手段也开始日益增加。 防apktool的方法有很多,其中一种就是针对.arsc文件或者XML配置文件做处理,例如增加一些字节,并且该字节不影响Android系统对APK文件的解析,但是能够造成apktool解包失败的手段。
解决该问题的方法就是首先需要熟悉标准序列化后的.arsc文件或者XML配置文件的数据结构,然后分析反序列化失败的文件并分析其中错误的原因。找到原因后要么手动修改其中的字节,也可以下载APKtool源码修改其中的逻辑并编译生成新版本APKTool。
010editor二进制解析工具,能够下载XML、arsc、elf等文件格式的模板,通过模板解析文件进行格式学习和分析是个不错的选择。也可以直接下载APKtool解析源码进行学习。
例如.arsc文件一个对抗:https://www.freebuf.com/articles/terminal/75944.html
二、增加花指令
由于DEX文件中的数据以字节码的形式存在,所以很容易被反编译出源代码。那些反编译器(如dex2jar、apktool)是以顺序解释字节码来反编译的,如果遇到无效的字节码,便会反编译失败。DEX加花的原理就是给DEX文件中加入无效的字节码,使反编译失败。但是要保证插入无效的字节码永远不会被执行到,否则自己的程序也会崩溃。也就是使用跳转语句转到下一个合法字节码,达到跳过非法字节码。
https://blog.csdn.net/Zhuang_stark/article/details/77370580
0x02 增加阅读障碍
一、java层
1、代码混淆
目前反编译APK工具很多,并且很容易得到java层源码。如果不进行反混淆,用户能够通过出现的名称很容易理解函数作用或者功能目的,或者通过常用函数名称轻松定位到核心机密程序。为了消除代码中出现的函数名称出现了java层代码的混淆操作。通过混淆操作后反编译的代码将较难理解其中的意图:
![](https://i-blog.csdnimg.cn/blog_migrate/0397a0f3bd0ae677244458a023962993.png)
上图中从类函数名中均无法揣测含义,唯有通过分析代码流程或者Hook数据猜测代码含义作用。
2、资源名称混淆
类似于代码混表,这里是对资源名称的混淆:
![](https://i-blog.csdnimg.cn/blog_migrate/6bb1eab6a0d611a0083b4f5a74f1e39a.png)
若资源名称不混淆,逆向分析者很容易从资源名称的含义猜测出实现功能,通过id值搜索到使用的地方,使得代码定位变得轻松。所以对资源名称的混淆一来掩盖了资源的含义,二来加大了分析者定位和分析的难度。
例如您要通过界面文字进行代码功能的定位,但是该文字在XML文件中查到两处:
![](https://i-blog.csdnimg.cn/blog_migrate/e66c4db4509288e039003afa8e1970fa.png)
如果要代码定位,如果没有资源名混淆可能很容易猜测出哪个是我们需要的,此时您智能查看name对应ID进行定位,并根据代码行为确定是否是需要分析的地方。加大了分析的难度。
3、动态加载
动态加载和dll动态库的好处是相似的,有利于模块化的实现便于维护、在线更新而不用重新下载和更新APP等等。在Android开发中他也有这些好处:1、间接解决方法数超过65535的限制,不再将jar打入apk文件中。2、反破解,关于加密等比较隐蔽及隐私的的代码放在远端加载,即使别人破解了你的apk文件,也无法获取这些关键的代码,可以随时更换安全策略,提高应用的安全性;
这篇文章介绍了Android开发中使用动态加载的方法:https://www.jianshu.com/p/6438f161875a
例如在分析某号码识别应用协议时,发现通过DDMS中的methodprofile中的函数调用情况发现,很多出现的方法在APK反编译的代码中均找不到对应的类。后面发现在APP首次启动时会从服务器请求所有这些jar包。这样是安全的,一方便用户想要smalidea进行java层代码调试,则无法进行下一步的跟踪。另一方面、若重要代码被破解,在用户无感的情况下可以实时更新这些jar包。
4、字符串加密或者编码
一般逆向开发者分析一个功能时最先开始需要代码定位,而一般定位的方式都是通过关键的特殊字段进行搜索。为了防止逆向者通过搜索字符串的方式轻松定位到代码实现的地方,可以将APK包中关键代码处出现的字符串进行加密或者编码,当程序运行时先解码解密再使用。一般要求效率不高的系统可以这样做,或者解密解码算法不能太复杂。这种字符串加密的情况下只能通过其他手段进行代码定位了。
5、使用JNI编程
我们知道有很多工具都能对APK的DEX进行反编译,并且反编译的代码几乎和原java 代码很相似了。DEX文件的反编译是如此的成熟,以至于dex层的java代码能够轻松的理解。为了增加逆向难度,保护核心代码,同时也是为了代码的更加效率,在安卓开发中使用JNI编程。JNI编程就是调用so中的C或者C++代码。Android中采用的是ARM汇编体系,一般分析so是通过IDA静态和动态分析,但是ARM汇编的难以理解并且反编译代码的晦涩,使得逆向工作难度提高了一个档次。
6、增加多进程和线程
本意上使用多进程或者多线程不是为了增加破解难度,而是为了效率等原因。只是因为使用了多进程与多线程间接使得破解难度加大。原因是数据的处理代码不会在一个函数调用堆栈中。一般的APP你通过界面点击事件定位到的代码,一直跟踪代码到最低层,经常只是把数据写到一个内存处供其他进程或者线程取数据进行下一步处理,这时候就需要重新分析或者定位代码找到处理该数据的地方。无形中增加了逆向破解中代码定位的难度。
7、防java层XposedHook
插件制作、函数打印堆栈、研究函数参数内容等都需要进行Hook。java层常用的Hook框架就是Xposed,有了这个神器能够帮助逆向人员轻松定位代码、查看代码处理流程(https://blog.csdn.net/wenrennaoda/article/details/86482250)。为了避免自己的APP被hook进行分析,监测Hook是必要的。
下面这篇文章详细介绍了如何检测Xposed框架:
https://www.52pojie.cn/thread-691584-1-1.html
8、发布版关闭或者删除log
在开发时,开发人员经常通过log打印日志为了监视程序运行状况,日志中包含了很多业务逻辑的信息。所以在发布正式版时需要关系日志或者删除。逆向工作者一般在无法定位代码时,首先会想到是否可以查看日志。通过APP发布时通过修改一个参数值对log函数进行关闭。所以逆向人员需要找到该debug开关并通过Hook修改参数值是的log生效。如果为了进一步防护,可通过自动化工具在发布的时候讲所有log语句进行删除,直接堵死了查看日志的操作。
二、so层
1、函数主动注册
上文提到为了增加逆向人员的工作难度,可使用JNI编程。如果你了解JNI编程,so中的函数分为手动注册和默认方式注册。默认方式注册如下所示:
java层申明的native方法为:
private native void sayHello();
so中对应的native方法为:
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj)
java层调用sayHello方法最终会调用到so中的Java_HelloJNI_sayHello方法。这种函数注册方式是有规则的:全路径名+函数名称。这种方式虽然方便了编程,但同时也让破坏者找到了进攻的路口。逆向人员能够在so中通过导出函数表快速找到java层申明的native函数的实现代码。为了增加破解难度建议采用手动注册的方式:env->RegisterNatives。该方法将自定义的函数和native函数进行了关联,使得找到实现函数不那么太容易。然而使用RegisterNatives中依然需要通过Findclass反射找到java对应的类,参数中包含了java中的类全字符串,可从该处入手搜索并分析是如何注册的。有时候对该类名字符串会进行加密运行时解密,您只能从JNI_load中查看逻辑,得到解密算法,解密所有字符串,找到对应需要的类,分析注册到了哪个函数。
下面是native函数注册方法:https://www.jianshu.com/p/216a41352fd8
2、字符串加密或者编码
和前文java层叙述的字符串加密一个意思
3、C++多态使用
多态是C++一个重要特性,这里并不是因为为了增强破解难度而出现了多态,只是因为使用了多态间接增加了静态分析的难度。我们这里讨论的是动态多态。我们知道动态多态是运行时类型确定,一般在函数参数中使用基类指针或者引用接收派生类对象。程序运行时根据参数实际对象的类型调用其正确的虚函数方法。所以在使用IDA静态分析时,一般遇到虚函数是无法知道确切的调用函数。此时只能通过IDA动态调试SO进行动态调试,分析确定实际调用了哪个类的虚函数。然而、动态调试操作远比静态分析操作复杂,如果加入了反调试手段那就更加麻烦。
多态的好处使得代码精简、维护简单,间接使得逆向分析者需要进行动态调试。
4、反调试手段
正由于上节由于使用了C++的多态,或者逆向人员需要分析和确定数据的变化过程,动态调试是必要的手段。为了防止破解者的动态调试,通过各种手段为so增加了反调试手段,一般会另开一个线程,在该线程中循环监测PTRACE:
https://blog.csdn.net/jltxgcy/article/details/50598670
5、异步执行
一般的调用native函数后,进入so对应的函数运行,最后直接返回结果。这种方式属于线性调用,很容易进行流程分析。如果so层采用异步方式,调用的native函数仅作为放置数据的手段,so中的其他线程取数据进行运算,最后反射调用java 层函数并带入之前运算的结果。这一方面使得so中真实数据处理的代码定位变得困难,另一方面也难以跨so和java打印函数调用堆栈了。
6、SMC(Self Modifying code)自修改代码
在so函数中可能存在先后调用两个函数,第一个函数解密第二个函数所在位置处的数据。运行时代码就可以正确运行。为能够静态分析可使用IDA的IDC脚本进行解密。
0x03防止重打包
在破解者付出努力后,修改了一些重要资源和其中代码(增加广告或者套取用户信息并发送给黑客),并想要重新打包运行或者发布,对用户会造成所示。为了防止重打包最简单有两种方式:
一、本地验证
运行时计算当前运行包的签名值和保存的签名值是否一致,如果不一致表明APK被改动,退出程序。但是无法防止破解这找到保存的签名值进行修改。
二、网络验证
因为1的弊端,可采用网络验证方式。程序一开始运行计算APK签名值,发送给服务器,服务器进行验证。若验证不通过,服务器将拒绝所有该客户端的所有请求。
0x04 数据安全
网络传输中任何人都可通过某些方式获取传输的流量,例如一些不安全的免费wifi。例如通过wireshark可抓取通过本路由器的所有网络流量。如果数据不进行加密将会造成不可估量的损失。所以网络数据加密是比不可少的。
一、采用加解密算法
APP在请求或者接受的网络数据包时,需要对关键字段数据进行加密。现在的加密可分为对称加密和非对称加密。对称加密常用的有DES、3DES、AES、TEA、RC4等算法。这类加密算法要求客户端与服务器使用相同的密钥。所以一般分析APK包能够找到对应的密钥。所以后来出现了非对称加密,也就是服务器和客户端使用不同的密钥,客户端加密的数据只能由服务器的密钥解密,反之也是如此。因为非对称加密的算法复杂度,一般通过非对称加密算法协商出一个共享密钥,再使用该密钥进行对称加密。
二、加解密算法内联实现
如上所述,如果使用对称加密算法,破解者很容易找到共享密钥。并且一般开发人员会使用JDK标准加解密接口,破解人员能够轻松识别到使用的加解密算法。为了增加破解者的分析难度,一些加解密算法可自行实现,这样破解者需要分析加解密算法的流程,以为了确实是何种算法。也许该算法是某种无法解密的非对称加密,使得逆向者白白花费了很多时间研究。
三、自定义TCP数据格式加密流
在分析网络协议时,如果是http则可根据其中的一些关键字段或者信息,在反编译代码中搜索,能够很容易定位到处理代码。为了防止这么容易定位,建议自定义数据结构的TCP数据流,例如常见的TLV格式。
在接下来的日子里面,将一一详细叙述上述提到的安全机制细节。
(这里是当前遇到的一些直接或者间接增加了破解难度的地方,有待添加,持续更新。如果您有其他的防破解或者增加破解难度的手段还请您不吝赐教,谢谢)要换工作了转行其他领域,做个记录