Ангр Менеджмент

У меня было очень мало времени на Defcon CTF этого года. Мне хватило времени только на то, чтобы поиграть с первым заданием RE ребенка, и, к сожалению, я решил его долгим путем. Выполнив задачу, я задумался, как ее решили другие, и нашел хороший инструмент для символьного выполнения/бинарного анализа, которого раньше не видел.

Долгий путь

Открыв бинарник в IDA видим программу:

  • Считывает 13 числовых значений с помощью scanf из стандартного ввода.
  • Вызывает функцию CheckSolution, передавая указатель на прочитанные значения.
  • Проверяет возвращаемое значение из «CheckSolution».
  • Печатает «неправильно», если возвращаемое значение равно 0, или печатает ASCII-представление числовых значений (по порядку), если возвращаемое значение не равно нулю.

Итак, чтобы передать функцию CheckSolution, мы уже должны знать ключ. Мы можем сделать несколько простых исправлений, чтобы увидеть, что наши входные значения на самом деле не изменяются до того, как они будут напечатаны. Таким образом, все, что мы передаем, вероятно, находится в пределах нормального диапазона ASCII, поэтому у нас есть относительно небольшое пространство для поиска. Углубившись в функцию проверки решения, мы можем увидеть некоторый код инициализации, за которым следуют 13 отдельных блоков кода, все из которых являются пройденными/не пройденными (что дает возможность хорошего шага на графике ниже).

После беглого просмотра кажется, что наш ключ должен пройти 13 проверок, чтобы быть принятым. Для начала нам нужно было ввести 13 значений, так что… может быть, каждый блок кода отвечает за проверку того, что значение каждого символа находится в определенных пределах или удовлетворяет какому-то заранее определенному условию?? Это было бы легко, мы могли бы просто атаковать каждый блок, используя грубую силу, и найти ключ! Хотя не тут-то было. Как выясняется, каждый символ используется в каждом блоке кода для создания окончательной суммы (контрольной суммы или хэша), которая сравнивается с окончательной статикой, поскольку проверенный результат записывается в код и не меняется при вызовах, значение. Таким образом, даже если нам удастся подобрать решение для первого блока кода, оно также должно быть решением для второго блока кода и т. д. (правильный ответ нисходит по правой стороне на графике выше). Теперь мы полагаемся на нашу способность создавать атаки столкновений для 13 различных хеш-функций… в любом случае.

И что теперь? Вернемся к разбору. Хорошо, если все 13 кодовых блоков используют все 13 переменных для создания суммы (так и есть), и мы уже знаем решение каждой суммы (мы знаем), то мы можем просто рассматривать каждый кодовый блок как уравнение в системе одновременных уравнений, например:

a*37485 - b*21621 - c*1874 - d*46273 … + m*54757 = 21399379 (блок 1)

a*50936 + b*4809 - c*6019 - d*38962 … + m*53228 = 1453872 (блок2)

….

a*51735 + b*35879 - c*63890 + d*4102 … +m*7314 = 1788229 (блок 13)

К счастью, решение систем одновременных уравнений — это простое применение основ линейной алгебры. Мы можем рассматривать каждый набор коэффициентов как строку в матрице, а решения — как вектор-столбец. Затем мы можем разделить вектор решения на матрицу, и значения переменных, которые решают систему уравнений, сразу же выпрыгнут!

Быстрый тест с октавой, и мы ясно видим наш ответ: Математика сложна! :)

Злой Путь

Это было все, на что у меня было время, когда дело дошло до Defcon, но мне было интересно посмотреть, как другие решили проблему с бэби-ре. Казалось, что мой подход был очень трудоемким методом для первого испытания ребенка. Посмотрев на Github, я заметил несколько решений, которые использовали инструмент angr для решения задачи. Глядя на используемые скрипты, казалось, что вы можете легко решить задачу, используя всего несколько строк Python и время выполнения менее минуты. Поиграв со сценариями решения на виртуальной машине, я очень хотел попробовать angr в другом задании. Я заметил, что приближается CTF securityfest, и решил попробовать angr на некоторых из их вызовов.

Глядя на открытые испытания, я заметил их испытание RE на 250 очков: Fairlight. Глядя на это с помощью IDA, я заметил знакомую схему: ввод 14 символов, который затем обрабатывался 14 функциями проверки.

Я хотел попробовать атаковать программу с помощью гнева, потратив как можно меньше времени на реверсирование. Глядя на check_0, было ясно, что программа выгрузится внутри функций проверки, так что не будет «окончательной» проверки входных данных, на которые мы могли бы нацелиться. Это не было проблемой, так как мы могли просто использовать angr для нацеливания на последнюю ветку check_13.

Как оказалось, это было довольно удачное решение. Я написал самый быстрый сценарий гнева, который только мог придумать (не пытаясь понять функции проверки). Все, что делает скрипт, это нацеливается на безусловный jmp в check_13, указанном выше, избегая ветки сбоя (также доступной в angr-docs):

import angr
proj = angr.Project('./fairlight', load_options={"auto_load_libs": False})
argv1 = angr.claripy.BVS("argv1", 0xE * 8)
initial_state = proj.factory.entry_state(args=[“./fairlight”, argv1])

начальный_путь = proj.factory.path(начальное_состояние)
path_group = proj.factory.path_group(начальное_состояние)
path_group.explore(find=0x4018f7, избежать=0x4018f9)
found = path_group.found[ 0]
напечатать found.state.se.any_str(argv1)

Ответ появился менее чем за минуту: 4ngrman4gem3nt.

Похоже, организаторы тоже фанаты. ›:)