I'll start with briefly going over the code for completeness's sake even though OP clearly understands what's going on and mostly asks about the reasoning behind it.
The first snippet of code can be easily written like the following in C:
dword var_4 = &func1 - &func2 - 5;
This piece of code, by itself, raises a few questions we'll answer in a bit but first lets dig a little deeper into the second assembly snippet:
mov edx, [ebp+func2]
mov [edx], 0E9h ;E9 is opcode for jmp
The first byte of func2
is set to 0xE9
, which is the opcode for a "Jump near, relative, immediate" jump.
mov eax, [ebp+func2]
mov ecx, [ebp+var_4]
mov [eax+1], ecx
Then, the next four bytes of func
(1 through 5) are set to the offset previously calculated in the first snippet.
Now, this may raise a couple of questions:
why is the offset then decreased by 5
?
This is done because a relative jump is relative to the next instruction, thus subtracting 5 removes the 5 additional bytes of the jump instruction itself. A more accurate way of looking at it is that the offset should be calculated from &func2 + 5
. The original equation (&func1 - &func2 - 5
) is obviously identical to &func1 - (&func2 + 5)
.
Why do we care so much about instruction length to begin with?
So, as some people here already implied, the length of a hook jump is important. That is very much true (although does not tell the whole reason behind the relative jump preference). The length of the hook (or jump sequence) is important because it can create weird edge cases. This isn't just about some minor performance optimization or keeping things simple, as one might assume.
One big consideration is that you'll need to replace any instructions you overwrite. Those bytes you use for your jump had a meaning. And they have to be preserved somewhere. Overwriting more bytes means you have to copy more of them elsewhere. With relative instructions on the original instruction sequence fixed, for example. You'll need to make sure you do not leave half-instructions after you.
why use a relative jump and not an absolute address?
Sorry it took a while to get here ;D
I think a lot of people overlook or forget that over time, but as carefully reviewing the jump instruction will reveal, the x86 jump opcodes lacks a near, immediate, absolute jump.
We've got three different types of jumps in x86:
E9
for near immediate offsets (offsets hard coded directly as an integer inside the instruction itself).
FF /4
for near absolute jumps.
- we've got
EA
for far immediate absolute jumps.
The far jump opcode (EA
) is slow and mostly used for changing segment registers (which have a completely different use in protected mode), it is therefore rarely used as a normal jump per-se, but as a call-gate, for switching between execution contexts, etc.
The absolute address jump opcode (FF /4
) does not accept an immediate value. It can only jump to a value stored in a register or stored in memory.Therefore, using it will require you either:
- Storing the absolute offset at some reserved memory space, specifically allocated by the hook routine for each hook function for that purpose, or
- Hard-coding an register load instruction, which will set a register to the absolute value. Something like
mov eax, <absolute value> / jump eax
or push <absolute value> / ret
.
Understanding this, it is clear that using the near, immediate, relative jump is far easier than both of these approaches.
So although it is accurate to say using an absolute address will require longer instruction sequence, it does not tell the whole story.
This, then raises another question:
Why, then, isn't there a near, immediate, absolute jump in x86?
Simple answer is that there just isn't one. One can speculate about the reasoning behind the instruction set design decisions but adding instructions is expensive and complex. I assume there was no real need for near absolute immediate jump, as it is indeed a rare occasion where you need to jump to an address known ahead of time and a relative jump won't do.