ctfshow-pwn第一课-程序编译过程、ELF文件基础
硬件基础
- 计算机低速I/O部件由南桥芯片收集信号连接北桥,北桥通过总线和CPU通信
- 操作系统负责提供抽象接口和管理硬件资源
- 操作系统通过多任务系统对CPU进行管理
- 操作系统使用分页模式对内存进行管理
- 操作系统读写I/O端口来实现对磁盘进行管理
- CPU通过MMU(memory management unit)使虚拟地址映射到物理地址
可执行文件编译过程
- 预处理阶段:主要将
#
开头的库源码导入,删除注释,添加行号和文件名标识
该步骤的命令为:gcc -E test.c -o test.i
- 编译阶段: 将C语言代码进行分析,编译为汇编语言
assembly
该步骤的命令为:gcc -S test.i -o test.s
- 汇编阶段:将汇编语言转换为机械代码
该步骤的命令为:gcc -c test.s -o test.o
- 链接阶段: 将可执行文件所依赖库进行链接,并生成可执行文件,分为
静态链接
和动态链接
该步骤的命令为: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
- 第一个
02
值的含义: 如果为02
这里就是x64
文件,如果是01
就是x86
32位文件 - 第二个
01
值的含义: 表示字节序,采用大端存储还是小端存储,后续学习 - 第三个
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 -S
和objdump -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 信息 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具