Android下经过hook技术实现透明加解密保障数据安全

Android下经过hook技术实现透明加解密保障数据安全


对于用户在Android移动设备商保存重要的隐私文件,一般采用一些加密保存的软件。但在手机上实现隐私空间的软件鳞次栉比,可是问题在于打开文件都须要使用该隐私空间,将加密文件解密到临时文件,而后再选择应用程序打开文件。html


 1、前言linux

对于用户在Android移动设备商保存重要的隐私文件,一般采用一些加密保存的软件。但在手机上实现隐私空间的软件鳞次栉比,可是问题在于打开文件都须要使用该隐私空间,将加密文件解密到临时文件,而后再选择应用程序打开文件。这将致使用户重要文件在设备上明文的存在,存在泄漏的风险。android

并且根据笔者的调研,对于360隐私空间,应用程序对临时文件修改后不能再逆向加密回密文,致使加密操做只能一次进行。LBE隐私空间,相对较好,但其临时文件存在生命周期过长。程序员

所以笔者经过现有知识讨论一种采用hook技术实现的透明加解密方法,不须要在设备上生成临时文件,从而保护用户重要隐私。编程

2、技术要点数组

因为Android是基于linux内核的开源系统,根据语言环境不一样能够分为Java层、Native C层、Linux Kernel层。Java层的安全是使用Java语言开发,基于SDK,能实现的功能相对简单。Linux Kernel层安全,须要从源码作起,编译本身的系统,通用性不强。所以在Native C层,经过JNI开发,可使用linux提供的函数实现更多的功能。安全

在hook API方面与linux的hook相似使用ptrace 函数与plt表实现,还能够采用Inline hook的方式实现,可是不是很稳定,操做难度大。其本质都是劫持函数调用。数据结构

可是因为处于Linux用户态,每一个进程都有本身独立的进程空间,因此必须先注入到所要hook的进程空间,修改其内存中的进程代码,替换其中过程表的符号地址,所以其生存空间是所注入的进程,只能对某一进程进行HOOK。函数

Ptrace函数是调试程序所用,功能强大,不只能够附加某一进程(PTRACE_ATTACH),并且能够任意修改目标进程的内存空间(PTRACE_PEEKDATA,读内存。PTRACE_POKEDATA,写内存),甚至是寄存器(PTRACE_SETREGS,PTRACE_GETREGS)工具

基本流程是利用寄存器指令中断:

① PTRACE_ATTACH,绑定目标进程。

② PTRACE_GETREGS,获取目标进程寄存器状态,并保存。

③ PTRACE_PEEKDATA与PTRACE_POKEDATA配合,保存原代码,写入要注入的代码到当前运行位置。

④ PTRACE_SETREGS,恢复寄存器状态,并继续执行,这是注入的代码开始在目标进程内执行,注入代码完成HOOK,过程与Windows下类似。

⑤ 在HOOK完成后,注入的代码执行int3被ptrace捕获,目标进程再次暂停执行。

⑥ PTRACE_GETREGS,再次保存寄存器。

⑦ PTRACE_PEEKDATA与PTRACE_POKEDATA配合还原代码。

⑧ PTRACE_SETREGS,恢复寄存器,目标进程继续执行。

⑨ PTRACE_DETACH,撤销绑定目标进程。

参考LBE实现原理和看雪上关于Hook Ioctl的文章都基本上按照这种流程实现HOOK。

在明白hook工做机制后,对于实现Android上的透明加解密须要找到open和close函数的符号存在哪一个动态连接库中,hook该应用程序的这个动态连接库,在open操做进行的时候,将密文文件分块解密到内存中,并将该内存中的文件标识符返回。在close操做进行的时候将内存中的明文加密到本地密文存储。

3、关键流程

一、阅读Android代码查找打开文件和关闭文件过程。这是咱们实现透明加解密的关键。

参考http://blog.chinaunix.net/uid-26926660-id-3326678.html的方式

能够发现读取文件流的函数最终经过JNI方式的read函数实现,一样打开文件的操做最终都归结于open函数。

而实现Java代码的JNI支持的动态库是nativehelper.so所以咱们须要hook的动态库即nativehelper.so。

注:在Android早期版本即android2.三、Android4.0上open和close符号在nativehelper.so中,该文件有140k大小。而在android4.1版本以上,谷歌重写了android原生库的实现,nativehelper.so被拆分,笔者在4.0平台进行的开发并无阅读寻找4.1版本之上的。

二、进行进程注入和ELF节替换

进程注入就是将一段代码拷贝到目标进程,而后让目标进程执行这段代码的技术。因为这样的代码构造起来比较复杂,因此实际状况下,只将不多的代码注入到目标进程,而将真正作事的代码放到一个共享库中,即.so文件。被注入的那段代码只负责加载这个.so,并执行里面的函数。因为.so中的函数是在目标进程中执行的,因此在.so中的函数能够修改目标进程空间的任何内存,固然也能够加钩子,从而达到改变目标进程工做机制的目的。

固然不是任何进程都有权限执行注入操做的。Android平台上的进程注入是基于ptrace()的,要调用ptrace()须要有root权限。目前市面上的主流安全软件也都是基于进程注入来管理和控制其余应用进程的。这也就是为何这些安全软件须要得到root权限的缘由。

关于如何.so注入的实现,能够参考看雪论坛的上的一个注入库LibInject 和洗大师的一个开源项目Android Injector Library。

笔者对这种两种方式都有实验,对于Libinject,就是向目标进程中注入libhook.so,首先调用ptrace()函数,挂起该进程。而后遍历进程加载的libc.so,经过dlopen和dlsym函数修改arm寄存器的值,而后压入参数,so路径,并将以前找到的dlopen地址压入寄存器,直接操做blx,就可让目标进程调用dlopen加载咱们的so,同理dlsym调用咱们的so里的函数。

注入完成后,会调用libhook.so库中的hook_entry()函数,该函数实现加载hook函数实现的动态库,并对libnativehelper.so的got表和plt表的遍历和修改。修改成本身编写的实现open函数和close函数的动态库中的符号地址。所以须要注入两个库,由于libhook.so在执行完后须要detach目标进程,从而释放,而具体操做的动态库须要常驻内存。实现常驻内存须要在hook_entry()函数中显式加载动态库。

以上注入之后的过程都由本身编程实现,可以加深对ELF格式理解。

而采用Android Injector Library则相对简单,在调用可执行程序的主函数中实现便可:

这个文件编译生成注入入口和符号表替换逻辑。

* 一、在该函数中加载libhook.so经过其中的do_hook函数返回原来的open和close地址以及要替换的新的open和close函数地址

* 二、而后静态打开libnativehelper动态库,读取其结构遍历节表,找到全局符号表(GOT表),该表存储了外部依赖符号的地址

* 三、遍历GOT表找到原先的open函数和close函数地址,分别替换为新的open函数和新的close函数便可

图片1

三、在学习这一过程当中,须要了解linux的ELF格式,如下是学习ELF的笔记:参见《程序员的自我修养》若是熟悉则可跳过。

图片2
图片3

ehdr->e_shstrndx索引指向shstrtab的节,能够用来索引节头的字符串名称描述。shstrtab表(Section Header String Table)保存段表中用到的字符串,最多见的就是段名、

经常使用的段名说明
.rodata1Read Only Data,这种段里存放的是只读数据,好比字符串常量、全局const变量。跟”.rodata”同样
.comment存放的是编译器版本信息
.debug        调试信息
.dynamic动态连接信息
.hash        符号哈希表
.line        调试时的行号表
.note        额外的编译器信息。好比程序的公司名、发布版本号等
.strtab        String Table.字符串表
.symtab        Symbol Table.符号表
.shstrtabSection String Table.段名表
.plt  .got动态连接的跳转表和全局入口表
.init   .fini程序初始化与终结代码段

符号节,遍历节头时候。判断每个节的类型是否是SHT_SYMTAB或SHT_DYNSYM,那么对应的节就是符号节。符号节存放的是一张符号表,符号表也是一个连续存储的结构数组.

编程过程当中用到的变量和函数均可以称之为符号,一个ELF文件中并不仅有一个符号节,一般是两个,一个为”.dynsym”的动态节类型为SHT_DYNSYM,全部引入的外部符号在这里有所体现,另外一个为SHT_SYMTAB,名字为“.symtab”保存了全部有用符号信息。

Symbol Table 符号表保存了一个程序在定位和重定位时须要的定义和引用的信息。一个符号表索引是相应的下标。符号表的存在乎义是体如今多个目标文件进行连接的时候,在连接中,目标文件之间相互拼合其实是目标文件之间对地址的引用,即对函数和变量的地址的引用,而函数和变量能够统称为符号(Symbol),函数名或变量名就是符号名(Symbol Name)。咱们能够将符号看做是是连接中的粘合剂,整个连接过程就是基于符号才可以正确完成。在符号表”.symtab“中,其也是像段表的结构同样,是一个数组,每一个数组元素是一个固定的结构来保存符号的相关信息,好比符号名(不是字符串,而是该符号名在字符串表的下标)、符号对应的值(多是段中的偏移,也多是符号的虚拟地址)、符号大小(数据类型的大小)等等。符号表中记录的通常是全局符号,好比全局变量、全局函数等等。

目标文件的符号表包含定位或重定位程序符号定义和引用时所须要的信息。符号表入口结构定义以下:

typedef struct{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
Unsigned char st_info;
Unsigned char st_other;
Elf32_Half st_shndx;
}Elf32_Sym;

其中st_name包含指向符号表字符串表(strtab)中的索引,从而能够得到符号名。St_value指出符号的值,多是一个绝对值、地址等。St_size指出符号相关的内存大小,好比一个数据结构包含的字节数等。St_info规定了符号的类型和绑定属性,指出这个符号是一个数据名、函数名、section名仍是源文件名;而且指出该符号的绑定属性是local、global仍是weak。

GOT表和PLT表

GOT(Global Offset Table)表中每一项都是本运行模块要引用的一个全局变量或函数的地址。能够用GOT表来间接引用全局变量、函数,也能够把GOT表的首地址做为一个基 准,用相对于该基准的偏移量来引用静态变量、静态函数。因为加载器不会把运行模块加载到固定地址,在不一样进程的地址空间中,各运行模块的绝对地址、相对位 置都不一样。这种不一样反映到GOT表上,就是每一个进程的每一个运行模块都有独立的GOT表,因此进程间不能共享GOT表。

图片4

动态连接机制

首先回顾一下Linux平台上,一个模块甲须要调用另一个模块乙中的函数时的动态连接机制:

一、模块甲在编译期间,将要引用的模块乙的名字与函数名写入自身的符号表。

二、运行期模块甲调用时,调用流程是从调用代码到PLT表到GOT表再跳入模块乙。

而如何保证模块甲的代码能从其PLT/GOT跳到正确的模块乙入口,这就是连接器作的事情。

标准Linux连接器是ld.so,支持懒绑定,也就是说,模块甲在编译期间生成的调用模块乙的原始代码,流程是从调用代码到PLT表到连接器。运行期第一次调模块乙时,首先进入连接器,连接器根据调用信息加载模块乙搜寻其符号并将找到的函数地址填入GOT表,以后的后续调用流程就直接走PLT/GOT表了。这种机制能减小加载时的开销,为Linux发行版等采用。

Android虽然内核基于Linux,但其动态连接机制却不是ld.so而是自带的linker,不支持懒绑定。也就是说,上述模块甲乙若是在Android平台上,则是模块甲加载时,linker就会根据模块甲中的.rel.plt表和字符串表中的内容加载模块乙并搜索其所需函数地址并预先填入GOT表。以后调用流程每次都直接走PLT/GOT表,再也不进linker,PLT表中也省去了跳至linker的代码,这种流程和“勤劳”绑定相似,却是为拦截提供了一点方便。若是拦截懒绑定的入口时模块乙还没加载地址也没找到,拦截就无法进行了。

要拦截模块甲对乙的调用,通常思路是经过ptrace远程注入并加载一新拦截模块至模块甲,并搜索模块甲的GOT表,找到对模块乙的调用地址,改为新模块内的某函数地址,而后新模块内的这个函数在进行了本身的处理后,再跳到模块乙中。

Android和Linux的连接器不一样致使了内存布局的差别,也致使了网上流行的Linux注入与HOOK的方法行不通。网上的方法是经过ptrace注入后,搜索dynamic的section中的PLTGOT区,去里头取link_map以遍历此进程所加载的模块来搜索须要hook的函数地址。但Android上,dynamic的section的PLTGOT区前几项都是空的,没有link_map这个数据结构,只能经过分析/proc/ /maps来遍历模块。

四、阅读代码中的注意事项

在Android Injector Library阅读过程当中有几个须要注意的地方。

1)利用捕捉SIGSEGV的无效内存引用或者段错误的异常信号来执行ptrace。

2)ptrace(PTRACE_PEEKTEXT, pid, addr, data)

描述:从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据。

在Linux(i386)中用户代码段与用户数据段重合因此读取代码段和数据段数据处理是同样的。

3)linker 主要用于实现共享库的加载与连接。它支持应用程序对库函数的隐式和显式调用。查找/system/bin/linker中加载的libdl.so,加载位置固定,定义了dlopen,dlcose,dlsym,dlerror。

4)有下列代码理解,即dynsym和symtab的关系

图片5

5)在代码中有关于dynsym符号读取顺序的错误。可是不影响使用。使用androidSDK下的工具readelf

图片6

五、须要编写本身的open函数和close函数实现加解密操做

该过程使用Android 平台下的openssl EVP编程,该过程的难度不大。

关键点一是在于使用密钥空间构造。推荐密钥空间使用数组。使用char*字符串即便在字符串最后存在’’也会因为内存中的其余内容影响密钥初始化,出现意想不到的问题。

关键点二在close时,参数只有文件描述符,能够经过下述代码得到文件名。

图片7

关键点三在于使用Openssl进行对称加解密时会填充到相应的块大小,须要手动剥离这些填充。能够采用国际通用填充方式构造填充,或者自主构造密文文件头记录填充大小。

http://en.wikipedia.org/wiki/Padding_(cryptography)

六、记得在Makefile文件中加入

LOCAL_LDLIBS+=-L$(SYSROOT)/usr/lib -llog

LOCAL_LDLIBS+=-L$(SYSROOT)/usr/lib -lcrypto

LOCAL_LDLIBS+=-L$(SYSROOT)/usr/lib -lssl

七、须要再进行密钥管理模块的开发,该过程再也不描述。

4、总结

该种方案可以实如今android平台上的透明加解密。不足之处在于须要使用root权限,提早捕捉用户程序启动,对其进行hook。在移动设备上效率是瓶颈,并且文件不宜过大。对docxlspdfppttxt等文本、jpg等图片支持较好,其余格式的文件笔者没有进行测试。

相关文章
相关标签/搜索