【问题标题】:Assembler instructions to modify the return address within the called function汇编指令修改被调用函数内的返回地址
【发布时间】:2019-07-17 03:27:36
【问题描述】:

我是汇编程序的新手,我仍然不知道大部分指令。

我正在尝试在 C 程序中添加一些 asm 行(只是为了好玩,玩弄它)来修改函数的返回地址。

C 代码看起来像

int my_function()
{
    int my_number = 1;

    __asm__ ("nop"); 
    __asm__ ("nop"); 

    return my_number;
}

int main(int argc, char *argv[])
{
    //declarations

    ...lots of stuff

    int my_number = my_function();

    do_something;

    ...lots of stuff

    do_other_thing;

    ... lots of stuff (46 bits in assembler)

    return 0;
}

所以,我尝试做的是在my_function 上修改一次堆栈中的返回地址,所以,返回时,它转到do_other_thing 而不是do_something

为了使它更好,动态地,我不喜欢硬编码返回地址,所以我想添加这 46 位。我也知道返回地址在EBP + 4。我已经在x32dgb 中手动测试过,它可以工作。

我想我必须这样做:

  1. 获取 EBP + 4 指向的内容(可能在 EAX 中)
  2. 将 46 与 EAX 相加
  3. 将此值写回 EBP + 4

到目前为止我已经理解了,但不确定如何将其编码为 ASM 语句......

你能帮帮我吗?

【问题讨论】:

  • my_function() 中,您可以使用 x=46 位执行 add dword [EBP+4], x。但是 46 位很可能是错误的......也许是 46 字节或其他东西 - 无论调用后和 do_other_thing 之间的区别是什么。
  • 并确保编译器选项-fomit-frame-pointer 未激活。
  • GCC 有 __builtin_return_address(0)读取当前函数的返回地址 (How can I determine the return address on stack?)。我想我记得有一个内置函数来获取返回地址的 address of,所以你可以用 C 来修改它,但我做不到这样的事情。那是有道理的,您可能应该在纯 asm 中编写上下文切换函数。在没有 asm 的情况下,你几乎无法用这样的东西做有用的事情,而在 asm 中编写一个完整的函数意味着你不必保存/恢复调用破坏的寄存器。
  • @zx485 这将是一个很好的解决方案,但在我的情况下它不起作用,语法上的东西??
  • 错误:表达式后出现垃圾 `[ebp+4]'

标签: assembly x86 32-bit


【解决方案1】:

@Brendan 提到,使用内联汇编似乎很危险:

对于用 C 编写的代码;编译器拥有堆栈,编译器生成适合自身及其优化的函数启动代码,编译器生成退出代码以适合自己的启动代码。任何对堆栈布局做出任何假设的内联汇编都是“脆弱的”......

但是假设“返回地址被放置在当前函数的堆栈帧的正上方”,也许你可以尝试这样的事情:

void foo(void)
{
    uintptr_t *p_return_address = __builtin_frame_address(0) + sizeof(size_t);
    *p_return_address = /* the new return address */;
}

尽管如此,如果foo由于修改了返回地址而没有返回给它的调用者,那么调用者的结语将永远不会被执行,进而导致许多其他令人讨厌的问题,例如堆栈溢出。所以,这仍然是一件非常危险的事情。

【讨论】:

    【解决方案2】:

    请注意 C 中的编码和符号

    正如@zx489 提到的,尝试类似:

    __asm__(".intel_syntax prefix");
    __asm__("add dword ptr [%ebp + 4], 46");
    

    这可能有用...

    【讨论】:

    • 需要-fno-omit-frame-pointer(这只是-O0的默认值)。当然也需要__attribute__((noinline))函数。此外,除非您使用-masm=intel 编译,否则您需要将模式设置回 AT&T。使用带前缀的 intel 语法非常奇怪,并且总是会破坏编译器的其余代码生成。 (除非这是文件中的最后一个函数,并且在此之后只有一个 leave/ret。)
    【解决方案3】:

    我正在尝试在 C 程序中添加一些 asm 行(只是为了好玩,玩弄它)来修改函数的返回地址。

    不要。

    对于用 C 编写的代码;编译器拥有堆栈,编译器生成适合自身及其优化的函数启动代码,编译器生成退出代码以适合自己的启动代码。任何对堆栈布局做出任何假设的内联汇编都是“脆弱的”——任何地方的微小变化(甚至只是更改一个无辜的局部变量、更改编译器命令行参数或将编译器更新到下一个版本)都可能并最终导致堆栈布局的差异将破坏内联汇编。在实践中它实际上比这更糟糕 - 编译器可以并且将生成同一个“C函数”的多个不同版本(并将某些版本内联到其他函数中以避免函数调用的成本等),所以你最终得到尝试(并保证会失败)处理多个完全不同的堆栈布局的同一块内联程序集;即使您确实成功地返回了其他地方,您也无法可靠地避免在函数返回后搞砸一切。

    另一个问题是,几乎从来没有一个理智的理由想要首先这样做。即使您正在编写可以完全控制堆栈布局的汇编语言(而不是使用无法控制堆栈布局的内联汇编语言编写 C)。

    基本上;对于试图学习汇编以花时间学习的人来说,有很多事情更重要(这些事情不是“脆弱的”,也不是没用的)。这就像学习如何用手站立驾驶汽车(将脸放在油门/刹车踏板上,但看不到周围的任何东西)——没有用处。

    【讨论】:

    • 我是赞成票之一。我建议在 gnu.org 上链接到 @DavidWohlferd 文章:Don't use inline Asm
    • "另一个问题是,几乎从来没有一个理智的理由要首先这样做;即使您正在编写可以完全控制堆栈布局的汇编语言(而不是使用无法控制堆栈布局的内联汇编语言编写 C)。”
    • 为了好玩,只是玩耍、学习、教育,我认为这些都是合理的理由,不是吗?
    • 我明白你的意思,我永远不会在生产环境中这样做,谢谢你的链接,
    • 危险就在这里。如果您不从尝试干预编译器或破坏程序开始,那么学习汇编编程会容易得多。这些是高级主题,并且有充分的理由。从基本的东西开始:在汇编源文件中编写自己的汇编代码并了解所有这些是如何工作的。一旦你深入熟悉了汇编和编译器的工作原理,你就可以尝试做一些奇怪的事情。
    猜你喜欢
    • 2015-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多