1

I'm currently working on a project and it involves replacing a function by another. Let's say I have this simple function I want to hook:

int bad_func(int a, int b) {
    return a * b;
}

I want to replace it with the following method:

int good_func(int a, int b) {
    return a + b;
}

At the moment here is how I proceed:

  1. Attach to the process with ptrace
  2. Get the address of bad_func in the code segment
  3. Inject mmap2 syscall to allocate a new rwx memory segment
  4. Copy the assembly code of good_func to the newly allocated segment
  5. Replace the first bytes of bad_func with a JMP good_func instruction

All of this is working great, I can confirm that the child process goes into the newly allocated memory and execute good_func. The problem is that it never returns to the rest of the code that hasn't been executed yet.

Since both methods contains the following instructions,

push %ebp
mov  %esp, %ebp
...
pop  %ebp
ret

I suppose that I don't need to save/change the ebp and esp registers for it to work, but I guess I'm wrong...

Does anyone know how I can achieve my goal?

Many thanks in advance for your time.

faku
  • 401
  • 3
  • 18
  • I suppose you've forgotten to store `good_fun` function return address in stack. You can try to replace your `JMP good_func` to `call good_func` off cause it should looks like `leal $good_function_address,%eax; call *%eax` Check [this](https://www.cs.uaf.edu/2012/fall/cs301/lecture/09_24_call_and_ret.html) – Victor Gubin Jun 26 '18 at 13:30
  • I replaced the `jmp` instruction by `call` but it still has the same behaviour... Furthermore, even if it had worked, I don't think the `call` instruction is the way to go. Indeed, it would execute the `good_func` function and then come back to `bad_func` and execute its code. Correct me if I'm wrong – faku Jun 26 '18 at 13:47
  • 1
    When you patch bad_func, I presume you are patching it with a relative jump. The address that the jump is relative to is the one following the jump opcode, so the patch should be 0xea, 0xcd, good_func_addr - ((long)bad_func+6). It is easy to get this wrong, and have the program ‘appear to almost work’ because it lands on a nearby opcode that messes up the stack. – mevets Jun 26 '18 at 17:09
  • Correct me if I'm wrong but the opcode of the relative `jmp` x86 instruction is `0xE9` and not `0xEA 0xCD` thus the instruction length is 5. Nonetheless, I tried your solution and it didn't work.. I don't even reach `good_func` anymore – faku Jun 26 '18 at 19:15
  • @mevets: faku is right, you want a [5-byte `jmp rel32`](http://felixcloutier.com/x86/JMP.html). Replacing the first 5 bytes of `bad_func` should do the trick, turning it into a tail-call of `good_func`. (like the compiler would create if you compiled `int bad_func(a,b) { return good_func(a,b); }` with optimization enabled. – Peter Cordes Jun 26 '18 at 19:36
  • If you replace the very first bytes of the instruction (and not just the bytes after the prologue like GDB takes you to when you set a breakpoint), the previous contents of `bad_func` don't matter *at all*: they don't execute. You should single-step your code with `gdb`. Use `si` to single-step instructions. See the bottom of [the x86 tag wiki](https://stackoverflow.com/tags/x86/info) for gdb asm tips. – Peter Cordes Jun 26 '18 at 19:37
  • See [How does $ work in NASM, exactly?](https://stackoverflow.com/q/47494744) for an example of manually encoding branch displacements with NASM (`jmp` will be the same as `call`, just a different opcode.) – Peter Cordes Jun 26 '18 at 19:40
  • @PeterCordes As I said before, I already replaced the very first bytes of `bad_func` with a `call good_func` instruction but no success... About single-stepping my code with gdb, can I do that even if I'm in a tracer process and don't have access to the tracee? At the moment, I implemented a simple debug function that prints the state of the `ebp` register for each instruction once I hit the newly allocated segment I mentioned in my question. Since it's pretty late here, I didn't have the courage to debug deeply but it seems like there is a problem with the stack. – faku Jun 26 '18 at 20:16
  • It needs to be a `jmp`, not a `call`. Victor was wrong. Otherwise you're pushing a new bogus return address that will take you back into the middle of the function you've overwritten, instead of tail-calling. re: debugging: have you tried triggering a core dump? IDK if you can get it to include the modified text segment, though. But that would let you check with external tools (like a disassembler or GDB) what the jump target address is. – Peter Cordes Jun 26 '18 at 20:20

0 Answers0