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