R0 CREW

Pwnium CTF [reverse300] breakpoint

Добрый день, уважаемые форумчане. В этот раз мы с вами будем исследовать задание breakpoint из категории «реверсинг», взятое с pwnium CTF:

re300
Зеркало 1
Зеркало 2

Сам таргет особо непримечателен. Можно даже назвать эту цель лёгкой. Но я в этой статье попытаюсь рассмотреть возможности дизассемблера IDA Pro вообще, и плагина IDA Python в частности. Не то, чтоб это была какая-то закрытая информация – нет, всё это можно найти в общем доступе в документации к IdaPython. Проблема в том, что эта самая документация структурирована из рук вон плохо, и найти в ней что-то, если не знаешь, что искать, часто бывает весьма затруднительно. Поэтому мы с вами, так сказать, в «боевых условиях», попробуем пойти дальше, чем множество уже доступных writeup’ов и рассмотрим, как применение этого плагина может упростить статический анализ.

Мне часто приходиться слышать мнение, будто реверс-инженерия, это «долгий вдумчивый взгляд в дизассемблерный код». Но я считаю иначе. Настоящий реверсинг – это не только понимание имеющегося у нас кода, но и способность манипулировать его разнообразными представлениями так, чтоб эти преобразования способствовали пониманию. Теперь попробуем воплотить эти красивые (или не очень) слова в реальность.

Для начала перейдём на точку входа приложения. Это стандартная точка входа для программ, написанных на gcc.

Переименуем функцию по адресу 0x40661Ah на main, и именно с неё мы и начнём наше исследование:

В самом начале нас ждёт один простой антиотладочный приём:

Здесь видно, что в случае обнаружения отладчика переменная stateValue будет увеличена на 1. Ниже легко найти функцию, которая выполнит проверку серийного номера, и в случае ввода «правильного» номера, выведет на экран смайлик :grin: :

Зайдя внутрь функции CheckSerialInput, видим что-то длинное и, на первый взгляд, сложное. На самом деле, эту функцию можно условно разделить на 2 части: часть модификации состояний и часть проверки символов. Рассмотрим первую часть:

Как только переменная stateValue становится равной какой-то из констант состояний, делается переход на базовый блок, в каждом базовом блоке переменная stateValue получает новое значение, а мы узнаём один из символов флага. Пример одного такого базового блока:

Странно видеть здесь термин «базовый блок», но это так. Если будет выполняться условный переход, то это значит, что введенное значение не соответствует флагу. Давайте подытожим. Идея в том, что каждому состоянию соответствует некий базовый блок. Внутри базового блока происходит сравнение текущего символа флага с некоторой предопределённой константой. Если текущий символ равен этой константе, значит переменная stateValue получает другое значение (происходит переключение состояния), и всё повторяется. Если всё так красиво и понятно, почему бы не автоматизировать этот процесс? В IdaPython есть неоспоримое преимущество – помимо возможности дизассемблера, у нас появляются ещё и возможности python’a. Для начала рассмотрим API IdaPython, который нам тут пригодится.
  • Byte(address) – вернёт байт, расположенный по указанному адресу.
  • Dword(address) – то же, только dword.
  • GetDisasm(address) – вернёт строку дизассемблированного текста. Что интересно, строка вернётся из базы, поэтому перед использование следует убедиться, что возвращаемое значение действительно соответствует вашим ожиданиям:

  • DecodeInstruction (address) – выполняет декодирование инструкции, возвращая некий объект insn_t. В общем, на этом – всё. Вот так немного нам понадобится, чтоб осуществить задуманное.
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)
Эта часть скрипта создаст словарь, ключами в котором будут значения состояний, а значениями – адреса базовых блоков. Потом определим несколько служебных функций:
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)
Первая – определит размер инструкции в байтах, вторая – считает значение символа флага (то, которое должно быть), третья – считает из текущего блока новое значение состояния. И последний цикл восстановит значение флага:
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

Полный текст скрипта приведен здесь

Элегантное решение. Автор за кого играл?? Реверса на pwnium было на удивление много, но имхо он был гораздо проще чем обычно. Во время игры поднял все 10+100+150+200+300, чем помог RDot прыгнуть на 3е место.

Мне так понравилась идея автоматизации, что не удержался от проверки по старинке на IDC:

auto eaB, eaE, ea, xval, value, addr, str_out, i;
eaB = 0x40064d;
eaE = 0x400ee5;
str_out = "";
xval = 0xFE129837;
for(i=0;i<45;i=i+1) {
  ea = eaB-1;
  for(;;) {
    ea = FindBinary(ea+1, 3, "3D ? ? ? ? 0F 84");
    if (ea==BADADDR)
      break;
    value = Dword(ea+1);
    addr = ea+Dword(ea+7)+11;     
    if(ea == eaE)
      break;
    if(value == xval)
      break;  
  }
  Message("%c", Byte(addr+8));  
  xval = Dword(addr+17);
}
Message("\n");

На IDC компактно получилось. Тоже так симпатично.

А что там с павном случилось? Он вроде был сначала доступен, а потом убрали таск.