C中的堆栈操作,不使用内联汇编

我一直在准备编码竞赛,并在互联网上遇到了这个问题:

#include  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 } 

解决方案是编写将打印出“hi there”而不是“there hi”的代码(不能使用额外的打印function,代码只能放在注释块中)。

由于我已经完成了一些基本的汇编编码,我意识到这可以通过使用整数x作为基础的堆栈操作来完成。

我尝试使用gdb查找函数的返回地址,然后交换ab的返回地址。 编译器抛出一个分段错误,因此我假设我没有使用正确的返回地址。

如何正确计算偏移量以找到返回地址? 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); } } 

您将收到有关隐式声明库函数memcpyexit警告。 虽然气馁,但它对C89来说是合法的。 但在你的情况下,我认为没有诡计太肮脏。 您可以通过手动复制字符来避免使用memcpy。 您可以通过freopen重定向stdout来避免exit 。 如果系统具有奇怪的小缓冲区大小(小于9),则可以将BUFSIZ更改为大常量。 这个解决方案的变体不需要你手动插入\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`) 

使用&x我们可以检测到FRAME_C的位置。

一个解决方案是

  1. 在函数c()中打印“Hi”
  2. 修改FRAME_B,使RET_A成为RET_MAIN
  3. 从函数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 */ 

如您所见,您需要了解有关编译器实现的大量详细信息,因此这根本不是一个可移植的解决方案。


其他解决方案是打印“hi,there”并将stdout重定向到/dev/null 。 我认为不允许使用exit()或其他编译器依赖技巧,否则问题对于竞赛没有意义。

我的解决方案是针对x86 / x64和CL编译器,但是想想gcc也存在。

仅限问题 – 存在等效于函数:

void ** _AddressOfReturnAddress();

__declspec(noinline)等效 – 告诉编译器永远不要内联特定的函数

let void* pb – 在c();之后的void b()地址c();void* pab();之后的void a()地址b();

因为ab几乎相同 – 我们可以假设

(ULONG_PTR)pa - (ULONG_PTR)&a == (ULONG_PTR)pb - (ULONG_PTR)&b;

当然, ab堆栈布局必须相同。 基于此和解决方案。 下一个代码测试/使用CL编译器 – 在x86 / x64(windows)和/Ox (Full Optimization)/Od (Disable (Debug)) – 都工作。

 extern "C" void ** _AddressOfReturnAddress(); void a(); void b(); void c(); int main() { a(); printf("\n"); return 0; } __declspec(noinline) void a() { b(); printf("hi "); } __declspec(noinline) void b() { c(); printf("there "); } __declspec(noinline) void c() { void** pp = _AddressOfReturnAddress(); void* pb = *pp; void* pa = (void*)((ULONG_PTR)&a + ((ULONG_PTR)pb - (ULONG_PTR)&b)); for (;;) { if (*++pp == pa) { *pp = pb; *_AddressOfReturnAddress() = pa; return; } } }