В прошлые выходные у меня была возможность принять участие в CTF STACK the Flags, организованном GovTech Cyber Security Group. Это был особенно интересный (и утомительный!) опыт — хотя моя команда застряла на многих задачах, это было напоминанием о том, что есть (и всегда будет) еще много всего, о чем нужно узнать.
Другой товарищ по команде (Джордж Чен) опубликовал статьи здесь и здесь для задач OSINT, и в этой записи подробно рассказывается об опыте решения первой задачи обратного проектирования, которой мы все занимались. относительно новый для.
На первый взгляд задача выглядит довольно просто:
Предоставленный файл представляет собой zip-архив, содержащий веб-страницу (приглашение), а также два файла JavaScript. Открытие веб-страницы в браузере показывает… пустую страницу. Однако консоль браузера дает первый намек на то, как решить эту проблему:
По понятным причинам invite.js
запутан, однако Firefox услужливо предлагает красиво распечатать исходный код, так что давайте сделаем это и посмотрим, где/какой должна быть переменная gl
:
Попытка запустить canvas[_0x3f3a[3]](_0x3f3a[2])
приводит к ошибке, что canvas
тоже равно null, поэтому давайте посмотрим, что должно быть canvas
— из содержимого массива _0x3f3a
мы можем сказать, что canvas
должен быть элементом DOM с классом G
, однако элемент canvas
на странице (преднамеренно) имеет класс CSS только GG
, поэтому давайте изменим исходный код страницы, чтобы элементы холста отражали правильный класс, и перезагрузим страницу.
После перезагрузки в консоли появляется другая ошибка:
Вернемся к источнику JS, чтобы посмотреть объявление для gl[_0x3f3a[11]]
:
Проверка shade
, ctype
и cid
подтверждает, что они соответствуют значениям атрибутов объекта <canvas>
на странице. К сожалению, не имея представления о том, какими должны быть «правильные» значения атрибутов, а также из-за нехватки времени во время CTF, я отказался от стратегии, направленной на то, чтобы страница работала «правильно». Вместо этого я углубился в код, чтобы увидеть, что происходит:
Здесь анонимная функция — давайте попробуем присвоить выражение функции новой переменной и посмотрим, произойдет ли что-нибудь:
После удаления теперь ненужных обратных слэшей и выполнения этого нового фрагмента кода мы получаем очевидную попытку сорвать анализ:
Оператор debugger
здесь функционально эквивалентен установке здесь точки останова и будет делать это 1000 раз — очевидно, это не то, что можно обойти, нажав «Продолжить» в консоли отладки. Давайте удалим цикл и перезапустим код:
Это тот момент, когда это стало довольно захватывающим. Однако браузер не показывал предупреждения. Судя по описанию вызова, либо hhh
, либо mmm
почти наверняка должны были быть переменной, содержащей флаг, поэтому давайте быстро взглянем на то, что они содержат в настоящее время:
Сейчас это не очень полезно, поэтому давайте сделаем шаг назад и посмотрим, что код делал раньше. Первое, на что я посмотрел, был вызов compare()
:
…который, вероятно, был назван так намеренно, чтобы ввести в заблуждение. Функция на самом деле содержит функцию XOR, и функция вызывалась для XOR имени хоста, сообщенного браузером, с предварительно определенной строкой you're invited!!!
(по символьному коду) перед сравнением со значением, декодированным в URL-адресе. К счастью, мы знаем, что можем найти неизвестное значение с помощью операции XOR над двумя известными значениями, так что давайте сделаем это:
Похоже, мы были на правильном пути к определению того, что проверяет скрипт, но из-за нехватки времени на репликацию среды, которую искал скрипт, это осталось просто интересной находкой.
Вот тут-то и становится неловко — среди волнения я совершенно упустил из виду значение массива x
и сразу перешел к объявлениям для hhh
и mmm
. Было определено, что hhh
связано с проверкой имени хоста, поэтому внимание переключилось на mmm
, который, скорее всего, должен был содержать флаг. После значительного количества потерянного времени, пытаясь подобрать возможные возвращаемые значения rrr()
, мне наконец пришло в голову, что функция возвращает разные значения при каждом вызове! Вернувшись к коду, мы увидим, где функция была заполнена:
Теперь все ясно — ooo
возвращает функцию с ttt
в качестве начального значения, из которого значение ttt
вычисляется с помощью iii
. Поскольку x
было передано в iii
, нам нужно увидеть, где устанавливались значения в массиве. В этот момент было тривиально пройтись по каждому if-условию, вручную установить элементы x
в их правильные значения, а затем вызвать функции вручную через консоль:
x = [57, 88, 54]
В целом, это был тот, который, к сожалению, потребовал немало проб и ошибок и переделки из-за моего относительно плохого знания JavaScript. В будущем следует подумать о том, чтобы попытаться воспроизвести среду браузера, чтобы различные проверки скрипта правильно устанавливали значения в массиве x
, и «исправление» приглашения, чтобы оно работало должным образом, путем представления флага в предупреждении браузера. вместо.