我一直在准备编码竞赛,并在互联网上遇到了这个问题:
#include <stdio.h>
void a();
void b();
void c();
int main() { a(); printf("\n"); return 0; }
void a() { b(); printf("hi "); }
void b() { c(); printf("there "); }
void c()
{
int x;
// code here and nowhere else
}
Run Code Online (Sandbox Code Playgroud)
解决方案是编写将打印出"hi there"而不是"there hi"的代码(不能使用额外的打印功能,代码只能放在注释块中).
由于我已经完成了一些基本的汇编代码,我意识到这可以通过使用整数x作为基础的堆栈操作来完成.
我试图用gdb找到的函数的返回地址,然后交换的返回地址a和b.编译器抛出了一个分段错误,因此我假设我没有使用正确的返回地址.
如何正确计算偏移量以找到返回地址?gdb上的信息框架命令没有帮助,因为使用给定的堆栈地址值不起作用.
我在Linux上使用gcc运行它.
我不确定以下内容是否会计算在内.它可以在POSIX上移植.基本上你在第一次调用之前更改了printf的缓冲区,并在它被刷新到终端之前进行操作
void c()
{
static int first = 1;
if (first) {
first = 0;
char buf0[BUFSIZ];
char buf1[BUFSIZ];
setvbuf(stdout, buf0, _IOFBF, BUFSIZ);
a();
memcpy(buf1, buf0 + 6, 3);
memcpy(buf1 + 3, buf0, 6);
memcpy(buf0, buf1, 9);
buf0[8] = '\n';
fflush(stdout);
exit(0);
}
}
Run Code Online (Sandbox Code Playgroud)
您将收到有关隐式声明库函数memcpy和警告的警告exit.虽然气馁,但它对C89来说是合法的.但在你的情况下,我认为没有诡计太肮脏.您可以通过手动复制字符来避免使用memcpy.您可以exit通过改为重定向stdout来避免freopen.BUFSIZ如果系统具有奇怪的小缓冲区大小(小于9),则可以更改为大常量.这个解决方案的变体不需要你手动插入它\n,而是让程序正常从main退出并具有printf("\n")放置该行的结尾
这个问题无法解决,除非你像攻击者破坏某个进程的堆栈一样破坏堆栈。
并且只有知道编译器实现的每个细节才能对堆栈进行粉碎,否则问题将无法解决。
如果您知道编译的详细信息(特别是堆栈结构),您可以使用局部 x 变量的地址来从堆栈(FRAME_C)中获取当前帧的地址;每个帧中都是前一帧的基指针并对其进行修改。
堆栈看起来像这样:
FRAME_MAIN = RET_OS some-data
FRAME_A = RET_MAIN some-data
FRAME_B = RET_A some-data
FRAME_C = RET_B some-data(including the variable `x`)
Run Code Online (Sandbox Code Playgroud)
使用&x可以检测FRAME_C 的位置。
一种解决方案是
return棘手的操作是2。但是如果每个帧的大小已知,那么我们可以修改帧B的返回指针RET_A并检测RET_MAIN,如下所示:
*(&x+FRAME_C_SIZE+some-small-offset1) = /* *&RET_A = */
*(&x+(FRAME_C_SIZE+FRAME_B_SIZE)+some-small-offset2). /* *&RET_MAIN */
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,您需要了解有关编译器实现的很多细节,因此这根本不是一个可移植的解决方案。
其他解决方案是打印“hi,there”并将其重定向stdout到/dev/null. 我认为 exit() 或其他依赖于编译器的技巧是不允许的,否则这个问题对于竞赛来说没有意义。