Единый интерфейс для любой подключенной функции

Я пишу простую программу внедрения и перехвата Dll, и все было в порядке, когда я вручную объявлял функции для перехвата следующим образом для CreateFileA:

HANDLE WINAPI Hook_CreateFileA(
    LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile
)
{
    ...

    return CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
        dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}

Но теперь мне нужно написать функцию, которая обрабатывает любую функцию из kernel32.dll, например. Это означает, что я ничего не знаю о функции, кроме ее имени и, следовательно, адреса.

Я немного знаю о соглашении о вызовах - параметры функции помещаются в прямом порядке (__stdcall), а затем вызывается функция. Я попытался написать функцию __declspec(naked), которая выглядит так:

PVOID __declspec(naked) HookAnyFunction()
    {
        /* do something */

        __asm {
            mov ebx, [esp]
            add esp, 4
            call pfnFuncAddr
            sub esp, 4
            mov[esp], ebx
            ret
        }
    }

pfnFuncAddr — адрес исходной функции. Но вылетает приложение с внедренной Dll. Я предполагаю, что мой код портит стек или что-то в этом роде. Что я делаю неправильно? Надеюсь, мое объяснение имеет смысл.


person Gleb Ignatev    schedule 14.05.2017    source источник
comment
Ну, вы уничтожаете регистр ebx вызывающего абонента, что может привести к сбою. Не уверен, что есть хороший способ обойти это, заметьте.   -  person Harry Johnston    schedule 15.05.2017
comment
если вы хотите иметь контроль после pfnFuncAddr, вам нужно заменить исходный адрес возврата (в [esp]) на выделенную исполняемую заглушку памяти, которая начинается с инструкции call до вашего обработчика почтового вызова. а затем должны быть некоторые данные - сохраненный исходный обратный адрес, имя функции, сохраненные параметры. в результате в обработчике сообщения вы получили адрес этого блока (это будет обратный адрес из инструкции call в заглушке) - можете отправить лог, включая возвращаемое значение, изменить его, если хотите. наконец, освободите блок-заглушку и вернитесь к исходному обратному адресу.   -  person RbMm    schedule 15.05.2017
comment
куски-заглушки (все они имеют одинаковый размер) лучше всего размещать в начале в большом исполняемом блоке памяти и реализовывать выделение/освобождение с помощью InterlockedPopEntrySList / InterlockedPushEntrySList   -  person RbMm    schedule 15.05.2017


Ответы (1)


ebx не является изменчивым, вы не можете просто писать в него, вы должны сохранить/восстановить исходное значение.

Написание общей функции ловушки будет трудным, потому что 32-битный Windows ABI имеет 3 соглашения о вызовах; stdcall (вызываемый очищает стек), cdecl (вызывающий очищает стек) и fastcall (первые два параметра в регистрах, но в общедоступном API он мало используется).

Если вы просто хотите регистрировать вызовы функций, вы, вероятно, могли бы сделать что-то вроде этого:

push esp
push pfnFuncAddr
call mylogger ; assumed to be stdcall in this case, it can also change the jump
jmp eax

и FARPROC __stdcall mylogger(FARPROC function, SIZE_T stackaddress) { ...; return function; } (помните, что esp изменилось, поэтому вам нужно настроить параметр стека на 8+4 байта, если вы хотите регистрировать содержимое стека). чтобы универсальный регистратор знал, находятся ли первые параметры в стеке или в регистрах.

person Anders    schedule 15.05.2017
comment
Спасибо! Это определенно то, что мне нужно! - person Gleb Ignatev; 15.05.2017