我在这里使用二进制混淆,所以有一个充满操作码的缓冲区,我使用的是 Linux,所以,所有函数调用都使用相同的调用方/被调用方约定,这里没有问题。
我的问题是关于 E8 操作码,该操作码使用相对地址接收近似调用。
我的问题是:我知道呼叫来自的地址,我知道我必须呼叫的地址,那么,我如何找到必须在E8呼叫中输入的班次地址?这是:
signed long src = (signed long)buffer + shift; //get the position where E8 instruction is
signed long dst = (signed long)srand; //get the destination position where i want to call (yes, srand(long) function in this case.)
所以在我的缓冲区中,我有:
buffer[] = "[....]xE8xFFxFAxFEx54[.....]"; //example
我需要替换为指向 srand 的有效指针,如何从我所拥有的中获取相对地址?
我只是想我可以使用FF指令直接调用,但是我不知道该怎么做。我无法将地址复制到(说)$eax因为我不能在替换中输入超过 5 个操作码(它会让上面的所有 jmp 调用都变成香蕉),而且我不明白是否有办法在 5 字节内直接调用。
因此,如果有人知道如何获得正确的值来替换 E8 相对移位地址,或者是否有一种方法可以进行某种直接调用,保持与 E8 调用相同的功能属性并且仅使用 5 个字节......
(在询问之前,我试图将 FF XX XX XX 作为 XX 作为真实地址,但它不起作用,x86 看起来不像一个电话,它解释为 INC(???)和一些随机的东西之后。我尝试以这种方式替换:
inline void endian_swap(long& x) {
x = (x>>24) |
((x<<8) & 0x00FF0000) |
((x>>8) & 0x0000FF00) |
(x<<24);
}
endian_swap(dst);
endian_swap(src);
unsigned int p = dst - src;
endian_swap(p);
并将我找到的地址输入E8呼叫。反正没用。
near call
和jxx
/near jmp
指令中的相对地址等于您要转移控制权的目标地址减去紧跟在call
或jump
指令之后的指令地址。相对地址相对于下一条指令的地址,而不是转移控制权的地址。IOW,您必须考虑call
或jump
指令的长度(如果其地址操作数是相对的)。
通常,没有等效于 5 个字节或更短的 call
或 jump
指令。
您可以将jmp
模拟为 push target address
+ ret
,但在具有任意目标地址的 32 位模式下,这 2 条指令至少获得 1+4+1=6 个字节。您可以以相同的方式模拟call
,但您将添加另一个push
或call
指令以将返回地址放置在堆栈上。因此,在这 6 个字节中,您再添加 5 个字节。
有一个"绝对"版本的"jmp"(以及IIRC"call"),它将地址操作数作为由目标偏移量和目标段组成的直接操作数。这样的指令长度至少为 1+4+2=7 字节(偏移量为 4 字节,段选择器为 2 字节)。
如果您使用 call
或 jmp
的变体从指定的内存位置获取目标地址(例如 call [ebx]
),该指令的长度至少为 1+1=2 字节(操作码 + ModR/M 字节),但您必须加载一个寄存器,其中包含包含目标地址的该内存位置的地址,这将花费您其他一些 1+4=5 字节,至少给你 7 个字节。还有一个变体允许您在寄存器中指定目标地址(例如 jmp ebx
),但同样,由于必须加载寄存器,您至少需要 7 个字节。
使call
/jump
指令更短的唯一方法是当目标地址非常接近该指令的地址时(在这种情况下,您可以使用rel16
形式(具有适当的操作数或地址(我不记得是哪一个)覆盖前缀)或rel8
形式(如果可用))或目标地址较小(在这种情况下push target address
可以是较短的 push Ib
或较短的operand size prefix + push Iw
)。
我通过以下方式解决了它:
long dst = (long)srand;
long src = ((long)buffer) + shift + 5; //begin of buffer + actual position + this instruction size
long p = dst - src;
p = htonl(p);
比我替换缓冲区上的调用,一切正常。