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

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

2019-10-26 14:03
17309

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

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

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

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

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

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

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


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



备注: 
本帖子重点在于记录自己学习过程中遇到的问题及解决方法,以及部分原理知识。      
本帖子作为学习记录,会部分赘述姜维前辈帖子里的内容,但不会全写,会附上具体的连接,有兴趣的同学最好提前先康康~
本文也将高亮自己学习过程中遇到的问题及对应的解决方法,方便大家查看。
还有一部分,是我逆向一款在native层进行加固的恶意样本,逆向的过程也会贴出来。
对于一些比较绕的地方,我根据自己的理解画了几张图,加深理解。


本帖子比较核心的内容为:遇到的问题及解决方法、逆向native层加固的样本。感兴趣的同学可以挑着看。



目录:

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//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、壳原理


[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

最后于 2019-10-30 14:35 被顺利毕业编辑 ,原因:
收藏
免费 15
支持
分享
赞赏记录
参与人
雪币
留言
时间
飘零丶
感谢你的贡献,论坛因你而更加精彩!
2025-3-10 05:58
Ronin_C9
为你点赞~
2024-1-9 18:49
zhczf
为你点赞~
2023-12-6 11:02
t0hka1
为你点赞~
2023-11-30 10:18
PLEBFE
为你点赞~
2023-1-21 04:14
asher.wu
为你点赞~
2020-8-7 14:18
0x指纹
为你点赞~
2020-6-21 22:03
飞翔的牧人
为你点赞~
2020-2-1 23:18
ReEasy
为你点赞~
2019-12-4 17:05
Isaac.Cai
为你点赞~
2019-10-30 20:22
余生@
为你点赞~
2019-10-28 22:36
qifuadmin
为你点赞~
2019-10-28 11:55
jmpcall
为你点赞~
2019-10-28 08:52
没图你说个图
为你点赞~
2019-10-27 02:00
Editor
为你点赞~
2019-10-26 21:02
最新回复 (31)
雪    币: 3487
活跃值: (1792)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
写得好
2019-10-26 16:00
0
雪    币: 8511
活跃值: (5131)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
3
学习了
2019-10-26 17:12
0
雪    币: 3592
活跃值: (800)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
用心了
2019-10-26 17:29
0
雪    币: 35662
活跃值: (64621)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
5
感谢分享!
2019-10-26 21:02
0
雪    币: 3799
活跃值: (4247)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
6

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

2019-10-26 21:38
0
雪    币: 7
活跃值: (268)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
krash 楼主可以看下我的dex2c(第五代?)项目[dcc](https://github.com/amimo/dcc),了解之后安卓dex的vmp也能搞.
哪个dcc测试 了下,转换的方法数太少了, 循环和 if语句都不支持  感觉意义不大
2019-10-26 21:44
0
雪    币: 3799
活跃值: (4247)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
8
bluth 哪个dcc测试 了下,转换的方法数太少了, 循环和 if语句都不支持 感觉意义不大
姿势不对.测试demo里面有所有控制结构,包括异常.
2019-10-26 23:35
0
雪    币: 144
活跃值: (413)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
thumb模式下,pc取函数地址时,取得地址实际上是函数真实地址加1。
2019-10-27 00:52
1
雪    币: 144
活跃值: (413)
能力值: ( 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
雪    币: 1109
活跃值: (3626)
能力值: ( 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
雪    币: 1349
活跃值: (2020)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
ollvm魔改+jni的.so差不多了。
2019-10-28 11:40
0
雪    币: 144
活跃值: (413)
能力值: ( 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
活跃值: (1043)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
感谢分享!
2019-10-28 14:13
0
雪    币: 144
活跃值: (413)
能力值: ( 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
活跃值: (268)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
krash 姿势不对.测试demo里面有所有控制结构,包括异常.
学习了, 从新测试是支持的,但是是通过goto label来实现的,源代码的语序结构要是能保持就更好了,期待更新 
2019-10-28 19:13
0
雪    币: 5268
活跃值: (5330)
能力值: ( 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
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册