so 文件是啥?so 文件是 elf 文件,elf 文件后缀名是.so,所以也被称之为so 文件, elf 文件是 linux 底下二进制文件,可以理解为 windows 下的PE文件,在 Android 中可以比作dll,方便函数的移植,在常用于保护 Android 软件,增加逆向难度。

解析 elf 文件有啥子用?最明显的两个用处就是:1、so 加固;2、用于 frida(xposed) 的检测!

本文使用 c 语言,编译器为 vscode。如有错误,还请斧正!!!

一、SO 文件整体格式

so 文件大体上可分为四部分,一般来说从上往下是ELF头部->Pargarm头部->节区(Section)->节区头,其中,除了ELF头部在文件位置固定不变外,其余三部分的位置都不固定。整体结构图可以参考非虫大佬的那张图,图片如下:

解析语言之所以选择 c 语言,有两个原因:

1、做 so 加固的时候可以需要用到,这里就干脆用 c 写成一个模板,哪里需要就哪里改,不像上次解析 dex 文件的时候用 python 写,结果后面写指令还原的时候需要用的时候在写一遍 c 版本代价太大了;

2、在安卓源码中,有个elf.h文件,这个文件定义了我们解析时需要用到的所有数据结构,并且给出了参考注释,是很好的参考资料。elf.h文件路径如下:

二、解析 ELF 头部

ELF 头部数据格式在 elf.h 文件中已经给出,如下图所示:

每个字段解释如下:

1、e_ident 数组:前4个字节为.ELF,是 elf 标志头,第 5 个字节为该文件标志符,为 1 代表这是一个 32 位的 elf 文件,后面几个字节代表版本等信息。

2、e_type 字段:表示是可执行文件还是链接文件等,安卓上的 so 文件就是分享文件,一般该字段为 3,详细请看下图。

3、e_machine 字段:该字段标志该文件运行在什么机器架构上,例如 ARM。

4、e_version 字段:该字段表示当前 so 文件的版本信息,一般为 1

5、e_entry 字段:该字段是一个偏移地址,为程序启动的地址。

6、e_phoff 字段:该字段也是一个偏移地址,指向程序头 (Pargram Header) 的起始地址。

7、e_shoff 字段:该字段是一个偏移地址,指向节区头 (Section Header) 的起始地址。

8、e_flags 字段:该字段表示该文件的权限,常见的值有 1、2、4,分别代表 read、write、exec。

9、e_ehsize 字段:该字段表示 elf 文件头部大小,一般固定为 52.

10、e_phentsize 字段:该字段表示程序头 (Program Header) 大小,一般固定为 32.

11、e_phnum 字段:该字段表示文件中有几个程序头。

12、e_shentsize: 该字段表示节区头 (Section Header) 大小,一般固定为 40.

13、e_shnum 字段:该字段表示文件中有几个节区头。

14、e_shstrndx 字段:该字段是一个数字,这个表明了.shstrtab 节区(这个节区存储着所有节区的名字,例如.text)的节区头是第几个。

e_type具体值(相关值后面有英文注释,这里就不再添加中文注释了):

解析代码如下:

struct DataOffest parseSoHeader(FILE *fp,struct DataOffest off){    Elf32_Ehdr header;    int i = 0;    fseek(fp,0,SEEK_SET);    fread(&header,1,sizeof(header),fp);    printf("ELF Header:\n");    printf("    Header Magic: ");    for (i = 0; i < 16; i++)    {        printf("%02x ",header.e_ident[i]);    }    printf("\n");    printf("    So File Type: 0x%02x",header.e_type);    switch (header.e_type)    {    case 0x00:        printf("(No file type)\n");        break;    case 0x01:        printf("(Relocatable file)\n");        break;    case 0x02:        printf("(Executable file)\n");        break;    case 0x03:        printf("(Shared object file)\n");        break;    case 0x04:        printf("(Core file)\n");        break;    case 0xff00:        printf("(Beginning of processor-specific codes)\n");        break;    case 0xffff:        printf("(Processor-specific)\n");        break;    default:        printf("\n");        break;    }    printf("    Required Architecture: 0x%04x",header.e_machine);    if (header.e_machine == 0x28)    {        printf("(ARM)\n");    }    else    {        printf("\n");    }    printf("    Version: 0x%02x\n",header.e_version);    printf("    Start Program Address: 0x%08x\n",header.e_entry);    printf("    Program Header Offest: 0x%08x\n",header.e_phoff);    off.programheadoffset = header.e_phoff;    printf("    Section Header Offest: 0x%08x\n",header.e_shoff);    off.sectionheadoffest = header.e_shoff;    printf("    Processor-specific Flags: 0x%08x\n",header.e_flags);    printf("    ELF Header Size: 0x%04x\n",header.e_ehsize);    printf("    Size of an entry in the program header table: 0x%04x\n",header.e_phentsize);    printf("    Program Header Size: 0x%04x\n",header.e_phnum);    off.programsize = header.e_phnum;    printf("    Size of an entry in the section header table: 0x%04x\n",header.e_shentsize);    printf("    Section Header Size: 0x%04x\n",header.e_shnum);    off.sectionsize = header.e_shnum;    printf("    String Section Index: 0x%04x\n",header.e_shstrndx);    off.shstrtabindex = header.e_shstrndx;    return off;}

三、程序头(Program Header)解析

程序头在elf.h文件中的数据格式是Elf32_Phdr,如下图所示:

每个字段解释如下:

1、p_type 字段:该字段表明了段 (Segment) 类型,例如PT_LOAD类型,具体值看下图,实在有点多,没办法这里写完。

2、p_offest 字段:该字段表明了这个段在该 so 文件的起始地址。

3、p_vaddr 字段:该字段指明了加载进内存后的虚拟地址,我们静态解析时用不到该字段。

4、p_paddr 字段:该字段指明加载进内存后的实际物理地址,跟上面的那个字段一样,解析时用不到。

5、p_filesz 字段:该字段表明了这个段的大小,单位为字节。

6、p_memsz 字段:该字段表明了这个段加载到内存后使用的字节数。

7、p_flags 字段:该字段跟 elf 头部的 e_flags 一样,指明了该段的属性,是可读还是可写。

8、p_align 字段:该字段用来指明在内存中对齐字节数的。

p_type字段具体取值:

解析代码:

struct DataOffest parseSoPargramHeader(FILE *fp,struct DataOffest off){    Elf32_Half init;    Elf32_Half addr;    int i;    Elf32_Phdr programHeader;        init = off.programheadoffset;    for (i = 0; i < off.programsize; i++)    {        addr = init + (i * 0x20);        fseek(fp,addr,SEEK_SET);        fread(&programHeader,1,32,fp);        switch (programHeader.p_type)        {        case 2:            off.dynameicoff = programHeader.p_offset;            off.dynameicsize = programHeader.p_filesz;            break;        default:            break;        }        printf("\n\nSegment Header %d:\n",(i + 1));        printf("    Type of segment: 0x%08x\n",programHeader.p_type);        printf("    Segment Offset: 0x%08x\n",programHeader.p_offset);        printf("    Virtual address of beginning of segment: 0x%08x\n",programHeader.p_vaddr);        printf("    Physical address of beginning of segment: 0x%08x\n",programHeader.p_paddr);        printf("    Num. of bytes in file image of segment: 0x%08x\n",programHeader.p_filesz);        printf("    Num. of bytes in mem image of segment (may be zero): 0x%08x\n",programHeader.p_memsz);        printf("    Segment flags: 0x%08x\n",programHeader.p_flags);        printf("    Segment alignment constraint: 0x%08x\n",programHeader.p_align);    }    return off;}

四、节区头(Section Header)解析

节区头在 elf.h 文件中的数据结构为Elf32_Shdr,如下图所示:

每个字段解释如下:

1、sh_name 字段:该字段是一个索引值,是.shstrtab表(节区名字字符串表)的索引,指明了该节区的名字。

2、sh_type 字段:该字段表明该节区的类型,例如值为SHT_PROGBITS,则该节区可能是.text或者.rodata,至于具体怎么区分,当然看 sh_name 字段。具体取值看下图。

3、sh_flags 字段:跟上面的一样,就不再细说了。

4、sh_addr 字段:该字段是一个地址,是该节区加载进内存后的地址。

5、sh_offset 字段:该字段也是一个地址,是该节区在该 so 文件中的偏移地址。

6、sh_size 字段:该字段表明了该节区的大小,单位是字节。

7、sh_link 和 sh_info 字段:这两个字段只适用于少数节区,我们这里解析用不到,感兴趣的可以去看官方文档。

8、sh_addralign 字段:该字段指明在内存中的对齐字节。

9、sh_entsize 字段:该字段指明了该节区中每个项占用的字节数。

sh_type取值:

解析代码:

struct DataOffest parseSoSectionHeader(FILE *fp,struct DataOffest off,struct ShstrtabTable StrList[100]){    Elf32_Half init;    Elf32_Half addr;    Elf32_Shdr sectionHeader;    int i,id,n;    char ch;    int k = 0;    init = off.sectionheadoffest;    for (i = 0; i < off.sectionsize; i++)    {        addr = init + (i * 0x28);        fseek(fp,addr,SEEK_SET);        fread(&sectionHeader,1,40,fp);         switch (sectionHeader.sh_type)        {        case 2:            off.symtaboff = sectionHeader.sh_offset;            off.symtabsize = sectionHeader.sh_size;            break;        case 3:            if(k == 0)            {                off.stroffset = sectionHeader.sh_offset;                off.strsize = sectionHeader.sh_size;                k++;            }            else if (k == 1)            {                off.str1offset = sectionHeader.sh_offset;                off.str1size = sectionHeader.sh_size;                k++;            }            else            {                off.str2offset = sectionHeader.sh_offset;                off.str2size = sectionHeader.sh_size;                k++;            }            break;        default:            break;        }        id = sectionHeader.sh_name;        printf("\n\nSection Header %d\n",(i + 1));        printf("    Section Name: ");        for (n = 0; n < 50; n++)        {            ch = StrList[id].str[n];            if (ch == 0)            {                printf("\n");                break;            }            else            {                printf("%c",ch);            }        }        printf("    Section Type: 0x%08x\n",sectionHeader.sh_type);        printf("    Section Flag: 0x%08x\n",sectionHeader.sh_flags);        printf("    Address where section is to be loaded: 0x%08x\n",sectionHeader.sh_addr);        printf("    Offset: 0x%x\n",sectionHeader.sh_offset);        printf("    Size of section, in bytes: 0x%08x\n",sectionHeader.sh_size);        printf("    Section type-specific header table index link: 0x%08x\n",sectionHeader.sh_link);        printf("    Section type-specific extra information: 0x%08x\n",sectionHeader.sh_info);        printf("    Section address alignment: 0x%08x\n",sectionHeader.sh_addralign);        printf("    Size of records contained within the section: 0x%08x\n",sectionHeader.sh_entsize);    }    return off;}

五、字符串节区解析

PS: 从这里开始网上的参考资料很少了,特别是参考代码,所以有错误的地方还请斧正;因为以后的so加固等只涉及到几个节区,所以只解析了.shstrtab.strtab.dynstr.text.symtab.dynamic节区!!!

在elf头部中有个e_shstrndx字段,该字段指明了.shstrtab节区头部是文件中第几个节区头部,我们可以根据这找到.shstrtab节区的偏移地址,然后读取出来,就可以为每个节区名字赋值了,然后就可以顺着锁定剩下的两个字符串节区。

在 elf 文件中,字符串表示方式如下:字符串的头部和尾部用标示字节00标志,同时上一个字符串尾部标识符00作为下一个字符串头部标识符。例如我有两个紧邻的字符串分别是ab,那么他们在 elf 文件中 16 进制为00 97 00 98 00

解析代码如下 (PS:因为编码问题,第一次打印字符串表没问题,但填充进 sh_name 就乱码,所以这里只放上解析.shstrtab的代码,但剩下两个节区节区代码一样):

void parseStrSection(FILE *fp,struct DataOffest off,int flag){    int total = 0;    int i;    int ch;    int mark;    Elf32_Off init;    Elf32_Off addr;    Elf32_Word count;    mark = 1;    if (flag == 1)    {        count = off.strsize;        init = off.stroffset;    }    else if (flag == 2)    {        count = off.str1size;        init = off.str1offset;    }    else    {        count = off.str2size;        init = off.str2offset;    }         printf("String Address==>0x%x\n",init);    printf("String List %d:\n\t[1]==>",flag);    for (i = 0; i < count; i++)    {        addr = init + (i * 1);        fseek(fp,addr,SEEK_SET);        fread(&ch,1,1,fp);        if (i == 0 && ch == 0)        {            continue;        }        else if (ch != 0)        {            printf("%c",ch);        }        else if (ch == 0 && i !=0)        {            printf("\n\t[%d]==>",(++mark));        }    }    printf("\n");    }

六、.dynamic解析

.dynamicelf.h文件中的数据结构是Elf32-Dyn,如下图所示:

第一个字段表明了类型,占 4 个字节;第二个字段是一个共用体,也占四个字节,描述了具体的项信息。解析代码如下:

void parseSoDynamicSection(FILE *fp,struct DataOffest off){    int dynamicnum;    Elf32_Off init;    Elf32_Off addr;    Elf32_Dyn dynamicData;    int i;    init = off.dynameicoff;    dynamicnum = (off.dynameicsize / 8);    printf("Dynamic:\n");    printf("\t\tTag\t\t\tType\t\t\tName/Value\n");    for (i = 0; i < dynamicnum; i++)    {        addr = init + (i * 8);        fseek(fp,addr,SEEK_SET);        fread(&dynamicData,1,8,fp);        printf("\t\t0x%08x\t\tNOPRINTF\t\t0x%x\n",dynamicData.d_tag,dynamicData.d_un);    }    }

七、.symtab 解析

该节区是该 so 文件的符号表,它在elf.h文件中的数据结构是Elf32_Sym,如下所示:

每个字段解释如下:

1、st_name 字段:该字段是一个索引值,指明了该项的名字。

2、st_value 字段:该字段表明了相关联符号的取值。

3、stz-size 字段:该字段指明了每个项所占用的字节数。

4、st_info 和 st_other 字段:这两个字段指明了符号的类型。

5、st_shndx 字段:相关索引。

解析代码如下(PS:由于乱码问题,索引手动固定了地址测试,有兴趣的挨个解析字符应该可以解决乱码问题):

void parseSoDynamicSection(FILE *fp,struct DataOffest off){    int dynamicnum;    Elf32_Off init;    Elf32_Off addr;    Elf32_Dyn dynamicData;    int i;    init = off.dynameicoff;    dynamicnum = (off.dynameicsize / 8);    printf("Dynamic:\n");    printf("\t\tTag\t\t\tType\t\t\tName/Value\n");    for (i = 0; i < dynamicnum; i++)    {        addr = init + (i * 8);        fseek(fp,addr,SEEK_SET);        fread(&dynamicData,1,8,fp);        printf("\t\t0x%08x\t\tNOPRINTF\t\t0x%x\n",dynamicData.d_tag,dynamicData.d_un);    }}
void parseSymtabSection(FILE *fp,struct DataOffest off){    Elf32_Off init;    Elf32_Off addr;    Elf32_Word count;    Elf32_Sym symtabSection;    int k,i;    init = off.symtaboff;    count = off.symtabsize;    printf("SymTable:\n");    for (i = 0; i < count; i++)    {        addr = init + (i * 16);        fseek(fp,addr,SEEK_SET);        fread(&symtabSection,1,16,fp);        printf("Symbol Name Index: 0x%x\n",symtabSection.st_name);        printf("Value or address associated with the symbol: 0x%08x\n",symtabSection.st_value);        printf("Size of the symbol: 0x%x\n",symtabSection.st_size);        printf("Symbol's type and binding attributes: %c\n",symtabSection.st_info);        printf("Must be zero; reserved: 0x%x\n",symtabSection.st_other);        printf("Which section (header table index) it's defined in: 0x%x\n",symtabSection.st_shndx);    }    }

八、.text 解析

PS:这部分没代码了,只简单解析一下,因为解析 arm 指令太麻烦了,估计得写个半年都不一定能搞定,后续写了会同步更新在 github!!!

.text节区存储着可执行指令,我们可以通过节区头部的名字锁定.text的偏移地址和大小,找到该节区后,我们会发现这个节区存储的就是 arm 机器码,直接照着指令集翻译即可,没有其他的结构。通过 ida 验证如下:

九、代码测试相关截图

十、frida 反调试和后序

frida 反调试最简单的就是检查端口,检查进程名,检查 so 文件等,但最准确以及最复杂的是检查汇编指令,我们知道 frida 是通过一个大调整实现 hook,而跳转的指令就那么几条,我们是否可以通过检查每个函数第一条指令来判断是否有 frida 了!!!(ps:简单写一下原理,拉开写就太多了,这里感谢某大佬和我讨论的这个话题!!!)

本来因为这个 so 文件解析要写到明年去了,没想到看起来代码量大,但实际要用到的地方代码量很少。。。

源码 github 链接:

https://github.com/windy-purple/parseso/

.so是什么文件_安卓 so 文件解析详解相关推荐

  1. 电脑手机wifi互传文件_安卓手机文件互传

    怎么不借用第三方工具,安卓手机实现相互文件快传呢? 苹果: 首先不用多说,苹果可以使用Air Drop功能,苹果全家桶可以无障碍互传. 长期以来,除开微信和QQ,不同品牌安卓手机互传文件依靠的途径只有 ...

  2. java集群解析文件_干货:一文详解Redis集群原理核心内容

    集群原理 一个系统建立集群主要需要解决两个问题:数据同步问题和集群容错问题. Naive方案 一个简单粗暴的方案是部署多台一模一样的Redis服务,再用负载均衡来分摊压力以及监控服务状态.这种方案的优 ...

  3. c盘gnway是什么文件_壹拓网科技详解金万维天联标准版软件下载安装登录流程

    金万维天联标准版,是一款问世十几年的稳定的智能组网软件,通过纯软件组网,从而可以方便安全快速的使得不在同一个局域网下的设备实现互通,接下来壹拓网科技将讲述金万维天联标准版软件下载安装登录的具体流程: ...

  4. Excel文件的上传下载解析详解

    Excel有两个版本Excel2003和Excel2007,"2003的后缀.xls","2007的后缀名.xlsx" 由于两者实现机制不同,当实现文件上传时两 ...

  5. pydicom读取头文件_.dcm格式文件软件读取及python处理详解

    要处理一些.dcm格式的焊接缺陷图像,需要读取和显示.dcm格式的图像.通过搜集资料收集到一些医学影像,并通过pydicom模块查看.dcm格式文件. 若要查看dcm格式文件,可下echo viewe ...

  6. java文件打包jar文件_把java文件打包成.jar (jar命令详解)

    把java文件打包成.jar (jar命令详解) 先打开命令提示符(win2000或在运行框里执行cmd命令,win98为DOS提示符),输入jar Chelp,然后回车(如果你盘上已经有了jdk1. ...

  7. java 输入流可以合并吗_Java 使用IO流实现大文件的分割与合并实例详解

    java 使用IO流实现大文件的分割与合并 文件分割应该算一个比较实用的功能,举例子说明吧比如说:你有一个3G的文件要从一台电脑Copy到另一台电脑, 但是你的存储设备(比如SD卡)只有1G ,这个时 ...

  8. 【运维】PowerShell编程 目录文件相关方法的封装与案例详解

    PowerShell 目录文件管理 目录文件相关方法的封装与案例详解 李俊才 的 CSDN 博客:https://blog.csdn.net/qq_28550263?type=blog 邮箱 :291 ...

  9. java文件流 m.jb51.net_FasfDFS整合Java实现文件上传下载功能实例详解

    今天使用Java代码实现文件的上传和下载.对此作者提供了Java API支持,下载fastdfs-client-java将源码添加到项目中.或者在Maven项目pom.xml文件中添加依赖 org.c ...

最新文章

  1. mysql一主两从_MySQL 网络延迟参数设置建议
  2. vscode使用教程python-用VSCode写python的正确姿势
  3. 【spring源码学习】spring的aop目标对象中进行自我调用,且需要实施相应的事务定义的解决方案...
  4. 因为计算机中丢失crlutl,crlutlintl.dll
  5. Linux命令:SAMBA配置与win10共享
  6. C# TextBox输入数字 TextBox输入限制 TextBox输入字符 KeyPress
  7. windows——JDK下载与安装及环境变量配置
  8. 经过事件还是箭头 html,箭头函数不合适什么场景?
  9. #36328;#36234;#23457;#26680;#26426;#21046;#30340;gladder#25554;#20214;
  10. WS2 安装ubuntu +迁移+vscode
  11. 剑指Offer对答如流系列 - 剪绳子
  12. mac系统添加VSCode到右键菜单
  13. ip地址分类和子网掩码
  14. iis 网站添加 身份验证_在10分钟内将身份验证添加到任何网页
  15. HMM隐马尔科夫时间序列预测 Markov马尔科夫时间序列预测(Matlab)
  16. 8000字解读全域用户体验丨星巴克的尖刀与钝点
  17. 唐伯虎点秋香中的一段对白!
  18. Notepad++--常用的配置
  19. 索骥馆-美工设计之《配色设计原理》扫描版[PDF]
  20. 通用型RS485通讯电池监测模块的功能及应用方案

热门文章

  1. c语言两个for语句并列执行_C语言两个for语句如何并列编写?
  2. C语言函数题-P字符串的比较
  3. unet脑肿瘤分割_2D UNet3+ Pytorch实现 脑肿瘤分割
  4. Java黑皮书课后题第3章:*3.32(几何:点的位置)给定一个从点p0(x0,y0)到p1(x1,y1)的有向线段,可以用以下公式判定定点p2(x2, y2)是在线段的左侧、右侧,或者在该线段上
  5. C语言学习之编程输入x,输出对应的y.
  6. numpy---one
  7. lintcode-93-平衡二叉树
  8. 【10】48. Rotate Image
  9. [APP] Android 开发笔记 001-环境搭建与命令行创建项目
  10. 最快最新最详细的IT电子书