Введение в реверсинг с нуля, используя IDA PRO. Часть 30.

Дата публикации 28 янв 2018 | Редактировалось 11 фев 2018
Мы попытаемся решить упражнение, предложенное в 29 части. Это DIFF двух последовательных версий VLC плеера. У Вас есть CVE в качестве помощи с информацией. И эта информация, это то, что у нас есть.

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

1.png

Если я посмотрю в папку, где установлен VLC, я увижу, что плеер создан для просмотра разных видео-файлов. Нужная нам папка называется PLUGINS.

2.png

Давайте посмотрим, есть ли здесь такие имена файлов, которые указывают, что плеер работает с форматом TIVO или TY?

3.png

Хорошо. Здесь есть файл LIBTY_PLUGIN.DLL, который кажется довольно подозрительным. Давайте сделаем DIFF для него.

4.png

Мы видим, что существует четыре измененные функции. То, что обычно мы делаем - это сначала осмотрим их, отмечая самые подозрительные места на потом, если мы начнем реверсить их более детально.

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

5.png

Кажется это изменение не предотвращает переполнение. Изменение есть только в одном адресе и затрагивает регистр ESI. Это изменение ни на что не влияет.

6.png

Мы видим, что в программе меняется только порядок инструкций, который ни на что не влияет. В уязвимой версии, программа поместила этот адрес в регистр ESI, который она сохраняет в переменную VAR_1B4. А в пропатченной версии, программа помещает адрес в регистр ECX и сохраняет его в переменную VAR_1B4. Ничего нужного здесь нет.

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

7.png

Мы видим некоторые изменения в способе вычисления переменной VAR_70, но я не вижу, чтобы программа читала или использовала эту переменную в любой другом месте внутри функции. Это локальная переменная, она не передаётся как аргумент и не сравнивается ни с чем. Мы учтём это изменений. На данный момент это изменение для нас не важно.

8.png

Эта функция не имеет ничего интересного. Она не рассматривается как кандидат. Давайте продолжим искать нужную функцию.

Следующая функция - очень неопрятная. Давайте посмотрим, сможем ли мы улучшить её внешний вид.

Делаем правый щелчок и выбираем DELETE MATCHES в несовпадающих блоках. Затем помечаем те, которые должны совпадать и выбираем пункт ADD BASIC BLOCK MATCH.

Есть блоки, которые выглядят совсем по другому, когда они плохо согласованы. Но когда они хорошо согласованы...

9.png

Если я посмотрю на строки сейчас, то они выглядят одинаково, только выглядят немного некрасиво.

Иногда есть функции, которые очень плохо согласованы. В таких случаях, рекомендуется использовать TURBODIFF, чтобы различия сводились к минимуму.

10.png

Давайте посмотрим на ID блоков.

11.png

Мы видим, что переменные распространяются, но сравнения аналогичные (VAR_68 и VAR_6C в уязвимой версии, и VAR_60 и VAR_64 в пропатченной версии).

Также нет изменения знака в сравнении (Например JB на JL или что-то похожее, оно остаётся равнозначным). Если инвертируются переходы в сравнения с JA на JB, то это то, же самое. Это ничего не меняет.

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

12.png

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

13.png

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

14.png

Здесь мы видим инструкцию JB. В уязвимой версии инструкция находится немного ниже. Здесь есть изменения, которые мы изучим позже, если мы не найдем ничего нужного.

15.png

Напомню, что нам не нужно углубляться в реверсинг. Мы просто ищем что-то, что должно привлечь наше внимание.

16.png

Здесь, Вы видите очень возможные инструкции, которые влияют на переполнение. Это поле структуры, которое сравнивается. В уязвимой версии программа принимает решение с помощью инструкции JLE, а в пропатченной версии, с помощью инструкции JBE - это изменение знака. Это очень возможная инструкция.

Это очень сложная функция. Мы будем анализировать её позже. Мы уже увидели что-то похожее и нужное нам. Отметим эту функцию и посмотрим немного на последнюю.

В этой функции, то же поле структуры влияет и его легче увидеть так.

17.png

Это цикл и значение, которое решает выходить из цикла или не является счетчиком, который находится в переменной VAR_48. Он увеличивается и сравнивается с максимальным значением.

18.png

Перед входом в ЦИКЛ, счетчик обнуляется. Я думаю, что легче начать реверсинг в этой функции, чем в предыдущих, хотя обе могут быть виновными в переполнении. Мы всегда начинаем с самых простых функций. В данном случае, она последняя.

19.png

Давайте начнём наш реверсинг с терпения, потому что функция выглядит сложной. Мы переименуем переменную VAR_48 в CONTADOR.

20.png

Очевидно EBP не является базой для функции. В этом случае EBP - это адрес структуры. Если мы просмотрим почти всю функцию, то поймём, что EBP остаётся таким же. Обращение по адресам EBP + XXXX, является обращением к полям структуры.

EBP инициализируется по этому адресу.

21.png

Здесь регистр EBP получает адрес структуры.

22.png

Здесь EBP меняет своё значение. Другими словами, между двумя адресами EBP остаётся постоянным и он содержит адрес структуры.

Мы видим, что это очень большая структура. Существует 0xBEXX полей, которые принадлежат большой структуре. Давайте создадим эту структуру. Я думаю, что большинство полей будут 0xBEXX, поэтому мы можем создать структуру длиной 0xBF00 байт, которая охватывает, то что мы видим. Её всегда можно увеличить или уменьшить.

Я иду на вкладку со структурами и нажимаю INSERT для того, чтобы добавить одну структуру.

23.png

24.png

На слове ENDS, я нажимаю D для того, чтобы добавить однобайтовое поле.

25.png

Я делаю правый щелчок и выбираю пункт EXPAND STRUCT TYPE.

Я добавляю 0xBF00 ещё для одного байта и ничего плохого не происходит.

26.png

27.png

Получается вот так.

28.png

Очевидно, если бы было возможно сделать, чтобы значение поля 0x0BEC8 стало бы отрицательным, например 0xFFFFFFFF, оно было бы меньше, чем положительные значения (1, 2, и т.д.) которое будет принимать счетчик так как знак рассматривается, и цикл будет повторяться намного больше раз чем мы задумали.

Я могу переименовать это поле в MAXIMO, так как предполагается, что это максимальное значение, которое цикл должен повторить перед тем как закончить цикл.

Нажимаем здесь T.

29.png

Теперь я должен пойти в структуру по адресу 0xBEC8 и создать поле с типом DWORD.

30.png

Я нажимаю здесь D несколько раз пока не появится DD.

31.png

Я переименовываю это поле в MAXIMO.

32.png

Я могу нажать Y и изменить тип поля, так как я знаю, что оно ЗНАКОВОЕ ЦЕЛОЕ, из-за инструкции JLE, которая сравнивает значения.

33.png

Так выглядит лучше. Я устанавливаю SIGNED INT, хотя это не сильно повлияет, за исключение того, что я использую декомпилятор HEX RAYS, который я пока не буду запускать, но мне нравится до конца разбираться в этих вещах.

34.png

Продолжаем.

35.png

Мы видим, что здесь есть вызов функции MALLOC. Это функция также используется для динамического резервирования памяти, но не в стеке, а в куче.

36.png

Вы передаёте один аргумент, который является размером выделяемой памяти.

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

37.png

Здесь мы видим, что аргумент SIZE находится в регистре EAX и рассчитывается здесь

38.png

Мы видим четыре переменные типа байт над которыми программа выполняет операции, которые создают переменную SIZE. Перед передачей аргумента в функцию MALLOC выполняется инструкция SHL EAX, 4.

39.png

Сдвиг байта SHL EAX, 4 аналогичен умножению EAX на 16, но перед умножением программа сохраняет рассчитанное значение в переменную MAXIMO. Если я нажму T я увижу, имя переменной.

40.png

Мы видим, что отрицательные значения MAXIMO фильтруются здесь.

41.png

Поэтому полученный объект не является отрицательным значением поля MAXIMO, потому что оно фильтруется. В пропатченной функции, мы видим, что программа не исполняет инструкцию SHL т.е. не умножает на 16, непосредственно значение аккаунта. Программа использует это значение как размер для функции CALLOC.

42.png

Мы видим, что вместо того чтобы умножать на 16 программа передаёт аргумент функции CALLOC, которая имеет ещё один аргумент. Размер каждого элемента который равен 0x10, причём умножение выполняется посредством API, и рассчитывается для каждого элемента.

43.png

Функции MALLOC или CALLOC используются для резервирования памяти. Адреса, которые они возвращают, являются переменными. Они не всегда дают нам область с те же адресом памяти. Позже мы будем изучать кучу или способ резервирования памяти, но на данный момент, функции будут давать нам область памяти для работы с размером, который мы попросим.

В уязвимой версии перед вычислением размера программа умножает поле MAXIMO на 16 и затем передает полученное значение в функцию MALLOC. В пропатченной версии это не так. Программа передаёт поле MAXIMO функции CALLOC напрямую и умножение является внутренним расчётом в функции на 16, и рассчитывается для каждого элемента.

Проблема заключается в том, что если переменная MAXIMO равна например 0x20 байт и умножая её на 16 это будет равно следующему значению

44.png

Функция будет резервировать 512 байтов и затем копировать 0x20 потому что программа сравнивает значения внутри цикла и выходит, когда счетчик больше, чем MAXIMO.

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

Если переменная MAXIMO равна 0x10000001, то при умножении этого значения на 16, мы получим значение 0x10 с которым будет зарезервировано только 0x10 байтов и когда значение будет скопировано в цикле программа будет записывать 1 за раз, пока счетчик не достигнет значения 0x10000001 который переполнит буфер копируя больше чем зарезервировано памяти или размера буфера, который определяет переполнение. Хотя, в этом случае, это не переполнение в стеке, а переполнение кучи.

В то время как в пропатченной версии функция CALLOC не разрешает и предотвращает внутреннее умножение и результат будет меньше чем поле MAXIMO.
Этим и устраняется уязвимость.

45.png

46.png

Здесь, я докажу, что передавая одни и те же значения в функцию MALLOC, функция возратит нуль, т.е. не выделит ничего и не возвратит любой зарезервированный адрес памяти. Делая же всё это с использованием функции MALLOC, будет выделено 0x10 байт, которая если отработает и запишет внутри цикла MAXIMO значение 0x10000001 вызывая ПЕРЕПОЛНЕНИЕ БУФЕРА,

47.png

Здесь Вы видите выход из функции CALLOC. Регистр EAX равен нулю, в том время как уязвимая версия использует функцию MALLOC и выделяет нужное значение.

Давайте продолжим анализировать, чтобы увидеть, что ещё здесь есть.

48.png

Здесь программа сохраняет адрес зарезервированного буфера в кучу. Я могу переименовать его. Для этого я пойду во вкладку структуры и буду нажимать D до тех пор пока не появится DWORD.

49.png

Я переименовываю это поле.

50.png

Хорошо. Я делаю это с помощью клавиши Y. Поле является указателем, которое хранит адрес буфера. Я зарезервировал память в куче. Поскольку я не знаю, что находится там есть, я оставляю его как массив символов , т.е. буфер байтов, но я могу изменить его если я увижу, что это что-то другое.

51.png

На языке IDA - это СМЕЩЕНИЕ, т.е. адрес, который указывает на что-то.

52.png

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

53.png

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

54.png

Также, здесь, внутри ЦИКЛА программа регистр в EDI в EBX который изменился. Нам нужно будет увидеть где это происходит.

55.png

Мы видим, что адрес назначения приходит отсюда.

56.png

Рассчитанное значение ECX + 0C сохраняется в регистр EDI, поэтому мы будем искать откуда приходит регистр ECX.

ECX заполняется здесь

57.png

Здесь перемещается значение из регистра EDI в ECX и добавляется регистр ESI.

Если я нажму T.

58.png

Я вижу, что регистр ESI - это адрес буфера в КУЧЕ и программа прибавляет к регистру ECX значение из регистра EDI, которое является счетчиком, поэтому в этой функции есть переполнение кучи. Поскольку мы видели, что поле MAXIMO может быть больше чем значение SIZE, которое было выделено и переполняется.

Здесь есть другое изменение, которое немного не заметно.

59.png

Я вижу, что существуют вызовы функции STREAM_READ. Я могу прочитать часть файла во временный буфер.

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

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

60.png

Так что буфер продолжается до этого адреса.

61.png

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

62.png

Сейчас я делаю правый щелчок, выбираю пункт ARRAY и соглашаюсь.

63.png

Я вижу, что РАЗМЕР равен 12 байт.

64.png

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

Если мы продолжим искать, мы увидим, что буфер продолжиться до этого адреса. Все другие промежуточные переменные имеют доступ только для чтения, поэтому они инициализируются в том же буфере.

65.png

Сейчас, даже если переменная VAR_1C – то это другой буфер, который используется для заполнения в другом вызове STREAM_CONTROL.

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

Здесь мы видим патч в исправленной версии, который проверяет значение. Если оно больше чем 8, программа не идет в вызов STREAM_READ.

66.png

67.png

Здесь, конечно, внутри вызова STREAM_READ. Здесь будет некоторая функция MEMCPY, которая скопирует DWORDS. Поэтому программа сравнивает значения и если оно больше чем 8, потому что если вы скопируете больше 8 слов, то это будет больше значения 32 = (8 * 4) что является длиной буфера, поэтому, помещая здесь значение больше чем 8, мы будем вызывать переполнение стека.

Давайте искать файл с расширением .TY чтобы попробовать переполнить буфер.

https://samples.libav.org/TiVo/test-dtivo-junkskip.ty+

Поработав над этим сэмплом, я сконвертировал его в POC, который вызывает переполнение стека. Изменяя значение, которое фильтрует программа и подправив ещё несколько мест, которые находятся вокруг, я сделал так, что достигается вызов функции STREAM_READ.

68.png

Здесь я тестирую полученный POC в OLLYDBG в системе XP, которая у меня есть для тестов и он работает. Программа переходит на адрес памяти, который содержит адрес 0x44434241, который я поместил в файл.

Следующее упражнение состоит в том, чтобы взять исходный файл и изменить его, установив POC, как этот, который я сделал для переполнения стека. Это просто, потому что все уже проанализировано. Отлаживая понемногу Вы добьётесь всего.

=====================================================
Автор текста: Рикардо Нарваха - Ricardo Narvaja (@ricnar456)
Перевод на английский: IvinsonCLS (@IvinsonCLS)
Перевод на русский с испанского+английского: Яша_Добрый_Хакер(Ростовский фанат Нарвахи).
Перевод специально для форума системного и низкоуровневого программирования — WASM.IN
11.02.2018
Версия 1.0

8 8.076
yashechka

yashechka
Ростовский фанат Нарвахи

Регистрация:
2 янв 2012
Публикаций:
90

Комментарии


      1. yashechka 24 фев 2018
        То же про это ничего не знаю:boredom:
        А вот про то что Вы с Краснодара - это откровение
        Но Вы же давно уже там не живете, а в Финляндии или Польше.
      2. RET 24 фев 2018
      3. jkmjkmjkm 20 фев 2018
        yashechka нравится это.
      4. yashechka 14 фев 2018
        [​IMG]
      5. yashechka 11 фев 2018
        :friends::friends::friends::preved::preved::preved:
        LastNoob нравится это.
      6. LastNoob 11 фев 2018
        Хехей, теперь я здесь первый !:good3:
        yashechka нравится это.
      7. yashechka 11 фев 2018
        Готово. Тяжеловатая глава, но вроде получилась.