我在应用程序中创建了一个DLL钩子 绕过这样的功能:
typedef void (WINAPI *pUCPackets)(int a1, int a2, char* a3, int a4, int a5);
void WINAPI MyUCPackets(int a1, int a2, char* a3, int a4, int a5);
pUCPackets MyUC2Packets = (pUCPackets)(0x408050);
(...) some irrelevant code (...)
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)MyUC2Packets, MyUCPackets);
if(DetourTransactionCommit() == NO_ERROR)
cout << "[" << MyUCPackets << "] successfully detoured." << endl;
然后我尝试通过以下方式在绕行函数的参数内显示值:
void WINAPI MyUCPackets(int a1, int a2, char* a3, int a4, int a5)
{
printf( "%d ", a5 );
printf("%d\n", a2);
return MyUC2Packets(a1, a2, a3, a4, a5);
}
但是当调用该函数并显示参数时,应用程序崩溃 但是,如果我只是离开这个函数:
void WINAPI MyUCPackets(int a1, int a2, char* a3, int a4, int a5)
{
//no prints whatsoever
return MyUC2Packets(a1, a2, a3, a4, a5);
}
它正常运行。为什么会这样?
OLLY coderipper:
Gate_00408050: ;<= Procedure Start
MOV EDX,DWORD PTR SS:[ESP+0xC]
PUSH EBP
PUSH EDI
MOV EDI,ECX
XOR EBP,EBP
MOV CL,BYTE PTR DS:[EDI+0x21C]
TEST EDX,EDX
JBE Gate_004080F0
MOV EAX,DWORD PTR DS:[EDI+0x218]
PUSH EBX
PUSH ESI
MOV DWORD PTR SS:[ESP+0x1C],EDX
Gate_00408074:
MOV EDX,DWORD PTR SS:[ESP+0x14]
DEC EAX
TEST EAX,EAX
MOV DL,BYTE PTR DS:[EDX]
JLE Gate_004080A5
LEA ESI,DWORD PTR DS:[EDI+EBP+0xEC7D]
Gate_00408086:
MOV BL,BYTE PTR DS:[ESI+EAX]
CMP BL,DL
JA Gate_00408091
SUB DL,BL
JMP Gate_00408097
Gate_00408091:
NOT BL
INC BL
ADD DL,BL
Gate_00408097:
MOV BL,BYTE PTR DS:[ESI+EAX+0xFFFF8AD0]
XOR DL,BL
DEC EAX
TEST EAX,EAX
JG Gate_00408086
Gate_004080A5:
MOV AL,BYTE PTR DS:[EDI+EBP+0xEC7D]
CMP AL,DL
JA Gate_004080B4
SUB DL,AL
JMP Gate_004080BA
Gate_004080B4:
NOT AL
INC AL
ADD DL,AL
Gate_004080BA:
MOV AL,BYTE PTR DS:[EDI+EBP+0x774D]
MOV EBX,DWORD PTR SS:[ESP+0x14]
XOR AL,DL
MOV EDX,DWORD PTR SS:[ESP+0x18]
XOR AL,CL
MOV BYTE PTR DS:[EDX],AL
XOR CL,AL
MOV EAX,DWORD PTR DS:[EDI+0x218]
ADD EBP,EAX
INC EBX
INC EDX
MOV DWORD PTR SS:[ESP+0x14],EBX
MOV DWORD PTR SS:[ESP+0x18],EDX
MOV EDX,DWORD PTR SS:[ESP+0x1C]
DEC EDX
MOV DWORD PTR SS:[ESP+0x1C],EDX
JNZ Gate_00408074
POP ESI
POP EBX
Gate_004080F0:
POP EDI
POP EBP
RETN 0xC ;<= Procedure End
答案 0 :(得分:3)
MyUC2Packets
的签名可能不正确。由于函数使用stdcall调用约定,因此需要在返回之前清理堆栈。如果使用错误数量的参数调用其中一个函数,则返回时堆栈指针将不正确。
删除print语句时不会发生这种情况的原因是编译器可能将转发调用优化为单个jmp
指令。当包含print语句时,detour函数实际上有工作要做,并在返回之前用不正确的值调整堆栈。如果MyUC2Packets
需要6个参数但功能签名只需要5个参数,那么无论何时迂回功能都无法优化,这都会导致问题。
下面的代码通过模拟示例中的绕行设置来演示这一点。被挂钩的函数需要4个参数,但绕行只需要3个。它模拟来自客户端的调用,期望一个函数需要4个参数。
#include <stdio.h>
#include <ios>
#pragma inline_depth(0)
typedef void (WINAPI *Function3)(int, int, int);
typedef void (WINAPI *Function4)(int, int, int, int);
void WINAPI FinalFunction(int x, int y, int z, int q);
void WINAPI DetourFunction(int x, int y, int z);
void WINAPI DetourFunctionPrint(int x, int y, int z);
Function3 callFinalFunction = reinterpret_cast<Function3>(FinalFunction);
Function4 callDetourFunction = reinterpret_cast<Function4>(DetourFunction);
Function4 callDetourFunctionPrint = reinterpret_cast<Function4>(DetourFunctionPrint);
void WINAPI FinalFunction(int x, int y, int z, int q)
{
std::cout << x << " " << y << " " << z << " " << q << std::endl;
}
void WINAPI DetourFunction(int x, int y, int z)
{
callFinalFunction(x, y, z); // Optimzed to a single jmp instruction.
}
void WINAPI DetourFunctionPrint(int x, int y, int z)
{
printf("%d", x);
printf("%d\n", y);
callFinalFunction(x, y, z);
}
int main()
{
// This works
callDetourFunction(0, 1, 2, -1);
// This does not
callDetourFunctionPrint(0, 1, 2, -1);
return 0;
}