嗨,亲爱的读者,我已经研究移动应用了一段时间了。 大多数应用程序都不会尝试以某种方式向我隐藏其“秘密”功能。 我现在很高兴,因为我不必研究某人的混淆代码。

在本文中,我想分享我对混淆的看法,并讨论一种相对有趣的方法,该方法是使用NDK在应用程序中隐藏业务逻辑的。 因此,如果您对Android中经过混淆的代码的实时示例感兴趣-我要求cat。
在本文框架的混淆下,我们了解了将Android应用程序的可执行代码简化为难以分析的形式的情况。 造成代码分析困难的原因有很多:
- 任何企业都不想被它的“内部”戳破。
- 即使您有虚拟应用程序,也始终可以在其中找到有趣的东西(例如instagram )。
许多开发人员仅通过分叉ProGuard配置即可解决该问题。 这不是保护数据的最佳方法(如果这是您第一次听说,请参阅wiki )。
我想举例说明为什么ProGuard所谓的“保护”不起作用。 以Google样本中的任何简单示例为例。

将ProGuard与标准配置连接到它后,我们得到了反编译的代码:

“哦,什么都不清楚,”我们平静地说。 但是,在文件之间切换了几分钟之后,我们发现了类似的代码片段:

在此示例中,应用程序代码似乎相当薄弱(数据记录,创建视频捕获),因此即使在使用ProGuard配置进行处理后,原始代码中使用的某些方法也很容易理解。
此外,请看一下Kotlin中的数据类。 默认数据类创建“ toString”方法,该方法包含实例变量的名称和类本身的名称。
源数据类:

它可能会变成一个反面的花絮:

(在Kotlin中自动生成toString方法)
事实证明,ProGuard远离项目的所有源代码。
如果我仍然没有说服您以这种方式保护代码不便,那么让我们尝试在我们的项目中保留“ .source”属性。
-keepattributes SourceFile
这条线在许多开源项目中都有。 当应用程序崩溃时,它允许您查看StackTrace。 但是,从smali代码中提取“ .source”,我们得到了具有完整类名的整个项目层次结构。
根据定义,混淆是“将源代码转换为不可读的形式,以抵消不同类型的接收”。 但是,ProGuard(与标准配置一起使用时)不会使代码不可读-它可以充当压缩程序,压缩名称并将多余的类抛出项目。
ProGuard的这种用法很简单,但并不完全适用于好的混淆解决方案。 一个好的开发人员需要使转销商(或攻击者)害怕“汉字”,而这很难消除混淆。
如果您有兴趣了解有关ProGuard的更多信息,那么我建议您阅读以下内容丰富的文章 。
我们隐藏了什么
现在,让我们看看应用程序中通常隐藏的内容。


代码中通常会隐藏一些更意外的内容(来自个人经验的观察),例如:
- 项目开发人员名称
- 项目的完整路径
- 用于Oauth2协议的Client_secret
- PDF图书“如何为Android开发”(可能永远存在)
现在我们知道了可以在Android应用程序中隐藏的内容,并且可以转移到主要内容,即隐藏此数据的方式。
隐藏数据的方法
选项1:不要隐藏任何东西,让所有东西都看不见
在这种情况下,我只给你看这张照片:)
“帮助达莎找到业务逻辑”

这种经济高效且完全免费的解决方案适用于:
- 不与网络交互且不存储敏感用户信息的简单应用程序;
- 仅使用公共API的应用程序。
选项2:以正确的设置使用ProGuard
该决定仍然具有生命权,因为首先,它是简单和自由的。 尽管有上述缺点,但它还有一个明显的优点:如果正确配置了ProGuard规则,则应用程序实际上可能会变得模糊。
但是,您需要了解,每个程序集之后的这种解决方案都需要开发人员进行反编译并检查一切是否正常。 花了几分钟的时间研究APK文件之后,开发人员(及其公司)可以对他们的产品安全性更有信心。
如何学习APK文件检查应用程序的混淆非常简单。
为了从项目中获取APK文件,有几种方法:
- 从项目目录中获取(在Android Studio中,通常该文件夹的名称为“ build”);
- 在您的智能手机上安装该应用程序,然后使用“ Apk Extractor”应用程序获取APK。
之后,使用Apktool实用程序,我们获得了Smali代码(此处的说明https://ibotpeaches.imtqy.com/Apktool/documentation ),并尝试查找可疑项目中的内容。 顺便说一句,要搜索可读的代码,您可以储备预制的bash命令。
该解决方案适用于:
- 玩具应用程序,在线商店应用程序等;
- 真正是瘦客户端的应用程序,所有数据仅从服务器端到达;
- 未在所有标语上写明“ 1号安全应用程序”的应用程序。
选项3:使用开源混淆器
不幸的是,我不知道用于移动应用程序的真正的免费混淆器。 在网络上可以找到混淆器,这会给您带来很多麻烦,因为为新版本的API组装这样的项目太困难了。
从历史上看,现有的酷混淆器是根据机器代码(对于C / C ++)制成的。 很好的例子:
例如,Movfuscator用movs替换所有操作码,使代码线性化,删除所有分支。 但是,强烈建议不要在战斗项目中使用这种混淆方法,因为这样代码可能会变得非常缓慢和繁重。
该解决方案适用于代码的主要部分是NDK的应用程序。
选项4:使用专有解决方案
对于专有软件,这是最适合严肃应用的最合适的选择:
a)支持;
b)永远是相关的。
使用此类解决方案时的混淆代码示例:

在此代码段中,您可以看到:
- 最难以理解的变量名称(带有俄语字母);
- 行中的中文字符无法清楚说明项目中真正发生的事情;
- 在项目中添加了很多陷阱(“开关”,“转到”),这极大地改变了应用程序代码流。
该解决方案适用于:
选项5:使用React-Native
我决定强调这一点,因为编写跨平台应用程序现在已成为非常流行的活动。
除了非常庞大的社区之外,JS还具有大量开放式混淆器。 例如,他们可以将您的应用程序转换为表情符号:

我真的很想为您提供有关此解决方案的建议,但是您的项目将比乌龟运行得快得多。
但是通过减少代码混淆的需求,我们可以创建一个真正受到良好保护的项目。 因此,谷歌“ js混淆器”,并混淆我们的输出捆绑文件。
该解决方案适合那些准备在React Native上编写跨平台应用程序的人。
Xamarin了解Xamarin上的混淆器会非常有趣,如果您有使用它们的经验,请在评论中告诉我们。
选项6:使用NDK
我自己经常不得不在代码中使用NDK。 而且我知道有些开发人员认为使用NDK可以将应用程序保存在反向器中。 这并非完全正确。 首先,您需要了解NDK如何进行隐藏。

事实证明很简单。 代码中有一些JNI约定,当您在项目中调用C / C ++代码时,将按以下方式进行转换。
NativeSummator本机类:

本机求和方法的实现:

本地静态求和方法的实现:

很明显,要调用本机方法,可以在动态库中使用Java_<package name>_<Static?><class>_<method>
函数搜索。
如果您查看Dalvik / ART代码,我们将发现以下几行:

( 来源 )
首先,我们将从Java对象生成以下行Java_<package name>_<class>_<method>
,然后尝试使用“ dlsym”调用在动态库中解析该方法,这将尝试在NDK中找到所需的函数。
这就是JNI的工作方式。 它的主要问题是,通过反编译动态库,我们将在全视图中看到所有方法:

因此,我们需要提出一个解决方案,以使函数的地址变得模糊。
最初,我尝试将数据直接写入JNI表,但是我意识到ASLR机制和不同版本的Android根本不允许我在所有设备上使用此方法。 然后,我决定找出NDK为开发人员提供的方法。
而且, 瞧 ,有一个“ RegisterNatives”方法完全可以满足我们的需要(称为内部函数dvmRegisterJNIMethod )。
我们定义一个描述本机方法的数组:

然后,在JNI_OnLoad函数中注册声明的方法(在动态库初始化后调用该方法, 千个 ):

糟糕,我们自己隐藏了“ hideFunc”功能。 现在应用我们最喜欢的llvm混淆器,并以最终形式享受代码的安全性。
该解决方案适用于已经使用NDK的应用程序(连接NDK会给项目带来很多困难,因此该解决方案与非NDK应用程序不太相关)。
结论
实际上,该应用程序不应存储任何敏感数据,或者只有在用户身份验证之后才可以访问它们。 但是,碰巧,业务逻辑迫使开发人员在应用程序内部存储令牌,密钥和代码逻辑的特定元素。 如果您不想共享这样的敏感数据并成为抄写员的“公开书”,希望本文对您有所帮助。
我相信混淆是任何现代应用程序的重要结构部分。
仔细考虑代码隐藏问题,不要寻找简单的方法! :)
顺便说一句,多亏了用户在某些问题上的帮助。 订阅她的电报频道,那里很有趣。
还要感谢sverkunchik和SCaptainCAP的用户在编辑文章方面的帮助。