Jump to content
Tuts 4 You

Is there any difference on offset between call & jmp on x64?


CreateAndInject

Recommended Posts

CreateAndInject

See the following code, Way 1 can work on both x86 & x64, but Way 2 can only work on x86.

Why does Way 2 fail? How to fix Way 2?

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace HookTest
{
    unsafe class Program
    {
        public static string MyReadLine() => "test";

        [DllImport("kernel32")]
        public static extern bool VirtualProtect(IntPtr address, int size, int newProtect, out int oldProtect);

        static void Main()
        {
            var oldMethod = typeof(Console).GetMethod("ReadLine");
            var newMethod = typeof(Program).GetMethod("MyReadLine");
            RuntimeHelpers.PrepareMethod(oldMethod.MethodHandle);
            var oldAddress = oldMethod.MethodHandle.GetFunctionPointer();
            var newAddress = newMethod.MethodHandle.GetFunctionPointer();
            VirtualProtect(oldAddress, 20, 0x40, out _);

            // Way 1 : jmp offset
            // Marshal.WriteByte(oldAddress, 0xE9); // jmp
            // Marshal.WriteInt32(oldAddress, 1, checked((int)((long)newAddress - (long)oldAddress - 5))); // offset

            // Way 2 : call offset + ret
            Marshal.WriteByte(oldAddress, 0xE8); // call 
            Marshal.WriteInt32(oldAddress, 1, checked((int)((long)newAddress - (long)oldAddress - 5))); // offset
            Marshal.WriteByte(oldAddress, 5, 0xC3); // ret

            string str = Console.ReadLine();
            Console.WriteLine(str);
            Console.ReadKey();
        }
    }
}

 

Link to comment

The 2nd way only has 4 bytes (32bits) length to "jump" to, so of course in x64 you would need 8 bytes (64bits) to properly "jump" to the location when using call. In x86 it of course works fine as thats expected that the E8 call instruction byte is followed by the 32bit value. Also check out short/near jumps and this: https://stackoverflow.com/questions/5757866/what-does-short-jump-mean-in-assembly-language

Link to comment
CreateAndInject
1 hour ago, fearless said:

The 2nd way only has 4 bytes (32bits) length to "jump" to, so of course in x64 you would need 8 bytes (64bits) to properly "jump" to the location when using call. In x86 it of course works fine as thats expected that the E8 call instruction byte is followed by the 32bit value. Also check out short/near jumps and this: https://stackoverflow.com/questions/5757866/what-does-short-jump-mean-in-assembly-language

Don't agree. Both E8(call) & E9(jmp) need 4 bytes rather than 8 bytes even if on x64. You can debug in any debugger.

Edited by CreateAndInject
Link to comment
CreateAndInject
1 hour ago, h4sh3m said:

Hi

Maybe it's because your call destroying stack !!!!

you can use "push xyz, ret" instead (xyz = destination address).

I already succeed by `jmp offset` and `push address + ret` on both x86 and x64 before, now, I want to know why `call offset + ret` failed on x64 and how to fix it.

Edited by CreateAndInject
Link to comment

What do you exactly mean with "Way 2 fail" ? What does exactly happen (or doesn't)?
If you mean that the called routine crashes or gives wrong results, you should pay attention to stack requirements.

Waiting for the experts, of course.

However, as far as I can tell, on x64 usually you have to reserve 0x20 bytes on the stack (add rsp,0x20) before calling a routine. In other words the callee expects it has 4 [additional] qwords at the top of the stack when called. 

Hope this helps.

Regards,
Tony

 

Edited by tonyweb
  • Like 1
Link to comment
Quote

// Way 2 : call offset + ret

Marshal.WriteByte(oldAddress, 0xE8); // call

Marshal.WriteInt32(oldAddress, 1, checked((int)((long)newAddress - (long)oldAddress - 5))); // offset

Marshal.WriteByte(oldAddress, 5, 0xC3); // ret

0x400000 call 0xE8 0x11223344

0x400005 ret 0xC3

The return belongs in the Detour proc
 

call Detour

...

proc Detour

   nop

ret

 

Or use ...

mov rax, Detour

call rax // or jmp rax

Link to comment

Tiny changes to code:
 

        public static string MyReadLine()
        {
        return "test";
        }

           Just for seeing what's going on:

            Console.WriteLine("oldAddress="+oldAddress.ToString("X8"));
            Console.WriteLine("newAddress="+newAddress.ToString("X8"));
            long diff = (long)newAddress-(long)oldAddress;
            Console.WriteLine(diff.ToString("X8"));

difference was not to big so it can be handled by 32 bits value. 0xE8 + 4 bytes should be just fine for x64 also.
I don't know why it isn't working!

000007FEE01F0B20 | 48 83 EC 28                               | sub rsp,28                                                       |
000007FEE01F0B24 | E8 67 C7 FF FF                            | call mscorlib.ni.7FEE01ED290                                     |
000007FEE01F0B29 | 4C 8B D8                                  | mov r11,rax                                                      |
000007FEE01F0B2C | 49 8B 03                                  | mov rax,qword ptr ds:[r11]                                       |
000007FEE01F0B2F | 48 8B 90 B8 00 00 00                      | mov rdx,qword ptr ds:[rax+B8]                                    |
000007FEE01F0B36 | 49 8B CB                                  | mov rcx,r11                                                      |
000007FEE01F0B39 | 48 8B C2                                  | mov rax,rdx                                                      |
000007FEE01F0B3C | 48 83 C4 28                               | add rsp,28                                                       |
000007FEE01F0B40 | 48 FF E0                                  | jmp rax                                                          |

it will becomes after patches:

000007FEE01F0B20 | E8 0B B5 E3 1F                            | call 7FF0002C030                                                 |
000007FEE01F0B25 | C3                                        | ret                                                              |
000007FEE01F0B26 | C7                                        | ???                                                              |
000007FEE01F0B27 | FF                                        | ???                                                              |
000007FEE01F0B28 | FF 4C 8B D8                               | dec dword ptr ds:[rbx+rcx*4-28]                                  |
000007FEE01F0B2C | 49 8B 03                                  | mov rax,qword ptr ds:[r11]                                       |

kernelbase.dll
000007FEFD9931F0 | 66 90                                     | nop                                                              |
000007FEFD9931F2 | CC                                        | int3                                                             |
000007FEFD9931F3 | C3                                        | ret                                                              |

If I attach x64dbg to the program it just gives an kernelbase.dll int3 exception.

 

 

Link to comment
CreateAndInject

I was told `call offset + ret` is completely equal to `jmp offset`.

This seems right on x86, I'm puzzle why x64 is different.

Is `offset` here has different meaning between x86 & x64 when it's negative number? Can it also work on x64 when the offset is positive number? 

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...