Перезапись хуков

Tema05

Известный
Автор темы
1,507
475
Я хочу при помощи мемвраппера хукнуть CNetGame::ShutdownForRestart. Прототип "void(__thiscall*)(CNetGame*)", адрес samp+0xA1E0 для R3. Посмотрев через чит энжин этот адрес я увидел, что там уже стоит джамп хук от лаучеровского плагина libcef.asi.

1) Как быть в данной ситуации если я хочу сохранить оба хука? Я не планирую нопать метод, а лишь выполнить другое действие при его вызове
2) Первое что мне приходит в голову это читать из памяти конечный адрес функции в libcef.asi и использовать его для своего хука. Но в зависимости от названия файла моего плагина он может загружаться позже или раньше libcef.asi, а в 2 случаи там ещё не будет джампа с адресом. Мне это не нравится. Ещё рассматривал вариант сразу искать адрес в libcef.asi по сигнатуре так как это файл потенциально может меняться. Но это решение мне тоже кажется не оптимальным. Как правильнее будет сделать? Мне кажется на такие случаи уже придумано оптимальное решение

1745055548277.png
1745055761201.png

1745055791304.png
 
  • Вау
Реакции: minxty

sazzas1978

Известный
139
128
Можешь отследить в хуке load library , или как то по другому, и проверить загрузку libcef, потом поставить хук на начало оригинальной функции, и вызвать в конце функцию которая вызывается в хуке (адрес куда идёт jmp хук от libcef).
 
Последнее редактирование:

Tema05

Известный
Автор темы
1,507
475
эта функция в принципе вызывается лишь в двух случаях на р3

Посмотреть вложение 268190
Дык её могут вызывать вообще любые другие скрипты и плагины, что у человека есть в сборке. Как минимум 2 скрипта лаунчера могут это делать. Реконнект у каждого 2 стоит, он тоже её триггерит. Мне нужно в целом отследить любые переподключения к серверу неважно по какой причине и чем инициированы.

Про срабатывание 1 раз я имел ввиду хук. Т.е. только при первом вызове ShutdownForRestart мой хук срабатывает. При последующих вызовах ShutdownForRestart мой хук не срабатывает
 

sazzas1978

Известный
139
128
Дык её могут вызывать вообще любые другие скрипты и плагины, что у человека есть в сборке. Как минимум 2 скрипта лаунчера могут это делать. Реконнект у каждого 2 стоит, он тоже её триггерит. Мне нужно в целом отследить любые переподключения к серверу неважно по какой причине и чем инициированы.

Про срабатывание 1 раз я имел ввиду хук. Т.е. только при первом вызове ShutdownForRestart мой хук срабатывает. При последующих вызовах ShutdownForRestart мой хук не срабатывает
Попробуй после вызова своего хука вызвать следующею функцию в которую идёт jump от libcef
 

whyega52

Eblang головного мозга
Модератор
2,851
2,813
Как вариант, чтобы не морочить голову с нейкид хуком из под луа, можешь либо поставить хук на call какой-то функции внутри CNetGame::ShutdownForRestart, к примеру CLocalPlayer::ResetData, или же ставить хук на начало какой-то функции, которая вызывается только внутри ShutdownForRestart, к примеру на р3 это sub_1009C580 или CPlayerPool::Deactivate
1745139370820.png


только щас заметил, что это тема по плюсам и если реализация правда на них, то можешь заюзать какой-нибудь kthook::kthook_naked, который ставится почти в любое место и ничего не ломает, но при этом. можно вызвать свою логику
 
  • Нравится
  • Вау
Реакции: cort и vmprotect

Tema05

Известный
Автор темы
1,507
475
Значит я разобрался. Пишу это больше для самого себя чтоб закрепить, но может и кому-то другому пригодиться так как вероятно устройство остальных хуков в лаунчере подобны этим.
P.S. я опушу различные проверки так как они всегда выполняются успешно если не будет стороннего вмешательства.

У хука есть 3 глобальных переменных, которые идут друг за другом и используются в нём (адреса могут меняться)
libcef.asi + 0x7E5F8 хранит адрес начала оригинальной функции samp.dll + 0xA1E0
libcef.asi + 0x7E5FC хранит адрес на структуру хука с оффсетом 12 байт от начала (4 элемент)
libcef.asi + 0x7E600 хранит адрес на начало структуры хука

Хуки ставятся в функции libcef.asi + 0x35610 и что в неё происходит:

Получаем и записываем в libcef.asi + 0x7E5F8 адрес оригинальной функции
Выделяем участок памяти из 5 байт, далее передаём указатель на него функции, которая заполнит его нопами (0x90 nop)
В 1 байт участка записываем джамп (0xE9 jmp), далее считаем адрес относительно адрес до функции обработчика и записывает в остальные 4 байта (не забываем -5 из-за размера команды)
Выделяем участок памяти в 44 байта. Это по сути та самая структура с данными хука. Представим как массив из 11 элементов 4-байтовых значений
После идёт функция куда передаются указатели и она заполняет эту структуру:
Код:
[0] = адрес std::_Ref_count_obj2<urmem::patch>::`vftable' (не используется)
[1] = 0x00000001 (не используется)
[2] = 0x00000001 (не используется)

[3] = адрес оригинальной функции (используется для перезаписи памяти)

[4] = указатель на новый 5-байтовый участок памяти куда будут скопированны данные из предыдущего
[5] = указатель + 5 (записывается уже после выделения памяти)
[6] = указатель + 5 (записывается в функции выделения памяти)

[7] = указатель на новый 5-байтовый участок памяти куда будут скопированны данные из оригинальной функции
[8] = указатель + 5 (записывается уже после выделения памяти)
[9] = указатель + 5 (записывается в функции выделения памяти)
[10] = 0x00000001 (если 1 то по адресу samp.dll + 0xA1E0 наш хук, если 0 то там оригинальные байты)
Также в нёй снимается защита памяти. 5 байтом начиная с samp.dll + 0xA1E0 устанавливается значение 0x40 (можно исполнять, читать и записывать)
И туда записываются эти 5 байт с джампом на функцию обработчик
На выходе в libcef.asi + 0x7E5FC записывается адрес 3 индекса нашего массика, который структура хука
На выходе в libcef.asi + 0x7E600 записывается адрес начала нашего массика, который структура хука
А далее самые первые 5 байт который мы выделили освобождаются. Мы их всё равно скопипастили в участок на который указывает 4 элемент массива
1745325520612.png


Теперь собственно что в функции обработчике:
Все обращения к структуре тут через libcef.asi + 0x7E5FC (т.е. начиная с 3 индекса)
При срабатывании первым делом возвращаются оригинальные байты в функцию (указатель на них по 7 индексу массива)
Далее при помощи libcef.asi + 0x7E5F8 мы вызываем оригинальную функцию и она выполняется как должно (ведь оригинальные байты мы вернули)
После мы копируем оригинальные байты обратно в памяти указатель на которую в 7 индексе и ставим обратно наш джамп из памяти указатель на которую в 4 индексе
Дальше выполняется собственная логика скрипта (получается уже после выполнения оригинальной функции)

Ровно такой же хук ставится из vorbisFile.dll и он ставится раньше. Получается что libcef.asi сохраняет нее оригинальные 5 байт, а 5 байт джампа, которые записал vorbisFile.dll
И при выполнении якобы оригинала функции в libcef.asi мы на самом деле триггерим хук vorbisFile.dll
обработчик libcef.asi -> обработчик vorbisFile.dll -> выполнение оригинала -> выполнение логики vorbisFile.dll -> выполнение логики libcef.asi
Таким образом у нас 2 независимых скрипта успешно хукают 1 и ту же функцию. И потенциально могут сколько угодно скриптом делать также.

Теперь почему мой хук срабатывал только 1 раз:
vorbisFile.dll по определению загружается и поставит хук раньше любых asi. Мой asi загружается вторым. libcef.asi последним
Выходит libcef.asi сохраняет мой джамп как оригинальные байты и ставит свой хук. При выполнении libcef.asi восстанавливаем мой джамп, запускает его, и тригеррит мой обработчик. Он сразу выполняет свою логику и после хочет выполнить в трамплине оригинальные 5 байт прежде чем вернуться, но это оказываются байты обработчика из vorbisFile.dll и он срабатывает. vorbisFile.dll возвращает уже реально оригинальные байты, выполняет оригинальные скрипт и ставит свой хук обратно. После исполнения его логики происходит retn, который возвращает нас в libcef.asi так как там была инструкции call, а моём хуке возврат был через jmp. libcef.asi обратно сохраняет оригинальные байты (которые хук vorbisFile.dll) и ставит свой хук, после выполняет свою логику.

Получается состояние после 1 исполнения ровно такой же как если бы моего хука вовсе не было. Он тупо затирается. vorbisFile.dll и libcef.asi ставят inline хуки. А мой asi ставит jmp хук. В этом и проблема. Мне нужно также использовать inline хук, чтобы все работало как надо.

Приветствую исправления моего объяснения на более грамотное.
 
Последнее редактирование:
  • Вау
Реакции: vmprotect и whyega52

Похожие темы

  1. У
    • Закрыта
    • Опрос
      • Нравится
    Ответы
    10
    Просмотры
    4K
    • Закреплено
    • Статья
    Ответы
    204
    Просмотры
    757K
  2. Ответы
    505
    Просмотры
    269K