程序代码:
#include <stdio.h>
#include <string.h>
#define PASSWD "1234567890"
int verify_passwd( char *passwd)
{
int authenticated;
char buffer[8];
authenticated = strcmp( passwd, PASSWD);
strcpy(buffer, passwd);
return authenticated;}
void main(){
int valid_flag= 0;
char passwd[1024];
while(1){
printf("please input passwd:");
scanf("%s",passwd);
valid_flag=verify_passwd(passwd);
if(valid_flag)
printf("incorrect passwd!\n\n");
else{
printf("Correct password!\n");
break;
}
}
}
子函数存在溢出:
int verify_passwd( char *passwd)
{
int authenticated;
char buffer[8];
authenticated = strcmp( passwd, PASSWD);
strcpy(buffer, passwd);//溢出处
return authenticated;
}
编译:
- 编译为32位程序,保护全部关闭,方便调试:
gcc -m32 -z execstack -no-pie -fno-stack-protector -z norelro -o test_x32 test.c
调试:
- 断点下在子程序入口:
- 测试输入到变量passwd的值为“aaaaaaaa”,用于之后在子函数填满
buffer[8]:
一直步入到strcmp函数的调用
- 查看栈中数据:
‘aaaaaaaa’是strcmp函数的第一个参数,0x8048620存储的‘1234567890’为第二个参数
注意栈底ebp=0xffffcc38
之前执行了strcmp函数,发现其结果也就是eax存入了[ebp-0xc],那么我们可以推断程序中定义的比较结果标志变量authenticated
的地址是[ebp-0xc]
- 步入strcpy函数附近:
- 再步入至strcpy函数调用处:
- 此时查看栈内数据:
观察栈空间发现,下一步应该就是向栈顶对应存入的地址(0xffffcc24)写入‘aaaaaaaa’
- 再往下执行一步:发现写入成功
strcpy函数是存在漏洞的,如果输入的数据足够大,那么buffer的地址装不下而向上溢出,这里从图可以看出,
buffer
的地址为0xffffcc24
- 查看此处数据:
发现了我们写入的8个a,
而之前我们知道标志变量
authenticated
的地址是[ebp-0xc],计算栈偏移,那么就是:
0xffffcc2c,观察栈上此地址处元素为0:
同时与buffer[8]
(0xffffcc24)的距离为8,所以标志变量authenticated
与buffer地址相邻的,那么我们可以输入超过8字符的数据实现对标志变量authenticated
的覆盖
溢出示例:
- 多输入一个‘b’
- 查看strcpy函数执行后的栈空间:
发现
标志变量authenticated
被覆盖为‘b’,实现了栈溢出
总结:
程序溢出点在于scanf函数和strcpy函数
- scanf函数并未对
passwd
输入大小做限制 - strcpy函数无法对复制到
buffer
的数据大小做限制 - 可以说,buffer的设置对这个程序整体来说本来就没有意义,写入后没有对buffer有任何别的操作
- 把
strcpy(buffer, passwd);
放在authenticated = strcmp( passwd, PASSWD);
之后我们能理解写程序的人的良苦用心,就是为了告诉我们:我们可以把标志位给覆盖,
解决假设:
如果把这个strcpy(buffer, passwd);
放在strcmp函数之前,那么即使authenticated被覆盖了,后执行strcmp函数也是会被修改为0或1的,就不会影响strcmp函数其比较结果了
那么这样这个程序就安全了吗?其实并不是,这里就不深入探讨了
拓展:
Tips:这里涉及到一点点Pwn入门知识
绕过思路:
实现此程序的栈溢出,修改变量authenticated内容为0,则通过主函数的判断检查
尝试输入8个a加一个0
接收到“incorrect password!”,验证失败
分析
此程序scanf接收的是char类型的字符串,通过scanf正常输入是无法写入我们想要的0的,即使我们在输入时输入0,想要通过检查,可是写到内存地址上的数据还是0吗?
答案是:不是0了,而是0x30
那么我们只能想办法让输入的溢出数据存在内存中为0x00才能实现溢出,如何实现?
32位程序定义的整形int为4字节,那么我们就需要覆盖这4字节
绕过实现
利用脚本,实现byte类型的0的写入
from pwn import*
io= process('./test_x32')
io.sendline(b'a'*0x8+p32(0))
print(io.recv().decode('utf8'))
运行结果:
接收到“Correct password!”,验证通过!