挂钩OutputDebugStringA引发异常

时间:2014-07-04 11:30:07

标签: c++ c windows winapi

我的目标是挂钩OutputDebugStringA,以便我可以读取传递给它的任何消息。我不是试图挂钩任何其他进程,而是试图挂钩当前进程,只是为了学习如何挂钩。

我没有使用Detours,我更喜欢不使用它们,因为我想学习如何在更深层次上工作。


我使用此代码挂钩:

void MakeJMP(BYTE *pAddress, DWORD dwJumpTo, DWORD dwLen);
void myCallback(LPCSTR s);
DWORD dwAddr = (DWORD) GetProcAddress(GetModuleHandleA("kernel32.dll"), "OutputDebugStringA");
// I cast it to byte and later to DWORD so it adds 5 
DWORD dwRetAddr = ((BYTE)dwAddr) + 5; // +5 because I will override 5 bytes

void __declspec(naked) My_OutputDebugStringA(){
    __asm{
        mov edi, edi
        push ebp
        mov ebp, esp
        // ^those 5 bytes are overriden from the original OutputDebugStringA stub from kernel32.dll, so I restore them here
        pushad;
        pushfd; // to prevent stack messing
        push [ebp+8]; // OutputDebugStringA takes a LPCSTR parameter which is ebp + 8 (KernelBase.dll)
        call myCallback; // call my callback to print the string
        popfd;
        popad;
        jmp [dwRetAddr]; // jump back to next instruction so execution continues
    }
}

int _tmain(int argc, _TCHAR* argv[]){
    printf("OutputDebugStringA address is: %8X\nPress any key to hook...", dwAddr);
    system("pause > nul");
    MakeJMP((BYTE*) dwAddr, (DWORD) My_OutputDebugStringA, 5);
    puts("Hooked. Press any key to call it...\n");
    system("pause > nul");
    printf("Calling OutputDebugStringA (%8X) with \"hi\"\n", dwAddr);
    OutputDebugStringA("hi");
    //puts("Called\n");
    //system("pause");
    return 0;
}

void myCallback(LPCSTR s){
    printf("\n===Inside hook!===\nParam address is %8X", &s);
}

void MakeJMP(BYTE *pAddress, DWORD dwJumpTo, DWORD dwLen){
    DWORD dwOldProtect, dwBkup, dwRelAddr;
    VirtualProtect(pAddress, dwLen, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    dwRelAddr = (DWORD)(dwJumpTo - (DWORD)pAddress) - 5;
    *pAddress = 0xE9;
    *((DWORD *)(pAddress + 0x1)) = dwRelAddr;
    for (DWORD x = 0x5; x < dwLen; x++) *(pAddress + x) = 0x90;
    VirtualProtect(pAddress, dwLen, dwOldProtect, &dwBkup);
    return;
}

我的函数被调用,如下所示: Console output

然而,它在那里打破,我得到一个例外: Exception

它引导我访问文件fflush.cfflush.c

我检查0x77020c02有什么(来自例外),我看到了: Disassembly

从上一张图片中,我想这可能是恢复上下文和/或刷新的问题,但是......老实说,我不知道为什么会发生这种情况,我已经挂钩了函数(非窗口)喜欢这样,我没有问题。


注意:我没有在实际代码中使用这种钩子,我只是在没有MS Detours等外部帮助的情况下尝试这样做。

我有点新,所以任何解释都会非常感激。 :)

1 个答案:

答案 0 :(得分:1)

我解决了它

首先,感谢Hans Passant,他帮我解决了我的第一次撞车事故。


解决方案

似乎挂钩存根根本不是一个好主意。我挂钩kernel32.dll的真实功能,而不是挂钩KernelBase.dll的存根。只需在IDA中打开DLL,搜索OutputDebugStringA,您就会找到该功能: OutputDebugStringA

如果你看一下,你会在.text:7D8634A4中看到lpOutputString存储ecx,这是我们需要的。因为它只有3个字节,所以不能JMP,所以我挂钩了下一个mov(OutputDebugStringA + 0x12),结果如下:

DWORD _temp; // here we'll store our address
void __declspec(naked) My_OutputDebugStringA(){
    __asm{
        mov _temp, ecx; // store ecx, or lpOutputString
        pushad; // preserve stack
        pushfd;
        call myCallback; // call our function
        popfd;
        popad; // pop to restore context
        mov dword ptr ss:[ebp-0234h], ecx; // restore overwritten function
        jmp [dwRetAddr]; // go back to caller + original instruction size
        // ^here the mov instruction was 6 bytes, so it'd be hookAddr+6
    }
}

然后,这里是捕捉。你可能会检查一些内存查看器中的地址是否正确,并且看到它确实包含正确的字符串,但是如果你*(char*) _temp读取它,你就会崩溃。原因是该函数需要LPCSTR而不是char*,如下所示:

void OutputDebugStringA(LPCSTR lpOutputString);

最后,你写下你的回调:

void myCallback(){
    printf("%s", (LPCSTR) _temp);
}

你明白了! :)