一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Android中对Native层进行加固

时间:2015-11-23 编辑:简简单单 来源:一聚教程网

下面我们来看一下如何对native层进行加密,从而增加破解难度。我们在使用native层的时候,我们都知道一般是和Java层调用native层函数,那么我们就需要对native层函数进行加密,把重要的功能实现存放到native层,加大破解难度,那么我们来看一下如何对so中的函数进行加密?

这里有两种方案:

1、我们知道so文件中有很多section,我们可以将我们的目标函数存到指定的section中,然后对section进行加密即可。

技术原理


加密:在之前的文章中我们介绍了so中的格式,那么对于找到一个section的base和size就可以对这段section进行加密了
解密:因为我们对section进行加密之后,肯定需要解密的,不然的话,运行肯定是报错的,那么这里的重点是什么时候去进行解密,对于一个so文件,我们load进程序之后,在运行程序之前我们可以从哪个时间点来突破?这里就需要一个知识点:
__attribute__((constructor));

关于这个,属性的用法这里就不做介绍了,网上有相关资料,他的作用很简单,就是优先于main方法之前执行,类似于Java中的构造函数,当然其实C++中的构造函数就是基于这个属性实现的,我们在之前介绍elf文件格式的时候,有两个section会引起我们的注意:


对于这两个section,其实就是用这个属性实现的函数存在这里,
在动态链接器构造了进程映像,并执行了重定位以后,每个共享的目标都获得执行 某些初始化代码的机会。这些初始化函数的被调用顺序是不一定的,不过所有共享目标 初始化都会在可执行文件得到控制之前发生。
类似地,共享目标也包含终止函数,这些函数在进程完成终止动作序列时,通过 atexit() 机制执行。动态链接器对终止函数的调用顺序是不确定的。
共享目标通过动态结构中的 DT_INIT 和 DT_FINI 条目指定初始化/终止函数。通常 这些代码放在.init 和.fini 节区中。

这个知识点很重要,我们后面在进行动态调试so的时候,还会用到这个知识点,所以一定要理解。
所以,在这里我们找到了解密的时机,就是自己定义一个解密函数,然后用上面的这个属性声明就可以了。

实现流程


第一、我们编写一个简单的native代码,这里我们需要做两件事:
1、将我们核心的native函数定义在自己的一个section中,这里会用到这个属性:__attribute__((section (".mytext")));
其中.mytext就是我们自己定义的section.
说到这里,还记得我们之前介绍的一篇文章中介绍了,动态的给so添加一个section:
http://www.lai18.com/content/1425305.html
2、需要编写我们的解密函数,用属性: __attribute__((constructor));声明
这样一个native程序就包含这两个重要的函数,使用ndk编译成so文件

第二、编写加密程序,在加密程序中我们需要做的是:
1、通过解析so文件,找到.mytext段的起始地址和大小,这里的思路是:
找到所有的Section,然后获取他的name字段,在结合String Section,遍历找到.mytext字段
2、找到.mytext段之后,然后进行加密,最后在写入到文件中。

技术实现


前面介绍了原理和实现方案,下面就开始coding吧,
第一、我们先来看看native程序

    #include  
    #include  
    #include  
    #include  
    #include  
    #include  
    #include  
    #include  
    #include  
      
    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;  
      
      __android_log_print(ANDROID_LOG_INFO, "JNITag", "nblock =  0x%x,nsize:%d", nblock,nsize);  
      __android_log_print(ANDROID_LOG_INFO, "JNITag", "base =  0x%x", text_addr);  
      printf("nblock = %d\n", nblock);  
        
      if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){  
        puts("mem privilege change failed");  
         __android_log_print(ANDROID_LOG_INFO, "JNITag", "mem privilege change failed");  
      }  
        
      for(i=0;i< nblock; i++){    
        char *addr = (char*)(text_addr + i);  
        *addr = ~(*addr);  
      }  
        
      if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 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 JNICALL  
    Java_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);  
    }  

下面来分析一下代码:


1、定义自己的段


    jstring getString(JNIEnv*) __attribute__((section (".mytext")));  
    jstring getString(JNIEnv* env){  
        return (*env)->NewStringUTF(env, "Native method return!");  
    };  

这里的getString返回一个字符串,提供给Android上层,然后将getString定义在.mytext段中。

2、获取so加载到内存中的起始地址


    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;  
    }  

这里的代码其实就是读取设备的proc//maps中的内容,因为这个maps中是程序运行的内存映像:


我们只有获取到so的起始地址,才能找到指定的Section然后进行解密。


3、解密函数

    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;  
        
      //获取so的起始地址  
      base = getLibAddr();  
        
      //获取指定section的偏移值和size  
      ehdr = (Elf32_Ehdr *)base;  
      text_addr = ehdr->e_shoff + base;  
        
      nblock = ehdr->e_entry >> 16;  
      nsize = ehdr->e_entry & 0xffff;  
      
      __android_log_print(ANDROID_LOG_INFO, "JNITag", "nblock =  0x%x,nsize:%d", nblock,nsize);  
      __android_log_print(ANDROID_LOG_INFO, "JNITag", "base =  0x%x", text_addr);  
      printf("nblock = %d\n", nblock);  
        
      //修改内存的操作权限  
      if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){  
        puts("mem privilege change failed");  
         __android_log_print(ANDROID_LOG_INFO, "JNITag", "mem privilege change failed");  
      }  
      //解密  
      for(i=0;i< nblock; i++){    
        char *addr = (char*)(text_addr + i);  
        *addr = ~(*addr);  
      }  
        
      if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC) != 0){  
        puts("mem privilege change failed");  
      }  
      puts("Decrypt success");  
    }  

这里我们获取到so文件的头部,然后获取指定section的偏移地址和size


    //获取so的起始地址  
    base = getLibAddr();  
      
    //获取指定section的偏移值和size  
    ehdr = (Elf32_Ehdr *)base;  
    text_addr = ehdr->e_shoff + base;  
      
    nblock = ehdr->e_entry >> 16;  
    nsize = ehdr->e_entry & 0xffff;  

这里可能会有困惑?为什么这里是这么获取offset和size的,其实这里我们做了一点工作,就是我们在加密的时候顺便改写了so的头部信息,将offset和size值写到了头部中,这样加大破解难度。后面在说到加密的时候在详解。
text_addr是起始地址+偏移值,就是我们的section在内存中的绝对地址
nsize是我们的section占用的页数

然后修改这个section的内存操作权限


    //修改内存的操作权限  
    if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){  
        puts("mem privilege change failed");  
        __android_log_print(ANDROID_LOG_INFO, "JNITag", "mem privilege change failed");  
    }  

这里调用了一个系统函数:mprotect
第一个参数:需要修改内存的起始地址
必须需要页面对齐,也就是必须是页面PAGE_SIZE(0x1000=4096)的整数倍

第二个参数:需要修改的大小
占用的页数*PAGE_SIZE
第三个参数:权限值

最后读取内存中的section内容,然后进行解密,在将内存权限修改回去。
然后使用ndk编译成so即可,这里我们用到了系统的打印log信息,所以需要用到共享库,看一下编译脚本Android.mk

    LOCAL_PATH := $(call my-dir)  
      
    include $(CLEAR_VARS)  
    LOCAL_MODULE := demo  
    LOCAL_SRC_FILES := demo.c  
    LOCAL_LDLIBS := -llog  
    include $(BUILD_SHARED_LIBRARY) 

第二、加密程序


1、加密程序(Java版)
我们获取到上面的so文件,下面我们就来看看如何进行加密的:

    package com.jiangwei.encodesection;  
      
    import com.jiangwei.encodesection.ElfType32.Elf32_Sym;  
    import com.jiangwei.encodesection.ElfType32.elf32_phdr;  
    import com.jiangwei.encodesection.ElfType32.elf32_shdr;  
      
    public class EncodeSection {  
          
        public static String encodeSectionName = ".mytext";  
          
        public static ElfType32 type_32 = new ElfType32();  
          
        public static void main(String[] args){  
              
            byte[] fileByteArys = Utils.readFile("so/libdemo.so");  
            if(fileByteArys == null){  
                System.out.println("read file byte failed...");  
                return;  
            }  
              
            /**
             * 先解析so文件
             * 然后初始化AddSection中的一些信息
             * 最后在AddSection
             */  
            parseSo(fileByteArys);  
              
            encodeSection(fileByteArys);  
              
            parseSo(fileByteArys);  
              
            Utils.saveFile("so/libdemos.so", fileByteArys);  
              
        }  
          
        private static void encodeSection(byte[] fileByteArys){  
            //读取String Section段  
            System.out.println();  
              
            int string_section_index = Utils.byte2Short(type_32.hdr.e_shstrndx);  
            elf32_shdr shdr = type_32.shdrList.get(string_section_index);  
            int size = Utils.byte2Int(shdr.sh_size);  
            int offset = Utils.byte2Int(shdr.sh_offset);  
      
            int mySectionOffset=0,mySectionSize=0;  
            for(elf32_shdr temp : type_32.shdrList){  
                int sectionNameOffset = offset+Utils.byte2Int(temp.sh_name);  
                if(Utils.isEqualByteAry(fileByteArys, sectionNameOffset, encodeSectionName)){  
                    //这里需要读取section段然后进行数据加密  
                    mySectionOffset = Utils.byte2Int(temp.sh_offset);  
                    mySectionSize = Utils.byte2Int(temp.sh_size);  
                    byte[] sectionAry = Utils.copyBytes(fileByteArys, mySectionOffset, mySectionSize);  
                    for(int i=0;i                        sectionAry[i] = (byte)(sectionAry[i] ^ 0xFF);  
                    }  
                    Utils.replaceByteAry(fileByteArys, mySectionOffset, sectionAry);  
                }  
            }  
      
            //修改Elf Header中的entry和offset值  
            int nSize = mySectionSize/4096 + (mySectionSize%4096 == 0 ? 0 : 1);  
            byte[] entry = new byte[4];  
            entry = Utils.int2Byte((mySectionSize<<16) + nSize);  
            Utils.replaceByteAry(fileByteArys, 24, entry);  
            byte[] offsetAry = new byte[4];  
            offsetAry = Utils.int2Byte(mySectionOffset);  
            Utils.replaceByteAry(fileByteArys, 32, offsetAry);  
        }  
          
        private static void parseSo(byte[] fileByteArys){  
            //读取头部内容  
            System.out.println("+++++++++++++++++++Elf Header+++++++++++++++++");  
            parseHeader(fileByteArys, 0);  
            System.out.println("header:\n"+type_32.hdr);  
      
            //读取程序头信息  
            //System.out.println();  
            //System.out.println("+++++++++++++++++++Program Header+++++++++++++++++");  
            int p_header_offset = Utils.byte2Int(type_32.hdr.e_phoff);  
            parseProgramHeaderList(fileByteArys, p_header_offset);  
            //type_32.printPhdrList();  
      
            //读取段头信息  
            //System.out.println();  
            //System.out.println("+++++++++++++++++++Section Header++++++++++++++++++");  
            int s_header_offset = Utils.byte2Int(type_32.hdr.e_shoff);  
            parseSectionHeaderList(fileByteArys, s_header_offset);  
            //type_32.printShdrList();  
              
            //这种方式获取所有的Section的name  
            /*byte[] names = Utils.copyBytes(fileByteArys, offset, size);
            String str = new String(names);
            byte NULL = 0;//字符串的结束符
            StringTokenizer st = new StringTokenizer(str, new String(new byte[]{NULL}));
            System.out.println( "Token Total: " + st.countTokens() );
            while(st.hasMoreElements()){
                System.out.println(st.nextToken());
            }
            System.out.println("");*/  
      
            /*//读取符号表信息(Symbol Table)
            System.out.println();
            System.out.println("+++++++++++++++++++Symbol Table++++++++++++++++++");
            //这里需要注意的是:在Elf表中没有找到SymbolTable的数目,但是我们仔细观察Section中的Type=DYNSYM段的信息可以得到,这个段的大小和偏移地址,而SymbolTable的结构大小是固定的16个字节
            //那么这里的数目=大小/结构大小
            //首先在SectionHeader中查找到dynsym段的信息
            int offset_sym = 0;
            int total_sym = 0;
            for(elf32_shdr shdr : type_32.shdrList){
                if(Utils.byte2Int(shdr.sh_type) == ElfType32.SHT_DYNSYM){
                    total_sym = Utils.byte2Int(shdr.sh_size);
                    offset_sym = Utils.byte2Int(shdr.sh_offset);
                    break;
                }
            }
            int num_sym = total_sym / 16;
            System.out.println("sym num="+num_sym);
            parseSymbolTableList(fileByteArys, num_sym, offset_sym);
            type_32.printSymList();
     
            //读取字符串表信息(String Table)
            System.out.println();
            System.out.println("+++++++++++++++++++Symbol Table++++++++++++++++++");
            //这里需要注意的是:在Elf表中没有找到StringTable的数目,但是我们仔细观察Section中的Type=STRTAB段的信息,可以得到,这个段的大小和偏移地址,但是我们这时候我们不知道字符串的大小,所以就获取不到数目了
            //这里我们可以查看Section结构中的name字段:表示偏移值,那么我们可以通过这个值来获取字符串的大小
            //可以这么理解:当前段的name值 减去 上一段的name的值 = (上一段的name字符串的长度)
            //首先获取每个段的name的字符串大小
            int prename_len = 0;
            int[] lens = new int[type_32.shdrList.size()];
            int total = 0;
            for(int i=0;i                if(Utils.byte2Int(type_32.shdrList.get(i).sh_type) == ElfType32.SHT_STRTAB){
                    int curname_offset = Utils.byte2Int(type_32.shdrList.get(i).sh_name);
                    lens[i] = curname_offset - prename_len - 1;
                    if(lens[i] < 0){
                        lens[i] = 0;
                    }
                    total += lens[i];
                    System.out.println("total:"+total);
                    prename_len = curname_offset;
                    //这里需要注意的是,最后一个字符串的长度,需要用总长度减去前面的长度总和来获取到
                    if(i == (lens.length - 1)){
                        System.out.println("size:"+Utils.byte2Int(type_32.shdrList.get(i).sh_size));
                        lens[i] = Utils.byte2Int(type_32.shdrList.get(i).sh_size) - total - 1;
                    }
                }
            }
            for(int i=0;i                System.out.println("len:"+lens[i]);
            }
            //上面的那个方法不好,我们发现StringTable中的每个字符串结束都会有一个00(传说中的字符串结束符),那么我们只要知道StringTable的开始位置,然后就可以读取到每个字符串的值了
           */  
        }  
          
        /**
         * 解析Elf的头部信息
         * @param header
         */  
        private static void  parseHeader(byte[] header, int offset){  
            if(header == null){  
                System.out.println("header is null");  
                return;  
            }  
            /**
             *  public byte[] e_ident = new byte[16];
                public short e_type;
                public short e_machine;
                public int e_version;
                public int e_entry;
                public int e_phoff;
                public int e_shoff;
                public int e_flags;
                public short e_ehsize;
                public short e_phentsize;
                public short e_phnum;
                public short e_shentsize;
                public short e_shnum;
                public short e_shstrndx;
             */  
            type_32.hdr.e_ident = Utils.copyBytes(header, 0, 16);//魔数  
            type_32.hdr.e_type = Utils.copyBytes(header, 16, 2);  
            type_32.hdr.e_machine = Utils.copyBytes(header, 18, 2);  
            type_32.hdr.e_version = Utils.copyBytes(header, 20, 4);  
            type_32.hdr.e_entry = Utils.copyBytes(header, 24, 4);  
            type_32.hdr.e_phoff = Utils.copyBytes(header, 28, 4);  
            type_32.hdr.e_shoff = Utils.copyBytes(header, 32, 4);  
            type_32.hdr.e_flags = Utils.copyBytes(header, 36, 4);  
            type_32.hdr.e_ehsize = Utils.copyBytes(header, 40, 2);  
            type_32.hdr.e_phentsize = Utils.copyBytes(header, 42, 2);  
            type_32.hdr.e_phnum = Utils.copyBytes(header, 44,2);  
            type_32.hdr.e_shentsize = Utils.copyBytes(header, 46,2);  
            type_32.hdr.e_shnum = Utils.copyBytes(header, 48, 2);  
            type_32.hdr.e_shstrndx = Utils.copyBytes(header, 50, 2);  
        }  
          
        /**
         * 解析程序头信息
         * @param header
         */  
        public static void parseProgramHeaderList(byte[] header, int offset){  
            int header_size = 32;//32个字节  
            int header_count = Utils.byte2Short(type_32.hdr.e_phnum);//头部的个数  
            byte[] des = new byte[header_size];  
            for(int i=0;i                System.arraycopy(header, i*header_size + offset, des, 0, header_size);  
                type_32.phdrList.add(parseProgramHeader(des));  
            }  
        }  
          
        private static elf32_phdr parseProgramHeader(byte[] header){  
            /**
             *  public int p_type;
                public int p_offset;
                public int p_vaddr;
                public int p_paddr;
                public int p_filesz;
                public int p_memsz;
                public int p_flags;
                public int p_align;
             */  
            ElfType32.elf32_phdr phdr = new ElfType32.elf32_phdr();  
            phdr.p_type = Utils.copyBytes(header, 0, 4);  
            phdr.p_offset = Utils.copyBytes(header, 4, 4);  
            phdr.p_vaddr = Utils.copyBytes(header, 8, 4);  
            phdr.p_paddr = Utils.copyBytes(header, 12, 4);  
            phdr.p_filesz = Utils.copyBytes(header, 16, 4);  
            phdr.p_memsz = Utils.copyBytes(header, 20, 4);  
            phdr.p_flags = Utils.copyBytes(header, 24, 4);  
            phdr.p_align = Utils.copyBytes(header, 28, 4);  
            return phdr;  
              
        }  
          
        /**
         * 解析段头信息内容
         */  
        public static void parseSectionHeaderList(byte[] header, int offset){  
            int header_size = 40;//40个字节  
            int header_count = Utils.byte2Short(type_32.hdr.e_shnum);//头部的个数  
            byte[] des = new byte[header_size];  
            for(int i=0;i                System.arraycopy(header, i*header_size + offset, des, 0, header_size);  
                type_32.shdrList.add(parseSectionHeader(des));  
            }  
        }  
          
        private static elf32_shdr parseSectionHeader(byte[] header){  
            ElfType32.elf32_shdr shdr = new ElfType32.elf32_shdr();  
            /**
             *  public byte[] sh_name = new byte[4];
                public byte[] sh_type = new byte[4];
                public byte[] sh_flags = new byte[4];
                public byte[] sh_addr = new byte[4];
                public byte[] sh_offset = new byte[4];
                public byte[] sh_size = new byte[4];
                public byte[] sh_link = new byte[4];
                public byte[] sh_info = new byte[4];
                public byte[] sh_addralign = new byte[4];
                public byte[] sh_entsize = new byte[4];
             */  
            shdr.sh_name = Utils.copyBytes(header, 0, 4);  
            shdr.sh_type = Utils.copyBytes(header, 4, 4);  
            shdr.sh_flags = Utils.copyBytes(header, 8, 4);  
            shdr.sh_addr = Utils.copyBytes(header, 12, 4);  
            shdr.sh_offset = Utils.copyBytes(header, 16, 4);  
            shdr.sh_size = Utils.copyBytes(header, 20, 4);  
            shdr.sh_link = Utils.copyBytes(header, 24, 4);  
            shdr.sh_info = Utils.copyBytes(header, 28, 4);  
            shdr.sh_addralign = Utils.copyBytes(header, 32, 4);  
            shdr.sh_entsize = Utils.copyBytes(header, 36, 4);  
            return shdr;  
        }  
          
        /**
         * 解析Symbol Table内容  
         */  
        public static void parseSymbolTableList(byte[] header, int header_count, int offset){  
            int header_size = 16;//16个字节  
            byte[] des = new byte[header_size];  
            for(int i=0;i                System.arraycopy(header, i*header_size + offset, des, 0, header_size);  
                type_32.symList.add(parseSymbolTable(des));  
            }  
        }  
          
        private static ElfType32.Elf32_Sym parseSymbolTable(byte[] header){  
            /**
             *  public byte[] st_name = new byte[4];
                public byte[] st_value = new byte[4];
                public byte[] st_size = new byte[4];
                public byte st_info;
                public byte st_other;
                public byte[] st_shndx = new byte[2];
             */  
            Elf32_Sym sym = new Elf32_Sym();  
            sym.st_name = Utils.copyBytes(header, 0, 4);  
            sym.st_value = Utils.copyBytes(header, 4, 4);  
            sym.st_size = Utils.copyBytes(header, 8, 4);  
            sym.st_info = header[12];  
            //FIXME 这里有一个问题,就是这个字段读出来的值始终是0  
            sym.st_other = header[13];  
            sym.st_shndx = Utils.copyBytes(header, 14, 2);  
            return sym;  
        }  
          
      
    }  

在这里,我需要解析so文件的头部信息,程序头信息,段头信息

    //读取头部内容  
    System.out.println("+++++++++++++++++++Elf Header+++++++++++++++++");  
    parseHeader(fileByteArys, 0);  
    System.out.println("header:\n"+type_32.hdr);  
      
    //读取程序头信息  
    //System.out.println();  
    //System.out.println("+++++++++++++++++++Program Header+++++++++++++++++");  
    int p_header_offset = Utils.byte2Int(type_32.hdr.e_phoff);  
    parseProgramHeaderList(fileByteArys, p_header_offset);  
    //type_32.printPhdrList();  
      
    //读取段头信息  
    //System.out.println();  
    //System.out.println("+++++++++++++++++++Section Header++++++++++++++++++");  
    int s_header_offset = Utils.byte2Int(type_32.hdr.e_shoff);  
    parseSectionHeaderList(fileByteArys, s_header_offset);  
    //type_32.printShdrList();  


获取这些信息之后,下面就来开始寻找我们的段了,只需要遍历Section列表,找到名字是.mytext的section即可,然后获取offset和size,对内容进行加密,回写到文件中。下面来看看核心方法:

    private static void encodeSection(byte[] fileByteArys){  
        //读取String Section段  
        System.out.println();  
      
        int string_section_index = Utils.byte2Short(type_32.hdr.e_shstrndx);  
        elf32_shdr shdr = type_32.shdrList.get(string_section_index);  
        int size = Utils.byte2Int(shdr.sh_size);  
        int offset = Utils.byte2Int(shdr.sh_offset);  
      
        int mySectionOffset=0,mySectionSize=0;  
        for(elf32_shdr temp : type_32.shdrList){  
            int sectionNameOffset = offset+Utils.byte2Int(temp.sh_name);  
            if(Utils.isEqualByteAry(fileByteArys, sectionNameOffset, encodeSectionName)){  
                //这里需要读取section段然后进行数据加密  
                mySectionOffset = Utils.byte2Int(temp.sh_offset);  
                mySectionSize = Utils.byte2Int(temp.sh_size);  
                byte[] sectionAry = Utils.copyBytes(fileByteArys, mySectionOffset, mySectionSize);  
                for(int i=0;i                    sectionAry[i] = (byte)(sectionAry[i] ^ 0xFF);  
                }  
                Utils.replaceByteAry(fileByteArys, mySectionOffset, sectionAry);  
            }  
        }  
      
        //修改Elf Header中的entry和offset值  
        int nSize = mySectionSize/4096 + (mySectionSize%4096 == 0 ? 0 : 1);  
        byte[] entry = new byte[4];  
        entry = Utils.int2Byte((mySectionSize<<16) + nSize);  
        Utils.replaceByteAry(fileByteArys, 24, entry);  
        byte[] offsetAry = new byte[4];  
        offsetAry = Utils.int2Byte(mySectionOffset);  
        Utils.replaceByteAry(fileByteArys, 32, offsetAry);  
    }  

我们知道Section中的sh_name字段的值是这个section段的name在StringSection中的索引值,这里offset就是StringSection在文件中的偏移值。当然我们需要知道的一个知识点就是:StringSection中的每个name都是以\0结尾的,所以我们只需要判断字符串到结束符就可以了,判断方法是Utils.isEqualByteAry:

    public static boolean isEqualByteAry(byte[] src, int start, String destStr){  
        if(destStr == null){  
            return false;  
        }  
        byte[] dest = destStr.getBytes();  
        if(src == null || dest == null){  
            return false;  
        }  
        if(dest.length == 0 || src.length == 0){  
            return false;  
        }  
        if(start >= src.length){  
            return false;  
        }  
      
        int len = 0;  
        byte temp = src[start];  
        while(temp != 0){  
            len++;  
            temp = src[start+len];  
        }  
      
        byte[] sonAry = copyBytes(src, start, len);  
        if(sonAry == null || sonAry.length == 0){  
            return false;  
        }  
        if(sonAry.length != dest.length){  
            return false;  
        }  
        String sonStr = new String(sonAry);  
        if(destStr.equals(sonStr)){  
            return true;  
        }  
        return false;  
    }  

这里我们加密的方法很简单,加密完成之后,我们需要做的是回写到so文件中,当然这里我们还需要做一件事,就是将我们加密的.mytext段的偏移值和pageSize保存到头部信息中:

    //修改Elf Header中的entry和offset值  
    int nSize = mySectionSize/4096 + (mySectionSize%4096 == 0 ? 0 : 1);  
    byte[] entry = new byte[4];  
    entry = Utils.int2Byte((mySectionSize<<16) + nSize);  
    Utils.replaceByteAry(fileByteArys, 24, entry);  

这里又有一个知识点需要说明?大家可能会困惑,我们这样修改了so的头部信息的话,在加载运行so文件的时候不会报错吗?这个就要看看Android底层是如何解析so文件,然后将so文件映射到内存中的了,下面我们来看看系统是如何解析so文件的?
源代码的位置:Android linker源码:bionic\linker
在linker.h源码中有一个重要的结构体soinfo,下面列出一些字段:

    struct soinfo{  
        const char name[SOINFO_NAME_LEN]; //so全名  
        Elf32_Phdr *phdr; //Program header的地址  
    int phnum; //segment 数量  
    unsigned *dynamic; //指向.dynamic,在section和segment中相同的  
    //以下4个成员与.hash表有关  
    unsigned nbucket;  
    unsigned nchain;  
    unsigned *bucket;  
    unsigned *chain;  
    //这两个成员只能会出现在可执行文件中  
    unsigned *preinit_array;  
    unsigned preinit_array_count;  


指向初始化代码,先于main函数之行,即在加载时被linker所调用,在linker.c可以看到:__linker_init -> link_image ->

    call_constructors -> call_array  
    unsigned *init_array;  
    unsigned init_array_count;  
    void (*init_func)(void);  
    //与init_array类似,只是在main结束之后执行  
    unsigned *fini_array;  
    unsigned fini_array_count;  
    void (*fini_func)(void);