push same value onto stack and ret behaves differently

感情迁移 提交于 2021-01-29 14:31:24

问题


In x-86, if you push a value from a register (for example, %eax), and then return, the program transfers control to the address corresponding to the value at %eax, to my understanding.

In another run of the program, if you edited the code so that you pushed onto the stack through a different way (for example, such as dereferencing a register, moving that value to another register, and then pushing this register), and then return, the program should also transfer control to the address corresponding the value pushed right?

In the two different runs of the program, if the values pushed were equivalent (even if they were not pushed the same way), then would the program behave the same way in each run?

I can't show my code, but I wanted to make sure my conceptual thinking was correct as mine throws errors in one case but not another. Thanks!

ex 1:

func1:
/* should push address onto stack when calling */
call func2
...
...

func2:
/* should pop address and transfer control back to func1 */
ret

ex 2:

func1:
...
...

func2:
/* %eax contains value equivalent to address from the first example */
pushl %eax

/* should pop %eax and transfer control to address contained in %eax */
ret

func2 should also return to func1, right? but second example does not work


回答1:


Yes you can mismatch call/ret and do them manually (at a large performance code), but you have to emulate them correctly (including what they do to the stack pointer) if you want to not break anything.

but second example does not work

push / ret is equivalent to jmp to an absolute address. (Except for performance: it always causes a branch mispredict and mismatches the call/ret predictor).

You jump to the right place (presumably) but you forgot to pop the return address off the stack. So you "return" with ESP pointing to the wrong place. That can easily lead to a crash in most callers; their own ret may pop the wrong return address.

(A caller that was using EBP as a frame pointer and didn't access anything relative to ESP before leave / ret might not notice. e.g. if the caller's asm was generated by a C compiler in debug mode. Even with that, modern Linux requires 16-byte stack alignment before a call, and function calls made after calling your func2 broke its ESP would no longer be made with 16-byte aligned stack. Some libc functions may crash when you violate the ABI that way.)


Normally you'd describe things as:

  • call target = push $retaddr ; jmp target ; retaddr:
  • ret = pop %tmp ; jmp *%tmp. It's an indirect branch.
    (Think of ret as a way to write pop %eip)

But yes if a function only has one caller, you could hardcode the return address and use add $esp, 4 / jmp after_call instead of ret. (Again breaking branch prediction for future rets by not using ret to return from a call.)

Or replace the caller's call with a jmp, and jmp back. This effectively makes func2 a block that's that part of func1 for some ways of looking at it. It can't be called from anywhere else because it doesn't take a return address.

Taking a return address (i.e. removing it from the stack before jumping back) but ignoring it and always jumping to a hard-coded location in func1 doesn't seem useful.



来源:https://stackoverflow.com/questions/58351556/push-same-value-onto-stack-and-ret-behaves-differently

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!