运行一个小的内部 CTF 来教人们一些计算机安全基础知识,我遇到了一个奇怪的行为。以下是 fork TCP 服务器的句柄函数。这只是一个可爱的小缓冲区溢出演示(取自 CSAW CTF)。
测试时,我只费心向它发送 4097 字节的数据,因为它会成功溢出到 backdoor
变量中。然而,许多参与者决定尝试发送恰好 4099 字节,但这实际上行不通。我不完全确定为什么。
在 GDB 中,recv
ing 4099 字节工作得很好,否则就不行了。我现在已经花了很多时间对此进行调试,因为我想为每个人很好地解释为什么该服务会如此运行。它是 recv()
调用的某种怪癖,还是我在这里做了一些根本性的错误?
void handle(int fd)
{
int backdoor = 0;
char attack[4096];
send(fd, greeting, strlen(greeting), 0);
sleep(3);
recv(fd, attack, 0x1003, 0);
if (backdoor)
{
dup2(fd, 0); dup2(fd, 1); dup2(fd, 2);
char* argv[] = {"/bin/cat", "flag", NULL};
execve(argv[0], argv, NULL);
exit(0);
}
send(fd, nope, strlen(nope), 0);
}
编辑 可执行文件编译为:
clang -o backdoor backdoor.c -O0 -fno-stack-protector
我没有使用不同的优化设置来调试/实时可执行文件。我可以运行以下命令:
python -c "print 'A'*4099" | nc <ip> <port>
这行不通。然后我通过 GDB 附加到正在运行的进程(在 recv
调用之后直接设置一个断点)并再次运行上面的命令,它确实工作了。我已经多次重复这个,但有一些变化,但结果相同。
这可能与操作系统处理发送到套接字的多余字节排队的方式有关吗?当我使用上述命令发送 4099 个字节时,我实际上发送了 5000 个字节(Python 的 print
隐含地附加了一个换行符)。这意味着 recv
的换行符被截断并留给下一次调用 recv
进行清理。仍然无法弄清楚 GDB 是如何影响这一点的,但这只是一个理论。
最佳答案
... am I doing something fundamentally wrong here?
是的,您期望未定义的行为是可预测的。它不是。
如果您使用 gcc
编译该函数, 使用 -O3
, 然后你会收到一个关于超过接收缓冲区大小的警告(当然你已经知道了);但你也会得到一个二进制文件,它实际上并不费心去检查 backdoor
.如果你使用 clang
,你没有收到警告,但你得到一个二进制文件,它甚至没有为 backdoor
分配空间。 .
原因很明确:修改backdoor
通过“后门”是未定义的行为,编译器没有义务在面对未定义的行为时做任何您可能认为合乎逻辑或可预测的事情。特别是,允许假设未定义的行为永远不会发生。因为没有有效的程序可以改变 backdoor
, 允许编译器假定 backdoor
永远不会发生突变,因此它可以抛弃 if
中的代码阻止为无法访问。
你没有提到你是如何编译这个程序的,但是如果你在没有优化的情况下编译使用gdb
并在您不打算使用 gdb
时进行优化,那么你不应该对未定义行为的不同处理感到惊讶。另一方面,即使您在两种情况下都使用相同的编译器和选项编译程序,您仍然不应该感到惊讶,因为正如它所说,未定义的行为是未定义的。
声明backdoor
作为volatile
可能阻止优化。虽然这不是重点,不是吗?
注意:我使用的是 gcc 4.8.1 版和 clang 3.4 版。不同的版本(甚至不同的构建)可能会有不同的结果。
关于c - 不一致的 recv() 行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21668131/