Как была взломана локальная версия сайта Intuit.Ru

в 10:25, , рубрики: Без рубрики

image

Connection establishment…

Просматривая результаты (довольно таки неутешительные) очередного экзамена на INTUIT-e, я в сотый раз задался вопросом «Ну почему опять этот вопрос не верно?! Ведь на 100% был уверен в ответе…». Проявив толику малодушия, я решил поискать правильные ответы на тесты и был сильно удивлен их отсутствием в открытом доступе. Кое что безусловно было, но это в основном была замануха вида «10 случайных ответов бесплатно, а за остальное будьте добры перечислить нам денежку». Деньги там не большие, но платить за ответы не хотелось и я решил пойти другим путем.

Первой шальной мыслью было попробовать поискать баги на сайте, но я быстро от нее отказался, потому что пришла следующая. Я вспомнил, что в былые времени INTUIT продавал диски с локальной версией своего сайта, которая позволяла проходить обучение без подключения к интернету и в т.ч. сдавать экзамены. Результаты потом можно было синхронизировать при подключении. Далее в мозгу возникла логическая цепочка «Можно проходить тесты offline => проверка производится тоже в offline => где-то там лежит файл с ответами».

Какие-то 4.5Гб отделяли меня от моего счастья и решения всех моих дилемм! Диск был найден довольно быстро на каком-то трекере и поставлен на закачку. От скуки начал читать комментарии. Среди россыпи благодарностей кто-то спрашивал: «а там же можно ответы посмотреть?». Ему так и не ответили, но меня тогда это совершенно не насторожило. И вот образ скачан и установлен (ставился он довольно долго). Посмотрим что тут у нас.

Погружение

Внутренняя структура корневого каталога выглядела следующим образом:

html
lib
local_web_server
intu32.ico
INTUIT.exe
intuit.ini
trayicon.ico
uninstall.exe

Как-то сразу меня потянуло в папку lib. И верно потянуло. Куча *.pm файлов и пара каталогов с интересными названиями «course» и «test». Заглянув в папку test и изучив ее содержимое я понял, что я ничего не понял. В ней лежало 3 .pm файла, которые, как вы уже наверное догадались, оказались perl-пакетами. Надо сказать, что я совершенно не гуру перла. Писал на нем что-то когда-то для экспериментов, но то дела давно минувших дней. Система же, которая попала ко мне в руки, была полностью написана на перле. Но это не повод сдаваться и раскопки были продолжены.

В директории course оказалось много файлов с названиями очень похожими на сокращенные называния курсов. Как же найти тот, который мне был нужен? Пора, видимо, запустить ярлык с рабочего стола и посмотреть, что нам там предлагают. Через некоторое время после запуска стартовал браузер по умолчанию и загрузил страницу по адресу localhost:3232. Занятно. Значит где-то внутри этого вигвама все таки обитает полноценный апач или на худой конец какой-нибудь денвер. Но об этом позднее.

Залогинившись под admin:admin я решил попробовать сдать экзамен по нужному мне курсу что бы посмотреть как вообще вся эта система работает. Отметив ответы на вопросы и нажав на кнопку отправки формы я увидел следующий текст: «Вы набрали {n}% баллов. Правильно решили {m} задач из {k}.» И тут взгляд упал на строку браузера в которой был указан путь к курсу, но не в каталог lib, а в каталог html. Там же было сокращенное название курса. Кажется похожий файл я уже видел… Бегом в каталог lib/courses, открываю oopbase.pm и вижу несколько процедур, какой-то хеш массив %lecture, а вот ниже картина гораздо интересней! Вот он красавец! Хеш массив %test со всеми вопросами, а так же вариантами ответов. «Все задача решена!» подумал я в тот момент, но по ходу изучения этого хеша, красивая розовая птица Обломинго все более навязчиво стучалась мне в окно… Ответов не было. Хеш очень большой, поэтому привожу его кусок, который представляет интерес:

830 =>
['830','50','5',"16ab48786a5d520fe6eeea7f1a6e140b",
    [['5708','830','1','10',"7ce21606425e2b20e566f422696b92de",
        [['16692','5708','1','1',"Корректность программы – это понятие","e9b0b5d2f98bf71489891a48f80cb868",
         "387b3b67d9c1248131c136eae3e6cab9",
            [['349301','16692','1',"неформальное",],
             ['349302','16692','2',"которое можно определить, используя только термины самой     программы",],
             ['349303','16692','3',"которое можно формализовать триадой Хоара",],
             ['349304','16692','4',"для формализации которого необходимо задание спецификации",],],],

Ключ 830 это как раз то значение, которое ставилось в соответствие ключу из хеша %lecture. Заодно привожу и его часть:

my %lecture = (
	11	=> 830,
	7	=> 826,
	17	=> 836,
	2	=> 821,
	1	=> 820,
	…
);

Теперь становиться понятным, что вопросы которые соответствуют ключу 830, относятся к 11-й лекции. И это единственное, что стало понятным на тот момент. Поскольку больше умных мыслей про этот файл у меня не было, было решено пойти в обход, как делают все нормальные герои. Путь мой лежал к дальнейшему изучению файловой структуры, авось найдется та самая таблица ответов. Я даже придумал как она могла бы выглядеть. Таблица найдена не была, зато внимательное изучение каталога lib привело меня по цепочке test.pm => etest.pm к довольно занятному файлу ext.pm который содержал процедуры:

sub check_answer_exam 
sub check_answer_exam_extern
sub check_answer

Которые в свою очередь вызывали одноименные процедуры из пакета “er”. Пакет был довольно быстро найден и анализ его содержимого показал, что он через XSLoader подгружает библиотеку er.dll, которая лежала недалеко (а рядом лежала точно такая же, но под *nix c расширением .so). Картина начала вырисовываться, и я пошел смотреть таблицу экспорта этой библиотеки.

We need to go deeper!

Как была взломана локальная версия сайта Intuit.Ru

Таблица экспорта убила во мне все чувство прекрасного. Там содержалась одна функция boot_er. А как же check_answer и прочие плюшки… Дело начинало пахнуть керосином и требовало контрмер: хорошего чая и IDA Pro!

Оказалось, библиотека не так проста как кажется и содержит в себе целую кучу всяких процедур, да и еще вдобавок имеет интересную таблицу импорта. Помимо вполне привычных импортов из KERNEL32 и msvcrt, я увидел целую кучу функций из библиотеки perl58.dll и все бы, наверное, было не так занятно, если бы эти функции легко находились в гугле. Но они не находились.

Как была взломана локальная версия сайта Intuit.Ru

Может, это все от недостатка перлового опыта и я не знаю как искать подобные функции, но исчерпывающего описания я нигде не нашел. Только обрывки информации на форумах. Идем дальше. Смелое нажатие Shift+F12 показало много потенциально интересных вещей.

Как была взломана локальная версия сайта Intuit.Ru

В частности, строка Usage: er::check_answer(a, local_user_id, t, answer) натолкнула меня на мысль о вызове этой библиотеки из собственного perl-скрипта. Единственная загвоздка была в формате входных данных. Поковырявшись в скриптах, с помощью которых осуществлялось тестирование, я увидел несколько вызовов print, которые меня заинтересовали. Выводились значения не в какой-то лог-файл, а прямо на консоль. Логично предположить, что это консоль web-сервера и дело оставалось за малым: найти эту консоль.

Осмотр каталога «local_web_server» показал, что никаким апачем и даже денвером там никогда не пахло. Сервер был написан на perl и предоставлялся в двух вариантах «server_unix.pl» и «server_win32.pl», а главный запускной файл «intuit.exe» который лежит каталогом выше, как раз и запускал все это хозяйство на что указывали следующие строки полученные с помощью IDA из этого файла

Как была взломана локальная версия сайта Intuit.Ru

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

Как была взломана локальная версия сайта Intuit.Ru

А вот и первые печеньки! Разберем значения, которые представляют практический интерес:

  • 820 – идентификатор теста, который соответствует первой лекции (исходя из хеш массива %lecture)
  • tasks => 4 – это количество верно решенных задач.
  • answer => [348966, 349080, 63232, 63234, 53240] – массив отмеченных мною вариантов ответов.

Числа в массиве answer это идентификаторы ответов которые были рассмотрены ранее в массиве %test.

Осталось получить параметры вызова функции er::check_answer(). В файл etest.pm был добавлен вызов функции print, в которой с помощью Data::Dumper в файл записывалось содержимое массива param, который и передавался в функцию. Привожу кусок кода:

if($type eq 'lecture') {
    open(F, ">>D:param.txt");
    print F Dumper(@param);
    close(F);

    ($mtime,$csa,$tasks,$points,$mark) = test::ext::check_answer(@param);
}

Далее на основании полученных данных я набросал скрипт следующего содержания:

#!/usr/bin/perl -w

require test::ext;
@var1 = [
	1388075531,
'8647ee8932669c9e0a00827bb82957d2',
[
	[
		5658,
		16542
	]
	# тут еще 4 таких же массива как выше, но с другими идентификаторами разделов и вопросов.
];
$var2 = '57d0c0e0304de48376b064b86cd36bc1';
@var3 = [ … ]; # Все варианты вопросов и ответов к этой лекции
$var4 = ['348973', '348975'];
my @param = (@var1, $var2, @var3, $var4);		
my ($mtime,$csa,$tasks,$points,$mark);
($mtime,$csa,$tasks,$points,$mark) = test::ext::check_answer(@param);
print "Tasks: " . $tasks;

Разберем что тут происходит. В @var1 содержаться те вопросы которые были заданы в текущем тесте всего там 5 массивов вида [int id раздела, int id вопроса]. Переменная $var2 содержит хеш-идентификатор пользователя, который проходит тест. Нужен скорее всего для сохранения результатов теста. Хеш @var3 содержит точную копию значения из массива %test, которое можно получить по лучить по ключу-идентификатору лекции. И наконец, массив $var4 содержит выбранные мною варианты ответов. В приведенном выше скрипте я оставил только ответы на первый вопрос, что бы было проще тестировать.

Запуск скрипта порадовал строкой «Tasks: 1», а это означало, что я на верном пути. Сразу возникла мысль по написанию простейшего брутфорса, который бы формировал различные варианты массива $var4 и проверял бы $task на равенство единице. Так бы и закончилась эта история, если бы не переполнявший меня интерес и желание узнать насколько же все таки глубока кроличья нора.

Wake up, Neo… The Matrix has you…

Изучив еще раз таблицу импорта в IDA, было решено начать с функции strncmp. Запустив под отладчиком свой тестовый скрипт (код которого приведен выше) и оттрассировав программу до загрузки библиотеки er.dll, я открыл список используемых ею функций и поставил точку останова на импорт strncmp. После перезапуска скрипта, отладчик радостно сообщил «Break point at msvcrt.strncmp», а в стеке лежали сравниваемые строки:

Как была взломана локальная версия сайта Intuit.Ru

Это был хеш из массива @var1 и я решил посмотреть, откуда вызывается эта функция. Нажав Ctrl+F9, а потом F8, я начал трассировать программу дальше. По ходу трассировки я опять оказался внутри strncmp, которой передавались две строки вида «57f260a2606af344753ffc00ad834581». Этот хеш мне был смутно знаком и, посмотрев код скрипта, я убедился, что это хеш, относящийся к одному из вопросов. А вот это было уже теплее! Нажав F9 и опять попав в функцию сравнения, я был немного озадачен теми параметрами, которые увидел в стеке:

Как была взломана локальная версия сайта Intuit.Ru

Строка «e8c178abd4f1114837d00771871b6379» была хешем относящимся к другому вопросу теста, а вторая мне была не знакома. Продолжив запуски, я составил список из 6-ти вызовов функции сравнения. Вот они:

Вызов Хеш #1 Хеш #2
1 8647ee8932669c9e0a00827bb82957d2 8647ee8932669c9e0a00827bb82957d2
2 57f260a2606af344753ffc00ad834581 57f260a2606af344753ffc00ad834581
3 e8c178abd4f1114837d00771871b6379 0f4632529bff4a4ebab04b5794c1518a
4 9ffcabf80ad0aea2ec7b8c7b89051c29 46916f5f972e01efa665f6cf2245f071
5 3f2fd6dc285372ee847ee9837718f0df a95478bfb5af215aa268d21b498b493c
6 7f29edf5e4fad4e5782ffd36512cc6b7 a2401e9ad6086aa57ea59b61ec0d55d2

Вызовы со 2-го по 6й содержали в качестве первой строки хеши, которые я на тот момент обозвал «идентификаторами вопроса». При этом в том вопросе, на который я ответил верно, хеши совпадали. Значит, тут была какая-то зависимость. Изменив в скрипте массив $var4, я окончательно в этом убедился, увидев вместо «57f260a2606af344753ffc00ad834581» совершенно другой хеш: «859c6288692d7037035a011ba54597aa». Теперь предстояло понять, откуда же берутся эти хеши.

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

Как была взломана локальная версия сайта Intuit.Ru

Как видно из кода, адрес, по которому лежит хеш, хранится в EAX, что подтверждал дамп памяти.

Как была взломана локальная версия сайта Intuit.Ru

Дело за малым — выяснить кто его туда положил.

Дальнейшее изучение алгоритма показало, что для каждого вопроса формируются строки вида
asdc*a*<id_вопроса>*a*<id_варианта_ответа_1>…*a*<id_варианта_ответа_N>, которые хешируются алгоритмом md5 и сверяются с тем хешем, который ранее я ошибочно назвал «идетификатором вопроса». На самом деле это оказался хеш-идентификатор правильного ответа. Шах!

И мат!

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

Дабы не оскорблять чувства верующих в perl (я думаю им хватило моего чудо-скрипта), реализовывать брутфорс я буду на C#, который, как известно, все стерпит. Разбор файлов с курсами было решено проводить с помощью регулярных выражений. Критикам этого подхода я хочу сразу сказать: Можете сделать лучше? Делайте. А мне удобнее так.

Интерес в файле курса представляют следующие блоки данных:

  • хеш-массив номеров лекций и их идентификаторов
  • списки вопросов к каждой лекции
  • варианты ответов к вопросам

Первый хеш массив можно получить достаточно простым регулярным выражением:

(?<lnum>d+)s=>s(?<lid>d+)

Далее разберем получение вопросов из курса. Для этого я применил следующее регулярное выражение:

['(?<tid>d+)','d+'(?:,'d'){2},"(?<ttext>.*?)","(?<ahash>[a-f0-9]*)","[a-f0-9]*",(?<avars>[{2}.*?(?:,]){2})

Оно может показаться сложным на первый взгляд, но на самом деле в нем тоже нет ничего сверхъестественного. Именованные группы содержат:

  • tid — идентификатор вопроса
  • ttext — текст вопроса
  • ahash — хеш правильного ответа
  • avars — варианты ответов в необработанном виде

Парсинг необработанных вариантов ответов осуществляется следующим выражением:

'(?<aid>d+)'.*?"(?<atext>.*?)"

Именованные группы содержат:

  • aid — идентификатор варианта ответа
  • atext — текст вопроса

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

int SetPower = (int)Math.Pow(2, answers.Count);

for (int i = 1; i < SetPower; i++)
{
    string aStr = "";
    answers.ForEach(x => x.Valid = false);

    for (int j = 0; j < answers.Count; j++)
    {
        if ((i & (1 << j)) != 0)
        {
            aStr += "*a*" + answers[j].AID;
            answers[j].Valid = true;
        }
    }
    string answerString = "asdc*a*" + task.TID + aStr;

    if (GetMD5Hash(answerString) == task.TrueAnswerHash)
    {
        return answers.Where(w => w.Valid == true).ToList();
    }
}

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

Connection terminated…

Так и закончилась эта история о борьбе за знания. Знания это та вещь которая добывается только трудом и любые знания полученные бесплатно (в широком смысле этого слова) не стоят ровным счетом ничего. Благодарю НОУ «ИНТУИТ» за их ресурс в целом и за те приятные моменты, которые они мне доставили своей магической библиотекой er.dll в частности.

Дополнительные материалы к статье

Perl-cкрипт использованный мной для отладки
Консольный брутфорсер ответов

Автор: Sp0tted_0wl

Источник


  1. Макс:

    Привет, прочитал статью очень сильно вдохновила, только я в программировании полный ноль. Сейчас Всем Универом проходим курс http://www.intuit.ru/studies/courses/2301/601/info очень нужна ваша помощь, можешь помочь, чтоб правильные ответы узнать – а то мы уже замучились, никак пройти не можем, еще в некоторых тестах реально ошибки, причем ни одна, в итоге не принимает ответы. Как пользоваться этим брутфорсером? Буду очень благодарен вам, Заранее спасибо

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js