ctfshow-pwn第一课-程序编译过程、ELF文件基础

硬件基础

  • 计算机低速I/O部件由南桥芯片收集信号连接北桥,北桥通过总线和CPU通信
  • 操作系统负责提供抽象接口和管理硬件资源
    • 操作系统通过多任务系统对CPU进行管理
    • 操作系统使用分页模式对内存进行管理
    • 操作系统读写I/O端口来实现对磁盘进行管理
  • CPU通过MMU(memory management unit)使虚拟地址映射到物理地址

可执行文件编译过程

  1. 预处理阶段:主要将#开头的库源码导入,删除注释,添加行号和文件名标识
    该步骤的命令为: gcc -E test.c -o test.i
  2. 编译阶段: 将C语言代码进行分析,编译为汇编语言assembly
    该步骤的命令为: gcc -S test.i -o test.s
  3. 汇编阶段:将汇编语言转换为机械代码
    该步骤的命令为: gcc -c test.s -o test.o
  4. 链接阶段: 将可执行文件所依赖库进行链接,并生成可执行文件,分为静态链接动态链接
    该步骤的命令为: gcc test.o -o test
    默认使用的是动态链接,若要使用静态连接则使用参数-static: gcc test.o -o test -static

具体步骤如下:
首先创建一个输出hello world的C文件

使用gcc -E预处理文件,查看该文件后会发现,最下方还是我们正常写入的代码,上面添加了stdio库的源代码还有一些行号,这里我只显示了10行。

接着使用gcc -S编译该文件,可以发现文件内容已经变为汇编代码

接着使用gcc -c将汇编代码编译为2进制(机械)码

可以看到这个时候程序已经成为了elf的可执行文件,接着做最后一步的动作就是链接依赖库
接着直接使用gcc链接test.o文件,如果想要静态链接可以使用-static参数

ELF文件格式

  • ELF文件是编译完毕后经过链接,可以直接运行的文件
  • ELF文件包含了完整的ELF文件头和段表、字符串表、符号表以及其他表
  • ELF文件是一系列代码、数据、表按照一定规则打包起来的数据包

使用readelf -h test可以查看elf文件头信息

该文件的开头成为魔术(magic)ascii码值的16进制数据为7f 45 4c 46
下面我使用python分别手动将7f 45 4c 46转换为10进制为127 69 76 70,然后使用chr()转换为字符为ELF


所以127是什么,使用ascii码表查看得知这是一个Delete字符

所以elf文件的开头一般为Delete(7f) + 45 + 4c + 46

ELF后面紧跟了02 01 01

  1. 第一个02值的含义: 如果为02这里就是x64文件,如果是01就是x8632位文件
  2. 第二个01值的含义: 表示字节序,采用大端存储还是小端存储,后续学习
  3. 第三个01值的含义: 表示ELF文件的版本号,一般固定为1

通过文件头可以看出节表的一些信息,如

  • Start of section headers: 6448(bytes into file): 节表的开始在6448这个字节开始
  • Size of section headers: 64(bytes): 每个节表的大小为64字节
  • Number of section headers: 29: 一共有29个节
    通过上述可以推出节的地址在6448开始的往后64 * 29=1856‬字节
  • Section header string table index: 28: 所有的section都有一个自己的section header,所有的section header按照顺序存储在string table中,string table中存储的是所有section header的名称,按照我的理解这里的28就是最后一个节的索引,可以简单理解为节表的数量为28+1,0也算一个

可以使用readelf -S test查看所有节头信息

我的理解是节头就是包含所有节的一个汇总

ctfshow@ubuntu:~/Desktop$ readelf -S test
There are 29 section headers, starting at offset 0x1930:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000000254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000000274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000000298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000000002b8  000002b8
       00000000000000a8  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000000360  00000360
       0000000000000084  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000000003e4  000003e4
       000000000000000e  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000000003f8  000003f8
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000000418  00000418
       00000000000000c0  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             00000000000004d8  000004d8
       0000000000000018  0000000000000018  AI       5    22     8
  [11] .init             PROGBITS         00000000000004f0  000004f0
       0000000000000017  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000000510  00000510
       0000000000000020  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000000530  00000530
       0000000000000008  0000000000000008  AX       0     0     8
  [14] .text             PROGBITS         0000000000000540  00000540
       00000000000001a2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000000006e4  000006e4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000000006f0  000006f0
       0000000000000010  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         0000000000000700  00000700
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000000740  00000740
       0000000000000108  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000200db8  00000db8
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000200dc0  00000dc0
       0000000000000008  0000000000000008  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000200dc8  00000dc8
       00000000000001f0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000200fb8  00000fb8
       0000000000000048  0000000000000008  WA       0     0     8
  [23] .data             PROGBITS         0000000000201000  00001000
       0000000000000010  0000000000000000  WA       0     0     8
  [24] .bss              NOBITS           0000000000201010  00001010
       0000000000000008  0000000000000000  WA       0     0     1
  [25] .comment          PROGBITS         0000000000000000  00001010
       0000000000000029  0000000000000001  MS       0     0     1
  [26] .symtab           SYMTAB           0000000000000000  00001040
       00000000000005e8  0000000000000018          27    43     8
  [27] .strtab           STRTAB           0000000000000000  00001628
       0000000000000204  0000000000000000           0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  0000182c
       00000000000000fe  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

若想要查看每个节的内容,可以使用objdump -s -d test

自定义节

我们可以自定义节,例如flag,但是自定义的节名不可以是.开头,语法如下:

#include <stdio.h>

// 自定义节的名称为flag,值为 353823806
__attribute__((section("flag"))) int global_int_var = 353823806;
int global_unint_var;

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

int main(void){
     static int static_var = 85;
     static int static_var2;
     int a = 1;
     int b;
     func1(static_var+static_var2+a+b);
     return a;
}

编译gcc diy_section.c -o diy_section -m32
使用objdump -h查看所有的节

ctfshow@ubuntu:~/Desktop$ objdump -h diy_section

diy_section:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  00000154  00000154  00000154  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  00000168  00000168  00000168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  00000188  00000188  00000188  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     00000020  000001ac  000001ac  000001ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
... ... ... ... ...
... ... ... ... ...
                  CONTENTS, ALLOC, LOAD, DATA
 22 .data         0000000c  00002000  00002000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 23 flag          00000004  0000200c  0000200c  0000100c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          0000000c  00002010  00002010  00001010  2**2
                  ALLOC
 25 .comment      00000029  00000000  00000000  00001010  2**0

使用objdump -s -d查看节的内容

ctfshow@ubuntu:~/Desktop$ objdump -s -d diy_section

diy_section:     file format elf32-i386

Contents of section .interp:
 0154 2f6c6962 2f6c642d 6c696e75 782e736f  /lib/ld-linux.so
 0164 2e3200                               .2.             
Contents of section .note.ABI-tag:
 0168 04000000 10000000 01000000 474e5500  ............GNU.
 0178 00000000 03000000 02000000 00000000  ................
Contents of section .note.gnu.build-id:
 0188 04000000 14000000 03000000 474e5500  ............GNU.
 0198 ccfc3b78 86602d9e bb1264ce 6b84d509  ..;x.`-...d.k...
 01a8 96319498                             .1..            
... ... ... ... ... ...
... ... ... ... ... ...      
Contents of section .data:
 2000 00000000 04200000 55000000           ..... ..U...    
Contents of section flag:
 200c 3eec1615                             >...            
Contents of section .comment:
 0000 4743433a 20285562 756e7475 20372e35  GCC: (Ubuntu 7.5
 0010 2e302d33 7562756e 7475317e 31382e30  .0-3ubuntu1~18.0
 0020 34292037 2e352e30 00                 4) 7.5.0.

得到16进制数据值为3eec1615,接着使用python转换为10进制查看
注意: 由于一般计算机都采用小端排序所以需要反过来,如下

为了更了解存储,我将自定义节flag的值改为__attribute__((section("flag"))) char global_int_var[] = "love LQL";
接着编译使用objdump查看如下:

Contents of section flag:
 200c 6c6f7665 204c514c 00                 love LQL.       
Contents of section .comment:
 0000 4743433a 20285562 756e7475 20372e35  GCC: (Ubuntu 7.5
 0010 2e302d33 7562756e 7475317e 31382e30  .0-3ubuntu1~18.0
 0020 34292037 2e352e30 00                 4) 7.5.0. 

得到16进制数据6c6f7665 204c514c
使用脚本转换为16进制

str1 = "6c6f7665204c514c"

for i in range(0,len(str1),2):
    # 两位切片出一个16进制数
    str_hex = str1[i:i+2]
    # 将16进制数转换为10进制
    str_dec = int(str_hex,16)
    # 将10进制数据转换为ascii值,并输出
    print(chr(str_dec),end='')


从而也可以得知20在16进制中为空格

objdump常用参数

在上述中,我们都是使用objdump查看多有节的内容,如果想要指定节查看,可以使用-j参数指定节名,一些常用功能的参数如下:

  • -s: 使用16进制显示每个节的数据
  • -d: 查看disassemble反汇编后的汇编代码
  • -h: 查看节的头信息
  • -x: 显示完整的头信息
  • -j: 指定需要查看的节

objdump -j .data -d diy_section

readelf常用参数

readelf和objdump这两个我们需要配合使用,这两个命令有一个类似的参数readelf -Sobjdump -h都是显示节的信息
常用的参数如下:

  • -a:显示所有信息
  • -h: 显示头部信息
  • -l: 显示所有段的信息
  • -S: 显示节(section)的头信息
  • -s: 显示符号表信息
  • -x section_name: 查看指定节hex信息
  • -p section_name: 查看指定节字符串信息
  • -r: 查看重定位表
  • -V: 显示版本信息

节结构

接着使用010 editor分析diy_section
根据readelf得知Start of section headers: 6248开始的位置在6248,我们找到该位置

根据Size of section headers: 40 (bytes)得知一个节占用40个字节,Number of section headers: 30一共有30个节
所以所有的节占用的大小为1200字节
我们可以选中6248这个字节开始到最后刚好是1200个字节

每个节占用40个字节,我们选中最后一个节到一个新的文件进行分析



40个字节可以没4个分为一组,第五组和第六组分别为数据存储位置和暂用字节大小

我们回到diy_section找到17 67,占用257个字节

所以得到如下:

常见的节

节名 说明
.comment 版本控制信息
.strtab 字符串表
.shstrtab 节名的字符串表
.symtab 符号表
.dynstr 动态链接的字符串表
.dynsym 动态链接的符号表
.got 全局变量偏移表,保存全局变量引用的地址
.got.plt 全局偏移量表,用于保存引用的地址
.plt 过程链接表,用于延迟绑定
.hash 符号哈希表
.rela.dyn 变量的动态重定位表
.rela.plt 函数的动态重定位表
.rela.text/.rel.text 静态重定位表
.note 额外的编译信息
.debug 调试信息
.init/.fini 程序初始化和终止的代码
.eh_frame 用于操作异常的frame unwind 信息
posted @ 2024-04-25 20:21  Junglezt  阅读(22)  评论(0编辑  收藏  举报