我正在尝试解决这个挑战,目标是让程序显示“你赢了!”为此,我必须找到正确的变量值,以使 if
条件和 assert
的计算结果均为 true。
本来我以为这个会比较简单。不过,似乎制作缓冲区溢出是一门需要高级编程技能的艺术(还有大端和小端的概念),但我在 C 编程方面没有太多经验。
任何帮助或建议将不胜感激。
#include<assert.h>
int
main(int argc, char*argv[]){
int size = 0;
int buf[1024];
read(0,&size,sizeof(size));
assert (size <= 1024);
read(0,buf,size*sizeof(int));
if (size > 0 && size < 100 && buf[999] == 'B')
printf("YOU WIN!\n");
return 0;
}
最佳答案
有趣的问题。
你必须了解你正在运行的机器,所以我做了 以下假设:
32 位机器,大端。
编译器执行“经典”堆栈布局,这将放置
size
毗邻buf
的末尾.数据对齐到 4 个字节或更少,因此
buf
末尾之间没有填充。和size
.
这台机器上的经典编译器按照变量在代码中出现的顺序在调用堆栈上分配变量,因此 size
首先被推送,然后 buf
。您的堆栈将如下所示:
buf byte
top of stack offset offset
+-----------+ 0 * sizeof(int) = 0
| buf[ 0] |
+-----------+ 1 * sizeof(int) = 4
| |
...
| |
+-----------+ 1023 * sizeof(int) = 4092
| buf[1023] |
+-----------+ 1024 * sizeof(int) = 4096
| size |
+-----------+ 1025 * sizeof(int) = 4100
bottom of stack
写信给buf[1024]
会覆盖size
。如果我们写入原始字节,
那么字节 4096、4097、4098 和 4099 将对应于 size
中的字节.
我们知道我们希望大小为负数以通过 assert
,我们想要 size * sizeof(int)
计算结果为 4100。因为如果我们读取 4100 字节,最后 4 个字节将覆盖 size
.
size * 4 = 4100. Solving for size:
size = 1025 (or 0x401)
现在我们通过设置最高有效位将其设为负值。
0x80000401
因此将 size 设置为该值将通过 assert (size <= 1024)
。因为任何事
最高有效位设置为负数,并且所有负数都小于 1024。
当我们到达缓冲区读取时,它将像这样进行评估:
read(0, buf, 0x80000401 * sizeof(int));
read(0, buf, 0x80000401 * 4);
read(0, buf, 0x00001004) == read(0, buf, 4100)
是的,没错,因为 32 位模运算,我们只是告诉它读取 4100 字节。或者,如果有帮助的话,您可以将乘以 4 视为左移 2,因此将我们的魔法值左移 2 可以去掉最高有效位。
要打印“你赢了!”,你需要编写一个字节流,其中 buf[999] 是字节 3996
字节4096、4097、4098和4099是size
的字节。关于这个假设
big-endian 机器,我们将这些字节分别设置为 0、0、0 和 1-99。
关于在 C 中导致缓冲区溢出以强制条件为真,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24899535/