Сейчас на форуме: igorcauret, Rio (+6 невидимых)

 eXeL@B —› Вопросы новичков —› Зачем нужна инструкция MOV EDI,EDI
Посл.ответ Сообщение

Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 22:14
· Личное сообщение · #1

Приветствую.

Почему реализация всех WinAPI-функций начинается с инструкции MOV EDI,EDI?

В интернете (например, тут) это объясняется тем, что данная инструкция позволяет написать двух-байтовый патч в виде JMP $-5, чтобы передать управление коду, который находится на пять байт раньше начала пропатченной WinAPI-функции. Но почему нельзя обойтись без MOV EDI,EDI и заменить JMP'ом другие инструкции?

Вот, допустим, есть функция CreateFileA. Начало её реализации на моей системе имеет следующий вид:

Code:
  1. 77701036 >  8BFF            MOV EDI,EDI
  2. 77701038    55              PUSH EBP
  3. 77701039    8BEC            MOV EBP,ESP
  4. 7770103B    51              PUSH ECX
  5. 7770103C    51              PUSH ECX
  6. 7770103D    FF75 08         PUSH DWORD PTR SS:[EBP+8]
  7. 77701040    8D45 F8         LEA EAX,DWORD PTR SS:[EBP-8]


В результате патчинга JMP'ом с относительным адресом я затру первые три инструкции (MOV EDI, PUSH EBP и MOV EBP,ESP). Но что было бы плохого, если бы тут не было инструкции MOV EDI,EDI? Ну, затёр бы я тогда ещё два PUSH ECX, что в этом такого? Или это сделано, чтобы можно было пропатчить даже те функции, у которых реализация занимает ровно пять байт вместе с этим MOV EDI,EDI? Но даже если так, WinAPI-функции всё равно разделяются между собой NOP'ами, так что проблем с этим быть не должно.

И ещё один момент, связанный с перехватом вызовов WinAPI-функций путём как раз подобного патча. На вики предлагается следующий вариант реализации подобной техники:

void BeginRedirect(LPVOID newFunction)
{
BYTE tempJMP[SIZE] = {0xE9, 0x90, 0x90, 0x90, 0x90, 0xC3}; // 0xE9 = JMP 0x90 = NOP oxC3 = RET
memcpy(JMP, tempJMP, SIZE); // store jmp instruction to JMP
DWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5); // calculate jump distance
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, // assign read write protection
PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(oldBytes, pOrigMBAddress, SIZE); // make backup
memcpy(&JMP[1], &JMPSize, 4); // fill the nop's with the jump distance (JMP,distance(4bytes),RET)
memcpy(pOrigMBAddress, JMP, SIZE); // set jump instruction at the beginning of the original function
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); // reset protection
}

int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType)
{
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, NULL); // assign read write protection
memcpy(pOrigMBAddress, oldBytes, SIZE); // restore backup
int retValue = MessageBoxW(hWnd, lpText, lpCaption, uiType); // get return value of original function
memcpy(pOrigMBAddress, JMP, SIZE); // set the jump instruction again
VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); // reset protection
return retValue; // return original return value
}


Скажите, пожалуйста, зачем после JMP'а они предлагают писать инструкцию RET? Ведь это же безусловный переход на нашу функцию, внутри которой мы вернём оригинальный код WinAPI-функции обратно, и никогда не попадём на этот самый RET. Какой в этом смысл?

Заранее благодарю за возможные ответы.




Ранг: 622.6 (!), 521thx
Активность: 0.330.89
Статус: Участник
_Вечный_Студент_

Создано: 17 июня 2015 22:46 · Поправил: plutos
· Личное сообщение · #2

Читаем тут, может поможет:
http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

-----
Give me a HANDLE and I will move the Earth.




Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 22:50
· Личное сообщение · #3

plutos пишет:
Читаем тут, может поможет:
http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

Так я же и скинул ссылку на эту статью. Собственно, я про неё и написал в начале следующее:
> В интернете (например, тут) это объясняется тем, что данная инструкция позволяет написать двух-байтовый патч в виде JMP $-5, чтобы передать управление коду, который находится на пять байт раньше начала пропатченной WinAPI-функции. Но почему нельзя обойтись без MOV EDI,EDI и заменить JMP'ом другие инструкции?



Ранг: 20.4 (новичок), 8thx
Активность: 0.030
Статус: Участник

Создано: 17 июня 2015 23:01
· Личное сообщение · #4

b0r3d0m пишет:
Но почему нельзя обойтись без MOV EDI,EDI и заменить JMP'ом другие инструкции?

В смысле, первые инструкции, после MOV EDI, EDI? Так ведь их эмулить придется после выполнения перехвата. А так - имеем инструкцию-пустышку, которая не нужна целевой функции. Это, вроде, очевидно!
b0r3d0m пишет:
WinAPI-функции всё равно разделяются между собой NOP'ами, так что проблем с этим быть не должно.

nop для выравнивания, и это уже другой район, который не относиться к хукам. Правда, годный для использования.
b0r3d0m пишет:
зачем после JMP'а они предлагают писать инструкцию RET?

А вдруг CPU заглючит и переход безусловный не сработает Мощности хватит, чтоб на следующую инструкцию прыгнуть Вон, после ExitThread, инструкция int3 стоит. А по хорошему: может туда управление, в конце концов передается.

Я бы по другому поставил вопрос еще: смысл в таких "паразитных" инструкциях, как MOV EDI, EDI. Положить регистр сам в себя?! Бессмысленно и беспощадно.



Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 23:07
· Личное сообщение · #5

hello пишет:
Так ведь их эмулить придется после выполнения перехвата

Зачем? Вот, допустим, в том же примере с вики после выполнения безусловного перехода на нашу функцию мы просто возвращаем изначальные байты оригинальной функции обратно и вызываем её снова.

hello пишет:
nop для выравнивания, и это уже другой район, который не относиться к хукам

Но ведь использовать же их можно, верно?

hello пишет:
А по хорошему: может туда управление, в конце концов передается

Каким образом? Там выполняется безусловный переход с последующим восстановлением оригинального кода перехваченной WinAPI-функции, так что не вижу такой возможности.




Ранг: 622.6 (!), 521thx
Активность: 0.330.89
Статус: Участник
_Вечный_Студент_

Создано: 17 июня 2015 23:13 · Поправил: plutos
· Личное сообщение · #6

b0r3d0m пишет:
Собственно, я про неё и написал в начале следующее:> В интернете (например, тут) это объясняется тем, что данная инструкция позволяет написать двух-байтовый патч в виде JMP $-5, чтобы передать управление коду, который находится на пять байт раньше начала пропатченной WinAPI-функции. Но почему нельзя обойтись без MOV EDI,EDI и заменить JMP'ом другие инструкции?


Ну извините, не догадался, что это именно о ней идет речь. Оно как-то не очевидно.
А самой ссылки не видал. Теперь вижу.

-----
Give me a HANDLE and I will move the Earth.




Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 23:16
· Личное сообщение · #7

plutos пишет:
Ну извините, не догадался, что это именно о ней идет речь

Я ссылку привёл. Ну да ладно, это не важно. В любом случае, спасибо. Сможете, пожалуйста, ответить на вопросы, обозначенные в теме?



Ранг: 431.7 (мудрец), 391thx
Активность: 0.730.32
Статус: Участник

Создано: 17 июня 2015 23:16 · Поправил: dosprog
· Личное сообщение · #8

hello пишет:
Я бы по другому поставил вопрос еще: смысл в таких "паразитных" инструкциях, как MOV EDI, EDI.


Если использовать заполнение обычными NOP, то очевидна "неэффективность" полученного года, видимая "невооружённым" глазом в обычном текстовом редакторе. А так код выглядит солидно, да и дополнительная возможность для проверки сигнатуры.

Вообще, такие вопросы сродни интересу к сигнатуре "MZ" - "почему-да-как?".
Изначально сделали так, а потом изменять это не было нужды.





Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 23:19 · Поправил: b0r3d0m
· Личное сообщение · #9

dosprog пишет:
Если использовать заполнение обычными NOP, то очевидна "неэффективность" полученного года, видимая "невооружённым" глазом в обычном текстовом редакторе

> Why not just use two NOP instructions at the entry point?

Well, because a NOP instruction consumes one clock cycle and one pipe, so two of them would consume two clock cycles and two pipes. (The instructions will likely be paired, one in each pipe, so the combined execution will take one clock cycle.) On the other hand, the MOV EDI, EDI instruction consumes one clock cycle and one pipe. (In practice, the instruction will occupy one pipe, leaving the other available to execute another instruction in parallel. You might say that the instruction executes in half a cycle.) However you calculate it, the MOV EDI, EDI instruction executes in half the time of two NOP instructions

dosprog пишет:
Вообще, такие вопросы сродни интересу к сигнатуре "MZ" - "почему-да-как?".
Изначально сделали так, а потом изменять это не было нужды.

Вопрос возник из-за необходимости патчить несколько интересующих меня WinAPI-функций. Наткнулся, погуглил, не понял причину, решил спросить, чтобы случайно не облажаться на ровном месте.



Ранг: 20.4 (новичок), 8thx
Активность: 0.030
Статус: Участник

Создано: 17 июня 2015 23:20
· Личное сообщение · #10

b0r3d0m пишет:
просто возвращаем изначальные байты

Ну это один из вариантов. Можно и так! Но если первая MOV EDI, EDI -тогда ничего не нужно восстанавливать и эмулировать.
b0r3d0m пишет:
использовать же их можно

Там обычно мизер остаетс. Впрочем, вроде слышал, что в некоторых библиотеках такой подход встречается. Но не у микрософт. Если поставить громадное выравнивание в байт 16, тогда наверняка можно.
Но это всё дело вкуса.




Ранг: 392.8 (мудрец), 108thx
Активность: 0.260.01
Статус: Участник
REVENGE сила, БеХоЦе могила

Создано: 17 июня 2015 23:21
· Личное сообщение · #11

Так по ссылке написано почему mov edi, edi, а не nop

Потому что на два nop надо два такта процессора, а на mov edi, edi один.

А вообще приведенный способ патча в шапке действительно какой-то извращенный.

-----
StarForce и Themida ацтой!




Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 23:22
· Личное сообщение · #12

Maximus пишет:
А вообще приведенный способ патча в шапке действительно какой-то извращенный

А что с ним не так?




Ранг: 392.8 (мудрец), 108thx
Активность: 0.260.01
Статус: Участник
REVENGE сила, БеХоЦе могила

Создано: 17 июня 2015 23:25
· Личное сообщение · #13

Я ссылку на перехват:
jmp моя процедура
jmp -5
Начало реальной процедуры

делаю через WriteProcessMemory с ProcessID = -1

-----
StarForce и Themida ацтой!


| Сообщение посчитали полезным: b0r3d0m

Ранг: 20.4 (новичок), 8thx
Активность: 0.030
Статус: Участник

Создано: 17 июня 2015 23:27
· Личное сообщение · #14

dosprog пишет:
использовать заполнение обычными NOP, то очевидна "неэффективность

Можно было придумать инструкцию типа "Double NOP" (естественно, выделить два байта под неё)




Ранг: 392.8 (мудрец), 108thx
Активность: 0.260.01
Статус: Участник
REVENGE сила, БеХоЦе могила

Создано: 17 июня 2015 23:28
· Личное сообщение · #15

А в процедуре MyMessageBoxW необязательно байты возвращать, достаточно выполнить процедуру со следующих байт идущих за jmp -5

-----
StarForce и Themida ацтой!




Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 23:28
· Личное сообщение · #16

Maximus пишет:
Я ссылку на перехват

Ну, т.е. Вы о том, что возвращать обратно изначальную реализацию нет особой необходимости в таком случае, да?

hello пишет:
Но если первая MOV EDI, EDI -тогда ничего не нужно восстанавливать и эмулировать

Да, но только в том случае, если мы уместим наш JMP в два байта, что можно сделать только в ситуациях, подобных JMP $-5, что Вас же и не устраивает:
> Там обычно мизер остаетс. Впрочем, вроде слышал, что в некоторых библиотеках такой подход встречается. Но не у микрософт



Ранг: 431.7 (мудрец), 391thx
Активность: 0.730.32
Статус: Участник

Создано: 17 июня 2015 23:30 · Поправил: dosprog
· Личное сообщение · #17

b0r3d0m пишет:
чтобы случайно не облажаться на ровном месте.

Надо пробовать. Облажаетесь - вернёте всё на место.



| Сообщение посчитали полезным: b0r3d0m

Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 23:31
· Личное сообщение · #18

hello пишет:
Можно было придумать инструкцию типа "Double NOP" (естественно, выделить два байта под неё)

А зачем? Тот же NOP не сильно от MOV EDI,EDI отличается, т.к. он, по сути, является синонимом для XCHG EAX,EAX или XCHG AX,AX.




Ранг: 392.8 (мудрец), 108thx
Активность: 0.260.01
Статус: Участник
REVENGE сила, БеХоЦе могила

Создано: 17 июня 2015 23:32
· Личное сообщение · #19

Ну, т.е. Вы о том, что возвращать обратно изначальную реализацию нет особой необходимости в таком случае, да?

Да, достаточно получить адрес процедуры MessageBox и прибавить к этому адресу 2 (пропускаем jmp-5) и каждый раз возвращать процедуру в исходное состояние не нужно.

-----
StarForce и Themida ацтой!




Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 23:35
· Личное сообщение · #20

Maximus пишет:
Да, достаточно получить адрес процедуры MessageBox и прибавить к этому адресу 2 (пропускаем jmp-5) и каждый раз возвращать процедуру в исходное состояние не нужно

Спасибо. А RET после безусловного перехода для чего делался в изначальном примере, не подскажете?

dosprog пишет:
Надо пробовать. Облажаетесь - вернёте всё на место

Уже попробовал, просто было интересно, как и почему лучше делать.




Ранг: 392.8 (мудрец), 108thx
Активность: 0.260.01
Статус: Участник
REVENGE сила, БеХоЦе могила

Создано: 17 июня 2015 23:43 · Поправил: Maximus
· Личное сообщение · #21

А RET после безусловного перехода для чего делался в изначальном примере, не подскажете?

Не подскажу. Но скажу, что этот пример не для конструкции
nop
nop
nop
nop
nop
mov edi,edi

Пример сделан для jmp который затирает первые байты перехватываемой процедуры.

-----
StarForce и Themida ацтой!




Ранг: 10.7 (новичок), 2thx
Активность: 0.060
Статус: Участник

Создано: 17 июня 2015 23:45
· Личное сообщение · #22

Maximus пишет:
Но скажу, что этот пример не для конструкции

Да, это я понял. В любом случае, спасибо.

Может, поможете с ответами в другой теме -- https://exelab.ru/f/action=vthread&forum=5&topic=23620?




Ранг: 392.8 (мудрец), 108thx
Активность: 0.260.01
Статус: Участник
REVENGE сила, БеХоЦе могила

Создано: 17 июня 2015 23:53
· Личное сообщение · #23

Я не знаю что сказать по той теме. Скажу только что в своих прогах, в которых надо переловить апи я сначала делаю поиск в kernelbase, и если возвращает ноль (к примеру на XP), то ищу функу в kernel32. Это полностью решает проблемы с совместимостью.

-----
StarForce и Themida ацтой!



 eXeL@B —› Вопросы новичков —› Зачем нужна инструкция MOV EDI,EDI
:: Ваш ответ
Жирный  Курсив  Подчеркнутый  Перечеркнутый  {mpf5}  Код  Вставить ссылку 
:s1: :s2: :s3: :s4: :s5: :s6: :s7: :s8: :s9: :s10: :s11: :s12: :s13: :s14: :s15: :s16:


Максимальный размер аттача: 500KB.
Ваш логин: german1505 » Выход » ЛС
   Для печати Для печати