C函数如何不通过传参修改外部变量

void fun_a(void) {
  ....
}
    
int main() {
    int x = 5, y = 6;
    fun_a();

    printf("%d, %d", x, y);
   
    return 0;
}

请问 fun_a 函数内部如何实现能够改变程序输入不为5,6?[一道笔试题,考虑了很久,感觉可以通过指针hack外部变量,但是没有进一步思路]

阅读 4.7k
4 个回答

linux进程内存空间布局图
看上图,这是linux下的进程内存空间布局图,是32位的,64位也差不多,地址不一样而已。
首先要明确一点,就是函数的调用是栈式的,而栈空间的地址是向下的,也就是后入栈的内存地址反而小。并且入栈的地址是对齐的。(这不是绝对的。不同的体系结构下可能是完全不同的)

main函数里面先入栈了y,然后入栈了x,再入栈函数func_a

出入栈顺序
    入main
        入main的返回值
            (因为main没有参数,所以此处不需要入参)
            入y,x       (int宽度对齐)
                入func_a    (指针宽度对齐)
                (func_a没有参数也没有返回值)

这里要在func_a里面修改x/y,就需要得到它们的地址,可以使用以下方式来达到目的。

struct offset{
    void(*pf2)();   // main
    int ret;        // main的返回值
    int x,y;        // x,y占位
    int(*pf1)();    // 表示func_a
};
void fun_a(void) {
  int a;
  // 此处需要注意结构体对齐
  int* py = (int*)((char*)&a + sizeof(offset)+sizeof(void*));
                 //|<-这部分获取到入栈的main->|
  int* px = py-1;
  printf("x = %d\ny = %d\n",*px,*py);
}

如果算偏移影响因素太多(机器字长、编译器优化),只是希望修改程序输出,那直接输出并退出就好了:

void func_a(void) {
    printf("2, 3");
    exit(0);
}

这种题目想从偏移上解决根本就是错误的。如果 func_a 没有副作用,可以直接把 func_a 优化没。即使没有把 func_a 优化没,也完全可以把 func_a 内联,此时根本就没有 push 进去的 return address。退一万步,如果 x, y 在寄存器里,你如何利用偏移修改?
而即使不开优化,编译器之间生成的代码也是不同的。我试图利用所谓偏移的办法解决问题:

#include <cstdint>
#include <cstdio>

void func_a()
{
    int x = 0;
    const auto px = reinterpret_cast<std::uintptr_t>(&x);
    *reinterpret_cast<int*>(*reinterpret_cast<std::uintptr_t*>(px + sizeof(int)) - 8) = 100;
}

int main()
{
    int x = 5, y = 6;
    func_a();
    printf("%d, %d", x, y);
}

我在 gcc.godbolt 里观察汇编代码时,GCC 7.0 把 func_a 里面的 x 放在 DWORD PTR [rbp-12],而 clang 3.8 放在 dword ptr [rbp - 4]。也就是说,上面的代码是不能在 clang 和 GCC 同时跑的。
我不认为这种题目有什么价值(当然,如果努力写出满篇 Undefined Behavior 以及不可移植的代码算是价值的话,那还是有价值的)。在这道题上,我赞同 exit(0) 的方案。

说时迟,那时快,我又想出一种办法:

#include <cstdio>

void func_a()
{
    #define printf(...) puts("hahah")
}

int main()
{
    int x = 5, y = 6;
    func_a();
    printf("%d, %d", x, y);
}
//通过溢出来破坏函数栈帧。
//这其实是一种非侵入debug技术的原型。
#include <stdio.h>

void func_a(void) {
 int point = 0;
 int *s = &point;
 int *x = s+7;
 int *y = x-1;
 *x = 3;
 *y = 4;
}

int main () {
 int x=1,y=2;
 func_a();
 printf("%d,%d\n",x,y);
 return 0;

}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进