Перейти к содержанию

FAQ. Все о дизассемблировании на Lua


MasterGH

Рекомендуемые сообщения

В данной теме будут ответы на вопросы по извлечению информации из дизассемблера при помощи  Lua Engine в Cheat Engine 6.4

  • Может быть много ситуаций, когда мы получаем много адресов инструкций отладочного кода и перебирать вручную, не упустив при этом мелочей,  требует много времени и энергии.

 

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

 

Основные данные адресов инструкций мы будем получать из окон:

 

1) из функции debugger_onBreakpoint, которая срабатывает при брейкпоинте

2) из окна Ultimap

3) из окна Tracer Log

4) из окна прервавшихся инструкций

5) из окна установленных Брейкпоинтов

5) из окна Code List

Окно1. Главное окно CE->Table->Show Cheat Table Lua Script (Ctrl+Alt+A)

Окно2. Главное окно CE->Кнопка Memory View->Tools->Lua Engine (Ctrl+L)

 

Разница между этими окнами в том, что Окно2 не сохраняет информацию и при исполнении кода выводит его полностью. Окно1 сохранет код в таблицу CE.

Писать можно в двух окнах.

local lineDissassemble = disassemble('7FEEFBC143A')print(lineDissassemble)--Вывод: "7FEEFBC143A - FF 15 4888B000 - call qword ptr [7FEF06C9C88]"
Допустим нужно прочитать инструкцию по адресу 7FEEFBC143A

print(getNameFromAddress('7FEEFBC143A'))--Вывод: "gamedll_x64_rwdi.dll+5C143A "

local lineDissassemble = disassemble('7FEEFBC143A')local extrafield, opcode, bytes, adressReturnHere = splitDisassembledString(lineDissassemble)print(extrafield)print(opcode)print(bytes)print(adressReturnHere)Вывод:"пустая строка"call qword ptr [7FEF06C9C88]FF 15 4888B0007FEEFBC143A
где 

 

extrafield - комментарии к инструкции, если их нет, то будет пусто 

opcode - дизассемблированная инструкция

bytes - байт код инструкии  

adressReturnHere - адрес инструкции

local lineDissassemble = disassemble('7FEEFBC143A')local extrafield, opcode, bytes, adressReturnHere = splitDisassembledString(lineDissassemble)local _,_,x = string.find(opcode, '%[(.*)%]')print(opcode)print(x)Вывод:call qword ptr [7FEF06C9C88] 7FEF06C9C88 
Бывают инструкции с квадратными скобками. Например, call qword ptr [7FEF06C9C88] . Чтобы вывести 7FEF06C9C88 можно использоваться следующий пример

if(targetIs64Bit()) thenprint('64bit')elseprint('32bit')end

EFLAGS - регистр флагов для обоих режимов (результат от сравнение, прыжков и других инструкций)

32-bit: EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP, EIP

64-bit: RAX, RBX, RCX, RDX, RDI, RSI, RBP, RSP, RIP, R8, R9, R10, R11, R12, R13, R14, R15

Это можно сделать тремя способами.

Вручную из главного окна CE, вручную в окне Дизассемблера и програмным способом.

local addressCode = '7FEEFBC143A'local size = 1 -- количество байт (обычно от 1 до 8, для bptExecute обычно 1)debug_setBreakpoint(addressCode, size, bptExecute)	-- поставить бряк на адрес на выполнение--debug_setBreakpoint(addressCode, size, bptAccess)	-- поставить бряк на адрес на чтение и запись--debug_setBreakpoint(addressCode, size, bptWrite)	-- поставить бряк на адрес на запись-- также можно не указывать размер и тип--debug_setBreakpoint(addressCode, size)--debug_setBreakpoint(addressCode)

Это можно сделать тремя способами.

Вручную нажав на кнопку Stop в окне срабатывания бряков. Вручную в окне Брейкпоинтов, которое можно вызывать из Окна дизассемблера (Главное окно CE-> Кнопка MemoryView->View->BreakPointList(Ctrl+ 8-)) и програмным способом.

address

local addressCode = '7FEEFBC143A'debug_removeBreakpoint(addressCode)

Пример

function debugger_onBreakpoint()  if(targetIs64Bit()) then     print(RIP) -- выведет значение регистра RIP в десятчиной системе     print(EFLAGS)-- выведет значение регистра EFLAGS в десятчиной системе     return 1 -- не показывать дизассемблер  else     print(EIP) -- выведет значение регистра EIP в десятчиной системе     print(EFLAGS)-- выведет значение регистра EFLAGS в десятчиной системе     return 1 -- не показывать дизассемблер  end return 0 -- показывать дизассемблер с остановкой процесса (код сейчас сюда не дойдет, но обычно по умолчанию стоит 0)endlocal addressCode = '7FEEDFD143A'debug_setBreakpoint(addressCode)Вывод:8791495873594 659 
Регистры RIP и EIP для режима 64- и 32-бит. У этих режимов свои регистры. Они всегда в десятичной форме. Для сравнений регистров в десятичной системе со hex значениями в строках нужен дополнительный перевод.

return 1 не покажет какие-либо окна в CE

return 0 обычно показывает окно дизассемблера с прерыванием на адресе (придет вручную отпускать процесс по F9)

Также существует возможность прерываться на следующей инструкции со входом в call или без входа в него

Для этого можно исползовать функцию continueFromBreakpoint передавая в аргумент фукнции

co_run=0 - обычное продолжение отладки

co_stepinto=1 - прерваться на следующей инструкции с вхождением в call

co_stepover=2 - прерваться на следующей инструкции не входя в call

Пример (из темы)

pmAddress=getAddress("1F72152C")max = 10count = 0function debugger_onBreakpoint() -- срабатывает всегда, когда срабатывает брейкпоинт    count = count + 1-- если понадобятся все регистры или некоторые, то вы можете исправить код ниже     s = string.format("%3d) 0x%08X: EAX = 0x%08X EBX = 0x%08X ECX = 0x%08X",count, EIP, EAX, EBX, ECX)    print(s)    if count>=max then      debug_removeBreakpoint(pmAddress);    end   --Breakpoint continue methods: co_run=0, co_stepinto=1, co_stepover=2   debug_continueFromBreakpoint(co_run)   -- return 1 --I handled it so dont tell the user   -- return 0 --unexpected breakpoint, show the the userend
Для этого нужно объявить функцию debugger_onBreakpoint и поставить брейкпоинт

Условный бряк можно делать вручную и программным способом.

Вручную нужно поставить на инструкцию F5 бряк на выполнение. Зайти в окно брейкпоинтов. И изменить условие в полях ввода

Например RIP==0x7FEEDFD143A или RIP==8791495873594

function debugger_onBreakpoint()	if(targetIs64Bit()) then		if(RIP==0x7FEEDFD143A) then			print('RIP==0x7FEEDFD143A')		end	end	return 0 -- показывать дизассемблер с остановкой процесса (код сейчас сюда не дойдет, но обычно по умолчанию стоит 0)endlocal addressCode = '7FEEDFD143A'debug_setBreakpoint(addressCode)Вывод:RIP==0x7FEEDFD143A
Регистры RIP и EIP для режима 64- и 32-бит. У этих режимов свои регистры.

return 1 не покажет какие-либо окна в CE

return 0 обычно показывает окно дизассемблера с прерыванием на адресе (придет вручную отпускать процесс по F9)

print(string.format('0x%X', 8791495873594)) -- вывод 0x7FEEDFD143Aprint(string.format('0x%s', 8791495873594)) -- вывод 0x8791495873594print(string.format('%d', '0x7FEEDFD143A')) -- вывод 8791495873594print(tonumber('0x7FEEDFD143A', 16)) -- вывод 8791495873594print(getAddress('7FEEDFD143A')) -- вывод 8791495873594print(getNameFromAddress('7FEEDFD143A')) -- вывод gamedll_x64_rwdi.dll+5C143A

Описание класса

	Disassembler Class (Inheritance: Object)			createDisassembler() - Creates a disassembler object that can be used to disassemble an instruction and at the same time get more data			getDefaultDisassembler() - Returns the default disassembler object used by a lot of ce's disassembler routines			getVisibleDisassembler() - Returns the disassembler used by the disassemblerview. Special codes are: {H}=Hex value {R}=Register {S}=Symbol {N}=Nothing special			registerGlobalDisassembleOverride(function(sender: Disassembler, address: integer, LastDisassembleData: Table): opcode, description): Same as Disassembler.OnDisassembleOverride, but does it for all disassemblers, including newly created ones.  Tip: Check the sender to see if you should use syntax highlighting codes or not			  This function returns an ID you can pass on to unregisterGlobalDisassembleOverride()  6.4+			unregisterGlobalDisassembleOverride(id)			properties			  LastDisassembleData : Table			  OnDisassembleOverride: function(sender: Disassembler, address: integer, LastDisassembleData: Table): opcode, description			  syntaxhighlighting: boolean : This property is set if the syntax highlighting codes are accepted or not			Methods			  disassemble(address): Disassembles the given instruction and returns the opcode. It also fills in a LastDisassembleData record			  decodeLastParametersToString() : Returns the unedited "Comments" information. Does not display userdefined comments			  getLastDisassembleData() : Returns the LastDisassembleData table.				The table is build-up as follow:				  address: integer - The address that was disassembled				  opcode: string - The opcode without parameters				  parameters: string - The parameters				  description: string - The description of this opcode				  bytes: table - A table containing the bytes this instruction consists of (1.. )				  modrmValueType: DisAssemblerValueType  - Defines the type of the modrmValue field (dvtNone=0, dvtAddress=1, dvtValue=2)				  modrmValue: Integer - The value that the modrm specified. modrmValueType defines what kind of value				  parameterValueType: DisAssemblerValueType				  parameterValue: Integer - The value that the parameter part specified				  isJump: boolean - Set to true if the disassembled instruction can change the EIP/RIP (not ret)				  isCall: boolean - Set to true if it's a Call				  isRet: boolean - Set to true if it's a Ret				  isConditionalJump: boolean - Set to true if it's a conditional jump
Пример работы с классом после подключения к процессу

function t2aob(t,sep) return table.concat(imap(t,byte2aob),type(sep)=='string' and sep or ' ') endfunction PrintValueType(caption, iValue)  if(iValue == 0) then print(caption.." dvtNone = 0") end  if(iValue == 1) then print(caption.." dvtAddress = 1") end  if(iValue == 2) then print(caption.." dvtValue = 2") endendfunction PrintData(data)  print(string.format('address  0x%X',data["address"]))  print('opcode '..data["opcode"])  print('parameters '..data["parameters"])  print('description '..data["description"])  print('bytes '..t2aob(data["bytes"]))  PrintValueType('modrmValueType ', data["modrmValueType"]) -- dvtNone=0, dvtAddress=1, dvtValue=2  print(string.format('modrmValue 0x%X', data["modrmValue"]))  PrintValueType('parameterValueType ', data["parameterValueType"]) -- dvtNone=0, dvtAddress=1, dvtValue=2  print(string.format('parameterValue 0x%X',data["parameterValue"]))  if(data["isJump"]) then print("Is Jump") end  if(data["isCall"]) then print("Is isCall") end  if(data["isRet"]) then print("Is isRet") end  if(data["isConditionalJump"]) then print("Is isConditionalJump") end  print('')enddisassembler = createDisassembler()print(disassembler.disassemble("0045465C")) -- is callPrintData(disassembler.getLastDisassembleData())print(disassembler.disassemble("00454684")) -- is retPrintData(disassembler.getLastDisassembleData())print(disassembler.disassemble("0045468A")) -- is jmpPrintData(disassembler.getLastDisassembleData())print(disassembler.disassemble("00454612")) -- is jePrintData(disassembler.getLastDisassembleData())print(disassembler.disassemble("00454650")) -- is mov eax,[address]PrintData(disassembler.getLastDisassembleData())print(disassembler.disassemble("0045464A")) -- is inc [address]PrintData(disassembler.getLastDisassembleData())print(disassembler.disassemble("00454661")) -- is mov edx,[ebp-04]PrintData(disassembler.getLastDisassembleData())
Вывод:

0045465C - E8 B73EFBFF - call 00408518 address  0x45465C opcode call parameters 00408518 description call procedure bytes E8 B7 3E FB FF modrmValueType  dvtNone = 0 modrmValue 0x0 parameterValueType  dvtAddress = 1 parameterValue 0x408518 Is Jump Is isCall  00454684 - C3 - ret  address  0x454684 opcode ret parameters  description near return to calling procedure bytes C3 modrmValueType  dvtNone = 0 modrmValue 0x0 parameterValueType  dvtNone = 0 parameterValue 0x408518 Is isRet  0045468A - EB F0 - jmp 0045467C address  0x45468A opcode jmp parameters 0045467C description jump short bytes EB F0 modrmValueType  dvtNone = 0 modrmValue 0x0 parameterValueType  dvtAddress = 1 parameterValue 0x45467C Is Jump  00454612 - 74 44 - je 00454658 address  0x454612 opcode je parameters 00454658 description jump short if equal bytes 74 44 modrmValueType  dvtNone = 0 modrmValue 0x0 parameterValueType  dvtAddress = 1 parameterValue 0x454658 Is Jump Is isConditionalJump  00454650 - A1 A4B54500 - mov eax,[0045B5A4] address  0x454650 opcode mov parameters eax,[0045B5A4] description copy memory bytes A1 A4 B5 45 00 modrmValueType  dvtNone = 0 modrmValue 0x0 parameterValueType  dvtAddress = 1 parameterValue 0x45B5A4  0045464A - FF 05 A4B54500  - inc [0045B5A4] address  0x45464A opcode inc parameters [0045B5A4] description increment by 1 bytes FF 05 A4 B5 45 00 modrmValueType  dvtAddress = 1 modrmValue 0x45B5A4 parameterValueType  dvtNone = 0 parameterValue 0x45B5A4  00454661 - 8B 55 FC  - mov edx,[ebp-04] address  0x454661 opcode mov parameters edx,[ebp-04] description copy memory bytes 8B 55 FC modrmValueType  dvtValue = 2 modrmValue 0xFFFFFFFFFFFFFFFC parameterValueType  dvtNone = 0 parameterValue 0x45B5A4 

Случаются инструкции на брейкпоинтах, в которых значение перезаписывается. Например. mov eax, [eax]. Чтобы решить эту проблему можно:

1) Изменить тип брейкпоинта на память в настройках CE. Он будет выполниться до записи. Соответсвенно, если ставить аппаратные брейкпионты, то определить можно уже после записи. Подробноее об этом в этой теме

2) Перейти в дизассемблер и определить проскакивающие адреса на инструкции. Если там будет один адрес, то определите. Но бывает много адресов.

3) Провести пошаговую отладку. Т.е. перети в дизассемблер. Поставить на инстурукцию выше F5 и подсмотреть eax до записи. Если же адресов много, то смотреть по ситуации.

Когда попадаются инструкции работающие со стеком, то может быть несколько способов как узнать какое значение там было.

1. Один из самыйх простых способов это дважды кликнуть на инстуркцию в окне инструкций и в далоге MemInfo нажать на кнопку "S". Подробнее этой теме

2. Перейти в дизассемблер и через контекстное меню с функцией "Break and trace instruction" и провести трассеровку кода установив в опциях количество выполняемых инструкций и поставив в опциях снимание стека.

Продолжение следует...
Изменено пользователем MasterGH
  • Плюс 3
Ссылка на комментарий
Поделиться на другие сайты

  • 3 месяца спустя...

Добавлен 14 пункт "Как работать с классом Disassembler?"

1) Можем узнать является ли инструкция: прыжком, условным прыжком, вызовом, ret-ом

2) Можем узнать opcode, например "mov"

3) Можем узнать parameters, например "edx,[ebp-04]"

2) Можем узнать modrmValueType (dvtNone=0, dvtAddress=1, dvtValue=2)

3) Можем узнать parameterValueType (dvtNone=0, dvtAddress=1, dvtValue=2)

4) Можем узнать значение modrmValue

5) Можем узнать значение parameterValue

Другие функции класса Disassembler не рассмотрены

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

  • 10 месяцев спустя...
  • 4 года спустя...

Дампну тему поскольку хотелось бы узнать, возможно ли сделать такое решение чтобы можно было зациклить выполнение Lua кода при срабатывании инструкции в скрипте ? например как только увеличилось значение (inc [ebx]), то выполнить функцию Lua print('Debug value = ' + ebx)

Ссылка на комментарий
Поделиться на другие сайты

45 минут назад, valeriyanka сказал:

возможно ли сделать такое

Устанавливай точку отладчика на нужную инструкцию, потом получай регистр (хранится в переменной с таким же названием, например EBX), а затем запускай выполнение кода дальше, и так каждый раз. Все необходимые функции описаны вот в этом местечке.

  • Понравилось 1
Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.