Перехват OutputDebugStringA вызывает исключение

Моя цель — перехватить 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;
}

Моя функция вызывается, например: Консольный вывод

Однако там он ломается, и я получаю исключение: Exception

И это приводит меня к файлу fflush.c: fflush.c

Я проверяю, что есть у 0x77020c02 (из исключения), и вижу это: Disassembly

Судя по последнему изображению, я предполагаю, что это может быть проблема с восстановлением контекста и / или очисткой, но... честно говоря, я понятия не имею, почему это происходит, я раньше подключал такие функции (не окна), и я не было проблем.


Примечание: я не использую такие хуки в реальном коде, я просто пытаюсь сделать это без внешней помощи, такой как MS Detours.

Я немного новичок, поэтому любое объяснение будет очень признательно. :)


person rev    schedule 04.07.2014    source источник
comment
Очевидным недостатком является то, что аргумент передается дважды (сначала вызывающим абонентом, а затем вами), но всплывает только один раз.   -  person Hans Passant    schedule 04.07.2014
comment
@HansPassant хорошо, я исправил это, удалив push, но теперь я не могу передать аргумент функции. Я пытался получить ebp, ebp+4 и ebp+8 (этот должен быть хорошим), но когда я смотрю на напечатанные адреса в памяти, я вижу случайный мусор вместо строки, которую я передал.   -  person rev    schedule 04.07.2014
comment
Вы не можете удалить толчок, который производит мусор. Это толчок в коде вызывающей стороны, о котором вы не позаботились, вы не можете его удалить. Если вы действительно хотите заново изобрести это колесо, то хотя бы посмотрите, как другие решили эту проблему.   -  person Hans Passant    schedule 04.07.2014
comment
@HansPassant Я имею в виду, что я удалил свой собственный толчок, потому что, как вы сказали, он был отправлен дважды, поэтому я удалил свой. Затем он один раз нажимается вызывающим абонентом и один раз выталкивается, так что это должно быть правильно. Пожалуйста, поправьте меня, если я ошибаюсь.   -  person rev    schedule 04.07.2014


Ответы (1)


я решил это

Прежде всего, спасибо Хансу Пассанту, который помог мне решить мою первую аварию.


Решение

Кажется, зацепить заглушку было вовсе не хорошей идеей. Вместо того, чтобы перехватывать заглушку kernel32.dll, я перехватывал настоящую функцию KernelBase.dll. Просто откройте DLL в IDA, найдите OutputDebugStringA, и вы найдете функцию: OutputDebugStringA

Если вы посмотрите на него, вы увидите, что .text:7D8634A4 хранит lpOutputString в ecx, что нам и нужно. Поскольку это всего 3 байта, его нельзя JMPed, поэтому я перехватил следующий 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);
}

И ты понял! :)

person rev    schedule 04.07.2014
comment
LPCSTR — это просто псевдоним для const char*. - person Remy Lebeau; 05.07.2014
comment
@RemyLebeau Я вижу, но у меня он разбился с char*. Может быть, const char* отличается от char*? - person rev; 06.07.2014
comment
Между const char* и char* нет никакой разницы. const не изменяет тип данных, на которые он указывает, а только то, разрешено ли коду изменять данные во время выполнения или нет. - person Remy Lebeau; 07.07.2014
comment
Спасибо, а с чего бы тогда зависать? - person rev; 07.07.2014