直接跳转到函数的某些部分

时间:2017-03-30 01:18:58

标签: c assembly x86

我想知道如何完成这个难题。我需要能够打印通常反向打印的内容。通常这会打印hello there。我需要它来打印there hello。我只允许在评论的地方添加代码。

以下是我的一些不起作用的想法。

  • 我考虑过使用堆存储一些数据,但我不能,因为stdlib.h没有包含在内。
  • 无法使用goto,因为我无法添加标签,而goto只能在一行中添加
  • 无法使用递归,因为没有输入参数,也没有要修改的全局变量。
  • 不可能想到汇编可以帮助但是嘿嘿?
  • 我不能做任何明显的事情,比如只是打电话给printf并提前退出程序。
  • 关于与函数指针有关的想法?我仍然看不出他们会如何帮助。
#include <stdio.h>
void f1(); void f2(); void f3();

int main() { f1(); printf("\n"); return 0; }

void f1() { f2(); printf(" there "); }

void f2() { f3(); printf(" hello "); }

void f3(){
    int x;
    //Can add whatever under here
}

4 个答案:

答案 0 :(得分:2)

我认为int x;的唯一目的是在不使用内联汇编的情况下获取堆栈指针。

关于如何完全执行此操作的解决方案将取决于您的平台,您使用的编译器以及您使用的优化级别。

我首先要说你需要分析调用堆栈。

你可以这样做 -

int i;
for (i = 0; i< 10; i++) {
     printf ("%p\n", *(void**)((char*) &x - i * 8)); // I am assumming 64 bit machines. If 32 bit replace 8 with 4
}

这将为您提供堆栈上的前10个8字节值。现在你需要找到两个看起来像返回地址的东西。识别它们的一种方法是打印f1和f2的函数指针值,并查看接近它们的值。

您现在知道存储它们的索引。继续交换它们。

对于交换它们,请说索引是12和14。

然后你可以这样做 -

*(void**)&x = *((void**)&x + 12);
*((void**)&x + 12) = *((void**)&x + 14);
*((void**)&x + 14) = *(void**)&x;

另外,请确保在获得索引后不要更改堆栈布局。这意味着不要删除/添加任何变量。不要申请&amp;运算符到任何新变量(或从中删除)并且不删除任何函数调用。

另外一个建议 - 你可以声明另一个unsigned long long y而不是使用int x,而不是使用int x。因为它有足够的字节来保存指针(在64位机器上)。通常在int x的情况下也会有填充,这样可以避免问题,但是安全。

答案 1 :(得分:2)

这是一个不依赖于堆栈操作的替代解决方案。根本&#39;诀窍&#39;是程序提供自己的printf()实现,而不是使用标准库。

在gcc(Mingw,Linux x86和Linux x64)和MSVC上测试:

#include <stdio.h>
void f1(); void f2(); void f3();

int main() { f1(); printf("\n"); return 0; }

void f1() { f2(); printf(" there "); }

void f2() { f3(); printf(" hello "); }

void f3(){
    int x;
    //Can add whatever under here
    return;
}

void putstr( char const* s)
{
    for (;*s;++s) {
        putchar(*s);
    }    
}


int printf(char const* fmt, ...)
{
    static char const* pushed_fmt = 0;

    if (*fmt == '\n') {
        putstr(fmt);
        return 0;
    }

    if (pushed_fmt == 0) { 
        pushed_fmt = fmt;
        return 0;
    }

    putstr(fmt);
    putstr(pushed_fmt);

    return 0;
}

答案 2 :(得分:1)

我认为这个想法是从f3()开始,堆栈上的返回地址一直向上,到目前为止还没有打印过。

你必须使用堆栈内容,以便f3()返回f1(),然后返回到f2()。

你能从这里拿走吗?根据编译器的不同,将有不同的方法来实现此目的。内联汇编可能需要也可能不需要。

编辑:专门针对GCC,请参阅the GCC return address intrinsics

答案 3 :(得分:1)

这应该是一个可移植的解决方案,不会弄乱堆栈和返回地址。很可能不是那些写这个挑战的人所期望的,但开箱即用的方式更有趣。

void f3(){
    int x;
    //Can add whatever under here
    static count = 0;
    static char buf[256];
    if(count==0) {
        setvbuf(stdout, buf, _IOFBF, sizeof(buf));
        int atexit (void (*func)(void)); 
        atexit(f3);
        count = 1;
    } else {
        const char *src = " there  hello \n";
        char *dest = buf;
        for(; *src;) *dest++ = *src++;
    } 
}

http://ideone.com/S4zMHP

这首先使用setvbufstdout缓冲区替换为我们提供的缓冲区,然后将其切换为完全缓冲(而不是行缓冲),以确保在结束之前不会发生刷新该程序(注意尚未写入任何输出,因此调用setvbuf是合法的)。我们还调用atexit以确保在程序结束之前调用(我们没有stdlib.h,但是当已知所需的原型时谁需要头文件)。

当我们再次被调用时(感谢atexit),已经调用了两个printf,但缓冲区尚未被刷新。我们用我们感兴趣的字符串(它和已经写好的字符串一样)直接替换它的内容,然后返回。随后的隐式fclose将转储我们缓冲区的修改内容,而不是printf所写的内容。