CreateAndInject Posted August 5, 2022 Posted August 5, 2022 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(); } } }
fearless Posted August 5, 2022 Posted August 5, 2022 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 1
CreateAndInject Posted August 5, 2022 Author Posted August 5, 2022 (edited) 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 August 5, 2022 by CreateAndInject
h4sh3m Posted August 5, 2022 Posted August 5, 2022 Hi Maybe it's because your call destroying stack !!!! you can use "push xyz, ret" instead (xyz = destination address).
CreateAndInject Posted August 5, 2022 Author Posted August 5, 2022 (edited) 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 August 5, 2022 by CreateAndInject
tonyweb Posted August 6, 2022 Posted August 6, 2022 (edited) 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 August 6, 2022 by tonyweb 1
maluc Posted August 7, 2022 Posted August 7, 2022 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
CodeExplorer Posted August 7, 2022 Posted August 7, 2022 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.
CreateAndInject Posted August 7, 2022 Author Posted August 7, 2022 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?
evlncrn8 Posted August 9, 2022 Posted August 9, 2022 call means put current address + 5 (size of the e8 xx xx xx xx opcode) on the stack and jump to the code... the ret would clean it up upon return (which it probably wont reach) , the call messed up the params passed by adding another to it... the return address of the call... they are NOT the same stack on hitting your code looks like this (example with 3 params) param3 param2 param1 so a jmp keeps that intact if you call its like this param3 param2 param1 returnaddress as for your e8 offset confusion... the opcode is e8 xx xx xx xx, where xx xx xx xx is a signed dword, so the range is current offset +/- 2 gigs ish.... if the address is out of range you need to get creative with jmp qword ptr / call qword ptr 1
CreateAndInject Posted August 10, 2022 Author Posted August 10, 2022 2 hours ago, evlncrn8 said: call means put current address + 5 (size of the e8 xx xx xx xx opcode) on the stack and jump to the code... the ret would clean it up upon return (which it probably wont reach) , the call messed up the params passed by adding another to it... the return address of the call... they are NOT the same stack on hitting your code looks like this (example with 3 params) param3 param2 param1 so a jmp keeps that intact if you call its like this param3 param2 param1 returnaddress as for your e8 offset confusion... the opcode is e8 xx xx xx xx, where xx xx xx xx is a signed dword, so the range is current offset +/- 2 gigs ish.... if the address is out of range you need to get creative with jmp qword ptr / call qword ptr But why `call offset + ret` always work on x86?
h4sh3m Posted August 10, 2022 Posted August 10, 2022 (edited) 1 hour ago, CreateAndInject said: But why `call offset + ret` always work on x86? did you tried all type of functions ?! functions that have more than 3 parameters (specially in stdcall calling convention) ? Edited August 10, 2022 by h4sh3m
CreateAndInject Posted August 10, 2022 Author Posted August 10, 2022 13 hours ago, h4sh3m said: did you tried all type of functions ?! functions that have more than 3 parameters (specially in stdcall calling convention) ? Thanks, you're right. `call offet + ret` can only work if there're less than 3 parameters AND on x86 So `call offset + ret` != `jmp offset` (`jmp offset` allways work) So it's coincidence which `call offet + ret` can work if there're less than 3 parameters AND on x86?
h4sh3m Posted August 10, 2022 Posted August 10, 2022 seems you're testing just with fastcall/cdecl calling conventions, this method will not work even for functions with 1 parameter in stdcall calling convention.
CreateAndInject Posted August 10, 2022 Author Posted August 10, 2022 1 hour ago, h4sh3m said: seems you're testing just with fastcall/cdecl calling conventions, this method will not work even for functions with 1 parameter in stdcall calling convention. Thanks, I remove this method, and keep `jmp offset` and `push address + ret`.
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now