Статья Разбор Detour, и Trampoline хук-метода для 32-х битных приложений (internal)

  • 677
  • 314
Думаю многие, кто пастят чит либо просто делают свой чит на готовой базе даже не задумывались, что стоит за "Hook Manager'ом", либо вообще даже не пользовались им, ведь в готовой чит-базе все сделано за вас..., или нет?
В прочем сегодня вы не узнаете, как хукать функции игр, потому-что в первую очередь вам нужно знать, как это работает, конечно, если вы не хотите остаться только пастером, и цель данной статьи это дать вам эти необходимые знания на примере Trampoline метода. Возможно я сделаю ещё статью по тому, как хукать функции с помощью Trampoline метода, но не сегодня.

Hooking или на русском хукингом называют методы, используемые для изменения поведения приложений путем перехвата вызовов функций.

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

То есть, цель хукинга в читах, это выполнить ваш код во время выполнения хукнутой функции приложения, и более того, так как ваша функция является шаблоном хукнутой функции, то вы также можете использовать аргументы хукнутой функции, например, если хукнуть функцию "GetViewPoint" в игре, которая сделана на движке Unreal Engine 4, то вы сможете используя аргумент типа "FMinimalViewInfo", который отвечает за камеру, написать функционал свободной камеры, изменение FOV'а, и другое связанное с камерой.

1641807393115.png
Все, что делает "обход", это заменяет 5 байтов функции размещая инструкцию JMP и адрес места на которое должен "перепрыгнуть" поток функции.
Untitled-2.png


Вам нужно выбирать область памяти функции грамотно, рекомендуется производить махинации не в самом начале функции, потому-что анти-читы с легкостью проверяют первые байты функции, и могут на этом вас обнаружить, но и это не все, вы также должны учитывать размер, для JMP инструкции с адресом потребуется 5 байтов(1 байт для инструкции и 4 байта для адреса, в 64-х битной системе 8 байтов), но и "разрывать" инструкции также нельзя, с помощью Cheat Engine вы легко можете просматривать область памяти приложения, и выбрать область памяти для размещения хука. Как видно на скриншоте, мы можем по адресу "MMU.exe + 49DF" разместить прыжок с размером 6 байт.

1641809516852.png


Для того, чтобы разместить прыжок вам потребуется изменить защиту данной области памяти с которой вы будете работать, чтобы мы могли её перезаписывать, делается это с помощью функции VirtualProtect, но сначала потребуется объявить переменную типа DWORD(unsgined long int) без инициализации, нужна она для того, чтобы указать текущую защиту.
VirtualProtect принимает 4 аргумента: адрес в памяти, размер области памятибайтах) для которой нужно изменить защиту, новая защита вида HEX, и старая защита(ссылочно указываем нашу объявленную переменную).
C++:
uintptr_t target_addr* = reinterpret_cast< uintptr_t* >( base_module + 0x49DF ); // Адрес модуля функции + адрес до места, где будет размещён джамп
uintptr_t relative_addr* = reinterpret_cast< uintptr_t* >( new_address - target_addr - 0x5 ); // JMP инструкция переходит к другому месту относительно
// своего адреса(как "смещение"), поэтому нам нужен относительный адрес до нашего адреса без 5 байтов(джамп инструкция и адрес).

DWORD cur_protect;
VirtualProtect( target_addr, 6, PAGE_EXECUTE_READWRITE, &cur_protect );

После этого можно изменять данную область памяти, сначала очищаем её от текущих байтов, чтобы не крашило, если "разрываем" инструкции нашим джампом, делается это также просто, вызываем функцию memset в которой указываем адрес памяти, новые байты, и размер области памяти. Для очистки записывается инструкция NOP(no operation), которая не делает каких-либо операций, опкод данной инструкции 0x90, который мы и указываем.
C++:
memset( target_addr, 0x90, 0x6 );

Теперь можно разместить JMP инструкцию, опкод которой 0xE9, делается это также просто, как изменение значения переменной игры.
C++:
*target_addr = 0xE9;

Предпоследнее действие - указание адреса для прыжка, делается почти также.
C++:
*reinterpret_cast< uintptr_t* >( target_addr + 0x1 ) = relative_addr; // Создаем указатель и разыменовываем, чтобы получить доступ к данным
// памяти, а не просто к инструкциям по адресу

Ну и последнее - возвращаем защиту.
C++:
VirtualProtect( target_addr, 0x6, cur_protect, &cur_protect )

Код детаур функции будет выглядеть примерно так:
C++:
void Detour( uintptr_t* target, uintptr_t* source, int length ) {
    // Если размер области памяти меньше 5, то мы попросту не сможем уместить джамп инструкцию с адресом
    if ( length < 5 ) return;

    // Изменяем защиту для доступа к записи в данной области памяти
    DWORD cur_protect;
    VirtualProtect( target, length, PAGE_EXECUTE_READWRITE, &cur_protect );

    // Очищаем область памяти
    memset( target, 0x90, length );

    // Размещаем JMP инструкцию
    *target = 0xE9;
    // Размещаем относительный адрес до нашего адреса
    *reinterpret_cast< uintptr_t* >( target + 1 ) = source - target - 5;

    // Возвращаем защиту
    VirtualProtect( target, length, cur_protect, &cur_protect );
}
Цель детаура лишь размещение прыжка, чего недостаточно для полноценного хукинга, потому-что вам самим потребуется размещать украденные байты, вместо которых был размещён джамп.
Trampoline или батут метод решает эту проблему, это уже полноценный хук-инструмент, он выделяет область памяти с помощью функции VirtualAlloc, размещая в ней замененные джампом байты, и джамп обратно к оригинальной функции. Батут также в конце использует детаур для джампа к вашей функции в конце которой вы разместите джамп к выделенной области памяти "батут" методом.
1641817039669.png


Теперь думаю можно начинать с реализации функции.

Создаем переменную, которая будет содержать адрес выделенной памяти, и инициализируем её вызовом функции VirtualAlloc, назову я её "шлюзом", как обычно.
Функция VirtualAlloc принимает 4 аргумента: адрес, где будет выделена память, размер выделяемой памяти, параметры выделяемой памяти, защита для выделяемой памяти. В параметры адреса пишем ноль, и функция сама выберет подходящий регион памяти, в параметры выделяемой памяти указываем MEM_COMMIT | MEM_RESERVE для того, чтобы память точно не была заполнена мусором, а только NOP инструкциями. Указывать точный размер выделяемой памяти необязательно, потому-что данная функция выделяет минимум одну страницу памяти, с защитой все просто, нам нужен доступ к записи, поэтому так и пишем.
C++:
uintptr_t* gateway = reinterpret_cast< uintptr_t* >( VirtualAlloc( 0, length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ) );

Копируем заменяемые байты с помощью функции memcpy.
C++:
memcpy( gateway, target, length );

Также, как и в детаур-функции размещаем джамп инструкцию и адрес, только теперь джамп к оригинальной функции(хукаемой), но не забываем, что в начале мы разместили заменяемые байты, поэтому добавляем ещё длину.
C++:
*( gateway + length ) = 0xE9;
*reinterpret_cast< uintptr_t* >( gateway + length + 0x1 ) = target - gateway - 5;

Очередное предпоследнее действие - вызов детаура с указанием наших аргументов.
C++:
Detour( target, source, length );

Финальная строка - возвращаем адрес выделенной памяти.
C++:
return gateway;

Код Trampoline хук-метода будет выглядеть примерно так:

C++:
uintptr_t* Trampoline( uintptr_t* target, uintptr_t* source, int length ) {
    if ( length < 0x5 ) return 0;

    // Create gateway
    uintptr_t* gateway = reinterpret_cast< uintptr_t* >( VirtualAlloc( 0, length + 0x5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ) );

    // Copy stolen bytes to gateway
    memcpy( gateway, target, length );

    // Add jmp instruction and our source address at end in gateway
    *( gateway + length ) = 0xE9;
    *reinterpret_cast< uintptr_t* >( gateway + length + 0x1 ) = target - gateway - 0x5;

    // Jump from target function to our function(where our code, call gateway with stolen bytes and jump back to target function)
    Detour( target, source, length );

    return gateway;
}
После всей этой информации теперь вы должны были понять, как это работает. Если вы не полностью поняли, как работает батут хук-метод, то объясняю.

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

Батут-функция вызывается инициализируя глобальную переменную с типом шаблона функции(которая и вызывается в конце вашей функции).

1641820025024.png
 
Последнее редактирование:
  • colby57
  • UNEXPECTED_KERNEL_MODE_TRAP_M (1000007f)
  • 155
  • 2
  • 240
После этого можно изменять данную область памяти, сначала очищаем её от текущих байтов, чтобы не крашило, если "разрываем" инструкции нашим джампом
такой себе совет, может я не так понял твое утверждение, но когда ты исполняешь тамполайн хук ты в своей функции для хука должен выполнить те инструкции, которые забрал твой джамп ( 5 байт ), а не нопить в оригинальной функции, так слишком легко сломать последовательность логики программы, если ты ставишь тамполайн хук вместо инструкции к примеру push _data_offset и не выполняешь эту инструкцию в своей функции для хука*, то логика программы уже по пизде пойдет
 
  • 677
  • 314
такой себе совет, может я не так понял твое утверждение, но когда ты исполняешь тамполайн хук ты в своей функции для хука должен выполнить те инструкции, которые забрал твой джамп ( 5 байт ), а не нопить в оригинальной функции, так слишком легко сломать последовательность логики программы, если ты ставишь тамполайн хук вместо инструкции к примеру push _data_offset и не выполняешь эту инструкцию в своей функции для хука*, то логика программы уже по пизде пойдет
Это не совет, так делается детаур функция.
В трамполайн методе создается шлюз, в котором находятся перезаписанные оригинальные байты и джамп обратно к оригинальной функции. Шлюз вызывается в конце нашей функции. В данном случае логика программы не ломается, и все работает, как требуется. Главное не разрывать инструкции, я это упомянул несколько раз.
1641835170530.png
 
  • colby57
  • UNEXPECTED_KERNEL_MODE_TRAP_M (1000007f)
  • 155
  • 2
  • 240
Это не совет, так делается детаур функция.
В трамполайн методе создается шлюз, в котором находятся перезаписанные оригинальные байты и джамп обратно к оригинальной функции. Шлюз вызывается в конце нашей функции. В данном случае логика программы не ломается, и все работает, как требуется. Главное не разрывать инструкции, я это упомянул несколько раз.
Посмотреть вложение 30569
да, ошибочка, добавлю, что для твоего "шлюза" необязательно выделять виртуальную память, ибо можно поступить как и с детурами
1641835962639.png
 
  • 677
  • 314
да, ошибочка, добавлю, что для твоего "шлюза" необязательно выделять виртуальную память, ибо можно поступить как и с детурамиПосмотреть вложение 30571
Будь чуть внимательнее, я тоже об этом сказал :)
Цель детаура лишь размещение прыжка, чего недостаточно для полноценного хукинга, потому-что вам самим потребуется размещать украденные байты, вместо которых был размещён джамп.
Trampoline или батут метод решает эту проблему, это уже полноценный хук-инструмент, он выделяет область памяти с помощью функции VirtualAlloc, размещая в ней замененные джампом байты, и джамп обратно к оригинальной функции. Батут также в конце использует детаур для джампа к вашей функции в конце которой вы разместите джамп к выделенной области памяти "батут" методом.
 
Сверху Снизу