
Сообщения
-
RE: CE Dissect Data Scaner 1.0.2 (4 hardware breakpoints)
Охота на структуры и удивительное путешествие в мир структур в L4D2
-
Игру L4D2 в оконный режим
-
Установка плагина (позже)
-
Установка VEH отладчика, иначе вылеты
-
Первая миссия. Ищем адрес патронов. Бряк на адрес. Нашли инструкцию
Красным показано, что я не всю структуру проанализирую позже. Долго было ждать. Структура больше 5К байт... -
Делаем сигнатуру любой инструкции при работе с патронами на всякий случай, если игра вылетит.
server.dll,83 BE 14 14 00 00 00 7F 4D
6. Переходим в код и ищем проскакивающие адреса на инструкции. Там один адрес нашего пистолета. Клик на него и переносим адрес начала будущей структуры в окно Dissect Window. Можно не создавать структуру (отказываемся). Кликаем "Scanner" (он будет на всех окнах dissrct data)
7. Далее работает сканирование.
В игре можно что-то делать, можно ничего не делать. Долго ждать..Меняю опции. Чувствительность как была так и осталась минимальная (это время ожидания прерывания на адресе умноженное на коэфициент чувсвительности и на 100 мс). Размер структуры меняю, до 1100
Наконец ~30 секунд дождался без вылетов (иногда бывают, возможно из-за VEH). Появился результат
Самые интересны это байтовые и float значения. и инструкции чтения. Их определит можно пока только по логам...Меняю первый байт на 1 и пистолет стал очень быстро стрелять.
Ради чего это все и делалось, чтобы похожие адреса искать...Если сравнить дефолтную расструктуризацию, она слева
Логи (для меня и для желающих) по определению типа по опкодам\+0 (2F3B49B0): vtDword 64FC73CB - mov edx,[ecx] \+28 (2F3B49D8): vtDword 6530433E - mov eax,[esi+28] \+4C (2F3B49FC): vtDword 651169A3 - cmp dword ptr [esi+30],00 \+4F (2F3B49FF): vtDword 651169B6 - mov eax,[esi+30] \+53 (2F3B4A03): vtDword 65116C60 - mov eax,[ecx+34] \+64 (2F3B4A14): vtByte 64F8627D - cmp byte ptr [esi+64],00 \+84 (2F3B4A34): vtDword 64F861F0 - movss xmm0,[esi+00000084] \+88 (2F3B4A38): vtDword 64F86215 - movss xmm2,[esi+00000088] \+8C (2F3B4A3C): vtDword 64F9F9A6 - movss xmm0,[eax+0000008C] \+90 (2F3B4A40): vtDword 64FF2CC6 - fld dword ptr [eax] \+C8 (2F3B4A78): vtDword 64FF2DA6 - mov ecx,[eax] \+CC (2F3B4A7C): vtDword 64FF2DE6 - mov ecx,[eax] \+E0 (2F3B4A90): vtDword 64FF2DA6 - mov ecx,[eax] \+104 (2F3B4AB4): vtDword 651141FA - mov eax,[eax] \+10C (2F3B4ABC): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+10D (2F3B4ABD): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+10E (2F3B4ABE): vtWord 64FF2D96 - movsx ecx,word ptr [eax] \+110 (2F3B4AC0): vtDword 64FF2DB6 - mov ecx,[eax] \+138 (2F3B4AE8): vtDword 64FBB772 - or [esi+00000138],edi \+171 (2F3B4B21): vtByte 64FBB7D4 - cmp byte ptr [esi+00000171],00 \+172 (2F3B4B22): vtByte 64FA6572 - cmp byte ptr [esi+00000172],03 \+173 (2F3B4B23): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+174 (2F3B4B24): vtDword 651141FA - mov eax,[eax] \+178 (2F3B4B28): vtDword 64FBB79D - mov eax,[esi+00000178] \+17C (2F3B4B2C): vtDword 64FBB7E8 - mov eax,[esi+0000017C] \+180 (2F3B4B30): vtDword 64FDD9A6 - mov eax,[ecx] \+188 (2F3B4B38): vtDword 64FF2D66 - fld dword ptr [eax] \+18C (2F3B4B3C): vtDword 64FF2D6D - fld dword ptr [eax+04] \+190 (2F3B4B40): vtDword 64FF2D73 - fld dword ptr [eax+08] \+194 (2F3B4B44): vtDword 64FF2D66 - fld dword ptr [eax] \+198 (2F3B4B48): vtDword 64FF2D6D - fld dword ptr [eax+04] \+19C (2F3B4B4C): vtDword 64FF2D73 - fld dword ptr [eax+08] \+1A0 (2F3B4B50): vtByte 64F9FD30 - movzx eax,word ptr [ecx+20] \+1A1 (2F3B4B51): vtByte 64F9FD30 - movzx eax,word ptr [ecx+20] \+1A2 (2F3B4B52): vtByte 64FDDBB0 - movzx eax,byte ptr [ecx+22] \+1A3 (2F3B4B53): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+1AA (2F3B4B5A): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+1AC (2F3B4B5C): vtDword 64FF2D66 - fld dword ptr [eax] \+1B0 (2F3B4B60): vtDword 64FF2D6D - fld dword ptr [eax+04] \+1B4 (2F3B4B64): vtDword 64FF2D73 - fld dword ptr [eax+08] \+1B8 (2F3B4B68): vtDword 64FF2D66 - fld dword ptr [eax] \+1BC (2F3B4B6C): vtDword 64FF2D6D - fld dword ptr [eax+04] \+1C0 (2F3B4B70): vtDword 64FF2D73 - fld dword ptr [eax+08] \+1E0 (2F3B4B90): vtDword 64FF2DA6 - mov ecx,[eax] \+1E4 (2F3B4B94): vtDword 64FF2DA6 - mov ecx,[eax] \+1E8 (2F3B4B98): vtDword 64FF2DA6 - mov ecx,[eax] \+1EC (2F3B4B9C): vtDword 64FF2DB6 - mov ecx,[eax] \+1F0 (2F3B4BA0): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+20C (2F3B4BBC): vtDword 651141FA - mov eax,[eax] \+210 (2F3B4BC0): vtDword 651141FA - mov eax,[eax] \+214 (2F3B4BC4): vtDword 64FF2CC6 - fld dword ptr [eax] \+218 (2F3B4BC8): vtDword 64FF2CC6 - fld dword ptr [eax] \+21C (2F3B4BCC): vtDword 64FF2CC6 - fld dword ptr [eax] \+220 (2F3B4BD0): vtDword 64FF2DE6 - mov ecx,[eax] \+22C (2F3B4BDC): vtDword 64FF2CC6 - fld dword ptr [eax] \+238 (2F3B4BE8): vtDword 64FF2DA6 - mov ecx,[eax] \+2B4 (2F3B4C64): vtDword 64FF2CC6 - fld dword ptr [eax] \+2E4 (2F3B4C94): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+2E5 (2F3B4C95): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+2E6 (2F3B4C96): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+2E7 (2F3B4C97): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+2E8 (2F3B4C98): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+2E9 (2F3B4C99): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+2EA (2F3B4C9A): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+2EB (2F3B4C9B): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+2EC (2F3B4C9C): vtByte 64FF2DC6 - movzx ecx,byte ptr [eax] \+374 (2F3B4D24): vtDword 64FA65AC - mov edi,[esi+00000374] \+378 (2F3B4D28): vtDword 64FF2DE6 - mov ecx,[eax] \+37C (2F3B4D2C): vtDword 64FF2DE6 - mov ecx,[eax] \+380 (2F3B4D30): vtDword 64FF2DE6 - mov ecx,[eax] \+384 (2F3B4D34): vtDword 64FF2DE6 - mov ecx,[eax] \+388 (2F3B4D38): vtDword 64FA65B8 - movss xmm1,[ecx] \+38C (2F3B4D3C): vtDword 64FA660B - movss xmm1,[ecx+04] \+390 (2F3B4D40): vtDword 64FA6653 - movss xmm1,[ecx+08] \+394 (2F3B4D44): vtDword 64FA68AD - movss xmm1,[eax] \+398 (2F3B4D48): vtDword 64FA68E1 - movss xmm2,[eax+04] \+39C (2F3B4D4C): vtDword 64FA6904 - movss xmm2,[eax+08] \+3A0 (2F3B4D50): vtDword 64FDEC3A - mov ecx,[edi] \+3B8 (2F3B4D68): vtDword 6508F54F - test [eax+000003B8],edx \+42C (2F3B4DDC): vtDword 64F84E81 - fstp dword ptr [esi+0000042C] \+434 (2F3B4DE4): vtDword 64FF2DA6 - mov ecx,[eax] \+438 (2F3B4DE8): vtDword 64FF2D66 - fld dword ptr [eax] \+43C (2F3B4DEC): vtDword 64FF2D6D - fld dword ptr [eax+04] \+440 (2F3B4DF0): vtDword 64FF2D73 - fld dword ptr [eax+08] \+444 (2F3B4DF4): vtDword 64FF2DA6 - mov ecx,[eax] \+448 (2F3B4DF8): vtDword 64FF2DA6 - mov ecx,[eax] \+44C (2F3B4DFC): vtDword 64FF2DE6 - mov ecx,[eax]
Плагин выложу позже. Надо еще доработать и потестить.
Вот к примеру float распознал как Pointer и там где fst тоже по +42C тоже фигня. Это ошибки. Это быстро поправить, но могут быть еще ошибки.
Не менее интересны еще вложенные структурки, которые удается раскрыть (не все). Вот одна из них и усеяна параметрами, которые можно покрутить
Вложенные структуры пока отдельно можно создавать в окнах, чистить сканером, потом подставлять их в основную структуру по имени. В общем пока рано об этом писать.
Больше всего меня волнует польза, т.е. что можно с этим сделать. Пока только сразу вышел на скорострельность. Еще шесть параметров покрутил байтовых, ничего не дало. Надо еще попробовать выводить только смещения, которые работают на инструкцияъ чтения, а не "чтении и записи". Запись скорее всего не нужна. Значения просто активно перезаписываются в структуре. А вот оставлять смещения, с которыми работает только "чтение" скорее всего даст куда больше вероятности найти параметр настройки.
-
-
CE Dissect Data Scaner 1.0.2 (4 hardware breakpoints)
- Точное определение типа данных на смещениях структуры (те которые срабатывают на аппаратных брейкпоинтах);
- Показать в имени элемента структуры один из опкодов чтения или его часть (с регистрами и смещением);
- Найти такие смещения, с которыми работают только инструкции чтения за указанное время равное Accuracy * 10мс на байт в структуре. Чем выше Accuracy, тем выше вероятность убрать больше лишний смещений;
Плагин пока не планируется развивать из-за его особенности работы с 4мя аппаратными брейкпоинтами. С одной стороны это не тормозит игру, но с другой стороны нужно в игре совершать одни и те же действия чтобы по 4 байта в структуре срабатывали друг за другом до конца структуры.
Есть другие способы снимать показания на любой размер структур установкой брейкпоинта на большую область памяти указанного размера (в другом плагине, который я пока не выложил). При этом чем структура больше и чем больше в ней прерываний тем сильнее тормозит, но оно того стоит, потому что показания снимаются практически одновременно.
Это тестовая версия, поэтому могут быть ошибки при сканировании. О них можно написать мне в личку с названием игры, сигнатуры инструкции.
Если плагин вдруг заключил и пишет что-то в консоли, то нажать на кнопку Stop или для остановки ввести Lua команду
stopDissectDataScanner()
УстановкаРаспаковать в основную директорию с программой. В архиве форма, файл настроек и lua исходник.
Как работать с плагином
- Ищем параметр в игре
- Ставим "точку останова на доступ"(брейкпоинт) или "на запись"
- Находим одну из инструкций. Например, в игре Кредо Убийцы 3 это инструкция
AC3SP.exe+11BAEEB - 66 89 4E 5C - mov [esi+5C],cx
- Можно пойти двумя способами по переносу базового адреса в окно структур
4.1 На инструкции смотрим например базовый адрес ESI, он будет началом структуры. Его переносим в окно Dissect Data (открывается в отладчике). Отказываемся от всех диалогов, чтобы не получать структуру, которую создает Cheat Engine.
4.2 Определить адреса например на "[esi+5c]" в дизассемблере и на любом вызвать контекстное меню и перебросить базовый адрес в Dissect Data
-
В окне Dissect Data запускаем "Scanner" нажав на эту надпись и вводим имя структуры, размер в hex, и чувствительность поиска от 1 до 10
-
Идем как можно быстрее в игру, а она уже должна быть активна и в оконном режиме, и не в меню. Ждем пока завершится сканирование, что-то делая в игре или простоя стоим там. Если очень долго, можно уменьшить чувствительность или размер структуры в hex-е
-
После того как структура получена, мы основывать на своем опыте, ищем в структуре такие данные, которые могут повлиять на игровой процесс. Т.е. меняем что-то в структуре в байтовых, сингловых типах и смотрим в игру. Очень редко 4-х байтовые что-то дают,а поинтеры менять не надо их можно по аналогии смотреть в новых окнах.
Результаты
Нет 100% гарантии сразу найти все рабочие адреса, в которых что-то будет интересное. Большая часть может себя не проявить в игре или привести к вылетам. После определенного количества вылетов, вы поймете какие типы данных и как менять и вылетов будет меньше.
Перед потенциальными вылетами делаем сигнатуры, чтобы вернуться на инструкцию.
Вызов Lua кода после crash-а вернет адрес инструкции
local sign = 'F3 0F 11 80 EC 00 00 00' function GetAddress(signature) local t = AOBScan(signature, '+X-C-W',0) local s = t[0] t.destroy() return s end print(GetAddress(sign))
...Много зависит от ползания по структуре и внутри структур (об этом в блоге) и соответственно, нужно время.
Если времени мало. Получаем около 20 параметров структуре байтовых и сингловых. Безопасней всего менять сингловые. Их можно увеличить все в 2 раза и зайти в игру и увидеть, что там происходит. Не забываем перед правками сделать копию структуры и нажать на Lock, чтобы сравнить и откатить изменения. За тем уже правим по байтикам на ноль или на единицу. Редко бывают вылеты. Каждый адрес отмечаем, что его проверяли или удаляем. Если интересные, то меняем название. Сохраняем структуру.
Ищем так несколько структур. Все их сохраняем. Не забываем делать сигнатурки для каждой структуры, чтобы получить базовый адрес.
Собрали структуры. Ищем связи между ними. Об этом я скриншотил в блоге (ссылка выше). Опять сохраняем структуры.
На три игры я находил 1-3 полезных параметра и до 6 каких-то забавных или баговых. Например, я нашел коэффициент скольжения персонажей в структуре с координатами и там же гравитацию. Попадались адреса, которые запрещали двигаться, т.е. можно морозить ботов. Попадались байтовые адреса, меняющие руки с оружием в L4D2.
Структуры сохраняются в файл таблицы или экспортируются в отдельный файл.
Далее делаем читы, как обычно. Либо меняем код, либо меняем данные по базовому указателю через скрипты Аutoasembler
- Исправлены ошибки с определением смещения и типов данных. Это позволил сделать новый алгоритм, который анализирует все опкоды работающие с адресом и выбирает наиболее подходящие для определения смещения адреса в структуре и для определения типа данных в структуре.
Выбор лучшего опкода для определения типа данных:
- опкоды с перезаписываемыми регистрами не выбираются;
- опкоды работающие с float приоритетнее чем dword;
- опкоды без квадратных скобок пока пропускаются: всякие repe, movsd и другие
- опкоды с не правильным дизассемблированием пропускаются (невозможно получить адрес в скобках)
- Много новых данных в логах, после формирования структуры. С его помощью можно просмотреть все опкоды, которые сработали на смещении структуры.
На скриншоте пример структуры, после работы плагина
-
CE Plugin: AA Maker 2.4.2
AAmaker предназначен для создания создания autoassembler скриптов для Cheat Engine 6.5 или выше
Предыстория. Изначально нужно было генерировать динамически АА-шаблоны по правилам, не зная регистров и прочего, на инструкции по сигнатуре. Это не было реализовано.
Вместо этого пошла другая ветвь - генерация статичного АА-шаблона. Есть некоторые особенности. Создает сигнатуру с пропусками между первыми байтами опкодов. Это сделано для поддержки множества версий игр (для первой ветви развития плагина), но это пропускает последующие за первым байтом опкоды и значит регистры в опкоде могут быть разными, а это значит, что сама идея таких сигнатур не очень универсальная и подойдет разве ,что для опкодов без регистров или же для ноппинга. Либо фиксить сигнатуру на нормальную. Либо пользоваться генерируемыми шаблонами в CE по умолчанию.
-----------
Это плагин CE Lua plugin для Cheat Engine 6.5 или вышеAAmaker предназначен для содания создания autoassembler скриптов.
- Установите aamaker.lua в "Autorun" директорию
- Запустите Cheat Engine.
- Присоедините процесс
- Перейдите в "Memory View" окно
- Выделите адрес кода
- Правой кнопки мышки вызовите контекстное меню
- Используйте функции "AA Maker" в контекстном меню
----------
+ исправления
+ новый директивы:
{$AddressInjection}
{$OriginalCode}
{$CheatCode}
{$Nops}
{$ArrayOfbyte}
{$ProcessName}
{$Date}
{$PrintLog}
+ исправления шаблонов
+ удален aobscan. Изменен aobscanmodule - Исправление ошибок (автор ++METHOS)
- Добавление шаблонов (автор ++METHOS)
Исправление. Совместимость с CE 6.5
-
CE Lua Крестики и нолики
Игра с рандомом. Есть счет.
Запускается через таблицу в аттаче.
Здесь мог быть AI на Lua, возвращающий номер клетки от 1 до 9.function InputAI() -- рандом return math.random (1, 9) end
player1Symbol = '1' player2Symbol = '0' whoStep = 0 player1Score = 0 player2Score = 0 function InputAI() -- рандом return math.random (1, 9) end \-- Проверка правил function caheckRules(symbol) -- Комбинации выигрыша local tableRules = { {1,2,3}, {4,5,6}, {7,8,9}, -- горизонтальные клетки {1,4,7}, {2,5,8}, {3,6,9}, -- вертикальные клетки {1,5,9}, {3,5,7} -- клетки по диагонали } -- Результат проверки клеток local mask = [[ return UDF1.CEButton%s.Caption == 'symbol' and UDF1.CEButton%s.Caption == 'symbol' and UDF1.CEButton%s.Caption == 'symbol' ]] for i = 1, #tableRules do local luaStringCode = mask:format(tableRules[i][1], tableRules[i][2], tableRules[i][3]):gsub('symbol', symbol) if loadstring (luaStringCode)() then return true end end return false end function StartGame() whoStep = 0 for i = 1, 9 do loadstring ('UDF1.CEButton'..i..'.Caption = ""')() end end \-- 1 - ход сделан, 0 - ход не удался function Input(indexInput) if loadstring ('return UDF1.CEButton'..indexInput..'.Caption')() ~= '' then return 0 end local writeSymbol = player1Symbol if whoStep == 0 then whoStep = 1 else whoStep = 0 writeSymbol = player2Symbol end local s = 'UDF1.CEButton'..indexInput..'.Caption = '..writeSymbol loadstring (s)() local somebodyWinner = false if caheckRules(player1Symbol) then player1Score = player1Score + 1 UDF1.CELabelScore.Caption = player1Score..':'..player2Score ShowMessage('Player1 is winner!') somebodyWinner = true elseif caheckRules(player2Symbol) then player2Score = player2Score + 1 UDF1.CELabelScore.Caption = player1Score..':'..player2Score ShowMessage('Player2 is winner!') somebodyWinner = true end -- Если кто-то выиграл, то очистить поле if somebodyWinner then StartGame() return 1 end -- Проверка ничьи local countEmpty = 9 for i = 1, 9 do if loadstring ('return UDF1.CEButton'..i..'.Caption ~= ""')() then countEmpty = countEmpty - 1 end end if countEmpty <= 0 then UDF1.CELabelScore.Caption = player1Score..':'..player2Score ShowMessage('Friendship!') StartGame() return 1 end -- Ходит IA if whoStep == 1 then ::repeat1:: local index = InputAI() if Input(index) == 0 then goto repeat1 end end end function CEButtonClick(sender) Input(tonumber(sender.name:match('%d'))) end function MenuItem1Click(sender) player1Score = 0 player2Score = 0 UDF1.CELabelScore.Caption = player1Score..':'..player2Score StartGame() ShowMessage('Restart!') end UDF1.show()
-
CE Stack Viewer
Информация о файле
НазначениеПлагин позволяет в реальном режиме игры наблюдать за стеком на определенной инструкции отладочного кода. Это в свою очередь позволяет в окне расструктуризации данных "Dessect Data Structure" сравнивать старые и текущие структуры стека в реальном режиме игры, наблюдать за появлением красных данных и обращать на них особое внимание. Красные данные показывают чем отличаются стеки при действиях в игре. Например, выстрелив один раз и выстрелив следующий раз можно увидеть отличия красным цветом в стеке в реальном режиме. Это могут быть не только изменения патронов, но и чего-то другого, когда уже стреляем другим оружием. Важно то, что можно видеть изменения в реальном времени, а не по копиям стека, которые приходилось раньше снимать много раз. Мы можем видеть как часто по времени что-то меняется, все в динамике...
Есть у меня сомнения по поводу постоянного ESP сравнения, при котором происходит "наблюдение" за стеком, но пока оставлю как есть.
Установка
Распаковать файлы архива в директорию "autorun". Например,
C:\Program Files (x86)\Cheat Engine 6.4\autorun
Краткое руководство-
Запускаем игру
-
Находим адрес параметра
-
Ставим брейкпоинт на адрес и появляются инструкции (или что-то делаем в игре чтобы инструкции появились)
-
Выделяем инструкцию, которую требуется исследовать
-
Останавливаем отладку
-
Открываем Stack Viewer из окна Дизассемблер-> Tools->* Stack Viewer [Plugin] и переносим в ESP, EIP (или RSP, RIP) в поля окна Stack Viewer. Нажимаем галку "Is Active" и идем в игру, и если надо что-то делаем в игре, потому что инструкция должна выполниться хотя бы один раз чтобы заполнить дамп стека в реальном времени
-
Открываем окно расструктуризации и пишем метку "MemStackRunTime" (или другую) и делаем расструктуризацию.
-
Открывает плагин Tiny Dumper и клонируем "MemStackRunTime" в другую метку "StackDump1" (или другую). Теперь у нас в окне две структуры: с разовой копией и постоянно обновляющейся копией стека.
-
Идем в игру и повторно делаем какие-то действия при этом наблюдаем за окном расструктуризации. Если в окне красные данные не появились, то следует проскролить структуру ниже. Если красные данные появились, то можно повторить действия в игре и пронаблюдать в реальном режиме закономерность изменения красных данных и частоту их изменения. Возможно найдутся интересные зависимости связанные с каким-то действием в игре. Если красных данных не нашлось, то никаких изменений в стеке не было. Попробуйте сделать что-то другое.
-
Для остановки наблюдения за стеком выключаем галку "Is Active"
-
-
RE: CE Tiny Dumper
Пошаговое руководство по Tiny Dumper
В этом руководстве по шагам описано как снять дамп со стека и раструктуризовать его. Если лень читать, то предлагаю посмотреть хотя бы последний скрин. Там можно увидеть результат. Это самый важный скрин.
-
Перед запуском CE устанавливаем файлы плагина в папку autorun (frmTinyDumper.xml и TinyDumper.lua)
-
Запускаем игру например "Painkiller - Battle out of Hell 1.64" поставленная в оконный режим через 3DAnalyze.
Оконный режим ставить обязательно, т.к. будет пошаговая отладка. В противном случае игра зависнет, а с CE рабтать нельзя будет
-
Запустить CE с установленным плагином и подключиться к игре
-
Ищем адрес здоровья (или брони, или патронов), тип Double (8 байт)
0x24571EA8 = 99.2900238037109 -
Ставим бряк на доступ или на запись на адрес 0x24571EA8
- Идем в игру и встаем под удар персонажем
Появляются инструкции
и там же нажимаем на кнопку Stop, чтобы бряки не мешались в окне бряков- Берем например инструкцию
1015C194 - 89 53 08 - mov [ebx+08],edx
По ней идем в дизассемблер и на инструкции жмем F5 - установка бряка на доступ- Открываем окно бряков, выделяем инструкцию и пишем условие на прерывание с остановкой процесса
EBX==0x24571EA0 (это условие взяли, потому что ebx находится в инструкции, а значение взяли EBX из отладчика)
Нажимаем Ок, идем в игру
9. Видим, что произошла остановка игры
10. Открываем окно ТиниДампера
-
Пишем значение регистра esp и название будущей зарегенной метки
-
Нажимаем "Dump and Close" и CTRL+U чтобы видеть под рукой зареганные ваши метки
-
Открываем окно расструктуризации CTRL+D и пишем метку в свободное поле
-
Далее следуем действия как на рисунках
-
Наконец результат
Не только сохраняются данные, которые пропадут на следующий такт процессора, но и расструктуризовали и можем сравнивать эти данные с другими дампами стека. Зачем, как и для чего это выходит за рамки этого руководства.
-
Сейчас процесс игры остановлен на бряке кода. Чтобы продолжить игру. Удаляем бряк из окна бряков и отпускаем игру по F9.
-
Теперь снимаем другие дампы на том же адресе для адресов здоровья врагов на предмет отличиях их или для поиска указателей... Во всяком случае сохраненный дамп может дать какие-то подсказки и зацепки. Главное не начать новый уровень игры или не загружать сохранения, т.к. обычно при переходе на новый уровень данные могут измениться... На этом пока все
-----
Tiny dumper 1.2 Сравнение структур после перезагрузки. Поиск фильтра свой-чужой-дружественый-
Запускаем игру, запускаем CE и присоединяем процесс игры к CE
-
Сохраняем игровой слот перед тем как искать структуры
-
Находим три структуры игроков: свой игрок, дружественный, враг
-
Дампим три структуры из пункта 3 указывая: базовый адрес структуры, уникальную метку
-
Игру закрываем, снова открываем, загружаем слот, присоединяем процесс игры к CE
-
В Tiny dumper 1.2 вызываем контекстное меню правой кнопки мышки и вызываем опцию "Rewrite dumps" - дампы из кеша перезаписываются в память в процесса игры
-
Снова находим три структуры игроков: свой игрой, дружественный, враг
-
В окне расструктуризации создаем три группы
Группа1 "Свой игрок":
-адрес после перезагрузки
-метка из тини дампера своего игрока до перезагрузки
Группа2 "Чужой игрок":
-адрес после перезагрузки
-метка из тини дампера чужого игрока до перезагрузки
Группа3 "Дружественный игрок":
-адрес после перезагрузки
-метка из тини дампера дружественного игрока до перезагрузки
-
Если повезет, то находим фильтр - фиолетового цвета строка. Если филетовых строк несколько, то запоминаем их смещения и значения на всякий случай
-
Пишем АА-скрипт со смещением фильтра. Правильно написанный АА-скрипт не будет крешить игру.
-
Активируем АА-скрипт и проверяем в игре, что свой и дружественный игроки не получают урон, а вражеские получают урон. Если это не так, то берем другие смещения из пункта 9 и повторяем 10.
Если фильтр найти не удалось, то ищем 3 многоуровневых указателя до верхего статического адреса для своего, для 2-х врагов. Открываем Structure Spider.
Указываем в поиске уровень вложенности указателей.
Попеременно вводим два статических адреса - начала структур своего и чужих игроков. Правило поиска выбираем исходя из типов игроков. Если это свой и чужой, то искать отличные. Если это чужой и чужой, то искать одинаковые. Перезапускам игру и опять проделываем эти действия.
Если не нашлось ничего, то что-то делали не так или отличия между группами игроков нет. Однако графический интерфейс обычно связан только с нашим героем и не связан с другими. Поэтому по связи GUI как минимум фильтр свой-чужой можно сделать всегда. Что касается дружественных игроков, то нужно будет исследовать более углубленно.
-
-
CE Tiny Dumper
Функции:
-
Копирование участка виртуальной памяти фиксированной длины
-
Связывание адреса копированных данных с новой меткой
-
Возможность сравнивать дампы во время игры с любого доступного адреса (в том числе стека)
-
Возможность сравнивать сохраненные дампы структур после перезагрузки игры
Основное назначение - снимать дампы со стека в пошаговой отладке по адресу ESP и сравнивать эти дампы в окне расструктуризации данных (Dessect Data/Structure). Таким образом можно найти сходства или различия в передаваемых аргументах функций, а также в адресах обратного вызова по инструкции ret. Сравнение аргументов может помочь определить указатели, смещения на данные. Сравнение адресов возврата может помочь определить развилки между ветками кода ботов и ветками персонажа за которого играете. Можно начать исследовать эту развилку для определения условий свой/чужой.
Второе назначение - сравнивать дампы после перезагрузки игры. Сравнения дампов после перезагрузки, загрузки уровней игры, загрузки слотов сохранения, смены оружия, изменения в инвентаре, смены уровня героя и многие другие изменения после перезагрузки игры могут позволить найти отличия или совпадения данных структур от начала базового адреса структуры и обратить на них гораздо большее внимание среди данных в структуре
Данные для ввода:
Source Address - адрес с которого будет сниматься дамп
Size dump - размер участка данных в байтах
Register label - метка для обращения к адресу
*Подсказки:
-
Плагин можно найти в первом окне дизассемблера в меню Tools (Утилиты). Окна-клоны дизассемблера не имеют подключенного подменю.
-
Зарегистрированные метки, которые вы забыли можно посмотреть и удалить через окно зарегистрированных меток "Symbol Config" (нажать на CTRL+U в окне дизассемблера)
-
Память дампов сама не очищается. Поэтому не рекомендуется снимать очень часто огромные дампы и тем самым засорять память. Если все же память переполнена, что мало вероятно, то просто перезапустить игру.
Что нового в версии 1.1
Размещено 6 марта, 2015- Добавлен список зарегистрированных меток
- Добавлено контекстное меню с опциями копирования меток в буфер обмена и удаление меток с освобождением памяти
- Кнопка снятия дампа больше не закрывает окно
Что нового в версии 1.2
Размещено 6 марта, 2015Добавления:
- При выделении записи в полях выводятся данные адреса, размер дампа и название зарегистрированной метки
- Добавлена опция сохранения дампов
- Добавлена опция загрузки дампов
- Добавлена опция очистить таблицу
- Добавлена опция перезаписи дампов
- После закрытия игры дампы можно сохранять
Исправления: - Исправлено подключение к русской версии
- Исправлены название опций
Описание контекстного меню: - Copy Name - копирует имя выделенной записи
- Remove selected - удаляет выделенную запись
- Save to file - сохранить все дампы в файл, в указанную директорию
- Load from file - загрузить дампы из файла
- Ramove all records - удаляет все дампы и зарегистрированные метки
- Rewrite dumps - перезаписывает дампы в новую память, регистрирует метки адресов дампов повторно
-
-
Tool C# Regular Expression
string allText = richTextBox1.Text; string pattern = textBox2.Text; string s = string.Empty; try { Regex regex = new Regex(pattern); MatchCollection matches = regex.Matches(allText); if (matches.Count > 0) { foreach (Match match in matches) s += match.Value + ";\r\n"; } else { s = "Совпадений не найдено"; } textBox3.Text = s; } catch (Exception) { //throw; }
-
CE mapbranches
(Другие записи: https://celua.ru/topic/113/ветвления-кода-связанные-с-адресом-старые-записи/2)
- На адрес устанавливается брейкпоинт.
- Идем в игру делаем или не делаем что-то
- Начинают срабатывать инструкции на брейкпоинте
- От каждой инструкции начинается трейслог, подобный тому, который есть у CE, но менее тормозной
- Заканчивается трейслог на инструкции, на 100-ом счетчике после ret-а (об этом пункте будет уточнение). Здесь выходим на коневой цикл
- Трейслог останавливается и ожидание следующей инструкции, которая сработает на брейкпоинте, она должна быть отличной
В итоге мы имеем кучу трейслогов начинающихся с адреса работающего с параметром (например, кол-во патронов в обойме) и заканчивающихся на корневом цикле. Также мы собираем данные об адресах с рет-ами и счетчиками.
По этим данным можно нарисовать схему, где ret-ы меняют направление пути прохождения потоком дизассемблерного кода
На скриншотах ниже
Вертикальные линии со кружками -— это ветвь трейслога
Белый кружок — адрес ret-a, который не повторялся
Пунктирный кружок — адрес ret-а, который повторялся. Стрелка от такого пунктирного круга будет указывать на белый круг.По схеме видим, где ret-ы меняют свое направление поднимаясь по рутине в корневом цикле
По плану по визуальной части:
-
Клик на кружок — переход на код
-
Придумать что-то, чтобы стрелки не накладывались друг на друга
-
Установка брейкпоинтов на кружках — становиться красными
-
Легенда. Номер ветви, связанная инструкция, состояние брейкпоинтов. Комментарий к ветви
-
Загрузка/сохранение легенды
Для чего нужно. Для поиска условий, которые включают и выключают ветви кода. Т.е. чтобы можно было это условие быстро найти и изменив его, повторить выполнение кода ветви, которая "пробьётся" из корневого цикла. Таким образом попытаться повторить действие в игре, а если не получаться повторить, то определить дополнительные условия.
-
RE: Ветвления кода связанные с адресом (старые записи)
Трассеровка по ретам
-------
На скриншоте 22 ветви трассеровки от адреса патронов не входя в call-ы до корневого цикла. Корневой цикл определяется, когда поиск происходит более трех секунд и не находит ничего. Вполне хватает этого времени. Буду еще повторно тестить и проверять доходит ли он цикла или надо еще что-то придумывать.
Желтым выделяется точка, вертикальная полоса ветви и связи с такими же узлами. Узел это адрес выхода из ret
Внизу ряд счетчиков от 00 до 99. Если более 99 то отсчет заного. Показывает дианамику обращения к ветвям после нахождения корневого цикла
Есть еще несколько функций, которых не видно на скриншоте — в контекстном меню.
Анализ можно запустить из окна CE из главной таблицы, выделив адрес и опцию либо на чтение, либо на доступ.
При тестах увидел, что логи неполные. Например, первый кружок это инструкция читающая патроны очень часто. От этой инструкции идут вверх множество вариантов ветвей,а у меня один вариант — вертикальная полоска с кружками. От первого круга надо сделать ветвление влево и вправо.
Занялся модернизацией этого рисования и оптимизацией. Например, чтобы определить является ли проход по ret, то не нужен дизассемблер. Залез в доки и посмотрел опкоды
function IsRet(address) local value = readBytes(address,1, false) return value == 0xC3 or value == 0xCB or value == 0xC2 or value == 0xCA end
Скорость трассеровки должна в несколько раз увеличиться. К сожалению, смогу это узнать, когда перепишу текущий плагин.
Там были уже сделаны изменения (все еще сырое для публикации плагина, пока не публикую)
- Нумерация ветвей
- Клик на круг — переход в дефоттный дизассемблер
- Подсвечивать выбранный круг
- Перерасчет позиций, когда поменялся размер окна
- Счетчики выполнений инструкций срабатывающих к обращению на адрес
- Изменен способ рисования связей между ветвями
- Контекстное меню Показать ветку
- Контекстное меню Скрыть ветку
- На скрытой ветви не показываются связи упрощая обзор. Код поднимается снизу вверх. Если одна ветвь ниже другой, то можно её скрыть. Ориентируемся по счетчикам хитов на инструкции
- Контекстное меню Сброс хитов
- Оптимизация поиска корневого цикла
- Выделение связей
- Оптимизация рисования новых свзей
- Контекстное меню пауза/продолжение
- Запуск из контекстного меню при выделеии адреса в главной таблице
- При закрытии окна снимаются все брейкпоинты
- Возможность ставить логи на запись из контекстного меню и на чтение в главной таблице CE
-
Ветвления кода связанные с адресом (старые записи)
В этой записи блога не будет чего-то, что показало бы "вау, это что-то новое и есть результат". Все сырое и результат пока мне только снится — быстрый поиск условий и включение, и выключение ветвей по этим условиям. Мыслю я не инструкциями, не группой инструкций, а ветвлениями кода и условиями, которые их запускают. Проще 20 ветвлений по 5 окон, чем тонна инструкций... Жаль пока теория, практики с результатом нет.
Рисунок. На нем слева прототип, справа текущий сырой вариант
-
ставим брейкпоинт на адрес патронов
-
от него расплетаются ветви кода от каждого хита, от каждой инструкции, и по ретам ветвление выходит из рутины
Кружи означают ret-ы. Клик на круг будет переходлм в дизассемблер
Стрелки будут показывать входы и выход связанные с соседними рутинами (поиск общих адресов будет стрелками)
На экране справа вариант без стрелок, т.к. не успел еще их отладить. 4 ветки трассера по ретам из рутины связанные с адресом патронов, выстрелами.Потом я продолжал стрелять, перезаряжаться, выкидывать и поднимать оружие. В итоге собрал 20 ветвлений по ретам от инструкций связанные с патронами
07232118 - 8B 85 90060000 - mov eax,[ebp+00000690] 0721C03F - 0FB7 8E 90060000 - movzx ecx,word ptr [esi+00000690] 0722FE69 - 39 86 58030000 - cmp [esi+00000358],eax 0722FEA8 - 83 BE 58030000 00 - cmp dword ptr [esi+00000358],00 0721C4A0 - 0FB6 96 B0030000 - movzx edx,byte ptr [esi+000003B0] 0723155C - 83 BE B0030000 00 - cmp dword ptr [esi+000003B0],00 07230357 - 83 BE 90060000 00 - cmp dword ptr [esi+00000690],00 0721E00C - 8B BE 90060000 - mov edi,[esi+00000690] 0722330C - 01 BE 90060000 - add [esi+00000690],edi 0722FFC7 - 83 BE 58030000 00 - cmp dword ptr [esi+00000358],00 072306DF - 83 BE 90060000 00 - cmp dword ptr [esi+00000690],00 0722E808 - 8B 86 90060000 - mov eax,[esi+00000690] 07231150 - 8B 8E 90060000 - mov ecx,[esi+00000690] 07231186 - 83 86 90060000 01 - add dword ptr [esi+00000690],01 072311D4 - 8B 86 90060000 - mov eax,[esi+00000690] 0723121A - 3B 96 90060000 - cmp edx,[esi+00000690] 07231FE9 - 83 BE 90060000 00 - cmp dword ptr [esi+00000690],00 0721CA71 - 0FB6 86 B0030000 - movzx eax,byte ptr [esi+000003B0] 0721E9B7 - 83 BE 90060000 00 - cmp dword ptr [esi+00000690],00 0721EA26 - DA BE 90060000 - fidivr [esi+00000690]
Как видно, довольно все еще сырое, но видно, что все 20 ветвлений по ретам сформировались до корневого цикла.
Потом можно будет, я надеюсь, увидеть связи между этим ветвлениями, когда я прикручу стрелки. По кружкам кликать и переходить в дизассемблер и смотреть условия выше. На условии искать другие адреса, которое запускает ветвь кода. На адресах опять делать ветвления (новое окно с ветвлениями о ретам)... На практике будет получаться более двух таких окон (для патронов). Нужно будет искать адреса связанные с условиями в новом окне.
ClassSettings = {} ClassSettings.__index = ClassSettings function ClassSettings:New(_fileName, _maskFile) local obj = {} obj.stringList = createStringlist() obj.fileName = _fileName obj.maskFile = _maskFile obj.directoryPath = getCheatEngineDir()..'autorun' obj.filePath = obj.directoryPath..'\\'..obj.fileName -- Есть ли такой ключ function obj:HasKey(keyString) local stringCount = self.stringList.Count for i=0,stringCount-1 do local items = self:Split(self.stringList[i]) if(keyString == items[1]) then return true end end end -- Получить значение ключа function obj:Get(keyString, defaultValue) if(obj:HasKey(keyString)) then local stringCount = self.stringList.Count for i=0,stringCount-1 do local items = self:Split(self.stringList[i]) if(keyString == items[1]) then return items[2] end end end return defaultValue end -- Записать ключ function obj:Set(keyString, stringOrDigitalValue) -- Искать номер строки local stringCount = self.stringList.Count for i=0,stringCount-1 do local items = self:Split(self.stringList[i]) if(keyString == items[1]) then items[2] = stringOrDigitalValue self.stringList.remove(self.stringList[i]) break end end self.stringList.add (keyString..' '..stringOrDigitalValue) end -- Возвращает числовой вариант function obj:GetDigital(keyString, defaultValue) if(obj:HasKey(keyString)) then return tonumber(obj:Get(keyString)) end return defaultValue end -- Сохранить все ключи function obj:Save() self.stringList.saveToFile(obj.filePath) end function obj:FileExist(directoryPath, pathToFile, mask) local paths = getFileList(directoryPath, mask, false) for i=1,#paths do if(paths[i] == pathToFile) then return true end end return false end function obj:Split(argString) local resultTable = {} for i in string.gmatch(argString, "%S+") do table.insert(resultTable, i) end return resultTable end -- Загрузка ключей в память if(obj:FileExist(obj.directoryPath, obj.filePath, obj.maskFile)) then obj.stringList.loadFromFile(obj.filePath) end setmetatable(obj, self) return obj end classSettings = ClassSettings:New('userdata2.txt', '*.txt') \---------- function DeleteAllBreakPoints() local tableAddressOnBreakPoint = debug_getBreakpointList() for i =1, #tableAddressOnBreakPoint do debug_removeBreakpoint(tableAddressOnBreakPoint[i]) end end ClassTraceBranch = {} function ClassTraceBranch:New(_dataAddress, _onEnd) local obj = {} obj.address = _dataAddress obj.OnEnd = _onEnd obj.Is64Bit = targetIs64Bit() obj.tableData = {} --Breakpoint methods: bpmInt3=0, bpmDebugRegister=1, bpmException=2 --breakpoint continue methods: co_run=0, co_stepinto=1, co_stepover=2 --Breakpoint triggers: bptExecute=0, bptAccess=1, bptWrite=2 --debug_setBreakpoint(self.address, 1, bptExecute, bpmDebugRegister) function obj:OnBreakpoint() local XIP = 0 if(self.Is64Bit) then XIP = RIP else XIP = EIP end local someFind = false local endAction = false for i=1,#self.tableData do --if(self.tableData[i].XIP == XIP) then if(self.tableData[i].XIP == XIP) then self.tableData[i].Count = self.tableData[i].Count + 1 someFind = true if(self.tableData[i].Count > 100) then endAction = true end break end end if(endAction) then --table.insert ( self.tableData, {XIP = XIP, Count = 1}) --self:PrintData() --print('-->'..string.format('%08X',XIP)) DeleteAllBreakPoints() debug_continueFromBreakpoint(co_run) self.OnEnd() else if(not someFind) then local _,opcode,_,_ = splitDisassembledString(disassemble(XIP)) local isRet = string.match(opcode,'ret') --if(isRet) then -- print('ret '..string.format('%08X', XIP)) --end table.insert ( self.tableData, {XIP = XIP, Count = 1, isRet = isRet}) debug_continueFromBreakpoint(co_stepover) end end return 1 end function obj:PrintData() local s = '' local addressPrevious = getPreviousOpcode(self.address) local _,opcode,_,_ = splitDisassembledString(disassemble(addressPrevious)) print(string.format('%08X - %s:\r\n', addressPrevious, opcode)) for i=1,#self.tableData do local _,opcode,_,_ = splitDisassembledString(disassemble(self.tableData[i].XIP)) s = s..string.format('%08X - %s - %s\r\n', self.tableData[i].XIP, self.tableData[i].Count, opcode) end print(s) end debug_continueFromBreakpoint(co_stepover) setmetatable (obj, self) obj.__index = ClassTraceBranch return obj end ClassManagerBranches = {} function ClassManagerBranches:New(mainAddress, sizeBreakPoint) local obj = {} obj.tableData = {} --XIP = 0, traceBranch = nil obj.mainAddress = mainAddress obj.sizeBreakPoint = sizeBreakPoint obj.currentTraceBranch = nil obj.isLogBranch = false local Is64Bit = targetIs64Bit() function obj:Containts(XIP) for i=1, #self.tableData do if(self.tableData[i].XIP == XIP) then return true end end return false end function obj:Stop() DeleteAllBreakPoints() for i = 1, #self.tableData do local addressPrevious = getPreviousOpcode(self.tableData[i].traceBranch.address) local _,opcode,_,_ = splitDisassembledString(disassemble(addressPrevious)) print(string.format('%08X %s', addressPrevious, opcode)) end print('\r\n') for i = 1, #self.tableData do self.tableData[i].traceBranch:PrintData() end debug_continueFromBreakpoint(co_run) end function obj:OnBreakpoint() if(self.isLogBranch) then return self.currentTraceBranch:OnBreakpoint() end local XIP = 0 if(Is64Bit) then XIP = RIP else XIP = EIP end if(not self:Containts(XIP)) then local hitAddress = getPreviousOpcode(XIP) local _,opcode,_,_ = splitDisassembledString(disassemble(hitAddress)) --print(string.format('Start Trace at : %08X %s', hitAddress, opcode)) DeleteAllBreakPoints() self.currentTraceBranch = ClassTraceBranch:New ( XIP, function () --print('Stop') self.isLogBranch = false debug_setBreakpoint(self.mainAddress, self.sizeBreakPoint, bptAccess, bpmDebugRegister) end ) table.insert (self.tableData, {XIP = XIP, traceBranch = self.currentTraceBranch}) self.isLogBranch = true end return 1 end print('Start at: '.. obj.mainAddress) debug_setBreakpoint(obj.mainAddress, obj.sizeBreakPoint, bptAccess, bpmDebugRegister) setmetatable (obj, self) obj.__index = ClassManagerBranches return obj end \--if(getOpenedProcessID() == 0) then openProcess('test.exe') end if(getOpenedProcessID() == 0) then openProcess('xrengine.exe') end managerBranches = ClassManagerBranches:New('37859970',4) function debugger_onBreakpoint() return managerBranches:OnBreakpoint() end \--managerBranches:Stop() \----------------------------- \-- КЛАСС ClassMapBranch ClassMapBranch = {} ClassMapBranch.__index = ClassMapBranch function ClassMapBranch:New(_frmMarkers, _managerBranches) local _frmMapBranch = createForm(false) --frmDissassembler -- createForm(false) _frmMapBranch.DoubleBuffered = true _frmMapBranch.Caption = 'Map branches' _frmMapBranch.Width = classSettings:GetDigital('obj.frmMapBranch.Width', 500) _frmMapBranch.Height = classSettings:GetDigital('obj.frmMapBranch.Height', 900) _frmMapBranch.Left = classSettings:GetDigital('obj.frmMapBranch.Left', 0) _frmMapBranch.Top = classSettings:GetDigital('obj.frmMapBranch.Top', 0) if(classSettings:Get('obj.frmMapBranch.Visible', '1') == '1') then _frmMapBranch.show() end _frmMapBranch.show() _frmMapBranch.setBorderStyle(bsSizeable) _frmMapBranch.FormStyle = 'fsNormal' local obj = {} obj.classManagerBranches = _managerBranches obj.frmMapBranch = _frmMapBranch obj.frmMarkedBranches = _frmMarkers obj.selectedAddress = -1 obj.mainCanvas = obj.frmMapBranch.Canvas obj.bufferGraphic = createBitmap(obj.frmMapBranch.Canvas.Width, obj.frmMapBranch.Canvas.Height) obj.dy = 20 obj.column0_X = 30 -- маркер X obj.column1_X = 65 obj.column2_X = 70 + 50 obj.column3_X = 70 + 50 + 40 obj.extraLineRender_Y = -20 obj.tableVisibleAddress = {} -- содержит данные рисуемой таблицы (см. свойства инже по коду) -- Можно поставить другие цвета для фона и шрифта obj.colorBackground = classSettings:GetDigital('obj.colorBackground', 0x00525252) obj.colorFont = 0x00FFFFFF --classSettings:GetDigital('obj.colorFont', ) obj.frmMapBranch.Color = obj.colorBackground obj.frmMapBranch.Font.Color = obj.colorFont obj.isDirtyData = true setProperty(obj.frmMapBranch, 'OnPaint', function () if(obj.isDirtyData) then obj.isDirtyData = false obj:Draw() end obj.mainCanvas.draw(0, 0, obj.bufferGraphic) end) setProperty(obj.frmMapBranch, 'OnResize', function () if(obj.bufferGraphic.Width ~= obj.mainCanvas.Width or obj.bufferGraphic.Height ~= obj.mainCanvas.Height) then obj.isDirtyResize = true end end ) -- Не уничтожать окно при закрытии setProperty(obj.frmMapBranch, 'OnClose', function (sender) return caHide --Possible options: caHide, caFree, caMinimize, caNone end ) obj.checkTimer = createTimer(obj.frmMapBranch) obj.checkTimer.Interval = 50 obj.lastTopAddress = 0 obj.isDirtyResize = false obj.needUpdateDraw = false obj.checkTimer.OnTimer = function () obj.needUpdateDraw = true if( obj.needUpdateDraw) then obj.needUpdateDraw = false obj.frmMapBranch.repaint() end if(obj.isDirtyResize) then obj.isDirtyResize = false obj.bufferGraphic.destroy() obj.bufferGraphic = createBitmap(obj.frmMapBranch.Canvas.Width, obj.frmMapBranch.Canvas.Height) obj.bufferGraphic.Canvas.Brush.Color = obj.colorBackground obj.bufferGraphic.Canvas.Font.Color = obj.colorFont obj.bufferGraphic.Canvas.floodFill(x,y) obj.isDirtyData = true obj.frmMapBranch.repaint() end end obj.mainAddress = string.format('%08X',obj.classManagerBranches.mainAddress) -- Рисует дизассемблерный код и может рисовать пути function obj:Draw() local bufferGraphicCanvas = obj.bufferGraphic.Canvas bufferGraphicCanvas.Font.Size = sizeFont bufferGraphicCanvas.Font.Color = obj.colorFont bufferGraphicCanvas.Pen.Style = 'psSolid' bufferGraphicCanvas.Brush.Style = 'bsClear' bufferGraphicCanvas.Brush.Color = obj.colorBackground bufferGraphicCanvas.clear() obj.frmMapBranch.color = 0xFFFFFFFF -- obj.colorBackground bufferGraphicCanvas.Brush.Color = 0xFFFFFFFF bufferGraphicCanvas.Pen.Style = 0xFFFFFFFF --bufferGraphicCanvas.Brush.Style = 'bsClear' local mainPoint = { x = obj.frmMapBranch.Width / 2, y = obj.frmMapBranch.Height - 50 } bufferGraphicCanvas.textOut(mainPoint.x, mainPoint.y, obj.mainAddress) bufferGraphicCanvas.line(mainPoint.x - 300, mainPoint.y - 30, mainPoint.x + 300, mainPoint.y - 30) local tableData = obj.classManagerBranches.tableData for i = 1, #tableData do --local addressPrevious = getPreviousOpcode(tableData[i].traceBranch.address) --local _,opcode,_,_ = splitDisassembledString(disassemble(addressPrevious)) --local s = string.format('%08X %s', addressPrevious, opcode) --bufferGraphicCanvas.textOut(100, 100 + i * 20, 'Test: '.. s) local dx = i*40 local dy = 20 --bufferGraphicCanvas.line --( -- mainPoint.x - 300 + dx, -- mainPoint.y - 30 - dy, -- mainPoint.x - 300 + dx, -- mainPoint.y - 30 --) local tableDataFromBranche = tableData[i].traceBranch.tableData local countRets = 0 for j = 1, #tableDataFromBranche do --table.insert ( self.tableData, {XIP = XIP, Count = 1, isRet = string.match(opcode,'ret')}) --table.insert ( self.tableData, {XIP = XIP, Count = 1, isRet = string.match(opcode,'ret')}) if(tableDataFromBranche[j].isRet) then countRets = countRets + 1 bufferGraphicCanvas.ellipse ( mainPoint.x - 300 + dx - 5, mainPoint.y - 30 - dy - countRets * 20 - 5, mainPoint.x - 300 + dx + 6, mainPoint.y - 30 - dy - countRets * 20 + 6 ) end end bufferGraphicCanvas.line ( mainPoint.x - 300 + dx, mainPoint.y, mainPoint.x - 300 + dx, mainPoint.y - dy - countRets * 20 - 10 ) end --local priority1 = math.random(0,155) --local someColor = byteTableToDword({priority1,priority1,priority1,0}) --bufferGraphicCanvas.Font.Color = someColor obj.mainCanvas.draw(0, 0, obj.bufferGraphic) end function obj:DrawArrow(point1, point2, distance, colorArrow, minY, a1_IsFarTop, a1_IsFarBottom, a2_IsFarTop, a2_IsFarBottom, a1_IsFarTop, a1_IsFarBottom, a2_IsFarTop, a2_IsFarBottom) local bufferCanvas = obj.bufferGraphic.Canvas bufferCanvas.Pen.Color = colorArrow bufferCanvas.Pen.Style = 'psDot' local height = bufferCanvas.Height isDrawSideLine1 = true if(point1.y < minY) then point1.y = minY isDrawSideLine1 = false elseif (point1.y > height) then point1.y = height isDrawSideLine1 = false end isDrawRow = true if(point2.y < minY) then point2.y = minY isDrawRow = false elseif (point2.y > height) then point2.y = height isDrawRow = false end if(isDrawSideLine1) then bufferCanvas.line(point1.x, point1.y, point2.x - distance, point1.y) end bufferCanvas.line(point2.x - distance, point1.y, point2.x - distance, point2.y) if(isDrawRow) then bufferCanvas.line(point1.x, point2.y, point2.x - distance, point2.y) obj.bufferGraphic.Canvas.Pen.Style = 'psSolid' --'psSolid' local x1 = point2.x - 5 local x2 = point2.x local y2 = point2.y -- Рисование стрелки костыльным способом bufferCanvas.line(x1, y2 - 3, x2, y2) bufferCanvas.line(x1, y2 - 7 + 10, x2, y2) bufferCanvas.line(x1, y2 - 3, x1, y2 - 7 + 10) bufferCanvas.line(x1 + 1, y2 - 2, x1 + 1, y2 - 7 + 9) bufferCanvas.line(x1 + 2, y2 - 1, x1 + 2, y2 - 7 + 9) bufferCanvas.line(x1, y2 - 1, x1 + 6, y2) end end obj.frmMapBranch.OnDestroy = function (sender) obj:SaveParameters() end function obj:SaveParameters() classSettings:Set('obj.frmMapBranch.Width', obj.frmMapBranch.Width) classSettings:Set('obj.frmMapBranch.Height', obj.frmMapBranch.Height) classSettings:Set('obj.frmMapBranch.Left', obj.frmMapBranch.Left) classSettings:Set('obj.frmMapBranch.Top', obj.frmMapBranch.Top) classSettings:Set('obj.colorBackground', obj.colorBackground) if(obj.frmMapBranch.Visible) then classSettings:Set('obj.frmMapBranch.Visible', '1') else classSettings:Set('obj.frmMapBranch.Visible', '0') end classSettings:Save() end obj:Draw() setmetatable(obj, self) return obj end mapBranch = ClassMapBranch:New(frmMarkedBranches, managerBranches)
-
-
RE: CE Dissect Data Scaner 1.0.2 (4 hardware breakpoints)
Проведено огромное количество опытов над структурами. Много переделок.
Очень кратко напишу, что поменялось.
- Название структуры состоит из адреса, количества адресов в ней и времени в миллисекундах на один байт в структуре.
Подчеркнуто красным
-
Смещения отбираются только те, на которых за X времени не было обнаружено ни одной инструкции на запись. Чем больше та самая чувствительность, тем точнее результат
-
В именах структур теперь ценная информация. Это смещение, регистр и тип
На скриншоте ниже можно посмотреть пункты 2 и 3.
Также на этом скриншоте я заморозил по соседству адрес (поставил Lock) и пару раз пострелял в игре Сталкере Зов Припяти
Выводы, которые я сделал за очень и очень скромное время пользования. За минут 15 и на паре структур оружия в двух разных играх L4D2 и Сталкер Зов Припяти-
Если править 4-х байтовые значения, то легко можно нарваться на вылет игры. Но подключившись снова, можно продолжить.
-
Правки значений с типом в 1 байт могут заблокировать оружие (в двух играх) или же устроить скорострельность на полную катушку в L4D2.
-
Правки значений с типом float. Можно легко нарваться на вылет в игре, если например поставить нолик. Деление на ноль или какая-то иная причина. Лучше ставить чуть больше нуля, можно положительные или отрицательные значения
-
Частенько бывают адреса в структуре, на которых включаются инструкции записи только после изменения значения. Тут я пока ничего не смог сделать, просто удалить их из структуры как лишние.
-
Для сканера используется 1 аппаратный брейкпоинт, остальные три штуки еще не используются, т.к. сложно их прикрутить. Если использовать все 4 аппаратных бряка, то скорость сканера была бы в 4 раза быстрее. Сейчас на 2К байт по 10 мс, у меня уходит где-то 150 секунд. Если я ставил 20мс, то находилось на 10 смещений больше или какие-то другие смещения пропадали.
Код будущего плагина все еще находится на стадии тестирования. Поэтому пока плагин не выкладываю.
upd1: инструкции cmp, add, sub, xor, and, not, test, mulss, fsub, fmul, dec, inc, mul; теперь выводятся в имя элемента структуры.
На скриншоте случайно вышел на координаты UI таймера. Сделал три скана трех структур в новых окнах
upd2: перемещение структуры
Итог перемещения двух структур в первую
-
[CE Lua Plugin] Способ раскраски значений найденных адресов
Пример раскрашивания адресов без каких-либо условий
MainForm.Foundlist3.OnCustomDrawSubItem = function(sender, ListItem, SubItemIndex, state, DefaultDraw) if ListItem.Index % 3 == 0 then if SubItemIndex % 2 == 0 then sender.canvas.font.color = 0xff0000 else sender.canvas.font.color = 0x00ffff end else sender.canvas.font.color = 0xffff00 end return true end
-
Из прошлой записи блога нашли "MainForm.Foundlist3" для работы с TListView (компонент от Lazarus среды разработки)
-
OnCustomDrawSubItem — функция обработчик раскрашивания вложенных элементов в Item. Не поленитесь, зайдите в файл документации (C:\Program Files\Cheat Engine 7.4\celua.txt)
Также есть OnCustomDrawItem — раскрашивание невложенных элементов.
Этот способ раскрашивания можно использовать по условиям. Чтобы связать условия с адресами и цветом смотри MemScan Class, FoundList class в celua.txt.
-
-
CE Action Logger 1.0 Beta
Cheat Engine 6.8.3 Cheat Engine Extensions
Как установить:
Распаковать файлы в директория Cheat EngineВерсия: Beta 1.0 (22.018.2019) + Добавлены базовые действия в логи + Добавлены ссылки на ресурс game hack lab Файлы: autorun->User actions logger.lua autorun->GHLLogo.lua autorun->forms->GHLLogo.frm
-
RE: CE Tool Lua Regular expressions 2
Lua поиск элемента до и после строки
Была задача получить два списка из документа, в котором было с пару десятков тысяч строк. На практике выяснилось, что искать текст после ключевого слова легче чем искать текст до ключевого слова. Об этом и будет дальше
В утилите "Lua Regular Expressions (v. 1.0)"
Текст во вкладке "gmatch"Game1
gameCompany
DAU
53.21k
-20%
Game2
gameCompany
DAU
20.35k
-20%Поиск элемента после строки:
"DAU "DAU%c%c(.-)%c%c"
53.21k;20.35k;
Поиск элемента до строки:
"%c%c(.-)gameCompany%c%c"
(паттерн со двигом скобок для поиска предыдущей фразы)
не прокатит для вывода списка игр над фразой gameCompany;53.21k
-20%Game2
;Очевидно, можно сделать поиск по похожим фрагментам, которые идут последовательно сверху вниз.
Cначала добавим первую пустую строку и видим повторяющиеся фрагменты
"%c%cGame1%c%cgameCompany%c%c"
пишем шаблон
".*%c%c(.-)%c%cgameCompany%c%c"
и опять мимоGame1;53.21k
-20%Game2;
Потому что текст над Game2 пошел выше Game2. Тогда делаем захват, только первой фразы и дальше не идем
"%c%c(%w-)%c%cgameCompany%c%c"
Game1;Game2;
И тогда все ок.Но это еще не все. Осталась первая пустая строка, которую добавили, если её удалить, тогда
"%c%c(%w-)%c%cgameCompany%c%c"
Game2;
Не видит Game1.Значит мы можем убрать
%c%c,
и будет"(%w-)%c%cgameCompany"
Game1;Game2;
Дальше название игры может быть таким "My Game: my Game". Здесь и пробел и двоеточие. В таком случае текст уже будет
My Game1: my Game
gameCompany
DAU
53.21k
-20%
My Game2: my Game
gameCompany
DAU
20.35k
-20%
Пробуем
"(%w-)%c%cgameCompany"
Game;Game;
Что не верно, т.к. захват одним(%w-)
Мы должны в скобках развернуть фразу имени игры. В ней могут быть пробелы, числа, текст и двоеточие
'([%w%s]-)%c%cgameCompany'
my Game; my Game;
Затем
([:%w%s]-)%c%cgameCompany
My Game1: my Game;
My Game2: my Game;
Затем
%c?%c?([:%w%s]-)%c%cgameCompany
My Game1: my Game;
My Game2: my Game;Вот и все. Если попариться один раз, то тексты уже парсить будет гораздо быстрее.
Так я вывел столбы DAU и названий игр в таблицу, что было в районе 500 строк из пару десятков тысяч строк
p.s. Текст в консоли Lua отличается %c%c, а %с
p.s.p.s. Можно раcсплитить текст по
"/n/r"
или"/n"
в таблицу строк и по индексам данных находить предыдущую или последующую фразу. Но мне проще две строки ввести"%c?%c?([:%w%s]-)%c%cgameCompany"
и"DAU%c%c(.-)%c%c"
-
CE Tool Lua Regular expressions 2
CT таблица для составления регулярных выражений 4-х функций
string.match (s, pattern [, init]) string.gmatch (s, pattern) string.gsub (s, pattern, repl [, n]) string.find (s, pattern [, init [, plain]])
Функция string.gsub может принимать в аргумент функции "repl" таблицу (тогда будет замена по ключам значений из таблицы ) или в аргумент "repl" может попадать некоторая другая функция с аргументом найденного слова (тогда будет вызов этой другой функции при каждом захвате символа или слова). Более подробное в документации Lua 5.3
Еще несколько ссылок с практическим руководством
-
CE disassembler
Кратко
-
Добавил сохранение/загрузку параметров. Хранение в текстовом файле в папке autoruns
-
Добавил настройки графического интерфейса (слайдеры, пункт меню)
-
Обновления UI
- Гладкая перерисовка, двойной буфер
- Стрелки прыжков сдвигаются вправо при увеличении их количества
- Слайдер яркости фона
- Слайдер количества адресов в невидимой области от 0 до +1К от верхнего и нижнего адресов. Увеличивает количество стрелок для прыжков.
- Стрелки в невидимой области обозначаются двумя цветами.
Один цвет для тех стрелок, у которых адрес прыжка и адрес назначения не выходят в область видимости.
Второй цвет для тех стрелок, которых адрес прыжка или адрес назначения входят в области видимости.
Видимые стрелки обозначаются белым.
На рисуемые стрелки прыжков влияет слайдер яркости. - Не показывать стрелки прыжков для инструкций типа 'jmp [...]', 'jne [...]'...
-
Сохранение и загрузка при заходе и выходе из CE
- значения слайдеров
- верхнего адреса
- выбранного адреса
- параметры шрифтов и других
С цветами экспериментировал не понравилось, пока оставляю градацию "черный-белый".
Основное назначение его показывать приоритетные инструкции более ярким цветом. Инструкции такие как арифметические и логические. Это первый и второй слайдеры. Третий - инструкции на запись, чтение. Четвертый - инструкции ветвления. Пятый слайдер - остальные инструкции. Остальные слайдеры связаны с настройками. Основные из них размер шрифта, яркость фона, вертикальный отступ между инструкциями и другие.
Также в плагин встроена поддержка работы с окнами трейслогов.
Происходит постоянный поиск новых и отсутствующих окон трейслогов... Делаем трейслоги. Двойной клик по адресам и переходим по адресу в дизассемблер (если он открыт) и видим в нем раскрашенные пути маркерами. Если окон много, то видим пересечения, расхождения путей и видим инструкции, которые не выполнялись...Встроено контекстное меню установки брейкпоинтов на call-ы в трейслоге. Если его вызывать, то после идем в игру и, не делая того, что исследуем, прерываемся на брейкпоинтах и снимаем их. Когда больше не прерываемся, то делаем в игре то, что нужно и прерываемся на оставшемся брейкпоинте. На нем исследуем условие входа в рутину. Тут как бы можно на страх и риск изменить условие, чтобы ветка кода повторила свое выполнение. Если позволяет, то выполнить поток вызвав call... Но я придерживаюсь варианта изменения данных для того чтобы поток сам повторил рутину. Либо подменить параметры функции или подменить параметры после выхода функции. На данный момент показать что-то на практике нечего. Как будет, сделаю и покажу видео.
Встроена поддержка маркирования путей на выбранном участке кода. Это специальное окно в меню "утилитах" в окне дизассемблера его можно найти. Указывается верхний адрес и нижний адрес области кода. Например, функции. При старте начинается логирование уникальных прохождения от начального до конечного адреса и раскраска маркерами. Возможных таких путей 8. Т.е. делая что-то в игре (разовые действия) видим уникальную ветвь, она тут же появляется и на этой ветви можно прерваться поставив опцию, и оттрейсить выход из рутины...
Более подробное описание и скриншоты в закрытом разделе в моем старом блоге. Имеют к нему доступ пользователи со статусом "Разработчики". Новую информацию я буду писать в этой теме.
Плагин сейчас на стадии чернового варианта, кто хочет пробуйте. Установка с репозитория. Чтобы подключить нужно загрузить .CT таблицу. Знаю что не удобно, но пока так.
Плагин планируется развивать дальше, сейчас это черновой вариант
-
-
RE: CE Compact View
Ниже находится обучающий пример плагина для CE Lua с установкой компактного режима применяя парадигму ООП — инкапсуляции.
Для программирования плагинов на Lua могут пригодится приемы ООП. Это касается сущностей, их поведений и взаимодействий между ними. В данном примере CECompactView — описание класса на основе мета-таблицы и оно является сущностью. Функции класса — uncompact() и compact(), которые реализуют поведения этой сущности. Основной скрипт с описанием сущности можно поместить в отдельный файл .lua и далее можно кратко инициализировать сущность и управлять ей
Например
\-- Создание экземпляра класса через new() функцию, где аргументом является состояние компактный или некомпактный ce_compact_view = CECompactView:new(true) \-- Проверить состояние \--print(ce_compact_view:get_state() and 'Compact View Mode' or 'Full View Mode') \-- Установить компактный режим \--ce_compact_view:compact() \-- Установить не компактный режим \--ce_compact_view:uncompact()
Сам класс или мета-таблица.
\-- Класс инкапсулирующий поведения компактного состояния окна CE CECompactView = {} function CECompactView:new(state) local obj = {} -- Состояние компактное или нет: true или false obj.compact_mode = state -- Форма CE, где getMainForm — поддерживаемая функция CE и возвращает главную форму CE obj.form_ce = getMainForm() -- Получение элементов контекстного меню главной формы local menu_items = obj.form_ce.menu.items -- Создание нового контекстного меню с названием CompactView и именем ItemCompact obj.menu_item_compact = createMenuItem(menu_items) obj.menu_item_compact.name = 'ItemCompact' obj.menu_item_compact.caption = 'CompactView' -- Добавление контекстного меню на главную форму menu_items.add(obj.menu_item_compact) -- Сменить состояние окна CE: компактное или нет function obj:set(state) -- view_components состояния скрытия компонентов obj.compact_mode = state -- видимость компонентов обратна компактному режиму local view_components = not state obj.form_ce.panel1.visible = view_components obj.form_ce.panel4.visible = view_components obj.form_ce.panel5.visible = view_components obj.form_ce.Splitter1.visible = view_components -- Записать имя по состоянию obj.menu_item_compact.caption = state and 'Full View Mode' or 'Compact View Mode' -- Поставить обработчик по состоянию obj.menu_item_compact.onClick = state and obj.compact or obj.uncompact print(state and 'Compact View Mode' or 'Full View Mode') end -- Некопактное окно CE function obj:uncompact() obj:set(true) end -- Компактное окно CE function obj:compact() obj:set(false) end -- Состояние окна CE: компактное или нет function obj:get_state() return obj.compact_mode end -- Связь таблицы (смотрим докуметацию по Lua) setmetatable(obj, self) obj.__index = self -- Активировать состояние obj:set(state) -- Возвращает ссылку на экземпляр return obj end \------------ \-- Создание экземпляра класса ce_compact_view = CECompactView:new(true) \-- Проверить состояние \--print(ce_compact_view:get_state() and 'Compact View Mode' or 'Full View Mode') \-- Установить компактный режим \--ce_compact_view:compact() \-- Установить не компактный режим \--ce_compact_view:uncompact()
Скрипт можно сохранить в Lua файл и добавить в папку autorun.
-
CE Compact View
Функции
- Контекстное меню. Компактное/ не компактное окно CE
- Сохранение и загрузка положения и размера формы CE
- Сохранение и загрузка компактного состояния формы или не компактного
После запуска, CE будет там, где её (CE) закрыли с позицией, размерами и с состоянием "Компактны режим", если последний был включен.
Параметры сохраняются на жесткий диск в файл "..\autorun\userdata.txt"