如何通过gets向C语言输入含空字符的字符串以演示缓冲区溢出
嘿,这个问题我太熟了——完全是C语言字符串的核心规则在跟你“搞事情”!
首先得回忆清楚:C语言里的字符串本质是字节数组,必须以'\0'(空字符)作为终止标志,而strcmp函数的工作逻辑很死:它会逐字节对比两个字符串的内容,要么遇到不相等的字节就停止,要么直到碰到双方的'\0'才会判定相等。
你的场景里,虽然通过gets溢出缓冲区覆盖了相邻的目标变量,但如果没把目标变量对应的字符串终止位置也覆盖成'\0',strcmp就会一直往后读内存里的垃圾数据,自然不会认为两者相等。
举个你大概率会用到的代码例子:
#include <stdio.h> #include <string.h> int main() { char buffer[16]; char target[16] = "original"; // 初始化后末尾自带'\0' printf("Input something: "); gets(buffer); // 危险函数,会自动给buffer加'\0' if (strcmp(buffer, target) == 0) { puts("Match success!"); } else { puts("No match..."); } return 0; }
编译命令:gcc -fno-stack-protector -z execstack -fno-pie -o overflow_demo overflow_demo.c
当你输入超过16个字符时,确实会覆盖target的内容,但如果只覆盖了target的前N个字符,没碰它原来的'\0',或者溢出后target的末尾没被设为'\0',那target的字符串会“失控”地延伸到内存里下一个随机的'\0'位置,这时候和自带'\0'的buffer比,肯定不相等。
给你两个实用的思路:
思路一:在溢出输入的末尾手动加'\0'
构造输入时,填满缓冲区后,先输入你想覆盖到target的字符串内容,再额外加一个'\0'字节。比如缓冲区是16字节,target是16字节,那输入就是:16个填充字符 + 你要的目标字符串 + 1个'\0',这样target的末尾就会被强制设为'\0',strcmp就能正确匹配了。思路二:精准覆盖到
target原有的'\0'位置
其实更高效的是,找到target原字符串的'\0'所在的内存偏移,在溢出时直接把那个位置覆盖成'\0'(或者覆盖成你要的字符串的一部分,同时确保末尾是'\0')。比如原来的target是"original",它的'\0'在第9个字节,那你溢出时除了覆盖前8个字符,还要把第9个字节改成'\0',这样target就变成了你想要的字符串,并且正确终止。
gets函数会自动给输入的缓冲区加'\0',所以buffer的字符串是天然终止的,但target的终止符必须你通过溢出手动设置。- 如果调试时发现地址乱飘,记得加
-fno-pie编译参数关闭地址随机化,这样栈地址固定,更容易精准溢出。
内容的提问来源于stack exchange,提问作者firedrillsergeant

