【Android NDK】(三)使用c++ 解析so文件结构

4,487 阅读18分钟

本文介绍了so结构,记录了so文件的解析方法和过程,为后续so库加解密打基础。

1.so文件是什么?

so文件是Linux下的程序函数库,即编译好的可以供其他程序使用的代码和数据,也叫动态链接库。

2.so的结构是怎样的?

文件格式被称为ELF文件格式。
二进制结构图(飞虫大神)如下:

头文件结构图如下:

总体结构图如下:

结构字段详解请参考滕启明老师的【ELF文件格式分析】:
链接:pan.baidu.com/s/1GEzePWkK…
提取码:ibcq

结构查看工具:IDA Pro,可自行在网上搜索下载。

2.1 ELF Header

ELF头结构,仅有一个,位于文件起始位置,长度为sizeof(ELF32_Ehdr)的部分。

struct Elf32_Ehdr {
  unsigned char e_ident[EI_NIDENT]; // 文件标识ELF,32位还是64位
  Elf32_Half    e_type;      
  Elf32_Half    e_machine;   
  Elf32_Word    e_version;   
  Elf32_Addr    e_entry;     
  Elf32_Off     e_phoff;     // Program header的文件中的偏移
  Elf32_Off     e_shoff;     // Section header在文件中的偏移
  Elf32_Word    e_flags;     
  Elf32_Half    e_ehsize;    // ELF 文件头本身的大小
  Elf32_Half    e_phentsize; // Program header 表的大小
  Elf32_Half    e_phnum;     // Program header 表的数量
  Elf32_Half    e_shentsize; // Section表大小
  Elf32_Half    e_shnum;     // Section表数量
  Elf32_Half    e_shstrndx;  // Section字符串表所在的Section在Section表中的下标
};

// c++文件流解析--fstream ioFile
Elf32_Ehdr* elf32Ehdr;  // ELF Header  文件头
elf32Ehdr = new Elf32_Ehdr[1];
ioFile.seekg(0, ios::beg);
ioFile.read((char*) elf32Ehdr, sizeof(Elf32_Ehdr));

// 字节数组解析--char* fileContent
Elf32_Ehdr* elf32Ehdr = (Elf32_Ehdr*) fileContent;

2.2 Program Header

程序头结构,紧接着ELF header之后,可能有多个。起始位置elf32Ehdr->e_phoff,个数为elf32Ehdr->e_phnum,长度为sizeof(Elf32_Phdr) * elf32Ehdr->e_phnum。

struct Elf32_Phdr {
  Elf32_Word p_type;   // 段类型
  Elf32_Off  p_offset; // 段在文件中的偏移
  Elf32_Addr p_vaddr;  // 段的第一个字节在虚拟地址空间的起始位置,整个程序表头中
  Elf32_Addr p_paddr;  // 段的物理装载地址,即 LMA(Load Memory Address),一般情况下 p_paddr 和 p_vaddr 是相同的
  Elf32_Word p_filesz; // 段在 ELF 文件中所占空间的长度,可能为 0
  Elf32_Word p_memsz;  // 段在进程虚拟空间中所占空间的长度,可能为 0
  Elf32_Word p_flags;  // 段的权限属性,比如可读 "R",可写 "W" 和可执行 "X"
  Elf32_Word p_align;  // 段的对齐属性,实际对齐字节等于 2 的 p_align 次方
};

// c++解析Program Header
Elf32_Phdr* elf32Phdr = new Elf32_Phdr[elf32Ehdr->e_phnum];
ioFile.seekg(elf32Ehdr->e_phoff, ios::beg);
ioFile.read((char*) elf32Phdr, sizeof(Elf32_Phdr) * elf32Ehdr->e_phnum);

// 字符数组解析
Elf32_Phdr* elf32Phdr = (Elf32_Phdr*) &fileContent[elf32Ehdr->e_phoff];

2.3 Section Header

节头结构,位于文件的末尾,可能有多个。起始位置elf32Ehdr->e_shoff,个数为elf32Ehdr->e_shnum,长度为sizeof(Elf32_Shdr) * elf32Ehdr->e_shnum。

struct Elf32_Shdr {
  Elf32_Word sh_name;      // 段名,位于 .shstrtab 的字符串表。sh_name 是段名在其中的偏移
  Elf32_Word sh_type;      // 段类型(SHT_*)
  Elf32_Word sh_flags;     // 段标志位(SHF_*)
  Elf32_Addr sh_addr;      // 段的虚拟地址,前提是该段可被加载,否则为 0
  Elf32_Off  sh_offset;    // 段偏移,前提是该段存在于文件中,否则无意义
  Elf32_Word sh_size;      // 段的长度
  Elf32_Word sh_link;      // 段的链接信息
  Elf32_Word sh_info;      // 段的额外信息
  Elf32_Word sh_addralign; // 段地址对齐
  Elf32_Word sh_entsize;   // 项的长度
};

// c++解析节点信息
Elf32_Shdr* elf32Shdr = new Elf32_Shdr[elf32Ehdr->e_shnum];
Elf32_Shdr* elf32ShdrPointer = elf32Shdr;
ioFile.seekg(elf32Ehdr->e_shoff, ios::beg);
ioFile.read((char*) elf32ShdrPointer, sizeof(Elf32_Shdr) * elf32Ehdr->e_shnum);

// 字符数组解析
Elf32_Shdr* elf32Shdr = (Elf32_Shdr *) &fileContent[elf32Ehdr->e_shoff];

2.4 .shstrtab字符串表

字符串表保存了表名称等,格式是\0abc\0efg\0acxd\0,c++中字符串以\0结尾。上面解析的Section Header里面的name都是字符串表中的偏移,比如字符串表的起始位置是1000,Section Header name=5,那么该Section Header的名字偏移就是1000+5,即efg.

// c++文件解析,这里解析只是解析出了Section name的字符串,后面再修正吧,如果使用printf("%s\n", shstrtabSection)打印,会输出空白,因为起始字符就是\0,需要使用循环打印字符。
Elf32_Off shstrtabOffset = elf32Shdr[elf32Ehdr->e_shstrndx].sh_offset;
char* buffer= new char[1];
int strTableSize = 0;
ioFile.seekg(shstrtabOffset, ios::beg);
for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
    buffer[0] = '\0';
    do {
        ioFile.read(buffer, sizeof(char));
        strTableSize++;
    } while (buffer[0] != '\0');
}
shstrtabSection = new char[strTableSize];
ioFile.seekg(shstrtabOffset, ios::beg);
ioFile.read(shstrtabSection, sizeof(char) * strTableSize);

// 字符数组解析
Elf32_Off shstrtabOffset = elf32Shdr[elf32Ehdr->e_shstrndx].sh_offset;
char* shstrtabSection = &fileContent[shstrtabOffset];

2.5 其他Section

解析方法都类似

	Elf32_Hash elf32Hash; // .hash结构
	Elf32_Dyn* elf32Dyn; // .dynamic 动态节区

for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
    Elf32_Off offset = elf32Shdr[i].sh_offset;
    Elf32_Word size = elf32Shdr[i].sh_size;
    if (offset == 0 || size == 0) {
        continue;
    }
    if (elf32Shdr[i].sh_type == SHT_HASH && strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".hash") == 0) {
    	ioFile.seekg(offset, ios::beg);
    	ioFile.read((char*)&elf32Hash.nbucket, sizeof(Elf32_Word));
        ioFile.read((char*)&elf32Hash.nchain, sizeof(Elf32_Word));
        elf32Hash.bucketArr = new Elf32_Word[elf32Hash.nbucket];
        elf32Hash.chainArr = new Elf32_Word[elf32Hash.nchain];
        ioFile.read((char*) elf32Hash.bucketArr, sizeof(Elf32_Word) * elf32Hash.nbucket);
        ioFile.read((char*) elf32Hash.chainArr, sizeof(Elf32_Word) * elf32Hash.nchain);
    }
}

for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
    Elf32_Off offset = elf32Shdr[i].sh_offset;
    Elf32_Word size = elf32Shdr[i].sh_size;
    if (offset == 0 || size == 0) {
        continue;
    }
    if (elf32Shdr[i].sh_type == SHT_DYNAMIC &&
        strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".dynamic") == 0) {
        Elf32_Word count = size / elf32Shdr[i].sh_entsize;
        elf32Dyn = new Elf32_Dyn[count];
        ioFile.seekg(offset, ios::beg);
        ioFile.read((char *) elf32Dyn, size);
    }
}


3.命令行查看so结构

使用readelf命令可查看,具体使用方式可参考:readelf命令使用说明
我这里使用使用命令将信息全部读取并且存储到一个文件里面,方便后续解析时对比解析数据是否正确。

// 读取xxx.so的数据并且保存到当前目录info.txt文件里面,这样可以对比解析的正确性
realelf -a xxx.so > info.txt

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00  // 第五位1代表32位,2代表64位。
  Class:                             ELF32  // 32位ELF文件
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          52 (bytes into file)  // program headers起始偏移量是52
  Start of section headers:          12920 (bytes into file) // section headers起始偏移量是12920
  Flags:                             0x5000200, Version5 EABI, soft-float ABI
  Size of this header:               52 (bytes) // ELF Header的大小
  Size of program headers:           32 (bytes) // program headers的大小
  Number of program headers:         8 // program header的数量
  Size of section headers:           40 (bytes) // section header的大小
  Number of section headers:         27 // section header的数量
  Section header string table index: 26 // 解析出来的Section headers列表,列表里第26个就是这个字符串表


Program Headers: // 移动到Offset位置,解析FileSiz长度的数据,就是该Program Header的数据
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00000034 0x00000034 0x00100 0x00100 R   0x4
  LOAD           0x000000 0x00000000 0x00000000 0x02de7 0x02de7 R E 0x1000
  LOAD           0x002e08 0x00003e08 0x00003e08 0x00204 0x00209 RW  0x1000
  DYNAMIC        0x002e58 0x00003e58 0x00003e58 0x00110 0x00110 RW  0x4
  NOTE           0x000134 0x00000134 0x00000134 0x000bc 0x000bc R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  EXIDX          0x002630 0x00002630 0x00002630 0x001a0 0x001a0 R   0x4
  GNU_RELRO      0x002e08 0x00003e08 0x00003e08 0x001f8 0x001f8 RW  0x4

Section Headers: // 移动到Off位置,解析Size长度的数据,就是该Section Header
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .note.android.ide NOTE            00000134 000134 000098 00   A  0   0  4
  [ 2] .note.gnu.build-i NOTE            000001cc 0001cc 000024 00   A  0   0  4
  [ 3] .dynsym           DYNSYM          000001f0 0001f0 000350 10   A  4   1  4
  [ 4] .dynstr           STRTAB          00000540 000540 000373 00   A  0   0  1
  [ 5] .gnu.hash         GNU_HASH        000008b4 0008b4 00015c 04   A  3   0  4
  [ 6] .hash             HASH            00000a10 000a10 000170 04   A  3   0  4
  [ 7] .gnu.version      VERSYM          00000b80 000b80 00006a 02   A  3   0  2
  [ 8] .gnu.version_d    VERDEF          00000bec 000bec 00001c 00   A  4   1  4
  [ 9] .gnu.version_r    VERNEED         00000c08 000c08 000040 00   A  4   2  4
  [10] .rel.dyn          REL             00000c48 000c48 0000c8 08   A  3   0  4
  [11] .rel.plt          REL             00000d10 000d10 0000f0 08  AI  3  20  4
  [12] .plt              PROGBITS        00000e00 000e00 00017c 00  AX  0   0  4
  [13] .text             PROGBITS        00000f7c 000f7c 0016b4 00  AX  0   0  4
  [14] .ARM.exidx        ARM_EXIDX       00002630 002630 0001a0 08  AL 13   0  4
  [15] .ARM.extab        PROGBITS        000027d0 0027d0 000180 00   A  0   0  4
  [16] .rodata           PROGBITS        00002950 002950 000497 01 AMS  0   0  1
  [17] .fini_array       FINI_ARRAY      00003e08 002e08 000008 04  WA  0   0  4
  [18] .data.rel.ro      PROGBITS        00003e10 002e10 000048 00  WA  0   0  4
  [19] .dynamic          DYNAMIC         00003e58 002e58 000110 08  WA  4   0  4
  [20] .got              PROGBITS        00003f68 002f68 000098 00  WA  0   0  4
  [21] .data             PROGBITS        00004000 003000 00000c 00  WA  0   0  4
  [22] .bss              NOBITS          0000400c 00300c 000005 00  WA  0   0  4
  [23] .comment          PROGBITS        00000000 00300c 000109 01  MS  0   0  1
  [24] .note.gnu.gold-ve NOTE            00000000 003118 00001c 00      0   0  4
  [25] .ARM.attributes   ARM_ATTRIBUTES  00000000 003134 000034 00      0   0  1
  [26] .shstrtab         STRTAB          00000000 003168 00010f 00      0   0  1

4.c++解析ELF

4.1 解析方式

解析分两种

  1. 文件流
    不用全部加载到内存,并且可以随时修改文件内容,就是解析稍微麻烦。
  2. 字符串
    全部加载到内存中之后,只需要使用指针指向偏移的位置即可,解析简单。

后续要做so加密,所以使用的文件流读取的形式,可以修改so文件实现加密。然后so库加载之后再使用解析字符串的方式来解密,搭配使用。

4.2 解析逻辑

4.2.1 准备资源

elf.h
ELF文件的解析,需要使用到elf.h这个头文件,linux环境是自带了这个,但是windows没有,不过android sdk里面有,可以拷贝出来使用,路径是:AndroidSdk\ndk-bundle\sysroot\usr\include\linux\elf.h,将这个头文件拷贝到工程里面去,此时会提示Elf32_Half这几个未定义,在刚刚那个目录下寻找types.h,可以将这里面的拷贝到elf.h里面去。

gcc
加密逻辑使用c或者c++编写代码之后,再用gcc编译,这个Linux支持,windows需要安装软件,可自行百度。

Hello World!
先编译一个最简单的程序试试吧。

// linux
g++ xxx.cpp
./a.out

// windows
g++ xxx.cpp
a.exe

4.2.2 代码

创建解析的ElfParser.cpp文件(下面只贴32位的解析,64位的解析只需要替换32变成64即可)

#include <iostream>
#include "ElfParser.h" // 可先不管

using namespace std;

void parseSoByPath(const string& soPath) {
    printf("parseSoByPath soPath=%s\n", soPath.c_str());

    fstream ioFile;
    ioFile.open(soPath.c_str(), ios::in | ios::out); // 读写方式打开文件
    if (!ioFile) {
        printf("open %s failed!\n", soPath.c_str());
        return;
    }
    char* soPathArr = new char[strlen(soPath.c_str())];
    strcpy(soPathArr, soPath.c_str());
    if (isElf64(ioFile)) { // 判断是32位还是64位
        ELF64Struct elf64Struct;
        elf64Struct.fileName = soPathArr;
        elf64Struct.parseSo(ioFile);
        elf64Struct.encryptSo(ioFile);
//        elf64Struct.decryptSo(ioFile);
    } else {
        ELF32Struct elf32Struct;
        elf32Struct.fileName = soPathArr;
        elf32Struct.parseSo(ioFile);
        elf32Struct.encryptSo(ioFile);
//        elf32Struct.decryptSo(ioFile);
    }
    ioFile.close();

    /*fstream ioFile;
    ioFile.open(soPath.c_str(), ios::in | ios::out);
    if (!ioFile) {
        printf("open %s failed!\n", soPath.c_str());
        return;
    }
    ioFile.seekg(0, ios::end);
    long long int fileSize = ioFile.tellg();
    char* fileContent = new char[fileSize];
    ioFile.seekg(0, ios::beg);
    ioFile.read(fileContent, fileSize);
    ioFile.close();
    char* soPathArr = new char[strlen(soPath.c_str())];
    strcpy(soPathArr, soPath.c_str());
    if (isElf64(fileContent)) {
        ELF64Struct elf64Struct;
        elf64Struct.fileName = soPathArr;
        elf64Struct.parseSo(fileContent);
        elf64Struct.encryptSo(fileContent);
        elf64Struct.decryptSo(fileContent);
    } else {
        ELF32Struct elf32Struct;
        elf32Struct.fileName = soPathArr;
        elf32Struct.parseSo(fileContent);
//        elf32Struct.encryptSo(fileContent);
        elf32Struct.decryptSo(fileContent);
    }
    ioFile.close();*/
}

int main() {

	// 指定路径
    string filePath[] =  {
            "../resources/arm64-v8a/libDataEncryptionLib.so",
            "../resources/armeabi-v7a/libDataEncryptionLib.so",
            "../resources/x86/libDataEncryptionLib.so",
            "../resources/x86_64/libDataEncryptionLib.so"
    };
    for (int i = 0; i < sizeof(filePath) / sizeof(filePath[0]); ++i) {
        parseSoByPath(filePath[i]);
    }
    return 0;
}

ElfParser.h文件(不要复制,参考即可)


#include <string>
#include <fstream>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

#ifndef ANDROID_PROGRAM
#define ANDROID_PROGRAM

#define PAGE_SIZE 4096
#include "include/c_log.h"
#include "include/elf.h"

#else

#include "include/android_log.h"
#include <elf.h>

#endif

/**
 * 判断是否是64位的elf文件
 * @param ioFile
 * @return
 */
int isElf64(fstream& ioFile) {
    // 读取ELF头部信息,判断32位还是64位
    Elf64_Ehdr elf64Ehdr;
    ioFile.seekg(0, ios::beg);
    ioFile.read((char*)&elf64Ehdr, sizeof(Elf64_Ehdr));
    if (elf64Ehdr.e_ident[EI_CLASS] == 2) {
        return 1;
    }
    return 0;
}

/**
 * 判断是否是64位的elf文件
 * @param ioFile
 * @return
 */
int isElf64(char* fileContent) {
    // 读取ELF头部信息,判断32位还是64位
    Elf64_Ehdr* elf64Ehdr = (Elf64_Ehdr*) fileContent;
    if (elf64Ehdr->e_ident[EI_CLASS] == 2) {
        return 1;
    }
    return 0;
}

class ELF32Struct {
private:

public:
    char* fileName;
    void parseSo(fstream& ioFile);
    void encryptSo(fstream& ioFile);
    void decryptSo(fstream& ioFile);
    void encryptOrDecryptSo(fstream &ioFile, int isDecrypt);

    void parseSo(char* fileContent);
    void encryptSo(char* fileContent);
    void decryptSo(char* fileContent);
    void encryptOrDecryptSo(char* fileContent, int isDecrypt);

    void decryptSo(unsigned long long base);
};

class ELF64Struct {
private:

public:
    char* fileName;
    void parseSo(fstream& ioFile);
    void encryptSo(fstream& ioFile);
    void decryptSo(fstream& ioFile);
    void encryptOrDecryptSo(fstream &ioFile, int isDecrypt);

    void parseSo(char* fileContent);
    void encryptSo(char* fileContent);
    void decryptSo(char* fileContent);
    void encryptOrDecryptSo(char* fileContent, int isDecrypt);

    void decryptSo(unsigned long long base);
};

void ELF32Struct::parseSo(fstream &ioFile) {
    LOGD("\n--ELF32Struct start-------------------------------------------\n");
    LOGD("filePath=%s\n", fileName);

    Elf32_Ehdr* elf32Ehdr;  // ELF Header  文件头
    Elf32_Shdr* elf32Shdr; // Section Header list  节区头列表
    char* shstrtabSection;   // .shstrtab 字符串表
    char* dynstrSection; // .dynstr   符号字符串表
    Elf32_Sym* elf32Sym; // .dynsym  符号表
    Elf32_Word elf32SymSize;
    Elf32_Rel* elf32Rel; // .rel.dyn 重定位
    Elf32_Hash elf32Hash; // .hash结构
    Elf32_Dyn* elf32Dyn; // .dynamic 动态节区
    Elf32_Phdr* elf32Phdr;  // Segment/Program Header list,段/程序头列表

    // 读取ELF文件头部信息,判断是32位还是64位
    // 读取elf头
    elf32Ehdr = new Elf32_Ehdr[1];
    ioFile.seekg(0, ios::beg);
    ioFile.read((char*) elf32Ehdr, sizeof(Elf32_Ehdr));

    if (printELFHeader == 1) {
        // 输入ELF头部信息
        LOGD("\nELF Header:\n");
        LOGD("  %s    ", "Magic:"); // 输出魔术
        for (int i = 0; i < sizeof(elf32Ehdr->e_ident) / sizeof(elf32Ehdr->e_ident[0]); ++i) {
            LOGD("%2.2x ", elf32Ehdr->e_ident[i]);
        }
        LOGD("\n");
        LOGD("  %-38s%s\n", "Type:", tranElfHeaderType(elf32Ehdr->e_type).c_str());
        LOGD("  %-38s%d\n", "Machine:", elf32Ehdr->e_machine);
        LOGD("  %-38s%d\n", "Entry point address:", elf32Ehdr->e_entry);
        LOGD("  %-38s%d\n", "Start of program headers:", elf32Ehdr->e_phoff);
        LOGD("  %-38s%d\n", "Start of section headers:", elf32Ehdr->e_shoff);
        LOGD("  %-38s%d\n", "Size of this header:", elf32Ehdr->e_ehsize);
        LOGD("  %-38s%d\n", "Size of program headers:", elf32Ehdr->e_phentsize);
        LOGD("  %-38s%d\n", "Number of program headers:", elf32Ehdr->e_phnum);
        LOGD("  %-38s%d\n", "Size of section headers:", elf32Ehdr->e_shentsize);
        LOGD("  %-38s%d\n", "Number of section headers:", elf32Ehdr->e_shnum);
        LOGD("  %-38s%d\n", "Section header string table index:", elf32Ehdr->e_shstrndx);
    }


    // 解析Program Header,段信息
    elf32Phdr = new Elf32_Phdr [elf32Ehdr->e_phnum];
    ioFile.seekg(elf32Ehdr->e_phoff, ios::beg);
    ioFile.read((char*) elf32Phdr, sizeof(Elf32_Phdr) * elf32Ehdr->e_phnum);

    if (printProgramHeader == 1) {
        LOGD("\n\nProgram Headers:\n");
        LOGD("  %-14s %-8s %-10s %-10s %-7s %-7s %3s %6s\n", "Type", "Offset", "VirtAddr", "PhysAddr", "FileSiz", "MemSiz", "Flg", "Align");
        for (int i = 0; i < elf32Ehdr->e_phnum; ++i) {
            LOGD("  %-14s 0x%-6.6x 0x%-8.8x 0x%-8.8x 0x%-5.5x 0x%-5.5x %-3x %-6x\n",
                    tranProgramHeaderType(elf32Phdr[i].p_type).c_str(),
                    elf32Phdr[i].p_offset,
                    elf32Phdr[i].p_vaddr,
                    elf32Phdr[i].p_paddr,
                    elf32Phdr[i].p_filesz,
                    elf32Phdr[i].p_memsz,
                    elf32Phdr[i].p_flags,
                    elf32Phdr[i].p_align
                    );
        }
    }


    // 解析Section,节点信息
    elf32Shdr = new Elf32_Shdr[elf32Ehdr->e_shnum];
    Elf32_Shdr* elf32ShdrPointer = elf32Shdr;
    ioFile.seekg(elf32Ehdr->e_shoff, ios::beg);
    ioFile.read((char*) elf32ShdrPointer, sizeof(Elf32_Shdr) * elf32Ehdr->e_shnum);

    // string table偏移量
    Elf32_Off shstrtabOffset = elf32Shdr[elf32Ehdr->e_shstrndx].sh_offset;
    char* buffer= new char[1];
    int strTableSize = 0;
    ioFile.seekg(shstrtabOffset, ios::beg);
    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        buffer[0] = '\0';
        do {
            ioFile.read(buffer, sizeof(char));
            strTableSize++;
        } while (buffer[0] != '\0');
    }
    shstrtabSection = new char[strTableSize];
    ioFile.seekg(shstrtabOffset, ios::beg);
    ioFile.read(shstrtabSection, sizeof(char) * strTableSize);

    if (printSectionHeader == 1) {
        LOGD("\n\nSection Headers:\n");
        // 输出Section头部信息
        LOGD("  %s%-26s%-16s%-9s%-7s%-7s%-3s%-4s%-4s%-4s%-3s\n", "[Nr]", "Name", "Type", "Addr", "Off", "Size", "ES", "Flg", "lk", "Inf", "Al");
        for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
            LOGD("  [%2d]%-25s %-15s %-8.8x %-6.6x %-6.6x %-2.2x %3s %2x %4x %2x \n",
                    i,
                    shstrtabSection + elf32Shdr[i].sh_name,
                    tranSectionHeaderType(elf32Shdr[i].sh_type).c_str(),
                    elf32Shdr[i].sh_addr,
                    elf32Shdr[i].sh_offset,
                    elf32Shdr[i].sh_size,
                    elf32Shdr[i].sh_entsize,
                    tranSectionHeaderFlagType(elf32Shdr[i].sh_flags).c_str(),
                    elf32Shdr[i].sh_link,
                    elf32Shdr[i].sh_info,
                    elf32Shdr[i].sh_addralign
                 );
        }
    }


    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        // 读取Section .dynstr 动态链接的字符串
        if (elf32Shdr[i].sh_type == SHT_STRTAB &&
            strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".dynstr") == 0) {
            dynstrSection = new char[size];
            ioFile.seekg(offset, ios::beg);
            ioFile.read(dynstrSection, sizeof(char) * size);
            if (printDynstr == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                for (int j = 0; j < size; ++j) {
                    if (j == 0) {
                        LOGD("%s\n", dynstrSection);
                    }
                    if (dynstrSection[j] == 0 && j != size - 1) {
                        LOGD("%s\n", dynstrSection + (j + 1));
                    }
                }
            }
            break;
        }
    }

    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        if (elf32Shdr[i].sh_type == SHT_DYNSYM &&
            strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".dynsym") == 0) {
            elf32SymSize = size / elf32Shdr[i].sh_entsize;
            elf32Sym = new Elf32_Sym[elf32SymSize];
            ioFile.seekg(offset, ios::beg);
            ioFile.read((char *) elf32Sym, sizeof(Elf32_Sym) * elf32SymSize);
            if (printDynsym == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                LOGD("   %-4s %-8s %-5s %7s %6s %8s %-4s %s\n", "Num:", "Value", "Size", "Type",
                       "Bind", "Vis", "Ndx", "Name");
                for (int j = 0; j < elf32SymSize; ++j) {
                    LOGD("   %-3d: ", j);
                    LOGD("%-8.8x ", elf32Sym[j].st_value);
                    LOGD("%-5d ", elf32Sym[j].st_size);
                    LOGD("%7s ", "");
                    LOGD("%6s ", "");
                    LOGD("%8s ", "");
                    LOGD("%-4x ", elf32Sym[j].st_shndx);
                    LOGD("%s", dynstrSection + elf32Sym[j].st_name);
                    LOGD("\n");
                }
            }
            break;
        }
    }

    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        if (elf32Shdr[i].sh_type == SHT_HASH && strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".hash") == 0) {
            ioFile.seekg(offset, ios::beg);
            ioFile.read((char*)&elf32Hash.nbucket, sizeof(Elf32_Word));
            ioFile.read((char*)&elf32Hash.nchain, sizeof(Elf32_Word));
            elf32Hash.bucketArr = new Elf32_Word[elf32Hash.nbucket];
            elf32Hash.chainArr = new Elf32_Word[elf32Hash.nchain];
            ioFile.read((char*) elf32Hash.bucketArr, sizeof(Elf32_Word) * elf32Hash.nbucket);
            ioFile.read((char*) elf32Hash.chainArr, sizeof(Elf32_Word) * elf32Hash.nchain);
            if (printHash == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                LOGD("  nbucket=%d:\n", elf32Hash.nbucket);
                LOGD("  [%2s] %8.8s\n", "Nr", "value");
                for (Elf32_Word j = 0; j < elf32Hash.nbucket; ++j) {
                    LOGD("  [%2d] ", j);
                    LOGD(" %8.8d", elf32Hash.bucketArr[j]);
                    LOGD("\n");
                }
                LOGD("  nchain=%d:\n", elf32Hash.nchain);
                LOGD("  [%2s] %8.8s\n", "Nr", "value");
                for (Elf32_Word j = 0; j < elf32Hash.nchain; ++j) {
                    LOGD("  [%2d] ", i);
                    LOGD(" %8.8d", elf32Hash.chainArr[j]);
                    LOGD("\n");
                }
            }

            break;
        }
    }

    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        if (elf32Shdr[i].sh_type == SHT_REL &&
            strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".rel.dyn") == 0) {
            int count = size / elf32Shdr[i].sh_entsize;
            elf32Rel = new Elf32_Rel[count];
            ioFile.seekg(offset, ios::beg);
            ioFile.read((char *) elf32Rel, sizeof(Elf32_Rel) * count);
            if (printRelDyn == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                LOGD("%8s  %8s %14s %10s %s\n", "Offset", "Info", "Type", "Sym.Value", "Sym.Name");
                for (int j = 0; j < count; ++j) {
                    LOGD("%-8.8x: ", elf32Rel[j].r_offset);
                    LOGD("%-8.8x ", elf32Rel[j].r_info);
                    LOGD("%14s ", "");
                    LOGD("%10s ", "");
                    LOGD("%s", "");
                    LOGD("\n");
                }
            }
        }
    }

    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        if (elf32Shdr[i].sh_type == SHT_DYNAMIC &&
            strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".dynamic") == 0) {
            Elf32_Word count = size / elf32Shdr[i].sh_entsize;
            elf32Dyn = new Elf32_Dyn[count];
            ioFile.seekg(offset, ios::beg);
            ioFile.read((char *) elf32Dyn, size);
            if (printDynamic == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                LOGD(" %-10s  %-28s %s\n", "Tag", "Type", "Name/Value");
                for (int j = 0; j < count; ++j) {
                    LOGD(" 0x%-8.8x ", elf32Dyn[j].d_tag);
                    LOGD("%-28s ", "");
                    LOGD("%s", "");
                    LOGD("\n");
                }
            }

        }
    }

    LOGD("\n");
    LOGD("\n--ELF32Struct end---------------------------------------------\n");
}

void ELF32Struct::parseSo(char* fileContent) {
    LOGD("\n--ELF32Struct start-------------------------------------------\n");
    LOGD("filePath=%s\n", fileName);

    // 读取ELF文件头部信息,判断是32位还是64位
    // 读取elf头
    Elf32_Ehdr* elf32Ehdr = (Elf32_Ehdr*) fileContent;

    if (printELFHeader == 1) {
        // 输入ELF头部信息
        LOGD("\nELF Header:\n");
        LOGD("  %s    ", "Magic:"); // 输出魔术
        for (int i = 0; i < sizeof(elf32Ehdr->e_ident) / sizeof(elf32Ehdr->e_ident[0]); ++i) {
            LOGD("%2.2x ", elf32Ehdr->e_ident[i]);
        }
        LOGD("\n");
        LOGD("  %-38s%s\n", "Type:", tranElfHeaderType(elf32Ehdr->e_type).c_str());
        LOGD("  %-38s%d\n", "Machine:", elf32Ehdr->e_machine);
        LOGD("  %-38s%d\n", "Entry point address:", elf32Ehdr->e_entry);
        LOGD("  %-38s%d\n", "Start of program headers:", elf32Ehdr->e_phoff);
        LOGD("  %-38s%d\n", "Start of section headers:", elf32Ehdr->e_shoff);
        LOGD("  %-38s%d\n", "Size of this header:", elf32Ehdr->e_ehsize);
        LOGD("  %-38s%d\n", "Size of program headers:", elf32Ehdr->e_phentsize);
        LOGD("  %-38s%d\n", "Number of program headers:", elf32Ehdr->e_phnum);
        LOGD("  %-38s%d\n", "Size of section headers:", elf32Ehdr->e_shentsize);
        LOGD("  %-38s%d\n", "Number of section headers:", elf32Ehdr->e_shnum);
        LOGD("  %-38s%d\n", "Section header string table index:", elf32Ehdr->e_shstrndx);
    }

    // 解析Program Header,段信息
    Elf32_Phdr* elf32Phdr = (Elf32_Phdr*) &fileContent[elf32Ehdr->e_phoff];
    if (printProgramHeader == 1) {
        LOGD("\n\nProgram Headers:\n");
        LOGD("  %-14s %-8s %-10s %-10s %-7s %-7s %3s %6s\n", "Type", "Offset", "VirtAddr", "PhysAddr", "FileSiz", "MemSiz", "Flg", "Align");
        for (int i = 0; i < elf32Ehdr->e_phnum; ++i) {
            LOGD("  ");
            LOGD("%-14x ", elf32Phdr[i].p_type/*tranProgramHeaderType(elf32Phdr[i].p_type).c_str()*/);
            LOGD("0x%-6.6x ", elf32Phdr[i].p_offset);
            LOGD("0x%-8.8x ", elf32Phdr[i].p_vaddr);
            LOGD("0x%-8.8x ", elf32Phdr[i].p_paddr);
            LOGD("0x%-5.5x ", elf32Phdr[i].p_filesz);
            LOGD("0x%-5.5x ", elf32Phdr[i].p_memsz);
            LOGD("%-3x ", elf32Phdr[i].p_flags);
            LOGD("%-6x", elf32Phdr[i].p_align);
            LOGD("\n");
        }
    }

    // 解析Section,节点信息
    Elf32_Shdr* elf32Shdr = (Elf32_Shdr *) &fileContent[elf32Ehdr->e_shoff];

    // string table偏移量
    Elf32_Off shstrtabOffset = elf32Shdr[elf32Ehdr->e_shstrndx].sh_offset;
    char* shstrtabSection = &fileContent[shstrtabOffset];

    if (printSectionHeader == 1) {
        LOGD("\n\nSection Headers:\n");
        // 输出Section头部信息
        LOGD("  %s%-26s%-16s%-9s%-7s%-7s%-3s%-4s%-4s%-4s%-3s\n", "[Nr]", "Name", "Type", "Addr", "Off", "Size", "ES", "Flg", "lk", "Inf", "Al");
        for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
            LOGD("  [%2d]", i);
            LOGD("%-25x ", elf32Shdr[i].sh_name);
            LOGD("%-15s ", tranSectionHeaderType(elf32Shdr[i].sh_type).c_str());
            LOGD("%-8.8x ", elf32Shdr[i].sh_addr);
            LOGD("%-6.6x ", elf32Shdr[i].sh_offset);
            LOGD("%-6.6x ", elf32Shdr[i].sh_size);
            LOGD("%-2.2x ", elf32Shdr[i].sh_entsize);
            LOGD("%3s ", tranSectionHeaderFlagType(elf32Shdr[i].sh_flags).c_str());
            LOGD("%2x ", elf32Shdr[i].sh_link);
            LOGD("%4x ", elf32Shdr[i].sh_info);
            LOGD("%2x ", elf32Shdr[i].sh_addralign);
            LOGD("\n");
        }
    }
    char* dynstrSection;
    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        // 读取Section .dynstr 动态链接的字符串
        if (elf32Shdr[i].sh_type == SHT_STRTAB &&
            strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".dynstr") == 0) {
            dynstrSection = &fileContent[offset];
            if (printDynstr == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                for (int j = 0; j < size; ++j) {
                    if (j == 0) {
                        LOGD("%s\n", dynstrSection);
                    }
                    if (dynstrSection[j] == 0 && j != size - 1) {
                        LOGD("%s\n", dynstrSection + (j + 1));
                    }
                }
            }
            break;
        }
    }

    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        if (elf32Shdr[i].sh_type == SHT_DYNSYM &&
            strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".dynsym") == 0) {
            Elf32_Word elf32SymSize = size / elf32Shdr[i].sh_entsize;
            Elf32_Sym* elf32Sym = (Elf32_Sym*) &fileContent[offset];
            if (printDynsym == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                LOGD("   %-4s %-8s %-5s %7s %6s %8s %-4s %s\n", "Num:", "Value", "Size", "Type",
                       "Bind", "Vis", "Ndx", "Name");
                for (int j = 0; j < elf32SymSize; ++j) {
                    LOGD("   %-3d: ", j);
                    LOGD("%-8.8x ", elf32Sym[j].st_value);
                    LOGD("%-5d ", elf32Sym[j].st_size);
                    LOGD("%7s ", "");
                    LOGD("%6s ", "");
                    LOGD("%8s ", "");
                    LOGD("%-4x ", elf32Sym[j].st_shndx);
                    LOGD("%s", dynstrSection + elf32Sym[j].st_name);
                    LOGD("\n");
                }
            }
            break;
        }
    }

    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        if (elf32Shdr[i].sh_type == SHT_HASH && strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".hash") == 0) {
            Elf32_Hash elf32Hash;
            elf32Hash.nbucket = *(Elf32_Word*) &fileContent[offset];
            elf32Hash.nchain = *(Elf32_Word*) &fileContent[offset + sizeof(Elf32_Word)];
            LOGD("  nbucket=%d:\n", elf32Hash.nbucket);
            LOGD("  nchain=%d:\n", elf32Hash.nchain);
            elf32Hash.bucketArr = (Elf32_Word *) &fileContent[offset + sizeof(Elf32_Word) * 2];
            elf32Hash.chainArr = (Elf32_Word *) &fileContent[offset + sizeof(Elf32_Word) * (2 + elf32Hash.nbucket)];
            if (printHash == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                LOGD("  nbucket=%d:\n", elf32Hash.nbucket);
                LOGD("  [%2s] %8.8s\n", "Nr", "value");
                for (Elf32_Word j = 0; j < elf32Hash.nbucket; ++j) {
                    LOGD("  [%2d] ", j);
                    LOGD(" %8.8d", elf32Hash.bucketArr[j]);
                    LOGD("\n");
                }
                LOGD("  nchain=%d:\n", elf32Hash.nchain);
                LOGD("  [%2s] %8.8s\n", "Nr", "value");
                for (Elf32_Word j = 0; j < elf32Hash.nchain; ++j) {
                    LOGD("  [%2d] ", j);
                    LOGD(" %8.8d", elf32Hash.chainArr[j]);
                    LOGD("\n");
                }
            }
            break;
        }
    }

    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        if (elf32Shdr[i].sh_type == SHT_REL &&
            strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".rel.dyn") == 0) {
            int count = size / elf32Shdr[i].sh_entsize;
            Elf32_Rel* elf32Rel = (Elf32_Rel*) &fileContent[offset];
            if (printRelDyn == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                LOGD("%8s  %8s %14s %10s %s\n", "Offset", "Info", "Type", "Sym.Value", "Sym.Name");
                for (int j = 0; j < count; ++j) {
                    LOGD("%-8.8x: ", elf32Rel[j].r_offset);
                    LOGD("%-8.8x ", elf32Rel[j].r_info);
                    LOGD("%14s ", "");
                    LOGD("%10s ", "");
                    LOGD("%s", "");
                    LOGD("\n");
                }
            }
            break;
        }
    }

    for (int i = 0; i < elf32Ehdr->e_shnum; ++i) {
        Elf32_Off offset = elf32Shdr[i].sh_offset;
        Elf32_Word size = elf32Shdr[i].sh_size;
        if (offset == 0 || size == 0) {
            continue;
        }
        if (elf32Shdr[i].sh_type == SHT_DYNAMIC &&
            strcmp(shstrtabSection + elf32Shdr[i].sh_name, ".dynamic") == 0) {
            Elf32_Word count = size / elf32Shdr[i].sh_entsize;
            Elf32_Dyn* elf32Dyn = (Elf32_Dyn*) &fileContent[offset];
            if (printDynamic == 1) {
                LOGD("\n\n%s:\n", shstrtabSection + elf32Shdr[i].sh_name);
                LOGD(" %-10s  %-28s %s\n", "Tag", "Type", "Name/Value");
                for (int j = 0; j < count; ++j) {
                    LOGD(" 0x%-8.8x ", elf32Dyn[j].d_tag);
                    LOGD("%-28s ", "");
                    LOGD("%s", "");
                    LOGD("\n");
                }
            }

        }
    }

    LOGD("\n");
    LOGD("\n--ELF32Struct end---------------------------------------------\n");
}

值得一提的是日志的输出,因为ElfParse.h这个头文件,我是本地运行和JNI共用的,我想实现在本地gcc编译时使用printf输出日志,在app jni调用是使用Log输出,所以定义了一个c_log.h,在本地编译时引入c_log.h,APP调用是引入android_log.h(见上一篇文章)。
c_log.h --封装了printf,适配了LOG

#ifndef SOLIBENCRYPTION_C_LOG_H
#define SOLIBENCRYPTION_C_LOG_H

#include <stdarg.h>
void LOG(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

void LOGV(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}
void LOGD(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}
void LOGI(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}
void LOGW(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}
void LOGE(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}
void LOGF(const char* format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

#endif //SOLIBENCRYPTION_C_LOG_H

在ElfParser.h中

#ifndef ANDROID_PROGRAM
#define ANDROID_PROGRAM

#define PAGE_SIZE 4096
#include "include/c_log.h"
#include "include/elf.h"

#else

#include "include/android_log.h"
#include <elf.h>

#endif

ANDROID_PROGRAM这个变量,我在jni编译的SoDecrypt.cpp中定义了,然后加载ElfParse.h是判断这个变量已定义,就代表是jni环境,就去加载android_log.h。我本地编译的ElfParse.cpp里面没定义这个变量,这样就是去加载c_log.h了。

5.小结

  1. 前期需要准备NDK开发环境,可参考我前两篇文章,c/c++基础语法。
  2. 了解ELF大致结构,查看【ELF文件格式分析】,对比结构图详细了解下结构字段的含义。
  3. 使用命令readelf,保存到文件中,之后解析时可对比参考解析是否正确。
  4. 依次解析ELF Header, Program Headers, Section Headers,其他节点。

本文只作参考,复制粘贴代码容易忘,而且理解不深,只有碰到问题去解决了才能加深理解。

6.参考文献

Android逆向之旅---SO(ELF)文件格式详解
Android so(ELF) 文件解析