Binary Analysis基础——未完待续

发布者:SL7
发布于:2022-04-23 14:14

Binary Analysis

只是简述一下整个编译流程,对编译有一个大致的框架和理解,欢迎跳过不看。
C程序的整个编译流程(C++也类似)大致如图,要经过4个阶段预处理、编译、汇编、链接( preprocessing, compilation, assembly, and linking)

  1. preprocess:预处理阶段把所有的宏#define和#include展开为下面的环节做好准备。
    通常源文件不止一个,这样会让一个比较大的项目更容易被管理,一旦有了什么修改,需要重新编译的可能只是一个或几个file.c。

    1

    gcc --P hello.c//-E告诉gcc预处理后就停,-P省略一些调试信息让输出简洁一些

  2. compilation:把与处理后的代码转变为汇编代码。
    至于为什么要使用汇编语言不直接使用机器码,一个原因是现在的语言有很多,C++、Go等等,如果对每一种语言开发一种转可以转化为机器码的编译器,那工作量就太大了,不如大家都按照某种硬件要求的格式转化,之后无论什么语言的代码都通过一套流程处理。

    1

    gcc --masm=intel hello.c//-S让gcc在编译后停下,一般汇编语言后缀是.s,可以指定为intel否则默认AT&T,可以加参数优化从-O0到-O3

  3. assembly:汇编就是把汇编代码转化为包含机器指令的object文件。

    1

    2

    gcc -c hello.c

    file hello.o

    不过现在的object互相是分开经过汇编流程的,可能file1.o里面调用了file2.o的函数这些目前都不能确定,还需要下一个阶段才能变为变为单个可执行文件。object文件里含有relocation symbols就是为了下一个链接阶段重定向准备的。
  4. linking:链接。把很多个object的相互关系理清楚,然后整合为单个可执行文件。经过这个过程原本比较清晰地函数会混杂到一起,变得难以辨认,所以accurate automated function detection才变得重要。这个时候动态链接库的地址仍然不知道,只有最后被加载进内存时地址才会被找到。

    1

    2

    gcc hello.c

    file a.out

目标文件

什么是目标文件?就是经过了前3个步骤以后生成的文件。

目标文件的格式

经过编译器编译后的目标文件,格式上讲已经是可执行文件格式了——ELF,但是还没有经过链接。有些符号和地址还没有完全协调好、对上。

Linux下的.o文件以及最后的可执行文件,动态链接库(DDL Dynamic Linking Library)和静态链接库(Static Linking Library)基本上都是按照可执行文件格式存的。但是结构有一些区别。目标文件是ELF格式但是ELF格式不限于目标文件。


ELF文件类型:

  1. 可重定位文件,Linux下的.o文件和静态链接库。顾名思义可以通过重定位被链接和执行。

  2. 可执行文件。./a.out和.exe

  3. 共享目标文件。Linux下的.so动态链接库。

  4. 核心转储文件。是好目标,进程意外中止的时候,系统可以进运行进程的地址和相关信息存在核心转储文件里,Linux下的core dump。


目标文件的内容

因为就是经过前三个步骤产生的,那现在目标文件里面肯定有机器码、数据和一堆先前留下的调试信息,以及下个链接阶段要用的符号表等ELF文件按照内容且分为File Header+N个Section。也就是ELF按照内容属性贴上不同的标签,叫做不同的节(Section),至于叫节Section还是段Segment要看ELF处在什么阶段,链接阶段就叫Section,装载到内存的阶段就叫Segment。


ELF的结构

ELF文件最开始是ELF Header,其中包含了一大堆基本属性,而接下来就是各个段,ELF文件,段后面还有ELF关键结构Section header table   string tables和Symbol tables等等

一般C语言编译后

代码编译后的指令在代码段Code Section—.code  .text

已初始化的全局变量和静态局部变量在数据段Data Section——.data

未初始化的全局变量和静态局部变量默认为0,本来要在.data段的,但是分配0没什么意思,只需要为之后内存运行预留空间就好——.bss

#include"stdio.h"
int printf(const char *format, ...);
int global_init_var=47;
int global_uinit_var;

void func1(int i){
    printf("%d\n",i);
}

int main(){
    static int static_var=85;
    static int static_var2;
    int a=1;
    int b;
    func1(static_var+static_var2+a+b);
    return a;
}
gcc -c simpleSection.c 
objdump -h simpleSection.o

gcc可以生成.o文件,objdump -h可以看各个段的基本信息,其中Size是各个段的绝对长度, "VMA" (or link address) and the "LMA" (or load address) ,VMA是虚拟内存地址(程序希望运行的内存地址,经常这个值比较高,但是因为没有足够多的物理内存,所以需要将内映射后成为LMA才能实际装载和运行),LMA是装载内存地址,目前既没链接也没装载,所以值都是0。File off是相对于0x0的偏移量,可以看见本机是64位。.bss的Size等于4其实跟两个未初始化的global_unt_var和static_var2的8字节大小不符合,其实这只是一个预留空间大小,现在还没有分配内存。

int global_uinit_var;
static int static_var2;


objdump -s -d simpleSection.o//objdump -s可以把段内的内容用16进制打印,同时-d可以把所有包含指令的段反汇编

会先得到各个段的十六进制,可以发现大小跟先前各个段的.Size一一对应。同时.data里面存放的是已初始化的全局变量和静态局部变量刚好就是0x2f和0x55从低到高还是从高到低,这个跟机器的端序相关

.rodata是制度数据,%d对应的是printf里面的%d,不仅在语义上支持了const关键字,而且OS在装载的时候映射为只读,对这个段进行修改会引发异常

static int static_var=85;
int global_init_var=47;


参考资料:

《程序员的系我修养——链接、装载与库》

《Practical Binary Analysis》


声明:该文观点仅代表作者本人,转载请注明来自看雪