Sergey99 Опубликовано 20 июня, 2021 Поделиться Опубликовано 20 июня, 2021 Всем привет, решил я написать простенький хук для OpengGL. Игрушка, которую я хочу ломать использует как раз OpenGL, это можно понять если присоединиться через Cheat Engine к процессу игры и вызвать функцию для перечисления всех DLL загруженных в процесс (Enumerate DLL's). Игрушка использует opengl32.dll соответственно и ломать я буду OpengGL. И так вот небольшой кусок кода для функции GetOpenGLFunctions, весь код написан на C++, он естественно закоментирован на английском языке так как английский это круто и по хорошему код нужно коментить: Спойлер //Gets address of function from OpenGL library void GetOpenGLFunctions() { HMODULE Moduleandle = GetModuleHandle(L"opengl32.dll"); if (Moduleandle != nullptr) { glBeginAddress = (DWORD)GetProcAddress(Moduleandle, "glBegin"); } else { MessageBox(0, L"Module handle not found", L"Message", MB_OK); } } Эта функция получает адрес функции glBegin из модуля opengl32.dll, который подргужен самой игрой и сохраняет его в переменную glBeginAddress. После этого вызывается функция HookOpenGLFunctions: Спойлер //Performs code injection void HookOpenGLFunctions() { oglBegin = (tglBegin)glBeginAddress; //Gets address of original function jump_glBegin[0] = 0xE9; DWORD addr_glBegin = (DWORD)hkglBegin - (DWORD)oglBegin - 5; //Gets address of injection (where to jump) memcpy(jump_glBegin + 1, &addr_glBegin, sizeof(DWORD)); //Adds address after 'jmp' opcode to get assembler instruction jmp [address] memcpy(codeFragment_glBegin, oglBegin, 5); //Saves address of original function VirtualProtect(oglBegin, 8, PAGE_EXECUTE_READWRITE, &savedProtection_glBegin); memcpy(oglBegin, jump_glBegin, 5); //Replaces address of original function with assembler instruction jmp [address] (jump to injection where hook is) } По сути это инъекция кода то есть jmp [address] вместо оригинальной функции glBegin. Я не использую Detours от Microsoft потому что вижу в них смысл если требуется перехват функции в x64 играх, так как там способ перехвата и инъекции кода немножко отличается от x86. И вот собственно сам код инъекции: Спойлер //Hooked function that will be executed instead of original function in the process void WINAPI hkglBegin(GLenum mode) { //Restores bytes of original function BYTE* codeDest = (BYTE*)oglBegin; codeDest[0] = codeFragment_glBegin[0]; *((DWORD*)(codeDest + 1)) = *((DWORD*)(codeFragment_glBegin + 1)); if (isEnabled) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } oglBegin(mode); //Restores bytes of hooked function so process of the game will execute hooked function instead of original function codeDest[0] = jump_glBegin[0]; *((DWORD*)(codeDest + 1)) = *((DWORD*)(jump_glBegin + 1)); } Тут происходит восстановление байт оригинальной функции, чтобы она смогла выполнится в конце хука, потом идёт проверка включён ли чит и вызов функции glPolygonMode, котоая устанавливает режим отрисовки полигонов грубо говоря. Соответственно GL_FRONT_AND_BACK - полигоны которые рисуются спереди и сзади, GL_LINE - отрисовка линиями и GL_FILL - полная заливка полигонов текстурами. Ну а дальше вызов оригинальной функции glBegin и восстановление байт для инъекции и всё это крутится в цикле. Вот тут ссылки на документацию по функциям только не с сайта Microsoft а уже с сайта OpenGL документации: glBegin, glPolygonMode Уж не знаю кому как удобнее ну вот сайтик с документацией от разработчика: Khronos В чём может быть проблема? Всё работает только вот при отрисовке линиями игра почему отображает мне предыдущий кадр в цикле, как будто изображение заморожено, вот пример до: И вот как это выглядит после хука: Тут видно наверное плохо но изображение как бы замараживается и не обновляется (даже сохраняется и рисуется предыдущая позиция курсора), но в момент выключения на долю секунды можно заметить отрисовку только линиями как здесь (пример из d3d9 игры): Ссылка на комментарий Поделиться на другие сайты Поделиться
Xipho Опубликовано 20 июня, 2021 Поделиться Опубликовано 20 июня, 2021 6 часов назад, Sergey99 сказал: изображение как бы замараживается и не обновляется Скорее всего, у тебя не отрабатывает оригинальная функция glBegin. Смотри под пошаговой отладкой. Ссылка на комментарий Поделиться на другие сайты Поделиться
Sergey99 Опубликовано 20 июня, 2021 Автор Поделиться Опубликовано 20 июня, 2021 3 минуты назад, Xipho сказал: Скорее всего, у тебя не отрабатывает оригинальная функция glBegin. Смотри под пошаговой отладкой. Если я пытаюсь очистить буфер экрана и буфер глубины к примеру зелёным цветом без рисования линиями, то оно как бы работает, но вот так вот: Код: OpenGL Microsoft docs - glColorMask, glClearColor, glClear glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE); glClearColor(0.0, 1.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); И пример после очистки зелёным цветом: Примечательно то что что если я буду рисовать линиями GL_LINE то экран будет весь зелёный будто весь очищается цветом. Также переменная glBeginAddress хранит адрес функции которую собственно я и перехватываю, вот тут даже jmp на инъекцию есть так как DLL уже загружена: Ссылка на комментарий Поделиться на другие сайты Поделиться
Xipho Опубликовано 21 июня, 2021 Поделиться Опубликовано 21 июня, 2021 Кстати, сейчас только вспомнил (давно не ковырял огл). glBegin и glEnd - это же, если я правильно помню, что-то вроде "операторских скобок", отделяющих некоторые блоки отрисовки. То есть, совершенно не обязательно на кадр будет строго один glBegin и строго один glEnd. В отличие от DirectX, упомянутых функций в рамках построения одного кадра может быть несколько. Возможно, у тебя хук срабатывает на тех вызовах, на которых не должен срабатывать, от того и результат такой "замороженный". Попробуй под отладкой найти именно тот, который тебе нужен, чтобы отрисовать объекты сеткой. Ссылка на комментарий Поделиться на другие сайты Поделиться
Sergey99 Опубликовано 21 июня, 2021 Автор Поделиться Опубликовано 21 июня, 2021 3 часа назад, Xipho сказал: Кстати, сейчас только вспомнил (давно не ковырял огл). glBegin и glEnd - это же, если я правильно помню, что-то вроде "операторских скобок", отделяющих некоторые блоки отрисовки. То есть, совершенно не обязательно на кадр будет строго один glBegin и строго один glEnd. В отличие от DirectX, упомянутых функций в рамках построения одного кадра может быть несколько. Возможно, у тебя хук срабатывает на тех вызовах, на которых не должен срабатывать, от того и результат такой "замороженный". Попробуй под отладкой найти именно тот, который тебе нужен, чтобы отрисовать объекты сеткой. Да действительно между glBegin и glEnd обычно указывают вершины, по типу такого: glBegin(GL_QUADS); glVertex3f(x, y, z); glVertex3f(x, y, z); glVertex3f(x, y, z); glVertex3f(x, y, z); glEnd(); Тут указываются вершины, так как в аргументах glBegin пришёл GL_QUADS то и вершин соответственно 4 По поводу перехватов, я пытался перехватить wglSwapBuffers из той же библиотеки opengl32.dll, читал на зарубежных форумах, там её используют но для рисования всяких там квадратиков и менюшек на эране. Но результат практически тот же что и при перехвате glBegin. К тому же очень странно но справочки по wglSwapBuffers я не нашёл однако она как бы работает и в opengl32.dll как бы есть. Вы предлагаете искать через отладку а в каком смысле? Я же DLL инжектирую, а VS не позволяет отлаживать так как DLL это не исполняемый файл. И разумеется функция GetProcAddress не найдёт больше адресов функции glBegin так как в библиотеке opengl32.dll вроде как не может быть больше таких функций, она же как бы одна и зачем там больше. glBeginAddress = (DWORD)GetProcAddress(Moduleandle, "glBegin"); Ссылка на комментарий Поделиться на другие сайты Поделиться
Garik66 Опубликовано 21 июня, 2021 Поделиться Опубликовано 21 июня, 2021 18 минут назад, Sergey99 сказал: странно но справочки по wglSwapBuffers я не нашёл не оно https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-wglswaplayerbuffers Ссылка на комментарий Поделиться на другие сайты Поделиться
Xipho Опубликовано 21 июня, 2021 Поделиться Опубликовано 21 июня, 2021 21 минуту назад, Sergey99 сказал: Я же DLL инжектирую, а VS не позволяет отлаживать так как DLL это не исполняемый файл Позволяет ) Просто аттачишься к процессу игры, затем инжектишь длл, и если код в нее будет попадать, будут срабатывать брейкпоинты. Только длл-ку надо будет собирать для дебага, а не для релиза. Ссылка на комментарий Поделиться на другие сайты Поделиться
Xipho Опубликовано 21 июня, 2021 Поделиться Опубликовано 21 июня, 2021 26 минут назад, Sergey99 сказал: И разумеется функция GetProcAddress не найдёт больше адресов функции glBegin так как в библиотеке opengl32.dll вроде как не может быть больше таких функций, она же как бы одна и зачем там больше. Вот это не совсем понял, к чему. Отладку я имел в виду еще в том смысле, чтобы найти те вызовы glBegin в оригинальном коде, которые предшествуют рисованию именно нужных тебе объектов, а не абсолютно все вызовы. Ну и можно заюзать вот эту [штуку], чтобы выяснить, как вообще в твоей игре работает конвеер OpenGL. Возможно, это облегчит тебе задачу. Ссылка на комментарий Поделиться на другие сайты Поделиться
Sergey99 Опубликовано 21 июня, 2021 Автор Поделиться Опубликовано 21 июня, 2021 6 минут назад, Garik66 сказал: не оно https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-wglswaplayerbuffers Функция wglSwapBuffers принимает один параметр HDC контекст устройства, а wglSwapLayerBuffers два параметра, контекст HDC и собственно для чего выполнить смену буферов будто плоскости или overlay. Сами функции разные хотя бы потому что принимают разное число аргументов и разные имена имеют. Ссылка на комментарий Поделиться на другие сайты Поделиться
Xipho Опубликовано 21 июня, 2021 Поделиться Опубликовано 21 июня, 2021 3 минуты назад, Sergey99 сказал: Функция wglSwapBuffers принимает один параметр HDC контекст устройства Очень похоже на стандартную обертку над GDI функцией SwapBuffers. Ссылка на комментарий Поделиться на другие сайты Поделиться
Sergey99 Опубликовано 22 июня, 2021 Автор Поделиться Опубликовано 22 июня, 2021 В 21.06.2021 в 13:15, Xipho сказал: Очень похоже на стандартную обертку над GDI функцией SwapBuffers. Решил проблемку, как оказалось перехватывать нужно было не glBegin а функцию именно для рисования как в том же Direct3D. Функция называется glDrawElements и рисует примитивы из массива, который приходит четвёртым аргументом. Конечно если почитать справочки от Microsoft и от Khronos, то из описания параметром понятно что где лежит, а вот если почитать справочку [здесь] по этой функции, то немножко запутаться можно в описании четвёртого параметра ну да ладно. Всё рисуется всё работает, проблема была в том, что как оказалось сам экран или камера через которую мы смотрим на мир игры тоже является примитивом и если мы отключаем рисование для него то он соответственно не будет обновляться и будет якобы висеть с одним изображением. Проблема решена через проверку сколько примитивов рисует функция (второй параметр): Спойлер //Hooked function that will be executed instead of original function in the process void WINAPI hkglDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices) { //Restores bytes of original function BYTE* codeDest = (BYTE*)oglDrawElements; codeDest[0] = codeFragment_glDrawElements[0]; *((DWORD*)(codeDest + 1)) = *((DWORD*)(codeFragment_glDrawElements + 1)); if (isEnabled) { if (count > 260) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } oglDrawElements(mode, count, type, indices); //Restores bytes of hooked function so process of the game will execute hooked function instead of original function codeDest[0] = jump_glDrawElements[0]; *((DWORD*)(codeDest + 1)) = *((DWORD*)(jump_glDrawElements + 1)); } При таком раскладе функция будет рисовать линиями только если число примитивов больше 260, а если нет, то рисовать сплошной заливкой. Вопрос теперь в том как сама функция это понимает? Ведь это хук и выполняется также в потоке или в цикле в игре. Эта функция вызывается и ей приходят аргументы и вот пришли ей на пример 261 примитив, то она рисует только один примитив линиями, хотя по логике должна все ведь условие стоит на это. Как такое происходит? Если посмотреть справочку то можно понять что второй параметр это число примитивов для рисования (то есть они все рисуются в этой функции), а условие будто выполняется для конкретного диапазона от 260 как так? Вот результат стараний, темновато но думаю видно: 1 Ссылка на комментарий Поделиться на другие сайты Поделиться
Xipho Опубликовано 22 июня, 2021 Поделиться Опубликовано 22 июня, 2021 Вот тут у тебя избыточное условие. Это видно по дублированию блока else Спойлер if (isEnabled) { if (count > 260) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } Предлагаю поменять на Спойлер if (isEnabled && count > 260) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } А в остальном - молодец, что разобрался и поделился решением. 1 час назад, Sergey99 сказал: как сама функция это понимает? Извини, я не совсем понимаю твой вопрос ЗЫ. Не забывай код прятать под спойлер. Ссылка на комментарий Поделиться на другие сайты Поделиться
Sergey99 Опубликовано 22 июня, 2021 Автор Поделиться Опубликовано 22 июня, 2021 (изменено) 3 часа назад, Xipho сказал: Извини, я не совсем понимаю твой вопрос Вот в функции перехвата я проверяю параметр count, который пришёл в функцию glDrawElements, он хранит количество примитивов для рендера и вот по идее рисование линиями должно работать для всех примитивов так как условие выполняется. К примеру если count равен 100 (то есть в функцию пришёл аргумент count со значением 100 то функция не будет рисовать линиями так как условие count > 260, а вот если в функцию придёт 261 то по идее он должен всё нарисовать линиями а получается только один примитив так как 261 > 260. Это то что нужно но сам алгоритм не понятен. Так как по логике условие сработало и выполняется glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); (по идее для всех примитивов) Изменено 22 июня, 2021 пользователем Sergey99 Ссылка на комментарий Поделиться на другие сайты Поделиться
Xipho Опубликовано 22 июня, 2021 Поделиться Опубликовано 22 июня, 2021 Это я, увы, без исследования игры, сказать не смогу Ссылка на комментарий Поделиться на другие сайты Поделиться
Sergey99 Опубликовано 22 июня, 2021 Автор Поделиться Опубликовано 22 июня, 2021 1 час назад, Xipho сказал: Это я, увы, без исследования игры, сказать не смогу Такая же штучка и в D3D работает к примеру в 9 версии, я пробовал, проверяя значение параметра primCount метода DrawIndexedPrimitive, похоже что функция не рендерит всё сразу а как то по очереди обрабатывает каждый примитив. Ссылка на комментарий Поделиться на другие сайты Поделиться
Sergey99 Опубликовано 23 июня, 2021 Автор Поделиться Опубликовано 23 июня, 2021 В 22.06.2021 в 17:07, Xipho сказал: Это я, увы, без исследования игры, сказать не смогу Разобрался как работают функции рисования примитивов в этих библиотечках. Всё очень просто, оказывается, что так как функция вызывается очень много раз, практически каждый кадр в игре, то и соответственно рисует она каждый кадр и каждый кадр количество примитивов, которые необходимо нарисовать может отличаться. К примеру в первый кадр рисуется мир, а во второй рисуется картинка с камеры игрока, через которую мы и видим мир игры и штука вся в том, что количество примитивов для рисования картинки с камеры нужно меньше чем для рисования мира (мир - от 100 примитивов, камера от 2 до ~10 примитивов). 1 Ссылка на комментарий Поделиться на другие сайты Поделиться
Рекомендуемые сообщения