基于dex与so文件动态执行的Android应用加固方法与流程

文档序号:11951455阅读:1206来源:国知局

本发明涉及信息安全技术领域,尤其涉及一种基于dex与so文件动态执行的Android应用加固方法。



背景技术:

Android是google公司于2007年推出的开源手机操作系统,由于其强大的功能和灵活的定制能力,使其在短短几年内便跃居智能手机操作系统市场份额的首位,据著名市场研究机构IDC的最新数据表明,2015年第三季度,Android系统智能手机的市场份额高达84.7%,远超其他系统。

虽然Android系统在设计之初便充分考虑到了安全问题,但随着其广泛应用,诸多潜在的安全问题还是逐渐地暴露出来,对其安全性的研究开始受到人们的广泛关注。由于Android平台软件使用的编程语言是Java,而Java源代码编译后的二进制代码极易被反编译,导致其破解难度远小于其他使用编译性语言编写的程序。虽然Android 2.3以后加入了代码混淆机制,但通过逆向工程,其API级别上的代码仍是难以隐藏,对攻击者来说,破解的可能性仍是很大。

现有的一些Android应用程序加固方法,大多采用静态处理的方法,即对dex文件进行一些修改来增加攻击的难度。这种加固方法只是增加了代码阅读难度。或者基于JNI调用机制,通过加密原so文件,使用壳程序加载并解密原so文件,间接调用原so文件的函数。然而,由于基于JNI调用机制该方案,不可避免地会在硬盘短暂的留下解密后的明文so文件,攻击者使用Hook系统API、自动化脚本可以轻易地破解此方案。因此攻击者依然能够得到程序的代码,无法保证应用程序的安全性。



技术实现要素:

为了克服上述现有技术的不足,本发明详细分析了Android的系统架构、程序结构、程序执行机制以及Android平台软件面临的逆向威胁,基于classes.dex文件动态加载、利用so库解密dex文件加固方案,提出和实现了一种能使so库脱离JNI调用机制依赖,从而在内存中完成so文件的加解密,保证没有临时so文件的生成、保证不能通过内存中映射表截断方法获得的so核心代码的具有更高安全性的加固方案。

本发明提供的技术方案是:

一种基于dex与so文件动态执行的Android应用加固方法,通过二进制流加密方法对Android应用程序中的关键代码进行加固,使得Android应用程序代码得到保护;所述加固方法包括加密过程和解密过程,具体包括如下步骤:

A.通过软件加固模块实现加密过程:

软件加固模块即为针对Android应用程序的加固模块,通过二进制流加密方法对Android

应用程序中的关键代码进行加固;

A1.解压软件主体部分apk文件,提取其classes.dex文件;

一个Android应用程序(软件)会打包成一个APK文件,对于APK文件进行解压可以得到dex文件、assets文件、arsc文件等;其中classes.dex包含是Android平台上可执行文件的类型;

A2.将核心功能部分代码编译成独立的apk文件并对其进行加密;

核心功能部分代码根据提取classes.dex文件之后对代码进行分析得到,一般地,核心代码是自己编写的程序,而不是引用的系统或者外部库的代码;

A3.把加密后的apk文件写入到软件主体部分的classes.dex文件的末尾,并在文件尾部添加加密数据的大小,用以解密时找到核心功能部分代码的起始位置;

应用程序编译成独立的apk文件之后可以得到此文件的大小的数值,即为加密数据的大小;

A4.重新计算classes.dex文件的checksum、signature和file_size字段的值。分别计算这几个字段变化之后的值,替换原位置的checksum、signature和file_size字段的值,替换原位置的内容即可;

A5.将修改后的classes.dex文件放回软件主体部分apk包中,使用Android SDK中提供的签名工具对程序进行签名;

A6.读取待加固的so库文件(假设为a.so),解析文件信息,得到待加密函数的偏移地址与大小,并向待加密函数末尾处写入decryDEX()解密函数,用于解密加密后的dex文件,重新计算待加密函数的大小并填写;具体地,修改解密函数中的数值,也就是将原来待加密函数中的数值修改为改变之后的数值,此数值为待加密函数的大小;

A7.对待加密函数进行异或加密;

A8.解析so库文件的文件头,修改文件头部部分字段值,包括so文件大小、so文件中函数的偏移地址;

A9.计算so库文件的MD5值,并将其写入文件末尾;

A10.在so文件中的.init_array节的代码段中添加运行环境检测、对抗动态调试、MD5完整性校验,以及对so文件进行解密的代码。

B.开始动态执行Android应用程序进行解密时,执行如下操作:

B1.软件主体部分从自身的apk文件中读取classes.dex文件,在classes.dex文件尾部得到加密数据的长度,根据加密数据长度计算出加密数据的起始位置,从而读取得到加密数据;

B2.调用loadLibrary系统函数加载a.so,这一阶段loadLibrary会加载共享库(即实施例中的so文件a.so)、控制权传递给动态连接器Linker,Linker会检查文件头部合法性、根据头部的数据分别读入对应的各种数据结构,并将所有PT_LOAD属性的段加载至合适的地址空间,然后为该库在共享库链表中分配一个soinfo节点并填充其数据结构,执行标记为.init,.init_array的节的代码进行代码初始化工作,若存在JNI_OnLoad函数,执行该代码;(.init_array过程会解密加密函数);

B3.调用decryDEX()解密得到核心功能部分的apk;

B4.通过Android API提供的DexClassLoader类,对核心功能部分码(核心功能部分的apk)进行反射调用,从而实现核心功能部分代码的动态加载;

B5.调用完成后,删除核心功能部分apk文件,从而避免核心功能部分代码暴露在系统内部存储之中,被攻击者得到。

与现有技术相比,本发明的有益效果是:

现有的一些Android应用程序加固方法,大多采用静态处理的方法,即对dex文件进行一些修改来增加攻击的难度,或者基于JNI调用机制,通过加密原so文件,使用壳程序加载并解密原so文件,间接调用原so文件的函数。然而,由于基于JNI调用机制该方案,不可避免地会在硬盘短暂的留下解密后的明文so文件,攻击者使用Hook系统API、自动化脚本可以轻易地破解此方案。本发明提供的加固方法对核心dex进行加密保护,对核心dex的解密函数用C++语言编写,增加了其反编译的难度,以动态链接库的形式存在并且也进行了加密。相当于对核心dex双重加密,对动态链接库的关键代码也进行了加密。并且解密过程摆脱了JNI调用机制,使得解密过程不会在硬盘留下解密后的明文so文件,全部在内存中进行,避免被攻击者得到。本发明方法能够保证没有临时so文件的生成、保证不能通过内存中映射表截断方法获得的so核心代码,是具有更高安全性的加固方案。

附图说明

图1是本发明提供方法的流程框图;

其中,A为提取dex文件核心功能部分并编译成一个独立的apk文件,并将它写入文件classes.dex文件尾部;B为将该独立的apk文件解密函数写入待加固的so文件中的待加密部分并加密该部分代码;C为在dex的软件主体写入相关加载so文件的代码,并计算classes.dex文件的checksum、signature和file_size字段的值并替换原位置的内容;D为读取classes.dex文件尾部,执行dex主体中的so文件,解密classes核心部分正常运行。

具体实施方式

下面结合附图,通过实施例进一步描述本发明,但不以任何方式限制本发明的范围。

本发明提供一种基于dex与so文件动态执行的Android应用加固方法,图1是本发明提供方法的流程框图,包括如下步骤:

A.软件加固模块:

A1.解压软件主体部分apk文件,提取其classes.dex文件;

A2.将核心功能部分代码编译成独立的apk文件并对其进行加密;

A3.把加密后的apk文件写入到软件主体部分的classes.dex文件的末尾,并在文件尾部添加加密数据的大小,用以解密时找到核心功能部分代码的起始位置;

A4.重新计算classes.dex文件的checksum、signature和file_size字段的值。分别计算这几个字段变化之后的值,替换原位置的内容即;

A5.将修改后的classes.dex文件放回软件主体部分apk包中,使用AndroidSDK中提供的签名工具对程序进行签名;

A6.读取待加固的so库文件(假设为a.so),解析文件信息,得到待加密函数的偏移地址与大小并向待价密函数末尾处写入decryDEX()解密函数用于解密加密后的dex文件,重新计算待加密函数的大小并填写;

A7.对待加密函数进行异或加密;

A8.解析文件头,修改文件头部部分字段值;

A9.计算文件的MD5值,并将其写入文件末尾;

A10.在.init_array节的代码段中添加运行环境检测、对抗动态调试、MD5完整性校验,对so文件相应加密函数解密的解密代码。

B.开始动态执行Android应用程序时,执行如下操作:

B1.软件主体部分从自身的apk文件中读取classes.dex文件,在classes.dex文件尾部得到加密数据的长度,根据加密数据长度计算出加密数据的起始位置,从而读取得到加密数据;

B2.调用loadLibrary加载a.so,这一阶段loadLibrary会加载的共享库、控制权传递给动态连接器Linker,Linker会检查文件头部合法性、根据头部的数据分别读入对应的各种数据结构,并将所有PT_LOAD属性的段加载至合适的地址空间,然后为该库在共享库链表中分配一个soinfo节点并填充其数据结构,执行标记为.init,.init_array的节的代码进行代码初始化工作,若存在JNI_OnLoad函数,执行该代码。(.init_array过程会解密加密函数)

B3.调用decryDEX()解密得到核心功能部分apk;

B4.通过Android API提供的DexClassLoader类,对核心功能部分码进行反射调用,从而实现核心功能部分代码的动态加载;

B5.调用完成后,删除核心功能部分apk文件,从而避免核心功能部分代码暴露在系统内部存储之中,被攻击者得到。

实施例

本实施例是对于一个Android应用进行加固操作,包括部分反调试步骤。此Android应用可以命名为b.apk,对于b.apk对于可以进行加固,经过以上的加固步骤,可以对于b.apk进行加固,防止此APK被调试破解,查看软件的源码。对于此APK的实施实例步骤如下所示,其中A、B步骤作为常规的防调试步骤加入,C、D、E步骤作为以上发明的核心步骤进行。具体步骤如下所示:

A.检测模拟器:

A1.检测“/dev/socket/qemud”,“/dev/qemu_pipe”这两个通道。//判断两个通道是否存在,存在则为模拟器access(“/dev/socket/qemud”,0)access(“/dev/qemu_pipe”,0)

A2.检测props。包括:ro.product.model:该值在模拟器中为sdk,通常在正常手机中为手机的型号;ro.build.tags:该值在模拟器中为test-keys,通常在正常手机中为release-keys;ro.kernel.qemu:该值在模拟器中为1,通常在正常手机中没有该属性。

B.防动态调试:

B1.多进程使用ptrace,//阻止被调试器附加ptrace(PTRACE_TRACEME,0,0,0);

B2.对proc/xxx/task和proc/xxx/status进行检测;默认情况下status中TracerPid值为0,若不为0,则程序正处于被调试状态。

C.自定义so库文件ELF头文件信息

ELF头部的各个字段如下:typedef struct{unsigned char e_ident[16];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry;Elf32_Off e_phoff;Elf32_Off e_shoff;Elf32_Word e_flags;Elf32_Half e_ehsize;Elf32_Half e_phentsize;Elf32_Half e_phnum;Elf32_Half e_shentsize;Elf32_Half e_shnum;Elf32_Half e_shstrndx;}Elf32_Ehdr;

通过对Android平台下动态库的加载过程的分析,发现许多字段并没有使用。修改这些字段值也不会影响动态库的正常使用。然而,在使用readelf、IDA等静态分析工具的时候,若这些字段值错误,会导致静态分析失败。本发明技术方案利用这一特性,修改部分字段,达到阻止程序被静态分析的目的。具体包括:1)修改e_ident字段后9个字节。2)修改e_type,e_machine,e_version,e_flag字段。3)修改e_shoff,e_shentsize,e_shnum,e_shstrndx字段。

D.so特定函数加密

D1.读取文件头,获取e_phoff、e_phentsize和e_phnum信息。

D2.通过Elf32_Phdr中的p_type字段,找到DYNAMIC。其实DYNAMIC就是.dynamic section,这是so文件中的一个段。从p_offset和p_filesz字段得到文件中的起始位置和长度。

D3.遍历.dynamic,找到.dynsym、.dynstr、.hash section文件中的偏移和.dynstr的大小。在我的测试环境下,Fedora 14和Windows 7Cygwin x64中elf.h定义.hash的d_tag标示是:DT_GNU_HASH;而Android源码中的是:DT_HASH。

D4.根据函数名称,计算hash值,进一步得到待加密函数的偏移地址与大小。

D5.根据hash值,找到下标hash%nbuckets的bucket;根据bucket中的值,读取.dynsym中的对应索引的Elf32_Sym符号;从符号的st_name所以找到在.dynstr中对应的字符串与函数名进行比较。若不等,则根据chain[hash%nbuckets]找下一个Elf32_Sym符号,直到找到或者chain终止为止,代码如下:

for(i=bucket[funHash%nbucket];i!=0;i=chain[i]){if(strcmp(dynstr+(funSym+i)->st_name,funcName)==0){flag=0;break;}}

D6.找到函数对应的Elf32_Sym符号后,即可根据st_value和st_size字段找到函数的位置和大小。

D7.将需要加密的区域进行加密,采用异或加密方法,即取反操作:*content=~(*content)。

E.解密流程为加密逆过程,大体相同,只有一些细微的区别,具体如下:

E1.找到so文件在内存中的起始地址。

E2.通过so文件头找到Phdr;从Phdr找到PT_DYNAMIC后,需取p_vaddr和p_filesz字段。

后续步骤与D7步骤中的加密(取反操作)相同。由此完成对应用的加固。

需要注意的是,公布实施例的目的在于帮助进一步理解本发明,但是本领域的技术人员可以理解:在不脱离本发明及所附权利要求的精神和范围内,各种替换和修改都是可能的。因此,本发明不应局限于实施例所公开的内容,本发明要求保护的范围以权利要求书界定的范围为准。

当前第1页1 2 3 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1