Добрый день, уважаемые форумчане. В этот раз мы с вами будем исследовать задание breakpoint из категории «реверсинг», взятое с pwnium CTF:
re300
Зеркало 1
Зеркало 2
Сам таргет особо непримечателен. Можно даже назвать эту цель лёгкой. Но я в этой статье попытаюсь рассмотреть возможности дизассемблера IDA Pro вообще, и плагина IDA Python в частности. Не то, чтоб это была какая-то закрытая информация – нет, всё это можно найти в общем доступе в документации к IdaPython. Проблема в том, что эта самая документация структурирована из рук вон плохо, и найти в ней что-то, если не знаешь, что искать, часто бывает весьма затруднительно. Поэтому мы с вами, так сказать, в «боевых условиях», попробуем пойти дальше, чем множество уже доступных writeup’ов и рассмотрим, как применение этого плагина может упростить статический анализ.
Мне часто приходиться слышать мнение, будто реверс-инженерия, это «долгий вдумчивый взгляд в дизассемблерный код». Но я считаю иначе. Настоящий реверсинг – это не только понимание имеющегося у нас кода, но и способность манипулировать его разнообразными представлениями так, чтоб эти преобразования способствовали пониманию. Теперь попробуем воплотить эти красивые (или не очень) слова в реальность.
Для начала перейдём на точку входа приложения. Это стандартная точка входа для программ, написанных на gcc.
Переименуем функцию по адресу 0x40661Ah на main, и именно с неё мы и начнём наше исследование:
В самом начале нас ждёт один простой антиотладочный приём:
Здесь видно, что в случае обнаружения отладчика переменная stateValue будет увеличена на 1. Ниже легко найти функцию, которая выполнит проверку серийного номера, и в случае ввода «правильного» номера, выведет на экран смайлик:
Зайдя внутрь функции CheckSerialInput, видим что-то длинное и, на первый взгляд, сложное. На самом деле, эту функцию можно условно разделить на 2 части: часть модификации состояний и часть проверки символов. Рассмотрим первую часть:
Как только переменная stateValue становится равной какой-то из констант состояний, делается переход на базовый блок, в каждом базовом блоке переменная stateValue получает новое значение, а мы узнаём один из символов флага. Пример одного такого базового блока:
Странно видеть здесь термин «базовый блок», но это так. Если будет выполняться условный переход, то это значит, что введенное значение не соответствует флагу.
Давайте подытожим. Идея в том, что каждому состоянию соответствует некий базовый блок. Внутри базового блока происходит сравнение текущего символа флага с некоторой предопределённой константой. Если текущий символ равен этой константе, значит переменная stateValue получает другое значение (происходит переключение состояния), и всё повторяется. Если всё так красиво и понятно, почему бы не автоматизировать этот процесс?
В IdaPython есть неоспоримое преимущество – помимо возможности дизассемблера, у нас появляются ещё и возможности python’a. Для начала рассмотрим API IdaPython, который нам тут пригодится.
- Byte(address) – вернёт байт, расположенный по указанному адресу.
- Dword(address) – то же, только dword.
- GetDisasm(address) – вернёт строку дизассемблированного текста. Что интересно, строка вернётся из базы, поэтому перед использование следует убедиться, что возвращаемое значение действительно соответствует вашим ожиданиям:
- DecodeInstruction (address) – выполняет декодирование инструкции, возвращая некий объект insn_t. В общем, на этом – всё. Вот так немного нам понадобится, чтоб осуществить задуманное.
Code:beginRawStatePos = 0x000000000040064D endRawStatePos = 0x0000000000400EF0 #after the last one currentStatePos = beginRawStatePos allStates = {} pattern1 = 'cmp eax, ' pattern2 = 'jz loc_' while currentStatePos < endRawStatePos: currentInstruction = idc.GetDisasm(currentStatePos) StateMatch = currentInstruction.find(pattern1) if StateMatch != -1: stateValue = int('0x' + currentInstruction[len(pattern1):-1],16) else: StateMatch = currentInstruction.find(pattern2) if StateMatch != -1: addressValue = int('0x' + currentInstruction[len(pattern2):],16) allStates[stateValue] = addressValue currentStatePos += GetInstructionSize(currentStatePos)Эта часть скрипта создаст словарь, ключами в котором будут значения состояний, а значениями – адреса базовых блоков. Потом определим несколько служебных функций:
Code:def GetInstructionSize(address): return idautils.DecodeInstruction(address).__get_size__() def GetStateSymbol(blockAddress): byteAddress = blockAddress + 8 return Byte(byteAddress) def GetStateValue(blockAddress): stateAddress = blockAddress + 17 return Dword(stateAddress)Первая – определит размер инструкции в байтах, вторая – считает значение символа флага (то, которое должно быть), третья – считает из текущего блока новое значение состояния.
И последний цикл восстановит значение флага:
Code:State = 0x0FE129837 flag = array.array('B','') try: while 1: currentBlock = allStates[State] State = GetStateValue(currentBlock) flag.append(GetStateSymbol(currentBlock)) except: pass print(flag.tostring())Таким образом, в консоли плагина появляется значение флага:
D3bugG1nG_Th1s_ObfuSc4t3d_C0d3_1s_R34lly_H4rD
Полный текст скрипта приведен здесь






:



Reply With Quote
Thanks
