360加固保
FB招聘站
分类阅读
专栏
公开课
FIT 2019
企业服务
用户服务
搜索
投稿
登录
注册
360加固保关键技术浅析 androiddongjian2017-08-24现金奖励共472170人围观 ,发现 17 个不明物体 其他终端安全
*本文作者:androiddongjian(微信号:androiddongjian),属于FreeBuf原创奖励计划,禁止转载
0 写在前面
阅读本文需要您有ELF和DEX文件结构等相关背景知识。为避免翻译习惯不同带来歧义,一些术语会使用英文描述。本文所用的APK样本地址点我下载,版本:32.1,文件大小:9,770,467字节。
360加固保关键技术浅析
图 1
图1描述的是360加固保技术方案示意图,序号表示其执行的先后次序。除原DEX外,剩余的组件均为360加固保所添加。更准确点,原DEX和AndroidManifest.xml里也被添加了代码。壳ELF来源于独立的so文件。图中原DEX和辅助DEX之间还有一段数据。
1 城门洞开迎司马
一般而言,加固后的APK都会加入一些干扰信息,破坏反编译工具的正常运行。最新版本的360加固保是个例外,壳DEX和壳ELF都可以静态反编译。
其原因可能有二:首先,反编译工具的不断完善,能轻松绕过其中的干扰信息;其次,真正有用的数据都已被加密。与其花精力构造无用的反编译陷阱,不如集中力量保护好重要数据。
各种APK加固方案在技术选择上可能不尽相同,但它们都有一个共同的理论基础——被加密的目标数据足够安全。安全到静态反编译无法提取完整的目标数据。如果该基础被攻破,那么整个加固方案都将如流沙上的高楼轰然倒塌。
既然360加固保大门不设防,纵使里面没有太多可用信息,我们还是进入略探其究竟。
1.1 DEX文件静态分析
israbye FreeBuf.COM360加固保关键技术浅析
图 2
解析壳DEX文件,如图2。其中map item个数是16,简单计算可获得壳DEX文件大小为0x3b18,小于当前文件大小0x1d34d4。
360加固保关键技术浅析
图 3
图3所示壳DEX文件被插入的额外数据。看到重复出现的52,很容易让人联想这是用0×52异或简单加密后的数据。
反编译壳DEX文件后可以获得两条信息。
首先在AndroidManifest.xml设置application为com.stub.StubApp,以确保APP启动时加固代码首先获得执行机会;
其次,StubApp根据CPU架构加载相应的so文件,把执行权转移至更为安全的native代码。在StubApp类中声明了上百个native函数,不过只有几个有用,这也算是首次释放烟雾弹,来个下马威。
1.2 ELF文件静态分析
360加固保的壳ELF以独立so文件存在,文件名为libjiagu.so(不同CPU架构对应不同的so文件)。壳ELF比较老实,各种元数据都保持完整,可以轻松反汇编。图4是该文件的部分section headers数据。
360加固保关键技术浅析
图 4
图4所示的.init_arraysection在壳ELF文件偏移0x030bec保存2个函数指针,值为0x5f41和0x5f85(见图5)。显然二者都是Thumb指令函数入口,但objdump.exe把它们当作ARM指令反汇编了(见图6)。不过也难怪出错,壳ELF的确包含了两种指令,这会给动态调试增加难度。主ELF则只使用Thumb指令。
360加固保关键技术浅析israbye FreeBuf.COM
图 5
360加固保关键技术浅析
图 6
再仔细查看图4,还会发现,.bmp和.mips这两个名字和编译/链接器备案的section名字有点差别。.compiler看起来很正统,但实际它们3个section都是一伙的,主ELF就散落在此处。
系统加载ELF文件时并不需要sectionheaders信息,但为何libjiagu.so还保留着?问题先搁一边,下文再解释。
2 改门换庭变新颜
根据安卓APP启动流程,派生于application的com.stub.StubApp最先获得执行。稍加判断CPU架构,便开始加载壳ELF(见图7)。
360加固保关键技术浅析
图 7
上文所述,既然.init_array不为空,那么其指向的函数将最先被执行,比JNI_OnLoad还要早一步。
.init_array至少完成下列操作:
① 初始化一些字符串;
② Dynamic section清零;
③ 把.bmp、.compiler和.mips三者数据整体向后(高地址)移0×1000。这样,一来恢复了各个section原始面貌;二者顺带用“随机数据”覆盖后面的.comment至.shstrtab各个section,自然文件最后的section headers未能幸免。
至此,在JNI_OnLoad执行前,.init_array既恢复了后续需要使用的数据,又擦除了相关section数据。
目的相当明确,破坏门庭,杜绝通过programheaders和section headers登堂入室。
3 三板斧头挡去路
.init_array完成后,DVM虚拟机稍作初始化,便调用JNI_OnLoad。大幕开启。
壳ELF毫不客气,一上来就通过反射调用相关接口获取设备的IMEI、model、CPU_ABI、SDK_INT、/proc/version、SERIAL和MAC等等一些关键参数。
接着遍历/proc/self/maps,找到/system/bin/linker后,将该文件再次映射进内存。
例行的反动态调试来了。
为增加难度,先在一些函数间随机跳转随机次数,然后打开/proc/self/status,提取TracerPid,又随机在几个函数间跳转N次后才把TracerPid转换为整数进行判断。若TracerPid值不为零,调用raise(9)自杀。若为零正常,也继续跳转N次后才关闭/proc/self/status文件。
判断是否存在IDA的动态调试端口(00000000:5D8A)的过程依然如此,在打开文件/proc/net/tcp,读取数据,关闭文件各个操作间插入大量的函数跳转。
两个文件名平常使用A5异或后保存,使用时才临时在callstack中恢复真正的名称。
很显然,整个判断过程给动态调试设置了巨大的障碍。
还有,想在open或fopen下断点的童鞋就不要守株待兔了,因为这两个文件没有通过常规系统调用来打开。整个文件读取过程正如迷雾中窜出两只猛虎,瞬间把猎物叼走。
究竟在哪打开文件?请大家自行思考。
即使通过了上面两关,还有第三板斧头接踵而来。通过判断运行时间来识别是否处于调试状态。一旦超时,依然raise(9)自杀。
4 袖里乾坤显真身
走到这里,环境已基本安全,可以释放出主ELF了。
主ELF寄宿在壳ELF的section中,需要借助sectionheaders才能准确定位,这就是壳ELF为何保留section headers的缘故。整个定位过程在.init_array完成,因此之后才可以用“随机”数据覆盖section headers。
主ELF是先被压缩再加密后放置于壳ELF的section中,释放过程先解密,然后再解压缩。
360加固保关键技术浅析
图 8
解密解压缩后的数据见图8,主ELF的头部和programheaders都使用B2做了简单的异或加密。主ELF截掉了Section headers,因为它不像壳ELF那样需要定位section来获取数据。
壳ELF是在java环境通过调用loadLibrary来加载执行的。而主ELF因安全考虑不能通过dlopen方式加载,因此壳ELF只能自己解析主ELF来加载后者。
加载过程比较简单,针对每一个主ELF的重定位符号,遍历主ELF依赖的lib文件,找到实际地址后填充至主ELF的GOT表。
最后,给各个programsegment设置好相应权限,主ELF就算安家落户了。
主ELF也提供类似.init_array功能,供壳ELF在加载完前者后调用做些简单处理。
5 布下八卦迷魂阵
主ELF还是遵循NDK,提供JNI_OnLoad函数作为执行入口。这个函数在主壳ELF模式下不是必须的,其存在的原因应该是为了主ELF可以独立开发调试。然后两个开发小组小小偷懒一下,就不再定义新的内部接口,仍继续沿用JNI_OnLoad作为主壳ELF间接口。
主ELF承担功能繁重,还需要负责与java世界沟通。因此一开始就注册native函数,如图9。回头再比较com.stub.StubApp声明的那堆native接口,现在就简洁多了。
360加固保关键技术浅析
图 9
5.1 定位壳DEX
原DEX数据保存在壳DEX,因此首要任务是找到壳DEX。
通过遍历maps,根据/dev/ashmem/dalvik-classes.dex,apk@classes.dex,或者.odex特征寻找壳DEX的内存地址。通过匹配apk@classes.dex找到已加载到内存且优化后的dex文件。比较头部magic数据dey\n确认为优化后的dex文件。为了确保是自家的壳DEX,360加固保在额外添加的DEX数据头部也加了magic。如图3,3b18地址指向的4个字节即为magic。更进一步,该地址的数据可以用InternalDexesHeader结构来描述,如图10上半部分。
360加固保关键技术浅析
图 10
即使壳ELF在前面已经极力保护执行环境安全,但此时主ELF仍不放心,在释放核心数据前布下迷魂阵——回调壳ELF的一些函数十多万次,尽力摆脱可能存在的跟踪者。
5.2 解析配置数据
参见图1、图3、图10,配置数据就是InternalDexesHeader结构后面的数据,也是用单字节异或加密。
配置数据实际上就是一个键值数组,结构可以描述为KeyValHeader,见图10下半部分。它们可以简单分为两类:
一类是原APK的一些元数据,例如activityName、Apk-md5、CheckSum、签名值和加固时间等。使用这些元数据来防止二次打包。
另一类是360加固保自身的一些元数据和配置数据,例如jiaguVersion(当前版本1.3.7.3),stubAppName,是否上报crash,,是否支持X86,是否直接升级等等。其中fastLevel标识压缩等级是否为快速压缩,在解压缩DEX数据时使用。还有一些如update等配置数据由辅助DEX所使用。
5.3 释放原DEX和辅助DEX
确认数据完整后,将开始释放原DEX和辅助DEX了。
这两个DEX的定位方法和配置数据一样,在各自头部用明文描述了自身大小,这样从InternalDexesHeader开始就可以方便定位原DEX和辅助DEX。
开了两个线程,同时对这两个DEX进行解密和解压缩。两次pthread_create之后又调用pthread_join,强制主线程和子线程间串行化。这样做逻辑上和直接函数调用是一样的,但使用多线程可以充分利用多核来加快释放速度。
这两个DEX的数据都先经过等长解密后再进行解压缩。
加密算法应该是DES(不一定正确,没有进一步深入研究);密钥在本地;DEX和主ELF二者的解压缩使用不同的接口。
6 偷梁换柱龙转凤
DEX文件已准备好,就待加载到DVM。(本文不讨论ART方式)
通过com/stub/StubApp->getClassLoader-> pathList-> dexElements-> dexFile->loadDex接口加载APK文件/data/app/com.fdsk.bfle-1.apk。
根据DVM分配的mCookie值,用原DEX文件替代apk文件即可。这充分利用了MultiDex技术,具体细节请参考安卓系统相关源码。
最后,没有忘记修改DEX的magic,以避免通过”dex”特征遍历内存定位DEX。
至此,主要工作已经完成,主ELF和壳ELF连续退出JNI_OnLoad,执行权回到DVM,继续执行一个的jninative函数StubApp.interface5。该函数主要检查一些配置信息,提取app的packagename。
之后,执行权转移至原APP的main activity,360 加固保在该类添加了一行static代码,如图11。于是继续进入jni native 函数StubApp.interface11。
360加固保关键技术浅析
图 11
7 结语
安卓APP加固,即使加持了各种强悍的保护措施,但囿于安卓体系架构,也难免留下阿克琉斯之踵。限于篇幅,个中细节不展开分析。
*本文作者:androiddongjian(微信号:androiddongjian),属于FreeBuf原创奖励计划,禁止转载
androiddongjian
androiddongjian
2 篇文章
等级: 2级
||
上一篇:远程终端管理工具Xshell被植入后门代码事件分析报告下一篇:CovertBand:连“啪啪啪”都能识别的身体运动信息测试
这些评论亮了
乔不死 回复
小伙子分析的可以,有兴趣来水果公司吗
)12(亮了
周绿祎 回复
你好,麻烦你跟我们走一趟。
)7(亮了
发表评论已有 17 条评论
乔不死 2017-08-24回复 1楼
小伙子分析的可以,有兴趣来水果公司吗
亮了(12)
死宅10086 (7级) 2017-08-24回复 2楼
666
亮了(2)
111 2017-08-24回复 3楼
我111服了!
亮了(2)
Rorschach 2017-08-24回复 4楼
要想成为大牛,先要把文采练好
亮了(2)
周绿祎 2017-08-24回复 5楼
你好,麻烦你跟我们走一趟。
亮了(7)
关公面前耍大刀,蠢 2017-08-24回复 6楼
666
亮了(3)
die-apple (2级) 2017-08-24回复 7楼
大神,请收下我的膝盖
亮了(2)
yfw123 (1级) 2017-08-24回复 8楼
关键地方没有分析到呀,被Native后的代码还没有分析呀。
亮了(4)
androiddongjian (2级) @androiddongjian 2017-08-24回复
@ yfw123 是的 ,你说得对。文章太长了,留了些坑,有时间再填平。简而言之:原DEX的oncreate及辅助DEX所有method都native化了,中间代码放在文章开头说的那里:“ 图中原DEX和辅助DEX之间还有一段数据。”。运行时,有一个函数专门解析每一会条DEX指令,通过jni函数反射执行。优点不言而喻,缺点也明显,性能损耗较严重。目前的现状应该是兼顾二者的妥协结果。
亮了(4)
路人LLD (1级) 2017-08-24回复 9楼
厉害!
亮了(1)
zw97073966 (4级) python是世界上最好的语言---0x024 2017-08-24回复 10楼
大婶!我想约你!我该怎么入门
亮了(3)
zw97073966 (4级) python是世界上最好的语言---0x024 2017-08-24回复
@ zw97073966 大神!我想学你!我该怎么入门
亮了(1)
孟孟孟孟孟孟孟孟 (1级) 这家伙太懒了,还未填写个人描述? 2017-08-25回复 11楼
向大佬学习
亮了(1)
周宏伟 2017-08-28回复 12楼
写文章学学尼古拉斯.赵四
亮了(3)
difcareer (3级) 我在简书上发起了一个Android安全专题,分为很多个子专题... 2017-09-13回复 13楼
最新版没有rtld_db_dlactivity反调试了?
亮了(2)
走码观花 2017-10-18回复 14楼
"回调壳ELF的一些函数十多万次,尽力摆脱可能存在的跟踪者。"
这个在哪呀。。。我都没有发现这个。。常规反调试过后F9几秒就报指令异常了。。。
亮了(2)
路过罢了 2018-10-03回复 15楼
看了半天全是前面简单的反调试和so的加载上面,写的文绉绉的,其实没啥技术含量,连修复指令都没有
亮了(1)
昵称
请输入昵称
必须您当前尚未登录。登陆?注册邮箱
请输入邮箱地址
必须(保密)表情插图
有人回复时邮件通知我
androiddongjian
androiddongjian
@androiddongjian
2
文章数
2
评论数
最近文章
借你一双慧眼,鸟瞰二进制世界的秘密
2018.06.12
360加固保关键技术浅析
2017.08.24
浏览更多
相关阅读
《史密斯夫妇》黑客版:黑掉智能枪并控制弹道(含视频)SECCON 2015 CTF精选:一道400分的Android APK逆向题安天AVL联合小米MIUI,首擒顽固病毒“不死鸟”新手教程:如何使用Burpsuite抓取手机APP的HTTPS数据教你搭建你自己的“深度学习”机器
特别推荐
关注我们 分享每日精选文章
活动预告
11月
FreeBuf精品公开课·双11学习狂欢节 | 给努力的你打打气
已结束
10月
【16课时-连载中】挖掘CVE不是梦(系列课程2)
已结束
10月
【首节课仅需1元】挖掘CVE不是梦
已结束
9月
【已结束】自炼神兵之自动化批量刷SRC
已结束
FREEBUF免责声明协议条款关于我们加入我们广告及服务寻求报道广告合作联系我们友情链接关注我们
官方微信
新浪微博腾讯微博Twitter赞助商
Copyright © 2018 WWW.FREEBUF.COM All Rights Reserved 沪ICP备13033796号
css.php0daybank
文章评论