ELF文件加密
来源:互联网 发布:战龙三国孙策进阶数据 编辑:程序博客网 时间:2024/06/07 01:43
ELF头的各个字段如下:
1. #define EI_NIDENT 16 2. typedef struct{ 3. unsigned char e_ident[EI_NIDENT]; //目标文件标识信息 4. Elf32_Half e_type; //目标文件类型 5. Elf32_Half e_machine; //目标体系结构类型 6. Elf32_Word e_version; //目标文件版本 7. Elf32_Addr e_entry; //程序入口的虚拟地址,若没有,可为0 8. Elf32_Off e_phoff; //程序头部表格(Program Header Table)的偏移量(按字节计算),若没有,可为0 9. Elf32_Off e_shoff; //节区头部表格(Section Header Table)的偏移量(按字节计算),若没有,可为0 10. Elf32_Word e_flags; //保存与文件相关的,特定于处理器的标志。标志名称采用 EF_machine_flag的格式。 11. Elf32_Half e_ehsize; //ELF 头部的大小(以字节计算)。 12. Elf32_Half e_phentsize; //程序头部表格的表项大小(按字节计算)。 13. Elf32_Half e_phnum; //程序头部表格的表项数目。可以为 0。 14. Elf32_Half e_shentsize; //节区头部表格的表项大小(按字节计算)。 15. Elf32_Half e_shnum; //节区头部表格的表项数目。可以为 0。 16. Elf32_Half e_shstrndx; //节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节区名称字符串表,此参数可以为 SHN_UNDEF。 17. }Elf32_Ehdr;
关于头部我们要记住的有这几点,就可以根据其中部分条件找另外的值了:
e_phoff = sizeof(e_ehsize);
整个ELF文件大小 = e_shoff + e_shnum * sizeof(e_shentsize) + 1
通常情况下:e_shstrndx = e_shnum – 1
e_shstrndx字段的值跟strip有关。Strip之前:.shstrtab 并不是最后一个section.则 e_shstrndx = e_shnum – 1 – 2;
而经过strip之后,动态链接库末尾的.symtab和.strtab这两个section会被去掉. 则e_shstrndx = e_shnum – 1。
使用ndk生成在\libs\ armeabi\下的.so文件是经过strip的,也是被打包到apk中的。
tips:但是如果e_shoff和e_shnum都改成任意值,那么修正起来比较麻烦。
但貌似e_shoff、e_shnum等与section相关的信息任意修改,对.so文件的使用毫无影响。
能找到的一句如下:
1.elf如何装载
2.linker如何链接
基于上面的结论,再来分析下ELF头的字段。
1) e_ident[EI_NIDENT] 字段包含魔数、字节序、字长和版本,后面填充0。对于安卓的linker,通过verify_elf_object函数检验魔数,判定是否为.so文件。那么,我们可以向位置写入数据,至少可以向后面的0填充位置写入数据。遗憾的是,我在fedora 14下测试,是不能向0填充位置写数据,链接器报非0填充错误。
2) 对于安卓的linker,对e_type、e_machine、e_version和e_flags字段并不关心,是可以修改成其他数据的(仅分析,没有实测)
3) 对于动态链接库,e_entry 入口地址是无意义的,因为程序被加载时,设定的跳转地址是动态连接器的地址,这个字段是可以被作为数据填充的。
4) so装载时,与链接视图没有关系,即e_shoff、e_shentsize、e_shnum和e_shstrndx这些字段是可以任意修改的。被修改之后,使用readelf和ida等工具打开,会报各种错误,相信读者已经见识过了。
5) 既然so装载与装载视图紧密相关,自然e_phoff、e_phentsize和e_phnum这些字段是不能动的。
再来分析so修改
三、 基于特定section的加解密实现
基于section的加解密,是指将so文件的特定section进行加密,so文件被加载时解密。下面给出实例。
假设有一个shelldemo应用,调用一个native方法返回一个字符串供UI显示。在native方法中,又调用getString方法返回一个字符串供native方法返回。我需要将getString方法加密。这里,将getString方法存放在.mytext中(指定attribute((section (“.mytext”)));),即是需要对.mytext进行加密。
加密流程:
1) 从so文件头读取section偏移shoff、shnum和shstrtab
2) 读取shstrtab中的字符串,存放在str空间中
3) 从shoff位置开始读取section header, 存放在shdr
4) 通过shdr -> sh_name 在str字符串中索引,与.mytext进行字符串比较,如果不匹配,继续读取
5) 通过shdr -> sh_offset 和 shdr -> sh_size字段,将.mytext内容读取并保存在content中。
6) 为了便于理解,不使用复杂的加密算法。这里,只将content的所有内容取反,即 *content = ~(*content);
7) 将content内容写回so文件中
8) 为了验证第二节中关于section 字段可以任意修改的结论,这里,将shdr -> addr 写入ELF头e_shoff,将shdr -> sh_size 和 addr 所在内存块写入e_entry中,即ehdr.e_entry = (length << 16) + nsize。当然,这样同时也简化了解密流程,还有一个好处是:如果将so文件头修正放回去,程序是不能运行的。
#include <stdio.h>#include <fcntl.h>#include <elf.h>#include <stdlib.h>#include <string.h>int main(int argc, char** argv){ char target_section[] = ".mytext"; char *shstr = NULL; char *content = NULL; Elf32_Ehdr ehdr; Elf32_Shdr shdr; int i; unsigned int base, length; unsigned short nblock; unsigned short nsize; unsigned char block_size = 16; int fd; if(argc < 2){ puts("Input .so file"); return -1; } fd = open(argv[1], O_RDWR); if(fd < 0){ printf("open %s failed\n", argv[1]); goto _error; } //从so文件头读取section偏移shoff、shnum和shstrtab if(read(fd, &ehdr, sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)){ puts("Read ELF header error"); goto _error; } //off_t lseek(int handle, off_t offset, int fromwhere); lseek(fd, ehdr.e_shoff + sizeof(Elf32_Shdr) * ehdr.e_shstrndx, SEEK_SET); // ELF section string table if(read(fd, &shdr, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)){ puts("Read ELF section string table error"); goto _error; } //分配str空间中 if((shstr = (char *) malloc(shdr.sh_size)) == NULL){ puts("Malloc space for section string table failed"); goto _error; } //Read string table lseek(fd, shdr.sh_offset, SEEK_SET); if(read(fd, shstr, shdr.sh_size) != shdr.sh_size){ puts("Read string table failed"); goto _error; } //通过shdr -> sh_name 在str字符串中索引,与.mytext进行字符串比较,如果不匹配,继续读取 lseek(fd, ehdr.e_shoff, SEEK_SET); for(i = 0; i < ehdr.e_shnum; i++){ if(read(fd, &shdr, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)){ puts("Find section .text procedure failed"); goto _error; } if(strcmp(shstr + shdr.sh_name, target_section) == 0){ base = shdr.sh_offset; length = shdr.sh_size; printf("Find section %s\n", target_section); break; } } //通过shdr -> sh_offset 和 shdr -> sh_size字段,将.mytext内容读取并保存在content中 lseek(fd, base, SEEK_SET); content = (char*) malloc(length); if(content == NULL){ puts("Malloc space for content failed"); goto _error; } if(read(fd, content, length) != length){ puts("Read section .text failed"); goto _error; } nblock = length / block_size; nsize = base / 4096 + (base % 4096 == 0 ? 0 : 1); printf("base = %d, length = %d\n", base, length); printf("nblock = %d, nsize = %d\n", nblock, nsize); ehdr.e_entry = (length << 16) + nsize; ehdr.e_shoff = base; //将content的所有内容取反 for(i=0;i<length;i++){ content[i] = ~content[i]; } lseek(fd, 0, SEEK_SET); if(write(fd, &ehdr, sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)){ puts("Write ELFhead to .so failed"); goto _error; } //将content内容写回so文件中 lseek(fd, base, SEEK_SET); if(write(fd, content, length) != length){ puts("Write modified content to .so failed"); goto _error; } puts("Completed");_error: free(content); free(shstr); close(fd); return 0;}
我们来看解密对应的加密操作
解密时,需要保证解密函数在so加载时被调用,那函数声明为:init_getString attribute((constructor))。(也可以使用c++构造器实现, 其本质也是用attribute实现)
解密流程:
1) 动态链接器通过call_array调用init_getString
2) Init_getString首先调用getLibAddr方法,得到so文件在内存中的起始地址
3) 读取前52字节,即ELF头。通过e_shoff获得.mytext内存加载地址,ehdr.e_entry获取.mytext大小和所在内存块
4) 修改.mytext所在内存块的读写权限
5) 将[e_shoff, e_shoff + size]内存区域数据解密,即取反操作:*content = ~(*content);
6) 修改回内存区域的读写权限
(这里是对代码段的数据进行解密,需要写权限。如果对数据段的数据解密,是不需要更改权限直接操作的)
#include <jni.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <elf.h>#include <sys/mman.h>jstring getString(JNIEnv*) __attribute__((section (".mytext")));jstring getString(JNIEnv* env){ return (*env)->NewStringUTF(env, "Native method return!");};void init_getString() __attribute__((constructor));unsigned long getLibAddr();void init_getString(){ char name[15]; unsigned int nblock; unsigned int nsize; unsigned long base; unsigned long text_addr; unsigned int i; Elf32_Ehdr *ehdr; Elf32_Shdr *shdr; base = getLibAddr(); ehdr = (Elf32_Ehdr *)base; text_addr = ehdr->e_shoff + base; nblock = ehdr->e_entry >> 16; nsize = ehdr->e_entry & 0xffff; printf("nblock = %d\n", nblock); if(mprotect((void *) base, 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){ puts("mem privilege change failed"); } for(i=0;i< nblock; i++){ char *addr = (char*)(text_addr + i); *addr = ~(*addr); } if(mprotect((void *) base, 4096 * nsize, PROT_READ | PROT_EXEC) != 0){ puts("mem privilege change failed"); } puts("Decrypt success");}unsigned long getLibAddr(){ unsigned long ret = 0; char name[] = "libdemo.so"; char buf[4096], *temp; int pid; FILE *fp; pid = getpid(); sprintf(buf, "/proc/%d/maps", pid); fp = fopen(buf, "r"); if(fp == NULL) { puts("open failed"); goto _error; } while(fgets(buf, sizeof(buf), fp)){ if(strstr(buf, name)){ temp = strtok(buf, "-"); ret = strtoul(temp, NULL, 16); break; } }_error: fclose(fp); return ret;}JNIEXPORT jstring JNICALLJava_com_example_shelldemo_MainActivity_getString( JNIEnv* env, jobject thiz ){#if defined(__arm__) #if defined(__ARM_ARCH_7A__) #if defined(__ARM_NEON__) #define ABI "armeabi-v7a/NEON" #else #define ABI "armeabi-v7a" #endif #else #define ABI "armeabi" #endif#elif defined(__i386__) #define ABI "x86"#elif defined(__mips__) #define ABI "mips"#else #define ABI "unknown"#endif return getString(env);}
注意:并不是所有的section都能全加,有些数据是不能加密的。比如直接对.text直接加密,会把与crt有关代码也加密,只能选择性的加密。
参考:http://bbs.pediy.com/thread-191649.htm
- ELF文件加密
- ELF文件加密相关
- 【转】ELF 加密文件解码初探(思路很好)
- 简单SO加密及ELF头文件修复
- ELF文件
- ELF文件
- ELF文件
- ELF文件
- ELF文件
- ELF文件
- elf文件
- ELF文件
- 几个elf加密程序
- ELF文件格式(一)--ELF文件头
- Elf文件图形详解
- Elf文件图形详解
- elf文件分析
- ELF文件加载分析
- Linux 下Epoll模型
- crm插件简单例子--给实体中的字段赋值
- 初涉vue--环境搭建
- JS 控制每行显示多少个字符,超出隐藏
- spring-tool-suite-3.9.0.RELEASE-e4.7.0-win32.zip下载
- ELF文件加密
- SAP实施Roll out项目经验谈(一)
- php中的curl的一些参数总结
- java设计模式之抽象工厂
- ios-info.plist文件和pch文件的一些介绍
- 意法半导体stm32系列芯片的省电原因
- iOS开发之copy与mutableCopy
- mysql数据库更改表相关操作
- 深入理解Zuul之源码解析