运行一个小的内部CTF来教人们一些计算机安全基础,我遇到了一种奇怪的行为。以下是分叉TCP服务器的句柄功能。这只是一个可爱的小缓冲区溢出演示(取自CSAW CTF)。
测试时,我只打算发送4097字节的数据,因为它会成功溢出到backdoor
变量中。但是,许多参与者决定尝试发送4099字节,这实际上并不起作用。我不完全确定原因。
在GDB中,recv
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如何影响这一点,而只是一个理论。
答案 0 :(得分:1)
......我在这里做了一些根本错误的事情吗?
是的,您期望未定义的行为是可预测的。事实并非如此。
如果使用gcc
使用-O3
编译该函数,那么您将收到有关超出接收缓冲区大小的警告(当然您已经知道了);但你也会得到一个二进制文件,它实际上并不打算检查backdoor
。如果您使用clang
,则不会收到警告,但您会得到一个甚至不为backdoor
分配空间的二进制文件。
原因很明显:通过“后门”修改backdoor
是未定义的行为,并且编译器没有义务做任何你认为在未定义的行为面前逻辑或可预测的事情。特别是,允许假设未定义的行为永远不会发生。由于没有有效的程序可以改变backdoor
,因此允许编译器假定backdoor
永远不会发生变异,因此它可以将if
块内的代码丢弃为无法访问。
你没有提到你是如何编译这个程序的,但如果你在没有优化的情况下进行编译以使用gdb
并且在你不打算使用gdb
时进行优化,那么你不应该对未定义的行为的处理方式感到惊讶。另一方面,即使您在两种情况下使用相同的编译器和选项编译程序,您仍然不应该感到惊讶,因为未定义的行为正如所述,未定义。
将backdoor
声明为volatile
可能会阻止优化。虽然这不重要,是吗?
注意:我正在使用gcc版本4.8.1和clang版本3.4。不同的版本(甚至不同的版本)可能会有不同的结果。