我试图基本上在dll中修补一个远调用到我自己的地址,而不是一个已经编译的。
这是我试图修补的字节:
{ 0xFF, 0x15, 0x30, 0x20, 0x00, 0x10 }
应该翻译成:call DWORD PTR ds:0x10002030
我认为这意味着:call [0x10002030]
是的,我正试图将对IAT函数的实际调用修补为其他东西。因为iat本质上是存储一个整数,这个整数稍后会被赋值给api函数的实际地址,为了调用函数,我们必须从iat中解引用给我们的地址。
现在,因为我想把这个修改成我自己的函数,我不需要去引用任何东西。现在的问题是原始的解引用调用是6字节长,所以我需要保持在6字节或更低。
我试图从本质上找到一种方法来调用一个地址,这不是相对的(重要!),不在我所在的当前模块内,最后在小于或等于6字节的时间内完成。
我想要这样的东西:
call 0xDEADBEEF
,但我不希望0xDEADBEEF是一个相对地址。我想直接调用0xDEADBEEF
重申一下,我需要专门修补这个调用,没有其他方法。在这种情况下,一个正常的IAT钩子是不能胜任的。
Edit:
我一直在进一步调查,似乎
push 0xdeadbeef
ret
可能工作,但我发现了一个问题。因为我必须自己处理返回,我不能在6字节内完成。但我想我有办法。
原呼叫来自:call [0x10002030]
To:
push jFunction
ret
和jFunction本质上是一个存根的排序:
mov eax, 0xDEADBEEF
call eax
但现在的问题是我如何回到原来的执行?
使用简单的
pop ebx
ret
使JMP返回到原来的main函数,而不是返回到ret后面的指令。
正如我之前所说的,因为我正在使用的包装器,我不能使用相对偏移量,所以在这种情况下,我不能确定主函数开始和我想要去的指令之间的相对偏移量。我不能把想要的地址推入"jFunction",因为我只有6个字节要处理。
我开始认为没有相对地址这是不可能的。
这是我目前的测试代码:x86(调试)MSVC 2015我使用内联汇编和__declspec(裸)来轻松调试解决方案,而不是修补我需要的和测试。
当前测试代码
您知道要修补的call
指令的地址,因此计算0xdeadbeef - start_of_next_insn
以获得正常的近直接相对call
(5字节)的正确rel32
位移。
另一个不太疯狂的选择是替换内存中的函数指针,或者改变寻址模式以从其他地方加载指针,但使其成为一个简单的直接调用显然是最好的。
这总是在32位模式下工作,但在64位模式下,两个地址可能相距超过+-2GiB(超出rel32的范围)。但是我们知道你处于32位模式,因为FF 15 4bytes
解码为绝对寻址模式。在64位模式下,这是一个与rip相关的函数指针。RIP-relative取代了[disp32]
寻址模式下的no-SIB编码。
这几乎是x86机器码中调用绝对指针的副本,主要是为了有效地回答地址填充,而不是在call
之前或之后放置NOP。
用0x67地址大小的前缀填充它(这将没有效果),使其成为6字节。GNU ld
已经在将间接call
放松为接近直接的call
时做到了这一点,对于objdump -d /bin/ls
,我可以看到一些addr32 call ...
指令,所以这是安全的(CPU供应商不能在不破坏大量现有二进制文件的情况下将67 E8
的含义更改为一些新指令)。
一般来说,假设前缀在将来没有影响是不安全的(例如rep bsr
在支持它的cpu上作为lzcnt
运行),但是这种情况是安全的,因为现有的使用,如rep ret
。