
3.5 全局偏移表
Windows和Linux在库函数的链接和控制转移方面使用了类似的机制。从安全的角度来看,二者主要的区别在于Linux使用的方法是可被利用的,而Windows则不然。
Linux、Solaris2.x和SVR4使用的默认二进制格式称为可执行和链接格式(Executable and Linking Format,ELF)。ELF最初由UNIX系统实验室(UNIX System Laboratories,USL)作为二进制应用程序接口(Application Binary Interface,ABI)的一个部分开发并发布。近来,工具接口标准(Tool Interface Standards,TIS) [1]委员会采纳了ELF标准,将其作为多种x86-32操作系统上的可移植目标文件格式。
任何ELF的二进制文件的进程空间中,都包含一个称为全局偏移表(Global Offset Table,GOT)的区。GOT存放绝对地址,从而使得地址可用,并且不会影响位置独立性和程序代码的可共享性。要使得动态链接的进程能够工作,这个表是必不可少的。该表的实际内容和形式取决于处理器的型号[TIS 1995]。
程序使用的每一个库函数在GOT中都拥有一个入口项,GOT中包含有实际函数的地址。这使得很容易在进程内存中对库函数进行重定位。在程序首次使用一个函数之前,该入口项包含有运行时链接器(RunTime Linker,RTL)的地址。如果该函数被程序调用,则程序的控制权被转移到RTL,然后函数的实际地址被确定且被插入到GOT中。接下来就可以通过GOT中的入口项直接调用函数,而跟RTL就无关了。
在ELF可执行文件中GOT入口项的地址是固定的。这就导致对任何可执行进程映像而言GOT入口项都位于相同的地址。如例3.6所示,可以利用objdump命令查看某一个函数的GOT入口项的位置。为每一个R_386_JUMP_SLOT重定位记录指定的偏移量,包含了指定函数(或RTL链接函数)的地址。
例3.6 全局偏移表
% objdump --dynamic-reloc test-prog format: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049bc0 R_386_GLOB_DAT __gmon_start__ 08049ba8 R_386_JUMP_SLOT __libc_start_main 08049bac R_386_JUMP_SLOT strcat 08049bb0 R_386_JUMP_SLOT printf 08049bb4 R_386_JUMP_SLOT exit 08049bb8 R_386_JUMP_SLOT sprintf 08049bbc R_386_JUMP_SLOT strcpy
攻击者可以利用任意内存写将一个函数的GOT入口项覆写为外壳代码的地址。这样,当程序调用对应于被改写的GOT入口项的函数时,程序的控制权就被转移到外壳代码。例如,每一个编写良好的C程序最后都会调用exit()函数,因此,只要覆写了exit()的GOT入口项,就可以在exit()被调用时将程序的控制权转移到指定的地址。ELF过程链接表(Procedure Linkage Table,PLT)具有类似的问题[Cesare 2000]。
Windows PE(Portable Executable,可移植的可执行)文件格式扮演着与ELF格式相似的角色。PE文件中包含一个数据结构数组,每一项对应一个导入的DLL。每一项都包含有导入的DLL的名称以及一个指向函数指针数组的指针(即导入地址表,Import Address Table,IAT)。每一个被导入的API在IAT中都有自己的保留槽,由Windows载入器为其填充导入函数的地址。一旦一个模块被载入,IAT就保存了需要调用的导入函数的地址。IAT的入口项是写保护的,因为它们在运行时无须修改。