入门级加固--3种加固方式学习记录
2019-10-26 19:33:30 Author: bbs.pediy.com(查看原文) 阅读量:148 收藏

最近刚开始接触Andorid加固,是从姜维前辈在2015年的一些帖子开始学习的,同时也从个别恶意样本中学到了其他的加固手段。

虽然本帖子涉及到的知识大概都是第一代或者第二代加固,对于大佬们来说已经是“陈年旧识”了,但对于本菜鸡来说,依旧还是一个新世界~

在进入这个新世界的时候,虽然有姜维前辈的帖子为导,但总有一些不适用的地方,比如其加固so文件的section帖子对Android7不完全使用的问题等,so文件的大小端问题;

且由于本人学习过程中未完全照搬姜维前辈的代码,故过程中也遇到了些问题,如加固后可成功运行原apk的组件但依旧显示壳信息等问题;

同时,对于姜维前辈一笔带过或者没解释的知识点,我在学习过程中也进行了些补充。

具体的,都会在下面呈现出来。

(看姜维前辈的帖子,感悟最深的就是“万物皆可二进制”。。。)

本帖子涉及到3种加固:
1、在java层为.apk文件进行加固:跟着姜维前辈学~
2、在native层为.dex文件进行加固:逆向一款恶意软件,从中学习到它的加固方式。在后面的帖子的第二部分将着重贴出逆向的全过程。
3、在native层为.so文件进行加固:依旧跟着姜维前辈的步伐~

目录:

1、在java层为.apk文件进行加固

       1.0、学习资料

       1.1、Android壳原理

              1.1.1、 Dex文件基础知识

       1.2、加固

              1.2.1、 原理

              1.2.2、 操作

       1.3、实践

              1.3.1、 基础操作

                     准备待加壳apk

                     加壳程序

                     脱壳程序

                     合体操作流程

              1.3.2、 注意事项

2、在native层为.dex文件进行加固

       2.1、壳原理

              2.1.1、 大致流程

       2.2、 样本分析

3、在native层为.so文件进行加固

       3.0、学习资料

       3.1、加密so的section

              3.1.1、 原理

              3.1.2、 实践

       3.2、加密so的函数

              3.2.1、原理

              3.2.2、 实践

       3.3、两者比较

4、3者比较

1、在java层为.apk文件进行加固

1.0、学习资料

《Android加固原理研究》:https://blog.csdn.net/jiangwei0910410003/article/details/48415225

       需补充前期知识:

(1)《动态加载技术解读》:https://blog.csdn.net/jiangwei0910410003/article/details/17679823

(2)《Java高新技术第一篇:类加载器详解》:https://blog.csdn.net/jiangwei0910410003/article/details/17733153

(3)《类加载器分析》:http://blog.csdn.net/jiangwei0910410003/article/details/41384667

(4)《资源加载问题(换肤原理解析)》:http://blog.csdn.net/jiangwei0910410003/article/details/47679843

(5)《动态加载Activity(免安装运行程序)》:http://blog.csdn.net/jiangwei0910410003/article/details/48104455

1.1、Android壳原理

1.1.1、 Dex文件基础知识

1、 学习资料:

(1)《Android加固原理研究》:https://juejin.im/entry/5a5c55426fb9a01c9f5b65ed

1.2、加固

1.2.1、 原理

1、学习资料:

(1)《Android中的Apk的加固(加壳)原理解析和实现》:https://blog.csdn.net/jiangwei0910410003/article/details/48415225

2、加固原理图:

3、加固的核心:

如何将源Apk和壳Apk进行合并成新的Dex。

4、核心原理:

只要关注上面红色标记的三个部分:

(1) checksum 

文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。

(2) signature 

使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。

(3) file_size

Dex 文件的大小 。

(4)原因:

要将一个文件(加密之后的源Apk)写入到脱壳Dex中,那么需要修改脱壳Dex的文件校验码(checksum).因为它是检查文件是否有错误。那么signature也是一样,也是唯一识别文件的算法。还有就是需要修改脱壳dex文件的大小。此外,还需要一个操作,就是标注一下加密的Apk的大小,因为在脱壳的时候,需要知道加密后的源Apk的大小,才能正确的得到加密后的源Apk。这个值直接放到文件的末尾就可以了。这样,就生成了一个壳dex文件。(脱壳dex追加源apk、源apk大小,并修改脱壳dex头部,从而生成了壳dex文件。)

即:修改Dex的三个文件头,将源Apk的大小追加到壳dex的末尾就可以了。

修改之后得到新的Dex文件样式如下:

对应涉及到三个工程:

(1)源程序项目(需要加密的Apk)

(2)脱壳项目(解密源Apk和加载Apk)

(3)对源Apk进行加密和脱壳项目的Dex的合并

1.2.2、 操作

1、代码流程:

(1)编写源程序项目,该项目需含有application类,生成origin.apk;

(2)编写脱壳程序项目,以生成“脱壳dex文件”:

(a)原理:通过反射置换android.app.ActivityThread 中的mClassLoader为加载解密出APK的DexClassLoader,该DexClassLoader一方面加载了源程序、另一方面以原mClassLoader为父节点,这就保证了即加载了源程序又没有放弃原先加载的资源与系统代码。随后找到源程序的Application,通过反射建立并运行。这里需要注意的是,我们现在是加载一个完整的Apk,让他运行起来,那么我们知道一个Apk运行的时候都是有一个Application对象的,这个也是一个程序运行之后的全局类。所以我们必须找到解密之后的源Apk的Application类,运行的他的onCreate方法,这样源Apk才开始他的运行生命周期。这里我们如何得到源Apk的Application的类呢?从源Apk的Androidmanifest.xml文件的meta标签获取源程序apk中的application对象。

(b)操作:从脱壳程序apk中找到源程序apk,并进行解密操作;从源程序apk中获取dex文件、so文件;在脱壳程序的application中的oncreate方法中执行操作,找到源程序的application程序,让其运行;需在脱壳程序的AndroidManifest.xml中声明一下源程序中的Activity。

(3)编写加壳程序项目:

       (a)以二进制形式读取origin.apk文件形成数据流dataA,并获取该文件的大小sizeA;

       (b)以二进制形式读取脱壳dex文件形成数据流dataB,并获取该文件的大小sizeB;

       (c)采用自定义的加密方法,对数据流dataA进行加密,即加密origin.apk文件,形成数据流dataC;

       (d)设置壳dex的大小sizeC=sizeA+sizeB+4;

       (e)申请一个新的byte数组newdex,大小为sizeC;

       (f)将dataC拷贝到newdex的头部,紧随其后放置dataB的数据,在newdex的最后4个字节放置sizeA;

       (g)修改newdex中的file_size字段,即修改脱壳dex的file_size字段:对newdex计算其长度length,将该值替换掉newdex的file_size字段,即将newdex的第32-35共4个字节的地方修改成length。

       (h)修改newdex中的signature 字段,即修改脱壳dex的signature 字段:对newdex计算其sha1的值,将该值替换掉newdex的signature 字段,即将newdex的第12-31共20个字节的地方修改成sha1计算后的值。

       (i)修改newdex中的checksum 字段,即修改脱壳dex的checksum 字段:调用Adler32类,利用该类的实例对newdex计算其adler值,将该值替换掉newdex的checksum字段,即将newdex的第8-11共4个字节的地方修改成adler计算后的值。

1.3、实践

1.3.1、 基础操作

准备待加壳apk

1、 程序存放地址:\AndroidStudioProjects\testAppName

2、 程序名称:testAppName

3、 该apk最好具有application类;

4、 在该apk的application类与mainActivity内,均打印出用于识别的信息;

5、 生成apk(在Andorid Studio里build的话,应该是直接用test来签名了)。

6、 Apk的包名为com.example.testappname,将该apk命名为A.apk;

7、 A.apk运行成功时,将会输出以下信息,即在application类中输出当前的包名,在mainActivity类中输出当前组件的名称:

运行界面将提示此时为testAppName:

加壳程序

1、 程序存放地址:\AndroidStudioProjects\shellTool(等脱壳dex生成后,再生成加壳apk的dex)

2、 程序名称:shellTool

3、 该程序仅用于将A.apk拼接在壳dex文件后部。

4、 将“准备待加壳apk”步骤中生成的A.apk文件,“脱壳程序”步骤中生成的B.dex文件放置在Assets目录下,用二进制形式将两者拼接在一起,B.dex在前,A.apk在中间,尾部是A.apk的大小,生成tmp.dex文件。

5、 修改tmp.class文件头部的fileSize字段、签名字段、checksum字段(这三个字段原先存放的都是B.dex的数据),生成classes.dex文件,保存在/sdcard/目录下。

脱壳程序

1、 程序存放地址:\AndroidStudioProjects\dumpShell\app,脱壳dex文件在\AndroidStudioProjects\dumpShell\app\build\outputs\apk\debug\ app-debug.apk内部

2、 程序名称:dumpShell

3、 该apk应该含有application类,且不能含有其他组件。

4、 在dumpShell的application类中,重写attachBaseContext方法,目的:

 *      1、解密源apk;

     *      2、初始化自定义类加载器

     *      3、利用反射,设置LoadedApk中加载器对象为自定义加载器

5、 在dumpShell的application类中,重写onCreate方法,目的:

     *      1、获取源apk的Application名称;

     *      2、利用反射,生成正确的Application对象

     *      3、利用反射,设置ActivityThread中的Application信息。(ActivityThread为当前主线程)

     *      4、调用源apk的application对象的oncreate方法。

6、 编译并build出dumpShell.apk,用7-zip对其进行解压,将classes.dex重命名为B.dex并将由“加壳程序”进行处理,并将“加壳程序”的输出结果classes.dex(与dumpShell.apk的原classes.dex不同)放置在该解压后的目录下,同时删除签名文件,将此时目录下的所有文件压缩到一个zip文件中,并对其进行签名得到最终的apk文件。此时的apk文件是本次“加固”的最终成果。

合体操作流程

1、 编译运行testAppName程序,生成源apk,并将其命名为A.apk;

2、 编译运行dumpShell程序,生成dumpShell.apk,从dumpShell.apk中提取classes.dex,并将其命名为B.dex,其作为脱壳dex;

3、 将A.apk与B.dex放置在shellTool项目的Assets目录下,编译运行shellTool项目,该项目将在/sdcard/目录下生成classes.dex文件,利用adb将其拷贝到PC端;

4、 用7-zip解压dumpShell.apk,删除里面的签名文件与classes.dex,并将步骤3得到的classes.dex文件放置到该文件夹下。将该文件夹下的所有文件一起压缩到dumpShell.zip文件,并对该文件进行签名成apk文件,安装运行该apk。

1.3.2、 注意事项
1、    在教程《Android中的Apk的加固(加壳)原理解析和实现》:https://blog.csdn.net/jiangwei0910410003/article/details/48415225 中,采用eclipse开发,其在将A.apk与B.dex合成为classes.dex时,利用new File()生成的classes.dex是直接生成在项目路径下的,但是用Android Studio不行,故直接在/sdcard/目录下生成该文件,并用adb将其拷贝到PC段。
2、    欲使用7-zip打开dumpShell.apk并删除签名文件与classes.dex时,提示只读无法进行删除,对操作对象是/data/app/<myAppPkgName>/base.apk时也是同样只读,即便对其使用chmod 777操作也无法。dumpShell.apk是自己用Android Studio生成的,若是用其他恶意软件的base.apk则可以正常用7-zip打开进行删除操作。具体原因暂未知道。故,用7-zip对其进行解压,解压后删除文件夹下的签名文件与classes.dex,放置目标classes.dex,随后将该文件夹下的所有文件一同压缩成一个zip文件,再对zip文件进行签名,称为apk文件。
3、    问题:加固后将A.apk放置在dumpShell.apk中,运行dumpShell.apk时能调用A.apk的application类,但无法按照A.apk的运行周期调用到A.apk的mainActivity类。
原因:dumpShell.apk不应包含mainActivity类,即应只包含application类,在application类中完成脱壳且加载A.apk的任务。否则,将存在两个mainActivity(dumpShell.apk的,与A.apk的),则将运行dumpShell.apk的。
解决方法:去除dumpShell.apk的mainActivity类,且在dumpShell.apk的manifest文件中应该声明A.apk的mainActivity类。
4、    问题:加固后将A.apk放置在dumpShell.apk中。单独运行A.apk时将输出A.apk的包名与mainActivity的组件名;运行dumpShell.apk时,尽管调用运行了A.apk,但其输出信息变成dumpShell.apk的包名,组件名称中的包名部分也变成了dumpShell.apk的包名。

(上面2张,是源apk单独运行的日志)

(上面2张,是将源apk加固进dumpShell.apk后,dumpShell.apk运行的日志)
原因:不详。不影响A.apk的运行,所以先不深究了。
5、    问题:加固后将A.apk放置在dumpShell.apk中,运行dumpShell.apk时发现此时A.apk的mainActivity类能够被加载了,但是运行界面显示的依旧是dumpShell.apk的信息:

(图片分别为:单独运行A.apk,将A.apk加固进dumpShell.apk后运行dumpShell.apk)
原因:A.apk的mainActivity类在设置contentView时是使用R类去调用A.apk自身的activity_main.layout文件,而由于加固后的运行环境中R类是dumpShell.apk的R类(而非A.apk的),故即便调用了A.apk的mainActivity类,其在设置contentView时是将使用dumpShell.apk的R类去调用dumpShell.apk的的activity_main.layout文件,因而运行界面显示的是dumpShell.apk的信息。

2、在native层为.dex文件进行加固

2.1、壳原理

将原始apk的.dex文件进行加密后,放在apk的资源路径下,随后通过native代码重新对该.dex文件进行解密、加载,从而执行原始.dex文件。

2.1.1、 大致流程

0、以样本923872474d2b49df6b2715f1a10ac0e2为例;

1、提取原始apk的.dex文件,加密后重命名为dmeod.jar,将其放置在apk的Assets目录下;

2、编写libdmeod.so文件,用于解密demod.jar文件并对其进行dexClassLoader操作;

3、在原始apk中,新建application类作为壳的启动类,将其命名为com.vod.wbmp.yobl.chjiv,并重写attachBaseContext方法和onCreate方法。这两个方法,将调用libdeod.so文件内的方法;

4、修改apk的manifest的application类名称为这个启动类的名称。

2.2、 样本分析

1、样本:923872474d2b49df6b2715f1a10ac0e2

2、用IDA打开libdmeod.so文件,找到JNI_Onload方法,按F5查看其C代码。易知v6与v3的实际类型为JNIEnv*,故将光标停在这两个变量上,按y键修改其类型,可见JNI_Onload的C代码变成如下:

3、查看off_8004,可知wefiz被重命名为i,izogr被重命名为r,即:com.vod.wbmp.yobl.chjiv这个application类在其attachBaseContext方法中实际调用的是so文件里的i方法,onCreate方法中实际调用的是so文件中的r方法。

4、先看方法i,按F5可查看其c代码,但该c代码的可读性较差,如下图:

因此对其进行修改:可知红色框中的数据类型应该是char型,但此时显示的是int型,故按R键(或右键点击该数据,选择Char)将其显示成char型,通过从v197拼接到v222可知,从v197开始表示的字符串“android/app/ActivityThread”(为方便后续操作,此时可将v197重命名为android_app_ActivityThread)。

 

继续对其进行修改:在绿色框中,此时无法得知调用的是哪个函数,但易知v的数据类型为JNI *,v4的数据类型应该为JNIEnv,按y键修改v和v4的数据类型,便可看到此时调用的函数为:

故该部分代码的功能即:FindClass(“android/app/ActivityThread”)

对方法i与方法r执行以上的相同操作,增强i与r的可读性。修改后的方法i部分截图为:

为方便查看,提取方法i里的主要功能代码,如下图:

5、方法i等同于以下java代码:

 

6、方法i的功能总结:调用edf方法来解密dmeod.jar(解密部分就不作说明啦),获取当前的主线程currentActivityThread,利用currentActivityThread获取mPackages,再把当前app的包名传递给mPachages来获得当前app的弱引用,从而找到当前app的类加载器mClassLoader。利用DexClassLoader来加载dmeod.jar文件,并将其dexClassLoader设置成新的mClassLoader的值,随后加载dmeod.jar文件里的真正入口Acitivity。

7、对方法r进行同样的修改操作,增加其可读性。方法r的代码较少,下面的截图已经包含了其主要的功能了,对应的java代码就不贴上来了。方法r的主要功能是:调用dmeod.jar里真正的application类的attachBaseContext方法与onCreate()方法,即启动真正的application类。

样本执行逻辑总结:

(1)    将原始apk的.dex文件加密成dmeod.jar文件,该dmeod.jar文件里包含apk的真实application类与入口activity类;

(2)    在AndoridManifest.xml文件中,将apk的application入口修改成壳application类,即类chjiv。

(3)    在chjiv类的attachBaseContext方法里调用native代码,实现功能:解密dmeod.jar文件并利用DexClassLoader对其进行加载,并将其设置成已加载apk的类加载器(即修改成为android.app.LoadedApk的mClassLoader),加载原始apk的入口activity类。

(4)    在chjiv类的onCreate方法调用native代码,实现功能:调用与那时apk的真正application类里的attachBaseContext方法与DexClassLoader方法。

备注:

1、若dmeod.jar文件里没有application类,即原始apk没有application类,则无需修改壳application类的onCreate方法也能使原始apk正常运行。

2、根据该样本在native层的加固,写了个java层的同功能的加固,放在:\AndroidStudioProjects\helloShell2

3、在native层为.so文件进行加固

3.0、学习资料

《Android中对Apk加固(加壳)续篇之---对Native层(so文件)进行加固》:https://blog.csdn.net/jiangwei0910410003/article/details/49967375/

       需补充前期知识:

(1)《Android逆向之旅---SO(ELF)文件格式详解》:https://blog.csdn.net/jiangwei0910410003/article/details/49336613

(2)《Android逆向之旅---Android应用的汉化功能(修改SO中的字符串内容)》:https://blog.csdn.net/jiangwei0910410003/article/details/49361281

(3)《Android逆向之旅---基于对so中的section加密技术实现so加固》http://blog.csdn.net/jiangwei0910410003/article/details/49962173

       (4)《Android逆向之旅---基于对so中的函数加密技术实现so加固》http://blog.csdn.net/jiangwei0910410003/article/details/49966719

3.1、加密so的section

3.1.1、 原理

利用c代码生成so文件时:

声明重要函数showMessage时指定将其存放在自定义的.mytext段(通过__attribute__((section(".mytext")));

编写解密函数并在其声明时指定其在main函数前运行(即通过__attribute(constructor)))。从而使得so文件在被加载到内存后可第一时间对showMessage函数进行解密。此时生成初始so文件section_origin.so,对so文件尚未加密。

写一个脚本对section_origin.so中的“.mytext”段进行加密并重写回原处。为了便于解密,将.mytext段的偏移地址与段大小保存在so文件的头部。此时生成section_encrypt.so即为被加密过的so文件。

 

3.1.2、 实践

1、在Andorid Studio里新建soShell项目,按照本文档上面的NDK开发步骤:

(1)在mainActivity中load 待生成的so文件并声明且使用目标函数showMessage;

(2)对mainActivity使用javah工具,来生成.h文件,便于获取目标函数showMessage的签名;

(3)新建.cpp文件,编写showMessage函数与解密函数decrypt_soShell,并为showMessage指定属性(即在函数声明时附上__attribute__((section(".mytext")))),为decrypt_soShell函数指定属性(即在函数声明时附上__attribute(constructor)))。

(4)新建并填写CMakeLists.txt文件;

(5)在build.gradle文件中,设置so文件生成的abi为armV7,并设置cmake的路径。

(6)编译,则在Android Studio该项目的我们预设的路径下将生成libsoShellDemo.so文件。

2、新建一个Andorid Studio项目作为加密的脚本,(可以直接在以前的shellTool项目里进行增加,反正目的都是加固),完成以下步骤:

(1)将soShell项目生成的libsoShellDemo.so文件放置到shellTool项目的Assets目录下且重命名为A.so文件,并以二进制的形式进行读取;

(2)新建ElfType32.java,该文件内容可百度,该文件用于将二进制数据解析成so文件格式;

(3)新建encryptSoSeciton.java文件,该文件以二进制形式读取,并调用ElfType32来进行解析,根据目标段名称.mytext来获取到该段的偏移和大小,对该段数据进行翻转(即ABC变成CBA),并将偏移放置在so文件的e_flags字段,将大小放在so文件的e_entry字段。保存新的so文件数据。

(4)编译,运行,获得新的so文件,B.so文件。

3、返回soShell项目中,将该项目从ndk开发转换为普通android项目:

(1)在libs目录下,新建armeabi-v7a文件夹,并将B.so文件放置在该目录下,重命名为libsoShellDemo.so文件;

(2)删除jniLibs目录;

(3)在build.gradle文件中,注释掉“设置cmake的路径,设置so文件生成的abi”部分,新增jniLibs.srcDirs。

(4)编译,运行,应用成功运行,且输出了目标日志。

4、对比so文件加密前后

用010Editor查看两者对比:

用IDA查看两者对比:

(加密前)

(加密后,IDA提示ELF-flag数据有问题,该值0x232c正是我们保存的.mytext的偏移)

3.2、加密so的函数

3.1.1、原理

利用c代码生成so文件时:

声明重要函数showMessage时指定将其存放在自定义的.mytext段(通过__attribute__((section(".mytext")));

编写解密函数并在其声明时指定其在main函数前运行(即通过__attribute(constructor)))。从而使得so文件在被加载到内存后可第一时间对showMessage函数进行解密。此时生成初始so文件func_origin.so,对so文件尚未加密。

写一个脚本对func_origin.so中的showMessage函数进行加密并重写回原处。此时生成func_encrypt.so即为被加密过的so文件。

 

 

3.1.3、 实践

1、在Andorid Studio里新建soShell项目,按照本文档上面的NDK开发步骤:

(1)在mainActivity中load 待生成的so文件并声明且使用目标函数showMessage;

(2)对mainActivity使用javah工具,来生成.h文件,便于获取目标函数showMessage的签名;

(3)新建.cpp文件,编写showMessage函数与解密函数decrypt_soShell,并为showMessage指定属性(即在函数声明时附上__attribute__((section(".mytext")))),为decrypt_soShell函数指定属性(即在函数声明时附上__attribute(constructor)))。

(4)新建并填写CMakeLists.txt文件;

(5)在build.gradle文件中,设置so文件生成的abi为armV7,并设置cmake的路径。

(6)编译,则在Android Studio该项目的我们预设的路径下将生成libsoShellDemo.so文件。

2、新建一个Andorid Studio项目作为加密的脚本,(可以直接在以前的shellTool项目里进行增加,反正目的都是加固),完成以下步骤:

(1)将soShell项目生成的libsoShellDemo.so文件放置到shellTool项目的Assets目录下且重命名为func_origin.so文件,并以二进制的形式进行读取;

(2)新建ElfType32.java,该文件内容可百度,该文件用于将二进制数据解析成so文件格式;

(3)新建encryptSoSeciton.java文件,该文件以二进制形式读取,并调用ElfType32来进行解析,根据目标段名称.mytext来获取到该段的偏移和大小,对该段数据进行翻转(即ABC变成CBA),并将偏移放置在so文件的e_flags字段,将大小放在so文件的e_entry字段。保存新的so文件数据。

(4)编译,运行,获得新的so文件,func_encrypt.so文件。

3、返回soShell项目中,将该项目从ndk开发转换为普通android项目:

(1)在libs目录下,新建armeabi-v7a文件夹,并将func_encrypt.so文件放置在该目录下,重命名为libsoShellDemo.so文件;

(2)删除jniLibs目录;

(3)在build.gradle文件中,注释掉“设置cmake的路径,设置so文件生成的abi”部分,新增jniLibs.srcDirs。

(4)编译,运行,应用成功运行,且输出了目标日志。

4、对比so文件加密前后

用010Editor查看两者对比:

用IDA查看两者对比:

(加密前)

IDA可正常打开加密后的文件:

3.3、两者比较

加固so文件的section时:

加固so文件的函数时:

 

4、3者比较

在java层为.apk文件进行加固:

 

在native层为.dex文件进行加固:

 

加固so文件:

(自认为我用excel表格画的几张图已经挺清晰的了,所以就不多做解释啦~)

[公告][征集寄语] 看雪20周年年会 | 感恩有你,一路同行

最后于 4小时前 被顺利毕业编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-255220.htm
如有侵权请联系:admin#unsafe.sh