Как передать метод в качестве обратного вызова для вызова Windows API (последующие действия)?

Этот пост является продолжением связанного вопрос, размещенный здесь пользователем Ran.

принятый ответ придерживается использования обычной простой старой функции.

Этот отрывок особенно привлек мое внимание:

Метод экземпляра имеет дополнительный неявный параметр, содержащий ссылку на экземпляр, то есть Self.

С твердым убеждением, что должен быть способ использовать своего рода адаптер «параметров» (чтобы перефразировать избавиться от ненужной неявной ссылки на Self и предоставить указатель на соответствующую адаптированную функцию обратного вызова), я в конечном итоге нахожу это статья под названием Обратный вызов класса от Питер Моррис.

Подводя итог, он использует технику thunking как адаптационный трюк. (Отказ от ответственности: я никогда не тестировал код).

Я знаю, что это не очень чистое решение, но оно позволяет использовать объектно-ориентированный дизайн со всеми предполагаемыми преимуществами.

Мой вопрос:

Зная, что TCallbackThunk основан на сигнатуре функции обратного вызова, каков был бы ответ в упомянутом выше сообщении, если бы поступить так, как это сделал Питер Моррис?

.


person menjaraz    schedule 13.02.2012    source источник
comment
Я бы добавил, что некоторые обратные вызовы API позволяют обходной путь с помощью lParam, а некоторые нет (например, WinNLS на иллюстрации). Кроме того, это должно называться StdcallThunk или что-то в этом роде.   -  person OnTheFly    schedule 13.02.2012
comment
Это не сработает, потому что созданный код не будет находиться на исполняемой странице. Вы можете решить эту проблему. Но тогда у вас будет код, который может работать только на x86 и не работает на x64.   -  person David Heffernan    schedule 13.02.2012
comment
@ Дэвид Хеффернан: Спасибо. Моя коробка - x86, и подойдет любой код, работающий в Delphi 5, 7, 2007, XE. Я должен отредактировать пост.   -  person menjaraz    schedule 13.02.2012
comment
Вам нужно будет использовать VirtualAlloc для выделения исполняемой страницы. Не делайте ничего глупого и сделайте свой стек исполняемым! Но на самом деле я не могу понять, чем такой подход лучше стандартной функции.   -  person David Heffernan    schedule 13.02.2012
comment
что приведет к сбою проиллюстрированного кода, так это DEP.   -  person OnTheFly    schedule 13.02.2012
comment
@ user539484: Спасибо за редактирование.   -  person menjaraz    schedule 13.02.2012


Ответы (1)


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

Чтобы адаптировать ответ для использования кода Морриса, сначала необходимо убедиться, что сигнатура метода обратного вызова соответствует сигнатуре функции обратного вызова API. Поскольку мы вызываем EnumWindows, нам нужна функция с двумя аргументами, возвращающая Bool. Соглашение о вызовах должно быть stdcall (поскольку код Морриса предполагает его, и трудно использовать любое другое соглашение о вызовах).

function TAutoClickOKThread.cbEnumWindowsClickOK(
  Wnd: HWnd; Param: LParam): Bool; stdcall;
begin
  // ...
end;

Затем мы настраиваем структуру данных TCallbackThunk со всем машинным кодом и смещением перехода, относящимся к предполагаемому методу обратного вызова.

Однако мы не используем способ, описанный Моррисом. Его код помещает структуру данных в стек. Это означает, что мы помещаем исполняемый код в стек. Современные процессоры и операционные системы больше этого не позволяют — ОС остановит вашу программу. Мы могли бы обойти это, вызвав VirtualProtect для изменения разрешений текущей страницы стека, разрешив ее выполнение, но это делает всю страницу исполняемой, и мы не хотим оставлять программу открытой для атаки. Вместо этого мы выделим блок памяти специально для записи thunk, отдельно от стека.

procedure TAutoClickOKThread.Execute;
var
  Callback: PCallbackThunk;
begin
  Callback := VirtualAlloc(nil, SizeOf(Callback^),
    Mem_Commit, Page_Execute_ReadWrite);
  try
    Callback.POPEDX := $5A;
    Callback.MOVEAX := $B8;
    Callback.SelfPtr := Self;
    Callback.PUSHEAX := $50;
    Callback.PUSHEDX := $52;
    Callback.JMP := $E9;
    Callback.JmpOffset := Integer(@TAutoClickOKThread.cbEnumWindowsClickOK)
      - Integer(@Callback.JMP) - 5;

    EnumWindows(Callback, 0);
  finally
    VirtualFree(Callback);
  end;
end;

Обратите внимание, что в этой записи это 32-битные x86-инструкции. Я понятия не имею, какими будут соответствующие инструкции x86_64.

person Rob Kennedy    schedule 13.02.2012
comment
+1: Очень поучительно! Прежде чем я приму ответ, я просто хочу убедиться в двух вещах, учитывая, что код Морриса стареет. 1) Корректировка указателя по-прежнему действительна (изменения VMT/TObject между версиями) 2) То же самое с жестко заданной инструкцией ASM. Должен признаться, я мало знаю о ASM и BASM. Спасибо. - person menjaraz; 13.02.2012
comment
Теперь я ясно понимаю, почему внедрение кода может поставить под угрозу безопасность. - person menjaraz; 13.02.2012
comment
Регулировка указателя предназначена для компенсации размера инструкции JMP. Смещение в инструкции JMP относится к инструкции следующая (поскольку EIP продвигается вперед до того, как происходит вычисление адреса). Это не имеет ничего общего с размерами или объектами VMT. Инструкции X86 не изменились. - person Rob Kennedy; 13.02.2012