Как я сдампил GeekBrains. Проходим тесты, не открывая браузер
Здравствуй, Codeby. В какой-то момент я понял, чего мне не хватает в этой жизни — сертификатов! Еще неделю назад я чувствовал себя хуже некуда: у меня была низкая самооценка, я был робок, застенчив и безынициативен. Жалкое зрелище. Теперь же, с гордостью сообщаю, что, став сертифицированным специалистом в областях вёрстки, программирования (на большинстве популярных языках), работы с разнообразными фреймворками, администрирования БД (разных вендоров), разработки игр (на Unity и не только), Linux-систем и компьютерных сетей, информационной безопасности, английского языка и многих, многих других, я чувствую себя хозяином этой жизни. И благодарю я за это отнюдь не свою Альма-матер, о нет. В этом мне помог образовательный портал GeekBrains, и это моя исповедь...
Шучу. Я всего лишь сдампил базу данных их тестов. А ниже мы поговорим о том, почему не стоит показывать результаты пройденного только что тестирования в виде списка заваленных вопросов; почему следует блокировать регистрацию новых пользователей с 10-минутных почт; а также о таком феномене, как «риторический вопрос в плоскости учебной площадки GeekBrains». Не проходите мимо, СЕРТИФИКАТЫ ДЛЯ ВСЕХ, ДАРОМ, И ПУСТЬ НИКТО НЕ УЙДЁТ ОБИЖЕННЫЙ!
Пруф того, что дамп существует —
Рисунок 1 — Случайно выбранный тест из собранного дампа
Ну а теперь к случившемуся...
Пролог
О существовании портала я знаю не первый год. Войдя в состав компании Mail.ru Group, GeekBrains стали вести достаточно агрессивную рекламную кампанию — баннеры, предлагающие пройти тот или иной курс, красуются на многих сайтах крупных агрегаторов IT-новостей, в социальных сетях, а также летают в почтовых рассылках (которые даже Яндекс.Почта уже зачастую трактует как спам). О качестве предлагаемых услуг говорить не буду, ибо самому никогда не доводилось присутствовать на вебинарах / проходить курсы виновника этого повествования, однако, судя по отзывам в сети с первых ссылок поисковика, в общем и целом народ не очень доволен организацией обучения.
Но мы немного отвлеклись, вернемся к теме. Когда я был молод и глуп (ох уж эти полгода назад), начитавшись о том, с каким благоговением на западе смотрят на обладателей сертификатов типа CCNA, CEH, OSCP и иже с ними, я думал, что любая бумажка (особенно на которой стои́т печать и роспись гендира компании с говорящем именем) может сыграть решающую роль при устройстве на работу. К счастью, мое наваждение длилось недолго, и очень скоро мне открылась истина: если сможешь «здесь и сейчас» продемонстрировать свои умения, мудрый собеседующий не взглянет даже на наличие у тебя диплома об окончании вуза. Гиперболизирую, конечно, но в целом это так. И это правильный подход.
Но на GeekBrains я все же время от времени заходил — просто когда было скучно и хотелось что-то порешать, и... очень часто уходил оттуда без призового сертификата, хотя считаю себя неплохим специалистом в тех областях, которые я выбирал на тестированиях. И я постараюсь объяснить, почему так происходило.
Часть I. Бесплатные сертификаты
Всем хорошо известно то место, где живет бесплатный сыр. Зачем, казалось бы, крупному образовательному порталу предоставлять возможность любому желающему (завершившему регистрацию на сайте) пройти тест и получить «задокументированное» подтверждение владения указанной проф. областью от своего имени?
Ответ, в свою очередь, лежит на поверхности, равно как и весь оригинальный (нет) бизнес-план этой организации (все, написанное ниже, является моим личным оценочным суждением, если что):
Часть II. Уязвимости в логике работы портала
В какой-то момент раздел «Тестирования» на сайте GeekBrains разросся до таких масштабов, что там появились тесты на знание английского, SMM-стратегий и скилов «фотошопирования». На момент написания статьи на портале доступны для прохождения 82 теста (один совсем новый — «Криптография») из 32-х областей, все области под спойлером ниже:
Путешествуя по очередному тесту, я обратил внимание на очень забавную, как мне показалось, ситуацию: на скриншоте ниже (рис. 2) правильный ответ выделен в самом вопросе. Всему виной подсветка синтаксиса.
Рисунок 2 — Вопрос из теста на базовое владение PHP
После такого ляпа для меня почему-то стало очевидным, что в логике работы системы тестирования будут вектора реализации автоматизированного сбора ответов на тесты из базы данных GeekBrains. Так оно и оказалось.
Первое: в структуре DOM динамически генерируемой странички с текущим вопросом по сути есть готовое API для построения бота, который будет нажимать за тебя на кнопки в виртуальном браузере (см. рис. 3).
Рисунок 3 — Признаки, по которым можно создать однозначный маппинг
Второе: в конце проваленного теста в красивом отчете тебе будет рассказано, в каких местах ты сфейлился, что дает возможность атаковать тест простым перебором (см. рис. 4).
Рисунок 4 — Финальный отчет после провала теста
Третье: после провала любого из тестов тебе дается тайм-аут, в течении которого ты не можешь заново пробовать пройти этот тест (для каких-то тестов это день, для каких-то — три). Разумно. Только вот для меня стало большим удивлением, что GeekBrains позволяет регистрировать новые аккаунты с одноразовых (или «десятиминутных») почт. Так как в этом случае процесс регистрации нового аккаунта так же легко поддается автоматизации, ценность таких «тайм-аутов» обращается в н0ль.
После комбинации трех вышеописанные слабостей образовательной платформы на свет родился многопоточный Python-бот, который, используя мощности инструмента Selenium для автоматизации действий веб-браузера, за непродолжительное время собрал в красивые json'ки около 70 % базы данных тестов GeekBrains.
Часть III. Технические подробности
Чтобы не утомлять читателя большим количеством подробностей, остановлюсь только на самых интересных моментах, с которыми пришлось столкнуться при реализации бота.
1. Проксирование соединения
Для проксирования трафика использовались анонимные SOCKS-прокси сервера. Во время написания кода в PyPI лежала не самая свежая версия Selenium'а, в которой не был исправлен баг с настройкой SOCKS-соединения, заключающийся в невозможности указания версии SOCKS-протокола.
В идеале конфигурация Лисички из-под Селениума с настроенной проксёй должна выглядеть так:
Где
2. Перебор вариантов ответов
Бóльшая часть вопросов имеет тип radio — случай, когда правильный ответ может быть только один (пример на рис. 2). Здесь все очевидно: в худшем случае, сколько вариантов ответа — столько раз и придется «пробежать» тест, чтобы гарантированно определить верный.
Интереснее вариант с вопросами типа checkbox, когда правильных ответов может быть несколько (и не сказано, сколько конкретно, см. рис. 5).
Рисунок 5 — Вопрос, в котором возможен не один верный вариант ответа (из того же теста на базовое владение PHP)
В этом случае вместе с самим текстом ответа я хранил два дополнительных значения: число
Если постановить, что идентификаторы ответов всегда отсортированы в одном порядке, то питоновский
На рис. 6 представлен пример checkbox-вопроса, ответами на который стали варианты с id 6 и 7 — это первые два элемента (о чем говорит значение
Рисунок 6 — Пример хранения вопроса типа checkbox
Здесь, рассматривая самый неблагоприятный исход, когда нужная нам комбинация окажется последней (т. е. правильными окажутся все ответы), число «прогонов» такого вопроса будет равна сумме сочетаний из
Например, для вопроса с рис. 6
Из-за того, что некоторые тесты практически полностью состоят из вопросов второго типа, и не получилось собрать 100 % базы, потому что уже просто надоело мучить компьютер.
3. А был лимальчик правильный ответ?
Отлаживая новорожденного бота, аки Франкенштейн свое чудовище, я заметил ряд странностей при просмотре получавшихся json'ок: некоторые вопросы для ключа
Вот, кстати, некоторые из этих вопросов:
4. Изображение в качестве ответа
Некоторое тесты (например, тест 13 — «Веб-дизайн. Начальный уровень») содержат изображения в качестве вариантов ответов. В этом случае при необходимости (если без них не получалось получить сертификат) ответы в базу заносились вручную.
Post Scriptum
На этот аккаунт через некоторое время после его создания на GeekBrains прилетали такого рода сообщения:
Рисунок 7 — Письмо от поклонника
Мне совестно, что я не отвечал людям, поэтому отвечу, пожалуй, под завершение этой статьи на все вопросы разом по порядку:
Отказ отдевственностиответственности
Данному тексту около полугода, поэтому я не могу утверждать, что все моменты, описанные в статье, продолжают существовать до сих пор. Статья лежала у меня «в столе» некоторое время после того, как я написал в поддержу Mail.ru (заявка была принята, но ответа не последовало), а после я как-то забыл об этой забавной истории. На текущий момент эту статью следует рассматривать скорее как «байку из склепа», хотя я практически не сомневаюсь, что вся логика проведения сбора дампа осталась такой же. Но заниматься последним я, разумеется, никого не призываю, никому не рекомендую и вообще осуждаю все плохие дела.
Здравствуй, Codeby. В какой-то момент я понял, чего мне не хватает в этой жизни — сертификатов! Еще неделю назад я чувствовал себя хуже некуда: у меня была низкая самооценка, я был робок, застенчив и безынициативен. Жалкое зрелище. Теперь же, с гордостью сообщаю, что, став сертифицированным специалистом в областях вёрстки, программирования (на большинстве популярных языках), работы с разнообразными фреймворками, администрирования БД (разных вендоров), разработки игр (на Unity и не только), Linux-систем и компьютерных сетей, информационной безопасности, английского языка и многих, многих других, я чувствую себя хозяином этой жизни. И благодарю я за это отнюдь не свою Альма-матер, о нет. В этом мне помог образовательный портал GeekBrains, и это моя исповедь...
Шучу. Я всего лишь сдампил базу данных их тестов. А ниже мы поговорим о том, почему не стоит показывать результаты пройденного только что тестирования в виде списка заваленных вопросов; почему следует блокировать регистрацию новых пользователей с 10-минутных почт; а также о таком феномене, как «риторический вопрос в плоскости учебной площадки GeekBrains». Не проходите мимо, СЕРТИФИКАТЫ ДЛЯ ВСЕХ, ДАРОМ, И ПУСТЬ НИКТО НЕ УЙДЁТ ОБИЖЕННЫЙ!
Минутка серьезности
Разумеется, выкладывать исходные коды написанной утилиты для автоматизации сбора дампа и/или сами ответы на тесты, тем самым обесценив труды команды GeekBrains по созданию последних, я не стану — это аморально, некрасиво и вообще фу таким быть. Но о сложившейся ситуации не могу не рассказать. В образовательных, развлекательных и поучительных целях.
Пруф того, что дамп существует —
Ссылка скрыта от гостей
. Некто B Rainfux за неделю прошел бо́льшую часть тестов, вот, кстати, и его
Ссылка скрыта от гостей
(нужно быть авторизованным, чтобы видеть подробности).Рисунок 1 — Случайно выбранный тест из собранного дампа
Ну а теперь к случившемуся...
Пролог
О существовании портала я знаю не первый год. Войдя в состав компании Mail.ru Group, GeekBrains стали вести достаточно агрессивную рекламную кампанию — баннеры, предлагающие пройти тот или иной курс, красуются на многих сайтах крупных агрегаторов IT-новостей, в социальных сетях, а также летают в почтовых рассылках (которые даже Яндекс.Почта уже зачастую трактует как спам). О качестве предлагаемых услуг говорить не буду, ибо самому никогда не доводилось присутствовать на вебинарах / проходить курсы виновника этого повествования, однако, судя по отзывам в сети с первых ссылок поисковика, в общем и целом народ не очень доволен организацией обучения.
Но мы немного отвлеклись, вернемся к теме. Когда я был молод и глуп (ох уж эти полгода назад), начитавшись о том, с каким благоговением на западе смотрят на обладателей сертификатов типа CCNA, CEH, OSCP и иже с ними, я думал, что любая бумажка (особенно на которой стои́т печать и роспись гендира компании с говорящем именем) может сыграть решающую роль при устройстве на работу. К счастью, мое наваждение длилось недолго, и очень скоро мне открылась истина: если сможешь «здесь и сейчас» продемонстрировать свои умения, мудрый собеседующий не взглянет даже на наличие у тебя диплома об окончании вуза. Гиперболизирую, конечно, но в целом это так. И это правильный подход.
Но на GeekBrains я все же время от времени заходил — просто когда было скучно и хотелось что-то порешать, и... очень часто уходил оттуда без призового сертификата, хотя считаю себя неплохим специалистом в тех областях, которые я выбирал на тестированиях. И я постараюсь объяснить, почему так происходило.
Часть I. Бесплатные сертификаты
Всем хорошо известно то место, где живет бесплатный сыр. Зачем, казалось бы, крупному образовательному порталу предоставлять возможность любому желающему (завершившему регистрацию на сайте) пройти тест и получить «задокументированное» подтверждение владения указанной проф. областью от своего имени?
Ответ, в свою очередь, лежит на поверхности, равно как и весь оригинальный (нет) бизнес-план этой организации (все, написанное ниже, является моим личным оценочным суждением, если что):
- Тесты на портале я разделяю на две категории, и первая — это «льстящие тесты». Тесты этого типа выполняют роль «ярморочных зазывал», с ними может справиться практически любой желающий, хоть как-то разбирающийся в выбранной тематике тестирования. Их задача (тестов) — ублажить эго всяк сюда входящего, «подкормить» его на портале и подвести к попытке прохождения теста из категории № 2 — «дразнящих тестов». Их же цель, как вы уже могли догадаться, «завалить» испытуемого неадекватными вопросами (на которые, порой, в прямом смысле нет правильного ответа в базе, о чем ниже) и показать в конце ободряющую новость, гласящую, что мол «не все потеряно, дружище, ты все еще респектабельный гражданин, только вот подучиться надо, и, разумеется, выгоднее и практичнее всего это сделать у нас» (вольный пересказ автора).
- Даже если человеку удается успешно завершить труднопроходимый тест, с портала взятки гладки, т. к. сами они прекрасно понимают, что ценность этой (электрической) бумажки равняется нулю, и кроме потенциального клиента в лице прошедшего данный тест человека они ничего не потеряют.
Часть II. Уязвимости в логике работы портала
«Я хотел велосипед и молился, чтобы он был. И Господь в мудрости своей решил его мне не давать. И я украл. И, конечно, молил о прощении. Это было началом, я не смог остановиться...»
В какой-то момент раздел «Тестирования» на сайте GeekBrains разросся до таких масштабов, что там появились тесты на знание английского, SMM-стратегий и скилов «фотошопирования». На момент написания статьи на портале доступны для прохождения 82 теста (один совсем новый — «Криптография») из 32-х областей, все области под спойлером ниже:
"Основы программирования",
"PHP",
"Unity 3D",
"Информационная безопасность",
"Django",
"HTML&CSS",
"JavaScript",
"Python",
"Java",
"C#",
"Photoshop",
"Веб-дизайн",
"C++",
"Android",
"Objective C",
"Linux",
"Ruby on Rails",
"Tarantool",
"Swift",
"Английский язык",
"Базы данных",
"Тестирование ПО",
"Алгоритмы и структуры данных",
"ASP.Net Core MVC",
"SMM",
"Spring Framework",
"C",
"myTarget",
"Компьютерные сети",
"Операционные системы",
"Разработка игр",
"Ruby".
"PHP",
"Unity 3D",
"Информационная безопасность",
"Django",
"HTML&CSS",
"JavaScript",
"Python",
"Java",
"C#",
"Photoshop",
"Веб-дизайн",
"C++",
"Android",
"Objective C",
"Linux",
"Ruby on Rails",
"Tarantool",
"Swift",
"Английский язык",
"Базы данных",
"Тестирование ПО",
"Алгоритмы и структуры данных",
"ASP.Net Core MVC",
"SMM",
"Spring Framework",
"C",
"myTarget",
"Компьютерные сети",
"Операционные системы",
"Разработка игр",
"Ruby".
Путешествуя по очередному тесту, я обратил внимание на очень забавную, как мне показалось, ситуацию: на скриншоте ниже (рис. 2) правильный ответ выделен в самом вопросе. Всему виной подсветка синтаксиса.
Рисунок 2 — Вопрос из теста на базовое владение PHP
После такого ляпа для меня почему-то стало очевидным, что в логике работы системы тестирования будут вектора реализации автоматизированного сбора ответов на тесты из базы данных GeekBrains. Так оно и оказалось.
Первое: в структуре DOM динамически генерируемой странички с текущим вопросом по сути есть готовое API для построения бота, который будет нажимать за тебя на кнопки в виртуальном браузере (см. рис. 3).
Рисунок 3 — Признаки, по которым можно создать однозначный маппинг
"{вопрос: ответы}"
Второе: в конце проваленного теста в красивом отчете тебе будет рассказано, в каких местах ты сфейлился, что дает возможность атаковать тест простым перебором (см. рис. 4).
Рисунок 4 — Финальный отчет после провала теста
Третье: после провала любого из тестов тебе дается тайм-аут, в течении которого ты не можешь заново пробовать пройти этот тест (для каких-то тестов это день, для каких-то — три). Разумно. Только вот для меня стало большим удивлением, что GeekBrains позволяет регистрировать новые аккаунты с одноразовых (или «десятиминутных») почт. Так как в этом случае процесс регистрации нового аккаунта так же легко поддается автоматизации, ценность таких «тайм-аутов» обращается в н
После комбинации трех вышеописанные слабостей образовательной платформы на свет родился многопоточный Python-бот, который, используя мощности инструмента Selenium для автоматизации действий веб-браузера, за непродолжительное время собрал в красивые json'ки около 70 % базы данных тестов GeekBrains.
Часть III. Технические подробности
Чтобы не утомлять читателя большим количеством подробностей, остановлюсь только на самых интересных моментах, с которыми пришлось столкнуться при реализации бота.
1. Проксирование соединения
Для проксирования трафика использовались анонимные SOCKS-прокси сервера. Во время написания кода в PyPI лежала не самая свежая версия Selenium'а, в которой не был исправлен баг с настройкой SOCKS-соединения, заключающийся в невозможности указания версии SOCKS-протокола.
В идеале конфигурация Лисички из-под Селениума с настроенной проксёй должна выглядеть так:
Python:
from selenium import webdriver
from selenium.webdriver.common.proxy import Proxy, ProxyType
proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.socks_proxy = '127.0.0.1:31337'
proxy.socks_version = 5
capabilities = webdriver.DesiredCapabilities.FIREFOX
proxy.add_to_capabilities(capabilities)
driver = webdriver.Firefox(capabilities=capabilities)
Где
127.0.0.1:31337
— айпи и порт используемого прокси-сервера. Однако корректный доступ к полю socks_version
класса Proxy()
завезли только с этим коммитом, поэтому пришлось править исходники либы вручную. Более детально с проблемой можно ознакомиться в соответствующем ишью.2. Перебор вариантов ответов
Бóльшая часть вопросов имеет тип radio — случай, когда правильный ответ может быть только один (пример на рис. 2). Здесь все очевидно: в худшем случае, сколько вариантов ответа — столько раз и придется «пробежать» тест, чтобы гарантированно определить верный.
Интереснее вариант с вопросами типа checkbox, когда правильных ответов может быть несколько (и не сказано, сколько конкретно, см. рис. 5).
Рисунок 5 — Вопрос, в котором возможен не один верный вариант ответа (из того же теста на базовое владение PHP)
В этом случае вместе с самим текстом ответа я хранил два дополнительных значения: число
k
, которое определяло, какой вариант сочетания (из общего количества возможных ответов по k
) в данный момент проверяется, и значение индекса массива, определяющее конкретную комбинацию из этого числа сочетаний, на проверке которой я остановился.Если постановить, что идентификаторы ответов всегда отсортированы в одном порядке, то питоновский
itertools.combinations
всегда даст один и тот же генератор сочетаний, который можно благополучно юзать для тестирования очередной комбинации ответов на соответствие Истине.На рис. 6 представлен пример checkbox-вопроса, ответами на который стали варианты с id 6 и 7 — это первые два элемента (о чем говорит значение
combination_id
) из сгенерированного списка сочетаний из четырех по два (о чем говорит значение combination_k
).Рисунок 6 — Пример хранения вопроса типа checkbox
Здесь, рассматривая самый неблагоприятный исход, когда нужная нам комбинация окажется последней (т. е. правильными окажутся все ответы), число «прогонов» такого вопроса будет равна сумме сочетаний из
n
по k
без повторений при k
, принадлежащему [1, n]
:Например, для вопроса с рис. 6
S
будет равно:Из-за того, что некоторые тесты практически полностью состоят из вопросов второго типа, и не получилось собрать 100 % базы, потому что уже просто надоело мучить компьютер.
3. А был ли
Отлаживая новорожденного бота, аки Франкенштейн свое чудовище, я заметил ряд странностей при просмотре получавшихся json'ок: некоторые вопросы для ключа
"status"
имели значение "False"
для всех вариантов ответов. Подумав, что сбоит где-то мой скрипт, я перепроверил код, вручную поставил "True"
для нужных ответов и повторил тестирования — нет, сайт уверенно продолжал настаивать на том, что ошибся я. Т. е. существует несколько вопросов (тривиальных, к слову), на которые даже GeekBrains не знает ответов! Ну или же это просто косяк их системы проверки тестов. Tertium non datur.Вот, кстати, некоторые из этих вопросов:
/tests/1: data-question-id=39 (Что выведет приведённый ниже код...)
/tests/6: data-question-id=514 (Класс std::map реализует...)
/tests/7: data-question-id=660 (Для реализации отправления уведомления о состоянии объекта необходимо...)
/tests/22: data-question-id=1607 (Что произойдет при вызове метода method...)
/tests/28: data-question-id=2573 (Как подключатся к другим тарантулам из тарантула...)
/tests/37: data-question-id=4422 (Выберите правильный вариант ответа для следующего выражения...)
/tests/39: data-question-id=4655 (Является ли язык программирования Java кроссплатформенным...)
/tests/39: data-question-id=4711 (Какой класс является суперклассом для всех классов...)
/tests/39: data-question-id=4723 (Укажите способ создания именованного экземпляра класса...)
/tests/40: data-question-id=4806 (Укажите все ошибки в указанном коде...)
/tests/66: data-question-id=6357 (Какая аннотация является аналогом XML-атрибута init-method...)
/tests/72: data-question-id=6824 (Может ли IPv4-адрес хоста содержать 255...)
/tests/72: data-question-id=6841 (Симметричное шифрование -...)
/tests/108: data-question-id=7185 (Укажите какое значение возвращает следующее выражение...)
/tests/109: data-question-id=7224 (Какие интерфейсы необходимы для реализации UserStore...)
/tests/167: data-question-id=9391 (Какую основную функцию выполняет программа objdump...)
/tests/169: data-question-id=9648 (Какое значение для свойства align-content масштабирует элементы, чтобы сетка могла заполнить всю высоту контейнера...)
/tests/6: data-question-id=514 (Класс std::map реализует...)
/tests/7: data-question-id=660 (Для реализации отправления уведомления о состоянии объекта необходимо...)
/tests/22: data-question-id=1607 (Что произойдет при вызове метода method...)
/tests/28: data-question-id=2573 (Как подключатся к другим тарантулам из тарантула...)
/tests/37: data-question-id=4422 (Выберите правильный вариант ответа для следующего выражения...)
/tests/39: data-question-id=4655 (Является ли язык программирования Java кроссплатформенным...)
/tests/39: data-question-id=4711 (Какой класс является суперклассом для всех классов...)
/tests/39: data-question-id=4723 (Укажите способ создания именованного экземпляра класса...)
/tests/40: data-question-id=4806 (Укажите все ошибки в указанном коде...)
/tests/66: data-question-id=6357 (Какая аннотация является аналогом XML-атрибута init-method...)
/tests/72: data-question-id=6824 (Может ли IPv4-адрес хоста содержать 255...)
/tests/72: data-question-id=6841 (Симметричное шифрование -...)
/tests/108: data-question-id=7185 (Укажите какое значение возвращает следующее выражение...)
/tests/109: data-question-id=7224 (Какие интерфейсы необходимы для реализации UserStore...)
/tests/167: data-question-id=9391 (Какую основную функцию выполняет программа objdump...)
/tests/169: data-question-id=9648 (Какое значение для свойства align-content масштабирует элементы, чтобы сетка могла заполнить всю высоту контейнера...)
4. Изображение в качестве ответа
Некоторое тесты (например, тест 13 — «Веб-дизайн. Начальный уровень») содержат изображения в качестве вариантов ответов. В этом случае при необходимости (если без них не получалось получить сертификат) ответы в базу заносились вручную.
Post Scriptum
На этот аккаунт через некоторое время после его создания на GeekBrains прилетали такого рода сообщения:
Рисунок 7 — Письмо от поклонника
Мне совестно, что я не отвечал людям, поэтому отвечу, пожалуй, под завершение этой статьи на все вопросы разом по порядку:
- У меня ушло примерно 0,01923 лет (одна неделя).
- Так и есть.
- Спасибо, ты тоже классный.
- Почти одновременно. Многопоточно. Если понимаешь, о чем я. Kappa.
- Потому что сдал ее («криптографию») ручками в конце всей этой истории.