二进制分析
什么是漏洞
漏洞的英文是leak
leak在英语语境中是一种气体,液体或者信息的泄露
我们的程序就像水流一样向下运行,用条件判断进行分流
在这个运行过程中一些设计问题会让攻击者有可乘之机,这就是漏洞
通过漏铜我们可以劫持进程,泄露信息
我们运行的程序是编译过后的机器语言程序,不是高级语言直接运行,所有这个运行过程的漏洞要从二进制层面来发掘,ELF文件格式就是必修的二进制知识
本文就来介绍ELF文件的知识
ELF文件包括了4种类型的组件
- ELF头部
- 程序头
- 节
- 节头
每一块都存储了文件的一部分信息
ELF头部
每个ELF二进制文件都是从ELF头部开始的,该头部是一系列结构化的字节
本质是什么,就是这个文件的头部存储了一些有用的数据,可理解为一个数据结构,就是一个结构体
下使用高级语言的定义
#define EI_NIDENT (16) typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr;
我们来一部分一部分进行一下解读
e_ident数组
010Editor
这里先介绍一款二进制编辑工具的使用
这是一款强大的二进制编辑工具和十六进制编辑工具
我们存储的文件都是二进制的形式,这款工具可以查看二进制,默认是十六进制表示
可以直接修改这些二进制
看上面的定义是一个16字节的数组
幻数
第一部分是一个4字节的幻数,由0x7F和ELF的ASCLL码组成
我们看一下010edior的开头
左侧是16进制表示的二进制数据,右侧是左侧数据对应的ASCLL表示,'.'表示不可见字符
紧跟在幻数后面的字节提供了有关ELF二进制文件类型规范,就是e_ident的4~15索引
下面介绍这些部分,参考上图,或者自己找一个ELF文件用010editor查看
EI_CLASS
表示ELF规范中二进制文件的类,这个字节表示用的是32位还是64位体系结构
32位是1,64位是2
EI_DATA
指示二进制文件中的字节序
等于1是小端法,等于2是大端法
EI_VERSION
指示的是ELF版本规范,当前唯一有效的有效值是1
EI_OSABI和EI_ABIVERSION
表示的是应用程序的二进制接口
EI_PAD
这个部分包含很多字节,是 e_ident的9~15字节
当前设置都是0,保留供将来用
使用readelf命令查看e_ident
除了010editor,我们还可以在Linux中使用readelf命令查看文件的e_ident
命令与显示如图
Magic所显示的就是 e_ident
而且下面对于e_ident表示的信息进行了解读
还有显示了接下来的字段的内容解读
e_type字段
这里经常遇到的值是ET_REL(可重定位的对象文件),ET_EXEC(可执行的二进制文件),ET_DNY(动态库)
e_machine字段
表示二进制文件计划在什么体系结构上运行
EM_X86_64 EM_386 EM_ARM
e_version字段
作用与 e_ident数组中的EI_VERSION相同
唯一可能的值是EV_CURRENT,指定版本规范是1
e_entry字段
表示二进制文件的入口点
开始执行的虚拟地址
e_phoff和e_shoff字段
程序头和节头不必位于ELF文件中的任意特定偏移
e_phoff和e_shoff字段指定了程序头表和节头表距离开始的偏移量
e_flags字段
保存了二进制文件在特定处理器的标志
对于x86二进制文件,通常设置为0,无须过多关注
e_ehsize字段
指定了ELF头部的大小
64位始终为64字节,32位始终为52字节
e_*entsize和e_*num字段
每个程序头或者表中各个节头的大小
e_shstrndx字段
包含一个名为.shstrtab的,与特殊字符串表结相关的头索引
节头
ELF二进制文件中的代码和数据在逻辑上被分为连续的非重叠块,称为节
没有预设的结构体,每个节的结构体取决于内容
sh_name字段
如果这个子段被设置,字符串表中包含索引
sh_type字段
每个节的类型
sh_flags字段
SHF_WRITE 该节在运行时可写
SHF_ALLOC 指示在执行二进制文件时将节的内容加载到虚拟内存
SHF_EXECINSTR 指示该节包含可执行指令
sh_addr,sh_offset及sh_size字段
分别描述该节的虚拟地址,文件偏移和大小
sh_link字段
有时链接器需要了解节与节之间的关系,例如与SHT_SYMAB,SHT_DYNSYM或者SHT_DYNAMIC类型的节有关联的字符串表节,其中包含相关符号的名称
sh_info字段
存放关于节的额外信息,这些额外信息依赖与解类型
sh_addralign字段
包含固定大小的条目
节
显示节
使用readelf命令显示节
显示了节的虚拟地址,文件偏移和大小
.init节
包含可执行代码,用于执行初始化工作,并且在二进制文件执行其他代码之前运行,类似面向对象编程的构造函数
.fini节
在主程序运行完后执行,类似析构函数
.text节
包含程序的主要代码,是逆向分析的重点
包含了很多执行初始化和终止任务的标准函数,如_start,register_tm_clones,frame_dummy
.bss,.data及.rodata节
.rodata 保存只读数据
.data 存储可写的数据
.bss 初始化为0,并且标记该节为可写
延迟绑定和.plt,.got和.got.plt节
加载二进制文件的时候很多重定位一般都不会立即完成,而是延迟到对未解析位置进行首次引用之前,这就是延迟绑定
延迟绑定和.plt
延迟绑定保证了动态链接器不会在重定位上浪费时间,只在运行有需要的时候执行
延迟绑定用.plt节和.got节
通常有一个.got.plt的GOT
.plt是包含可执行代码的代码节
.got.plt是数据节
查看plt
PLT的格式如下
首先有一个默认存根,然后是一系列函数存根,每个库函数有一个存根
然后是一系列函数存根
压入栈的值依次递增,观察上图的push
使用PLT动态解析库函数
假设调用puts函数
已知该函数是libc库的一部分
可以直接调用相应的PLT存根puts@plt,不是直接调用该函数
push指令把一个整数压入栈,该整数是PLT存根的标识符
下一条转到所有PLT函数的存根之间共享的通用默认存根
默认存根会push另一个标识符,以表示可执行文件自身
然后间接地,通过GOT跳转到动态链接器
其他节
.rel.和.rela.*节
有几个名为.rela.*的节
类型为SHT_RELA
包含链接器用于执行重定位的信息
每个SHT_RELA类型是一个重定位条目
.dynamic节
.dynamic节将充当操作系统和动态链接器的“路线图”
包含了一个Elf64_Dyn的结构体数组
也称为标签,有各种类型,每个标签有个关联值
DT_NEEDED的标签会通知通知动态链接器关于可执行文件的依赖问题
.init_array和.fini_array节
.init_array包含一个指向构造函数的指针数组
.fini_array包含一个指向析构函数的指针
.shstrtab、.symtab、.strtab、.dynsym及.dynstr节
.shstrtab只是一个以NULL结尾的字符串数组,包含所有二进制文件中所有节的名称
程序头
提供了二进制文件的段视图
ELF包括零或多个节,实际上就是把这些节捆绑成单个块
段提供的可执行视图,只有二进制文件会用到
p_type字段
标识了段的类型
主要类型包括PT_LOAD,PT_DYNAMIC,PT_INFERP
PT_LOAD类型的段会在创建进程时加载到内存中
PT_INFERP类型的段包含了.interp节,该节提供了加载二进制文件的解释器的名称
PT_DYNAMIC包含了.dynamic节,该节告诉解释器如何解析二进制文件用于执行
p_flags字段
指示了段在运行时的访问权限
有三种重要的类型
PF_X(可执行),PF_W(可写),PF_R(可读)
p_offset、p_vaddr、p_paddr、p_filesz和p_memsz字段
改段的起始文件偏移量,加载的虚拟地址以及段大小
p_align字段
指定了段所需的内存对齐方式(字节为单位)
本文介绍了ELF文件的各部分