Ветвления кода связанные с адресом (старые записи)
-
В этой записи блога не будет чего-то, что показало бы "вау, это что-то новое и есть результат". Все сырое и результат пока мне только снится — быстрый поиск условий и включение, и выключение ветвей по этим условиям. Мыслю я не инструкциями, не группой инструкций, а ветвлениями кода и условиями, которые их запускают. Проще 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)
-
-
Трассеровка по ретам
-------
На скриншоте 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
-