首页
社区
课程
招聘
[原创]入门级加固--3种加固方式学习记录
发表于: 2019-10-26 14:03 16988

[原创]入门级加固--3种加固方式学习记录

2019-10-26 14:03
16988

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

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

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

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

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

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

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





目录:

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者比较



《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加固原理研究》:https://juejin.im/entry/5a5c55426fb9a01c9f5b65ed


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、代码流程:

(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、 程序存放地址:\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。




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


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-10-30 14:35 被顺利毕业编辑 ,原因:
收藏
免费 14
支持
分享
最新回复 (31)
雪    币: 3486
活跃值: (1617)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
写得好
2019-10-26 16:00
0
雪    币: 8447
活跃值: (5041)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
3
学习了
2019-10-26 17:12
0
雪    币: 3572
活跃值: (760)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
用心了
2019-10-26 17:29
0
雪    币: 26205
活跃值: (63302)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
5
感谢分享!
2019-10-26 21:02
0
雪    币: 3818
活跃值: (4228)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
6

楼主可以看下我的dex2c(第五代?)项目dcc,了解之后安卓dex的vmp也能搞.

2019-10-26 21:38
0
雪    币: 7
活跃值: (263)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
krash 楼主可以看下我的dex2c(第五代?)项目[dcc](https://github.com/amimo/dcc),了解之后安卓dex的vmp也能搞.
哪个dcc测试 了下,转换的方法数太少了, 循环和 if语句都不支持  感觉意义不大
2019-10-26 21:44
0
雪    币: 3818
活跃值: (4228)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
8
bluth 哪个dcc测试 了下,转换的方法数太少了, 循环和 if语句都不支持 感觉意义不大
姿势不对.测试demo里面有所有控制结构,包括异常.
2019-10-26 23:35
0
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
thumb模式下,pc取函数地址时,取得地址实际上是函数真实地址加1。
2019-10-27 00:52
1
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
小端在我们个人pc喝手机上还是很普遍的。大端可能用在linux服务器上。我实际没见过
2019-10-27 00:58
1
雪    币: 2968
活跃值: (319)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
11
动态符号表的st_value需要减一才能作为funcOffset的原因。
thumb指令模式函数真实的调用地址为真实地址减一。
实际上,在运行时原函数真实地址加1,实际是一个标志位,表明程序要从ARM状态跳转到Thumb状态,此时CPSR寄存器T位会从0变成1,代表arm变成thumb.故逆推时,st_value需要减一才是真实调用地址。
2019-10-28 09:29
0
雪    币: 1110
活跃值: (3274)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
12
zylyy 小端在我们个人pc喝手机上还是很普遍的。大端可能用在linux服务器上。我实际没见过[em_33]
X86小端,ARM默认小端,这些没问题。但是在 MIPS 上,高通/雷凌/MTP/螃蟹这几个厂家有的是大端有的是小端,麻烦常见于 Linux OpenWRT 移植这块。
2019-10-28 10:49
0
雪    币: 516
活跃值: (117)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
krash 楼主可以看下我的dex2c(第五代?)项目[dcc](https://github.com/amimo/dcc),了解之后安卓dex的vmp也能搞.
谢谢您的建议,我循序渐进地学~
2019-10-28 11:13
0
雪    币: 516
活跃值: (117)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
14
zylyy thumb模式下,pc取函数地址时,取得地址实际上是函数真实地址加1。
谢谢您的解答~
2019-10-28 11:14
0
雪    币: 516
活跃值: (117)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
15
deff 动态符号表的st_value需要减一才能作为funcOffset的原因。 thumb指令模式函数真实的调用地址为真实地址减一。 实际上,在运行时原函数真实地址加1,实际是一个标志位,表明程序要从A ...
谢谢您的解答~
按您所说,动态符号表的st_value的值在生成时是在thumb指令模式下;那么,在第三种加固方式中的第一个加固so的section时,获取目标section的偏移采用的是sh_offset而非sh_offset-1,是不是以为着sh_offset的值在生成时是在ARM指令模式下呢?
2019-10-28 11:16
0
雪    币: 1319
活跃值: (1960)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
ollvm魔改+jni的.so差不多了。
2019-10-28 11:40
0
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
跟你说一下,arm指令blx,bx之类得跳转得时候,会根据目标地址最低位是否是1切换到thumb模式。也就是如果一个函数是以thumb模式编译得,那么调用得时候要地址加1.基本上现在安卓系统系统,armv7指令,系统库的导出函数基本上都是thumb的.
2019-10-28 13:51
0
雪    币: 516
活跃值: (117)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
18
zylyy 跟你说一下,arm指令blx,bx之类得跳转得时候,会根据目标地址最低位是否是1切换到thumb模式。也就是如果一个函数是以thumb模式编译得,那么调用得时候要地址加1.基本上现在安卓系统系统,ar ...
哇谢谢~结合您的解答与11楼的回复,算是有答案啦。
2019-10-28 14:06
0
雪    币: 41
活跃值: (823)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
感谢分享!
2019-10-28 14:13
0
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
其实还有一点对齐原则。arm或thumb指令总数2字节或者4字节对齐的。你要知道,这些指令在内存中总是2字节对齐的(内存分配起始点总是页对齐的,编译的so也保证指令相对模块起始位置对齐),也就是最低位不可能为1。所以最低位可以充当标志位。实际上pc取指令的,总是忽略掉最低位。所以那个加1.准确的说是或运算。
2019-10-28 14:44
0
雪    币: 758
活跃值: (78)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
感谢分享
2019-10-28 15:42
0
雪    币: 7
活跃值: (263)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
krash 姿势不对.测试demo里面有所有控制结构,包括异常.
学习了, 从新测试是支持的,但是是通过goto label来实现的,源代码的语序结构要是能保持就更好了,期待更新 
2019-10-28 19:13
0
雪    币: 4731
活跃值: (4674)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
学习啦,新手学习中
2019-12-7 20:23
0
雪    币: 1705
活跃值: (676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
老板能不能把附件放上来,研究一下
2020-6-21 14:16
0
雪    币: 203
活跃值: (24)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
精品贴,支持一下
2020-7-31 12:49
0
游客
登录 | 注册 方可回帖
返回
//