如何破坏C程序中的堆栈

时间:2017-03-18 23:39:17

标签: c stack function-calls

我必须更改function_b的指定部分,以便它以程序打印的方式更改堆栈:

Executing function_a
Executing function_b
Finished!

此时它还会在Executed function_bExecuting function_b之间打印Finished!

我有以下代码,我必须在其中所说的部分填写内容// ...在此处插入代码

#include <stdio.h>

void function_b(void){
char buffer[4];

// ... insert code here

fprintf(stdout, "Executing function_b\n");
}

void function_a(void) {
int beacon = 0x0b1c2d3;
fprintf(stdout, "Executing function_a\n");
function_b();
fprintf(stdout, "Executed function_b\n");
}

int main(void) {
function_a();
fprintf(stdout, "Finished!\n");
return 0;
}

我正在使用带有gcc编译器的Ubuntu Linux。我使用以下选项编译程序:-g -fno-stack-protector -fno-omit-frame-pointer。我正在使用英特尔处理器。

2 个答案:

答案 0 :(得分:0)

这是一个解决方案,在各种环境下并不完全稳定,但适用于Windows / MinGW64上的x86_64处理器。 它可能不适合你开箱即用,但你仍然可能想要使用类似的方法。

void function_b(void) {
    char buffer[4];
    buffer[0] = 0xa1; // part 1
    buffer[1] = 0xb2;
    buffer[2] = 0xc3;
    buffer[3] = 0x04;
    register int * rsp asm ("rsp"); // part 2
    register size_t r10 asm ("r10");
    r10 = 0;
    while (*rsp != 0x04c3b2a1) {rsp++; r10++;} // part 3
    while (*rsp != 0x00b1c2d3) rsp++; // part 4
    rsp -= r10; // part 5
    rsp = (int *) ((size_t) rsp & ~0xF); // part 6
    fprintf(stdout, "Executing function_b\n");
}

诀窍是function_afunction_b中的每一个只有一个局部变量,我们只需在内存中搜索即可找到该变量的地址。

  1. 首先,我们在缓冲区中放置一个签名,让它成为4字节整数0x04c3b2a1(记住x86_64是little-endian)。

  2. 之后,我们declare两个变量来表示寄存器:rsp是堆栈指针,而r10只是一些未使用的寄存器。 这允许以后不在代码中使用asm语句,同时仍然可以直接使用寄存器。 重要的是变量实际上并不占用堆栈内存,它们是对处理器寄存器本身的引用。

  3. 之后,我们以4字节为增量移动堆栈指针(因为int的大小为4字节),直到我们到达buffer。我们必须记住堆栈指针到第一个变量的偏移量,我们使用r10来存储它。

  4. 接下来,我们想知道堆栈中function_bfunction_a的实例有多远。一个很好的近似值是bufferbeacon的距离,因此我们现在搜索beacon

  5. 之后,我们必须从beacon的第一个变量function_a推回到堆栈上整个function_a的实例的开头。 我们通过减去r10

  6. 中存储的值来完成
  7. 最后,这里有一个werider位。 至少在我的配置中,堆栈恰好是16字节对齐,而buffer数组对齐到16字节块的左边,beacon变量对齐到右边的这样的块。 或者是具有类似效果和不同解释的东西? 无论如何,我们只需清除堆栈指针的最后四位,使其再次对齐16字节。 32位GCC没有为我调整任何内容,因此您可能希望跳过或更改此行。

  8. 在处理解决方案时,我发现以下宏非常有用:

    #ifdef DEBUG
    #define show_sp() \
        do { \
            register void * rsp asm ("rsp"); \
            fprintf(stdout, "stack pointer is %016X\n", rsp); \
        } while (0);
    #else
    #define show_sp() do{}while(0);
    #endif
    

    在此之后,当您在代码中插入show_sp();并使用-DDEBUG进行编译时,它会在相应时刻打印堆栈指针的值。 在没有-DDEBUG的情况下进行编译时,宏只会编译为空语句。 当然,其他变量和寄存器也可以用类似的方式打印。

答案 1 :(得分:0)

好吧,假设}function_a结尾(即function_b行的代码)相同

尽管函数AB不对称,我们可以假设这是因为它具有相同的签名(没有参数,没有返回值),相同的调用约定和相同的局部变量大小(4字节) - int beacon = 0x0b1c2d3 vs char buffer[4];)并且通过优化 - 两者都必须删除,因为未使用。但是我们不能在function_b中使用额外的局部变量来打破这个假设。这里最有问题的一点 - function_Afunction_B将使用非易失性寄存器(结果将其保存在 prologue 中并在结尾中恢复) - 但是看起来这里没有这个地方。

所以我的下一个代码基于这个假设 - epilogueA == epilogueB(@Gassa的真正解决方案也基于它。

还需要非常明确地指出function_afunction_b不能内联。这非常重要 - 没有这个解决方案是不可能的。所以我让你自己为function_afunction_b添加noinline属性。注意 - 不是代码更改而是属性添加,此任务的作者隐含地暗示但未明确说明。不知道 GCC 标记的功能如何作为noinline,但在 CL __declspec(noinline)中用于此。

我为 CL 编译器编写的下一个代码,其中存在下一个内部函数

void * _AddressOfReturnAddress();

但我认为GCC也必须拥有此功能的analog。我也用

void * _ReturnAddress();

但是真的_ReturnAddress() == *(void**)_AddressOfReturnAddress(),我们只能使用_AddressOfReturnAddress()。只需使用_ReturnAddress() make source(但不是二进制 - 它相等)代码更小,更易读。

下一个代码适用于x86和x64。并且此代码使用任何优化工作(测试)。

尽管我使用了2个全局变量 - 代码是线程安全的 - 我们真的可以从并发多个线程中调用main,多次调用它 - 但是所有这些都是正确的(当然我只能在开始时说)如果epilogueA == epilogueB

希望代码中的评论足够自我解释

__declspec(noinline) void function_b(void){
    char buffer[4];

    buffer[0] = 0;

    static void *IPa, *IPb;

    // save the IPa address
    _InterlockedCompareExchangePointer(&IPa, _ReturnAddress(), 0);

    if (_ReturnAddress() == IPa)
    {
        // we called from function_a
        function_b();
        // <-- IPb
        if (_ReturnAddress() == IPa)
        {
            // we called from function_a, change return address for return to IPb instead IPa
            *(void**)_AddressOfReturnAddress() = IPb;
            return;
        }
        // we at stack of function_a here.
        // we must be really at point IPa
        // and execute fprintf(stdout, "Executed function_b\n"); + '}' (epilogueA)
        // but we will execute fprintf(stdout, "Executing function_b\n"); + '}' (epilogueB)
        // assume that epilogueA == epilogueB
    }
    else
    {
        // we called from function_b
        IPb = _ReturnAddress();
        return;
    }

    fprintf(stdout, "Executing function_b\n");
    // epilogueB
}

__declspec(noinline) void function_a(void) {
    int beacon = 0x0b1c2d3;
    fprintf(stdout, "Executing function_a\n");
    function_b();
    // <-- IPa
    fprintf(stdout, "Executed function_b\n");
    // epilogueA
}

int main(void) {
    function_a();
    fprintf(stdout, "Finished!\n");
    return 0;
}