rfCTF 2011

17 ноября 2011 в Санкт-Петербурге прошло соревнование по информационной безопасности rfCTF 2011.


Тема соревнования — безопасность систем, использующих технологии RFID. Участникам был представлен набор заданий и 6 часов на их выполнение (стиль task-based CTF).

Место проведения — ВК Ленэкспо: Санкт-Петербург, Васильевский остров, Большой пр., 103 корп. 7
Время проведения — с 11:00 до 17:00.

Участие приняли команды петербургских вузов:

  • RDOT.ORG
  • PeterPen (СПбГУ, матмех)
  • KillDozer (СПбГУ, матмех)
  • noob technology (ИНЖЭКОН)
  • Cлоупок (ИНЖЭКОН)

На соревновании для решения тасков участникам были предоставлены карточки Mifare разных типов, а также ридер IronLogic Z-2 USB MF.

Полезные ссылки:

rfCTF 2011 прошел в рамках II международной специализированной выставки «Информация: техника и технологии защиты».
Организаторы соревнования — команда Leet More. ;)


UPD: соревнования закончились! финальный скорборд:


UPD2: фотографии с закрытия

Разбор заданий

1. PYFI console (100) [задание] [хинт 1]


Это задание было самое простое из всех. Оно состояло из двух частей:
– проверка данных с карточки:

    xx = [0, 1]
    for i in xrange(14*12-1):
        xx.append(xx[i] + xx[i+1])

Легко видеть, что массив заполняется числами Фиббоначи.
Затем выбирается случайная ячейка на карточке, и число из неё сравнивается с соответствующим числом последовательности.
То есть, необходимо заполнить всю карточку числами из этой последовательности.
– выполнение кода:

code = str(card.read(15, 0)) + str(card.read(15, 1)) + str(card.read(15, 2))
code = code.rstrip("\x00")
try:
    __builtins__ = __import__ = None
    os = sys = quit = locals = globals = None
    eval(code)

Так как было известно, что флаг лежит в c:\flag.txt, то можно было использовать следующий код для записи его на карточку:

card.write(0,1,open("c:/flag.txt").read())

После выполнения кода флаг лежит во втором блоке первого сектора.

Исходники сервиса
Решение – скрипт, записывающий на карточку правильный код.

2. LeetSec (300) [задание] [хинт 1] [хинт 6] [хинт 7] [хинт 9]


Этот квест состоит из двух этапов проверки, после прохождения которых флаг записывается на карточку.
– код MAGIC в третьем блоке второй секции:

MAGIC = "\xde\xad\xfa\xce" + "\xa9" * 8 + "\xf1\xf2\xf3\xf4"

Здесь всё просто – записываем на карточку строчку (в хексах) DEADFACEA9A9A9A9A9A9A9A9F1F2F3F4.
– основная проверка

N = 308437044275067938131653453561001047357000826062737759785845990336
C = 111111111111111111111111111111111111111111111111111111111111112000
E = 31337
 
key = (s2n(card.read(1, 0)) << 128) | s2n(card.read(1, 1))
 
if pow(key + pow(serial, E, N), 2, N) == C:
    return grant(card)
else:
    return deny("Bad key")

Если внимательно изучить этот код, то можно понять, что нам необходимо найти такое число X, что

мы можем вычислить, поэтому

Также для упрощения мы можем заменить X + K на M:

То есть нам просто нужно извлечь квадратный корень по модулю N.

Для извлечения квадратных корней по простому модулю существуют эффективные алгоритмы (например). В данном случае N составное (можно факторизовать здесь или здесь):
2^6 · 3^3 · 761364314999 · 891083343019 · 263094610336089343590963140117371337377
Поэтому, необходимо извлечь корень по каждому простому модулю, а потом получить полный корень с помощью Китайской Теоремы об Остатках. Единственная проблема – большинство реализаций алгоритмов извлечения корня по простым модулям не справляются со степенями простых – например 2^6 или 3^3. Но, так как эти числа достаточно малы, можно перебрать все остатки (найти корень перебором).

Исходники сервиса
Решение


UPD: еще одно решение, без кодинга и матана — vos

Зачем нам все эти теоремы, если есть Вольфрам =)

Нам надо решить уравнение:

1. Посчитаем часть, которая нам известна, т.е. S^E mod N. Допустим, у нашей карточки серийник 0xAABBCCDD12345678AABBCCDD12345678. Wolfram »
Результат:

2. Решим квадратное уравнение по модулю (x^2 mod N = C). Wolfram »
Результат:

3. Вычтем из второго первое. Wolfram »
Результат:

Итак, наш ключ: 0x00000000015d1c3d3f84b44b6929bd2c41650d711f16d00df87b32cf7f802d68 (нули спереди добавлены, чтобы было ровно 32 байта)

4. Проверяем.

c:\>\Python26\python.exe
Python 2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)]
Type "help", "copyright", "credits" or "license" for more information.
>>> N = 308437044275067938131653453561001047357000826062737759785845990336
>>> C = 111111111111111111111111111111111111111111111111111111111111112000
>>> E = 31337
>>> serial = 0xAABBCCDD12345678AABBCCDD12345678
>>> key = 0x00000000015d1c3d3f84b44b6929bd2c41650d711f16d00df87b32cf7f802d68
>>> pow(key + pow(serial, E, N), 2, N) == C
True

3. Arigato (250) [задание] [хинт 3]

Программа запакована UPX, однако, помимо этого имеет одну особенность. Между 2 и 3 секциями запакованного файла расположен оверлей (overlay) – область, которая не будет загружена в память при запуске файла и как следствие – будет отсутствовать в дампе памяти.

Конец второй секции: 0x01AA00 + 0x400 = 0x01AE00

Начало третей секции: 0x01BE00

Размер оверлея: 0x01BE00 – 0x01AE00 = 0x1000

Оверлей запакован:

Так же, если распаковать семпл UPX.exe, то оверлей так же станет не валидным.

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

В этой функции сначала происходит чтение карточки (одного из 4 секторов, выбранных случайно):

После этого подряд вызываются 2 функции 0x0401550 и 0x0401630.

В функции 0x0401550 происходит чтение оверлея из файла:

В функции 0x0401630 происходит расшифровка и выполнение оверлея. После чего оверлей заново шифруется. Алгоритм, используемый для шифрования TEA, ключи для расшифровки генерируются правильно только в случае, если присутствие дебаггера скрыто (чего легко можно достигнуть множеством плагинов). В случае успешной расшифровки, код из оверлея выполняется, а в качестве параметра ему передается считанная с карты информация. Далее, если код из оверлея возвращает true  – генерируется секретный флаг и записывается на карту.

Все, что нужно сделать для получения доступа к «секретному» коду, включить скрытие дебаггера и поставить брекпоинт по адресу 0x04016DB. После остановки под дебаггером можно было проанализировать достаточно простой код проверки:

В этом коде первая часть входных данных сравнивается со строкой:

“The obstacle is “

Если в гугле вбить данную строчку (а в коде есть подсказка выводимая как

OutputDebugStringA (“You should google a little for full answer :)”)), то первой же строчкой выпадает:

“The obstacle is the path”

Это и есть ответ, если его записать на первые 4 секции карточки, то на один из 8 секторов с 32 по 39 запишется флаг.

Прикладываю полный код программы, а так же распакованный оверлей.


4. Gosuslugi (250) [задание] [хинт 2] [хинт 5] [хинт 10]

Сервис Gosuslugi представлял собой оболочку терминала госуслуг России 2050 года =)

Код задания написан на PHP, БД — SQLite.

Команды могли авторизоваться на терминале под своими аккаунтами. Чтобы решить задание, нужно было войти в терминал под именем другого пользователя.

Авторизация под легитимным пользователем:

Обход аутентификации возможен благодаря двум уязвимостям, одна из которых находится в коде оболочки терминала, а другая содержится в штатном SDK rfid-ридера.
Разбираемся по порядку.

1) SQL-инъекция в коде авторизации

Файл ui_pages/01_login.php:

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
  update_device_status("Чтение карты...");
  sleep_with_client_ping(0.2);
 
  $cardSn = mifare_read_card($cardId, 0, 8);
  if ($cardSn === false) {
    update_device_status("Ошибка чтения карты!");
    sleep_with_client_ping(2);
    continue;
  }
 
  update_device_status("Вход в систему...");
  sleep_with_client_ping(0.2);
 
  $result = sqlite_query($GLOBALS['SQLITE_DB'], "SELECT id FROM people WHERE auth_rfid_sn = '$cardSn'");
  if ($result === false) {
    update_device_status("Ошибка работы с БД!");
    sleep_with_client_ping(2);
    continue;
  }
 
  if (!sqlite_num_rows($result)) {
    update_device_status("Карта не опознана!");
    sleep_with_client_ping(2);
    continue;
  }
 
  $row = sqlite_fetch_array($result);
  @session_start();
  $_SESSION['auth_person_id'] = (int)$row['id'];
  update_device_status("Вход произведен!", 'success');
  js_timeout_redirect("?page=account", 3);
127
  $cardSn = mifare_read_card($cardId, 0, 8);

В переменную $cardSn считываются первые 8 байт с карточки Mifare Ultralight.
В первых 8 байтах содержится серийный номер карточки; этот блок данных защищен от записи.

137
  $result = sqlite_query($GLOBALS['SQLITE_DB'], "SELECT id FROM people WHERE auth_rfid_sn = '$cardSn'");

Серийный номер подставляется в авторизационный SQL-запрос в бинарном виде.
Возможна SQL-инъекция длиной 8 байт при условии, что мы контролируем первые 8 байт карточки.

Чтобы использовать SQL-инъекцию, необходимо иметь возможность влиять на первые 8 байт, прочитанные ридером с карты. На самой карте этот блок перезаписать невозможно, из-за этого остается два варианта:

  1. 1. Использовать дорогой эмулятор Mifare-карточек
  2. 2. Использовать вторую уязвимость в SDK ридера ;)

Эмулятора карточек у нас нет…

2) Ошибка наложения секторов в SDK ридера

Уязвимость существует из-за того, что библиотека SDK неверно обрабатывает ситуации прерывания контакта карточки со считывателем.
Вместо того, чтобы заполнить неверно считанный блок данных нулями или вернуть ошибку чтения, библиотека заполняет блок, который не удалось считать, данными из ранее успешно считанного блока.

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

Видео этого эффекта:

С помощью этой уязвимости мы можем контролировать 8 байт, которые попадут в SQL-запрос в неизменном виде.

3) Эксплоит

Для того, чтобы войти в систему от имени другого пользователя, нам нужно подставить в SQL-запрос такие данные (8 байт), чтобы он вернул чужой id.

SELECT id FROM people WHERE auth_rfid_sn = '$cardSn'

Если в переменной $cardSn будет содержаться ‘ OR 1/*, запрос примет вид:

SELECT id FROM people WHERE auth_rfid_sn = '' OR 1/*'

В результате, он вернет все записи из таблицы в БД, и для авторизации будет использована самая первая запись.

Для эксплуатации уязвимости можно использовать одну карточку, на всех записываемых секторах которой записаны байты ‘ OR 1/*.

После использования трюка с прерыванием контакта с такой карточкой, нас пускает в терминал под пользователем с наименьшим id:

Сливаем флаг себе на карточку:

Задание решила одна команда, бонусный флаг не нашёл никто :-(

P.S.

Бонусный флаг лежал у пользователя со вторым id. Его можно было вытащить, использовав немного другую инъекцию: ‘=id-2/*

Запрос превращается в:

SELECT id FROM people WHERE auth_rfid_sn = ''=id-2/*'

Интерпретируется так:

SELECT id FROM people WHERE (auth_rfid_sn = '') = (id - 2)
 
(auth_rfid_sn = '') == 0  (FALSE)
 
значит  0 = (id - 2)
т.е.    id = 2

Обладатель бонусного флага:

8 comments

1 ping

Skip to comment form

    • FIX on November 18, 2011 at 10:26
    • Reply

    кто бы сомневался :)

    • mammut on November 23, 2011 at 11:52
    • Reply

    а фотографии будут?)

  1. Надо поискать. А ещё потрясем нашего фотографа :)

    • reverse1234 on November 26, 2011 at 16:54
    • Reply

    Ребятули, я конечно все понимаю, и что RuCTFe, и 0nights, но фитбек полностью хочется очень увидеть да и фоточки не помешали бы) Трясите фотографов!

  2. Добавил ссылку на фото: фотографии с закрытия

  3. Добавил разбор своего таска и содержание ;)

  4. Добавил еще одно решение LeetSec

  5. Читер!

Leave a Reply

Your email address will not be published.