0.elf文件格式
0-1.概述
ELF(Executable and Linkable Format)文件是Linux环境中的一种二进制可执行文件。
elf的基本信息存在于elf的头部信息中,这些信息包括指令的运行框架、程序入口等,可以通过 readelf -h <elf_name> 来查看头部信息。
elf文件中包含许多节(section),各个节中存放不同的数据,这些节的信息存放在节头表中,可用 readelf -S <file> 查看,主要包括:
0-2.elf文件被加载到内存中的布局
elf文件在加载进内存时,elf文件的节会被映射到内存中的段(segment),这一映射过程遵循的机制是根据各个节的权限来进行映射的。可读可写的节被映射入一个段,只读的节被映射入一个一个段。
如下图所示:
1.编译与链接
1-1.动态链接库
一般来说,在程序的开发过程中都会用到一些系统函数。这些系统函数不需要我们实现,只需调用即可,而存放这些函数的库文件就是动态链接库。
通常情况下,我们在pwn题接触到的动态链接库就是libc.so文件。
1-2.静态编译与动态编译
一个程序在运行的过程中可能会调用许许多多的库函数,这些库函数在一次运行中无法保证被全部调用。
静态编译的思路是将所有可能运行到的库函数一同编译到可执行文件中。
这一方式的优点是在程序运行中不需要依赖动态链接库。适用的场合是本地编译的程序需要的动态链接库版本比较特殊,在别的地方运行可能会因为对方的动态链接库版本不一样而出错。
缺点是编译后的程序体积大、编译速度慢。
动态编译的思路是遇到需要调用库函数的时候再去动态链接库中寻找。(逢山开路、遇水架桥)
其优点是一方面缩小了可执行文件本身的体积,另一方面是加快了编译的速度,节省了系统资源。
缺点一是即便是很简单的程序,只要用到了链接库中的命令,也需要一个相对庞大的链接库;二是如果其它计算机上没有安装对应的运行库,则用动态编译的可执行文件就无法运行。
2.延迟绑定
got表全称Global Offset Table,即全局偏移量表。
在程序开始运行时,got表并不保存库函数的地址,只有在第一次调用后,程序才将这一地址保存在got表中。
GOT(Global Offset Table,全局偏移表)是数据段用于地址无关码的Linux ELF文件中确定全局变量和外部函数地址的表。ELF中有.got和.got.plt两个GOT表。.got表用于全局变量的引用地址,.got.plt用于保存函数引用的地址。
PLT(Procedure Linkage Table,程序链接表)是Linux ELF文件中用于延迟绑定的表。无论是第几次调用外部函数,程序真正调用的其实是plt表。plt表其实是由一段段汇编指令构成的。
在第一次调用外部函数时,plt表首先会跳到相应的got表项中。
由于没有被调用过,此时的got表存储的不是目标函数的地址,而是plt表中的一段指令的地址,其作用就是准备一些参数,进行动态解析。
跳回到plt表后,plt表又会跳转回plt的表头,表头内容就是调用动态解析函数,将目标函数地址存放入got表中。
如下图所示:
之后第二次及以上的调用后,程序已经完成了延迟绑定,got表中已经存储了目标函数地址,直接跳转即可。
如下图所示:
3.保护机制
Linux中常见的保护机制有CANARY、NX、ASLR、PIE、RELRO。
我们知道,栈的作用是存储函数调用相关信息以及函数的局部变量。这些局部变量通常为数组或着输入的缓冲区(buf)。而函数调用相关的信息主要是返回地址和栈底指针(rbp)。
- CANARY
canary中文翻译为金丝雀,源于科技不发达时,矿工会在下井工作时带一只金丝雀,用于判断地下环境有没有煤气之类的毒气泄漏。若金丝雀没事,则大家继续干活;若金丝雀死了,大家赶紧逃跑。
在Linux中,canary的作用如同它引用的一样,用来判断程序的执行环境,主要是针对检测栈溢出。
canary是一个开头字节为\x00的一段长度为八字节的随机数,这个随机数本体存放于fs段偏移为0x28的区域。
在每次函数调用中,程序都会将这段随机数存放于栈底,每次运行结束返回时,都会将这一随机数与他的本体进行比对。(如果这个值被改变,意味着发生了栈溢出,程序直接退出,程序直接退出;若没改变则程序继续执行。)
由于canary开头字节为\x00,所以在通常情况下不能被打印出来。(通常情况下,程序如果开启了canary保护,大概率说明此题不是栈溢出题目。但也要具体情况具体分析。)
绕过的主要方法是修改canary或者泄露canary。 - NX
NX即Not Executable,开启这个保护后,程序中的栈、堆、bss段等可写的段就不可以执行了。
这意味着如果开启了NX保护,通常情况下我们就不能执行自己编写的shellcode了。
绕过的方式通常是用mprotect函数来改写段的权限,NX保护对于rop或者劫持got表利用方式不影响。 - PIE & ASLR
在我们编写ROP或者shellcode时,有一个问题无法绕开,那就是找到函数的地址。
PIE指的是程序内存加载基地址随机化,意味着我们不能很快确定程序的基地址。
ASLR与其大同小异,ASLR是程序运行动态链接库、栈等地址随机化。
绕过方式是泄露函数地址,然后通过函数的偏移来确定基地址。
(通常来说,CTF中的PWN题这两种保护出现频率最高。)
- RELRO
这个保护主要针对延迟保护机制,即got表这种和函数动态链接相关的内存地址,对于用户是只读的。
开启了这个保护,意味着我们不能劫持got表中的函数指针。