我有一个奇怪的代码片段,无法理解:

push 0xC           ; arg1 for call
mov ecx,edi        ; set the this pointer for call
call sdk.100039F0  ; make the call (internally calls DeviceIOControl)
push ecx           ; ECX now points to a function within kernelbase.dll
mov ecx,edi        ; set the this pointer for call
call sdk.10003BD0  ; make the call


在第一个函数调用中调用DeviceIoControl会修改ECX。为什么将它作为第二个调用的参数?第二个调用本身不接受任何参数,它根本不引用[ebp + n],但是它仍然以ret 4结尾。 。第三次通话之前没有神秘的推动。所有这些功能(包括容器)都使用DeviceIOControl

以防万一我错过了一些东西,这是第二个调用的内容:

push ebp
mov ebp,esp
sub esp,0x30
push ebx
push esi
mov eax,ecx
xor esi,esi
push edi
mov edi,dword ptr ds:[<&DeviceIoControl>]
mov ecx,sdk.10002690
mov dword ptr ss:[ebp-0x8],eax
mov edx,0x8000
test ecx,ecx
je aura_sdk.10003C35
mov ax,word ptr ds:[eax+0x4]
push 0x0
mov word ptr ss:[ebp-0x20],ax
lea eax,dword ptr ss:[ebp-0x14]
push eax
push 0x4
lea eax,dword ptr ss:[ebp-0x4]
mov dword ptr ss:[ebp-0x4],0x0
push eax
push 0x7
lea eax,dword ptr ss:[ebp-0x20]
mov byte ptr ss:[ebp-0x1A],0x1
push eax
push 0x80102050
push dword ptr ds:[0x100375C0]
call edi
mov bl,byte ptr ss:[ebp-0x4]
mov ecx,sdk.10002690
mov edx,0x8000
jmp sdk.10003C37
xor bl,bl
test bl,0x9E
jne sdk.10003C8A
test ecx,ecx
je sdk.10003C7D
push 0x0
mov eax,0xED
mov dword ptr ss:[ebp-0xC],0x0
mov word ptr ss:[ebp-0x28],ax
lea eax,dword ptr ss:[ebp-0x18]
push eax
push 0x4
lea eax,dword ptr ss:[ebp-0xC]
mov byte ptr ss:[ebp-0x22],0x1
push eax
push 0x7
lea eax,dword ptr ss:[ebp-0x28]
push eax
push 0x80102050
push dword ptr ds:[0x100375C0]
call edi
mov ecx,sdk.10002690
mov edx,0x8000
mov eax,dword ptr ss:[ebp-0x8]
inc esi
cmp si,dx
jb sdk.10003BF0
test bl,0x82
je sdk.10003CA3
pop edi
test bl,0x1C
mov eax,0x0
pop esi
sete al
pop ebx
mov esp,ebp
pop ebp
ret 0x4


这是编译器自行纠正的情况还是有目的的?

评论

我可以想象ecx在第一个调用中被保存和恢复,并且可能被用作第三个函数的参数吗?我们需要看更多我猜的代码...

@NirIzr第一个函数调用几乎完全相同,只是它对DeviceIOControl进行了更多调用。内部使用ECX来计算基于arg1的一些变量,但最终,最终的DeviceIOControl调用将其设置为kernelbase.dll函数指针。请不要让我的低分给人以我是一个新手的印象;)如果您想要第一个功能代码,我会发布它,但是请相信我,ECX是该呼叫的this指针,该指针已复制到EDI在允许将ECX用作普通寄存器之前。

抱歉,如果我的评论不屑一顾,那绝对不是我的意图!我要说的是,感觉好像缺少ecx周围的代码,而且我猜测我们需要看到更多代码才能找到答案。
如您所知,ASM可能占用很多行。因此,我尽量保持其必要性。我只是感到奇怪的是,在调用之后不应该包含任何有意义值的寄存器仅在下一次调用时才被推送以清理堆栈,甚至不看该值。我猜编译器正在经历一瞬间。

@twifty欢迎来到RE.SE!好了,编译器可能会决定调用约定,如果零件是用汇编编写的,那么所有的赌注都无效。有趣的是edi中的内容以及第二次调用之后的内容。但是,我们可以告诉我们的是,第二个调用使用了ecx值,这与我们在this调用中看到的类似(除了它看起来不像是vtable访问)。但是第二次通话后会发生什么?该程序是否真的只是在尝试保存寄存器值并随后弹出呢?

#1 楼

这看起来像编译器优化的结果。第二个被调用方(一个fastcall函数)接受一个显然不使用的参数。编译器处于无法修改第二个调用的调用约定的情况,因此它仍必须采用一个参数并将其从堆栈中删除。最有可能的是,编译器无法证明该函数未被无法调整的外部代码调用。但是,编译器可以通过删除函数计算参数并推入伪参数来使用已知的知识,即函数在编译调用方时不使用其参数。 push ecx是一个单字节指令,因此是提供所需的哑元参数的一种非常有效的方法。

对于处于释放模式的Visual C ++,您应该知道默认情况下链接时代码生成已启用。如果应用程序和SDK都使用链接时代码生成进行编译,则“目标文件”实际上包含该代码的某些抽象中间表示形式,并且当所有具有链接时代码的库都在链接时实际转换为二进制代码启用生成,并合并应用程序。即使目标文件或静态库是彼此独立生成的,这也可以实现参数省略。

评论


我觉得很奇怪,编译器将使用未编译的函数中未使用的变量的信息。

– NirIzr
18年8月31日在18:26

@NirIzr好点。也许可以用我对答案的解释来解释。另外,我认为我们不知道调用者是否也是SDK的一部分,在这种情况下,编译器实际上可能同时编译了所有三个函数

– Michael Karcher
18年8月31日在18:30



鉴于所有四个函数都使用此调用,并且相对较小,我想说它们都是从同一源文件编译的。如果有人要自己查看,则SDK是公共代码。

–二十
18年8月31日在19:15