IDA Pro и техники реверс-инжиниринга

0×00 start

; {EN} entry point, do nothing, just run _main {EN}

Статья для начинающих «воинов тьмы», тех, кто хочет погрузиться в темную сторону силы: реверс-инжиниринг. На нашем «операционном столе» будет небольшой кустарный сервер, который работает по протоколу TCP/IP. Для анализа протокол обмена данными нам поможет стандарт де-факто в области реверса — IDA Pro.

Статей по реверс-инжинирингу и по IDA Pro уже написано немало (хотя и не столько, как по PHP), но поскольку процесс реверса — исследовательский, то мысли, как с «другого боку» подойти к задаче обратной разработки, полезны новичкам. По крайней мере, как автор, я руководствовался тем, чтобы изложить основные практики и техники, о которых говорю в первые дни всем стажерам и на первых парах курса по реверс-инжинирингу в университете.


Чего не будет в статье?

Поиска уязвимостей и разработки эксплоитов для Google Chrome или Apple iPhone… Поэтому если вы исследователь со стажем и с большим количеством CVE на счету, маловероятно, что вы найдете для себя что-то новое.

6tkm_2r_gulckeqjxzls8ah3g-w.jpeg
«Once you start down the dark path, forever will it dominate your destiny».

Подопытный: crackme — не запускайте бинари на основной системе, лучше это делать на виртуалке!

Системные требования: виртуальная машина с установленной Windows 7/0xA


0×01 mov eax, ds:__stack_chk_guard

; {EN} Disclaimer {EN}

Практически вся статья написана относительно ассемблера и, соответственно, процессора x86, поэтому работа с памятью, условными переходами (флагами), инструкциями и т.п. будет описываться в контексте именно этого процессорного ядра. На момент чтения статьи считаем, что других процессорных архитектур не существует.

prlwbor_myuddgm_4d249est5q0.jpeg


0×10 Что нам нужно, чтобы понять о чём пойдет речь?

; {EN} Setups prerequisites for the consequent execution {EN}

Чтобы статья была интересна и понятна, предполагается, что читатель знает:


  • язык программирования C или C++;
  • основы языка ассемблера x86;
  • о существовании форматов исполняемых файлов (PE, ELF);
  • о кадре стека функции, поверхностно — достаточно;
  • как работать с сокетами;
  • математический анализ;
  • теорию ядерной физики.

Итак первое, что нам понадобится — сама IDA Pro от фирмы Hex-Rays (на русском произносится, как «ида про» или просто «ида», для тех, кто любит соблюдать правила английского звучания «ай да про», но звучит в русскоговорящей среде немного необычно). Вы можете скачать бесплатную версию для того, чтобы попробовать освоить реверс-инжиниринг, однако у неё есть ряд ограничений, среди которых: нельзя сохранять результат (в терминах IDA — базу данных) и подходит только для x86.

Второе, что нам понадобится — подопытный. Подойдет любой исполняемый файл (он же бинарник, он же файл с расширением *.exe для Windows), но рекомендуется взять тот, что прикреплен к статье. Если вы возьмёте свой бинарник, то лучше, если это будет скомпилированный для x86 файл (для первого раза не стоит брать собранные под x64 — в них ассемблер сложнее).

Третье — конечно же, хотя бы начальные знания языка ассемблера x86 (assembler language).

grk6jk1e6xopgtwpvfjly_sugim.jpeg

Чтобы осознанно копаться в программе и реверсить её, очень желательно знание языка ассемблера x86: инструкций (mov, lea, push/pop, call, jmp, арифметических и условных переходов), регистров и принципов работы процессора. Если их совсем нет, то настоятельно рекомендуется в начале изучить, например:

Может показаться, что инструкций огромное количество. На самом деле достаточно понять порядка 10 штук, остальные мало чем отличаются. Смотреть референс по инструкциям можно здесь или же в самой документации на процессорное ядро (предпочтительней).

Примечание автора. Хочу отметить, что когда сам только начинал заниматься этим, ассемблер выглядел, как сейчас продолжает выглядеть regexp (регулярные выражения) — вроде все буквы знаешь, а в слова не складываются. Однако постепенно начал понимать, что делают инструкции и что происходит в процессоре.

Четвертое — 30 минут времени (хотя, может быть, 30 часов) и желание научиться.


0×20 Минутка философии или что такое IDA Pro и почему?

; {EN} Whatta hell? {EN}

ln2xlsujoswxw2kkvd8chq_mjg0.jpeg


Реверс-инжиниринг (или обратное проектирование) — «это процесс извлечения знаний из того, что когда-либо было сделано человеком… Фактически, реверс-инженер — исследователь (научный работник), с той лишь разницей, что разбирается в том, что получено не естественным образом, а кем-то создано» — Reversing: Secrets of Reverse Engineering, Eldad Eilam.

Что же в первую очередь нужно исследователю? Блокнот и ручка, чтобы вести и систематизировать полученные знания. Так, а при чем тут IDA? IDA — аббревиатура, которая расшифровывается как «интерактивный дизассемблер». Ключевым и революционным в свое время было именно «интерактивный». Это означает, что в результате работы вы получаете не просто длиннющий ассемблерный листинг, а что-то, где вы можете оставить свои заметки, то есть как будто это действительного листинг, но в котором можно сделать «заметочки на полях», и они меняются по ходу всего листинга. Можно еще сравнить с функцией рефакторинга в современных программерских IDE — переименовали функцию в одном месте, она переименовалась везде, переменную — аналогично и т.д. Ваша задача как исследователя — из огромного массива сложно анализируемой информации оставить только важную и придать ей форму хорошо понятную для человека. Именно интерактивность IDA Pro и позволяет делать это очень эффективно, и на сегодняшний день никто её в этом не превосходит.

Стоит все же заметить, что с учетом постоянно возрастающей закрытой кодовой базы и напечатанного мартышками кода в интернете, программное обеспечение реверсить вручную и интерактивно становится все сложнее и сложнее. Из-за этого сейчас активно развиваются средства анализа кода, нацеленные на автоматизированную обработку — radare2 (что бы ни говорили адепты r2, интерактивный интерфейс Cutter не настолько интерактивен, как в IDA Pro). Более того, IDA Pro также очень хорошо автоматизирована с помощью встроенного в неё интерпретатора Python и API к нему.

И все-таки, как бы крут не был автоматизированный анализ, он не автоматический, то есть заменить на все 100% исследователя не может и рано или поздно исследователю нужно вступать в бой. Поэтому, когда все приготовления наконец-то закончены, приступаем к изучению IDA Pro! Начнём постепенно разбирать наш подопытный образец.

dhu4649l7k5lo8m3xgklfy9gcao.jpeg


0×30 Смотрим на IDA Pro и познаем основы её интерфейса

; {EN}
; This function is too complex.
; Perhaps it interacts with user.
; But currently I am not sure about it.
; A lot of calls to Qt-framework.
; {EN}


0×31 Загрузка бинарника в IDA Pro

Открываем IDA Pro. Перед нами после всех стартовых окошек (которые можно сразу же закрыть), появляется начальное окно программы. Все, что нужно сделать, — перетащить в него исследуемый бинарник. После этого в появившемся окне нажать кнопку «ОК».

weahierpwadwc3adccph1oxnxzs.png
Главное окно IDA Pro при загрузке бинарника

В нашем случае (если вы грузили тот самый exe-файл в IDA, который приложен к статье) никаких настроек производить не нужно, IDA Pro сама распознает формат этого файла (PE, portable execute), а вот если туда кинуть прошивку или дамп из микросхемы памяти от, например, роутера, всё будет намного сложнее.

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

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

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


Скрамблер в студию

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

fef3upsfi7vt9kqnjhdb3bpjvcy.png
Так выглядит граф потока выполнения скрамблера


0×32 Что нам IDA Pro показала?

После загрузки бинарника IDA Pro проводит его предварительный автоматический анализ: определяет функции, глобальные переменные, строки — всё, что можно автоматически вытащить из анализируемого файла. Анализ выполняется процессорным модулем IDA Pro (не путать с самим процессором). Фактически это плагин для IDA Pro (при желании можно написать свой на Python или C++). В нашем случае IDA использует так называемый Meta PC — вариант x86/x64, учитывающий большинство твиков, которые были добавлены в архитектуру от Intel и AMD. Автоматический анализ выполняется опять же на основе того, что есть полное описание формата файла, который мы загрузили в IDA, в нашем случае тот самый Portable Executable. Данный формат чётко указывает какие секции есть в файле, в какой из секций лежит код, где его точка входа, а в какой секции данные (константы или глобальные переменные). Как обычно, с прошивками такой финт не проходит и необходимо вручную размечать входной файл (или же писать loader-плагин для IDA, который сможет «рассовать» куски бинарника прошивки как нужно, после чего процессорный модуль IDA сможет приступить к анализу).

Автоматический анализ под капотом крайне сложная штука, но для каждой инструкции он состоит в следующем:


  • декодирование инструкции из бинарного представления во внутреннее (analyze);
  • связывание инструкции с учётом специфики её выполнения (emulate — не путать с эмуляцией, как, например, в QEMU);
  • преобразование инструкции и аргументов в мнемонику.

Поверх этого с учётом информации и «связей» между инструкциями IDA выбирает путь для анализа следующего адреса, выполняя условный обход «дерева» инструкций от начальной точки анализа. За счёт того, что в стандартных форматах десктопных программ точка входа всегда известна (иначе загрузчик ОС не смог бы создать процесс из этого файла), IDA также может использовать эту информацию.

Следует отметить, что это очень общее описание того, что происходит на самом деле. Приведено оно с той целью, чтобы дать базовое понимание, что такое автоматический анализ, выполняемый в IDA.

Пару слов о том, как IDA Pro хранит результат реверса. После или во время автоматического анализа «проект» можно сохранить (если, конечно, у вас полная версия). IDA хранит результат анализа в виде специальной базы данных со своей структурой (нет, это не модная MongoDB) на жёстком диске. Она представляет собой один файл с расширением .idb (или .i64).

После того как автоматический анализ завершён, вы увидите окно, примерно такое, как на рисунке ниже. Основные элементы, на которые стоит обратить внимание начинающему исследователю, подписаны на самом скриншоте. Завершение автоматического анализа можно определить по надписе AU: Idle в левом нижнем углу IDA Pro.


UX/UI IDA Pro

GUI IDA Pro написан на Qt, поэтому, если вы когда-нибудь работали с приложениями, написанными на Qt, тут действуют те же самые правила:


  • тотальный drag&drop окошек;
  • все открывается в разных вкладках, которые можно переставлять как вам угодно. Иногда их можно случайно закрывать и потом долго искать меню, где открывается эта вкладка.

clal0o6ags5zcvanu7grv5os6v0.png
Граф потока выполнения функции main

Давайте рассмотрим основные окна, которые первоначально отображает нам IDA Pro.


  • Граф потока выполнения — основной вид дизассемблера (можно сказать, представление в виде блок-схемы алгоритма функции). В нем отображаются инструкции процессора, полученные после дизассемблирования из бинарного вида. В этом графе показывается только одна функция. Каждая функция может быть разделена на блоки по инструкциям условного или безусловного перехода (пошли направо — один блок, налево — другой блок). Кроме этого вида есть и другой, где результат дизассемблирования отображается в виде сплошного листинга.


Proximity View

Есть и еще один вид, который называется Proximity View. Он отображает взаимосвязь между функциями и глобальными переменными.

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


  • Список функций — окно, в котором выводятся функции, которые нашлись в бинарнике. Это те самые функции, которые были написаны программистом при создании программы (не учитывая оптимизацию). Если разработчик при компиляции не убрал отладочную информацию (в gcc это опция -s) или вообще собрал программу с опцией -g, тогда мы увидим все имена функций точно в таком же виде, как и программист. Иначе IDA Pro отобразит их в виде sub_<виртуальный_адрес _функции>;
  • Список строк (Strings) — не показан, но можно построить, нажав на SHIFT+F12. Одна из самых важных менюшек IDA Pro — в ней показаны все строки, которые есть в программе. Используя строки можно в некоторых случаях, найти всю интересующую нас функциональность.


0×33 Трогаем «лапой» IDA Pro

Наконец-то открыли бинарник и увидели, как это выглядит изнутри. Что же с ним делать и с чего начать реверс? С чего начать трогание нашего основного инструмента в реверсе, IDA Pro? Первое — виртуозное владение пианино горячими клавишами. Без знания горячих клавиш жизнь реверс-инженера скучна, так как сочетания клавиш сильно помогают её разнообразить и очень поднять эффективность и скорость реверса.

-gfg48wnj1tqk02smxzu0kbbyjk.png

Когда только учишься работать в IDA Pro лучше постоянно держать перед глазами табличку (чит-шит, cheatsheet) с горячими клавишами. Ниже приведен пример моего чит-шита. Есть и официальный чит-шит от самой фирмы разработчика IDA Pro — Hexrays. Официальный чит-шит можно найти здесь, в нем больше сочетаний кнопок, но, на мой взгляд, на первое время будет достаточно того, что приведено на чит-шите ниже.

3gwmlbsn8obbaqndh6xxt-z3cgm.png


Где CTRL+S и CTRL+Z?

Внимательный читатель может заметить, что в этой таблице нет двух важных сочетаний клавиш. Первое — сохранение базы данных результатов реверса (CTRL+W, но в бесплатной версии сохранения нет), второе — undo. Так вот, забудьте про undo — его нет. Настоящие реверс-инженеры слишком суровы, чтобы использовать всем привычный CTRL+Z. Если серьезно, то функцию undo завезли только в версии 7.3 (бесплатная — 7.0), а всё потому что выполнить undo — не очень тривиальная задача. Дело в том, что какое-то изменение в базе данных IDA, внесенное пользователем, может привести к последующим множественным лавинным изменениям. Например, создание функции (make code) ведет к рекурсивному созданию всех вызываемых функций.

После того, как ознакомитесь с табличкой горячих кнопок, рекомендую попробовать некоторое время понажимать их в самой IDA Pro. При этом не стоит бояться, что вы что-то сломаете или перейдете «не туда», потому что именно так и будет. Как и любую сложную систему, освоить IDA Pro до виртуозного владения за один вечер невозможно.

Пройдемся и заодно опробуем различные кнопки в IDA Pro.


0×33a Навигация по графу и листингу

Навигация — одна из самых простых и понятных задач, однако, чтобы не теряться в IDA Pro, следует потренироваться в следующем:


  • перемещения по графу функции с помощью мыши;
  • перейти на различные функции с помощью двойного нажатия мыши, вернуться обратно с помощью ESC и снова вперед с помощью CTRL+ENTER (нет в чит-шите — для продвинутых);
  • переключения между графом и листингом — SPACE, если вдруг у вас включился другой вид (листинга);
  • переход по перекрестным ссылкам: поставить указатель мыши на любое имя (функции или переменной) и нажать X. После этого вы увидите окно с другими инструкциями, которые ссылаются на это имя (или же указатель в памяти программы);
  • прямой переход на имя или адрес (g): в открывшемся окне написать любое из существующих имен или же адрес (можно без 0x) из текущей база данных IDA Pro — вы перейдете на ту часть графа (листинга), где определено это имя.


0×33b Именование и заметки на полях

По изменению имен (рефакторингу) следует попробовать и отработать следующие действия:


  • Переименование имен (для этого необходимо нажать мышью на имя и после этого N): функций, переменных (локальных и глобальных), регистров и меток. Постарайтесь разобраться, что можно переименовать, а что нет.
  • Простановка комментариев к инструкциям. В целом все просто: выбрали строку, к которой хотим поставить комментарий, и нажали ;.


0×33c Представление данных (data representation)

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


  • байт (byte);
  • слово (word);
  • двойное слово (dword);
  • указатель (offset);
  • неопределенное (undefined);
  • дизассемблированный код (code).

Иначе говоря, каждый адрес в бинарнике можно попытаться дизассемблировать, если получится — будет код, иначе просто данные (байт, слово и т.д.). По умолчанию, если не проводить никакого анализа (в том числе и автоматического), весь бинарник представляется IDA Pro как undefined. Можно сказать, что в процессе реверса или автоматического анализа происходит разметка того, как представить каждый из байтов загруженного бинарника. То, что мы с вами видим уже размеченным (причем практически все байт), является результатом автоматического анализа, который выполнила IDA Pro.


0×33d Отличие кода от функции

Отдельно можно отметить и представление кода:


  • Код без функции (в таком случае IDA Pro не сможет построить граф);
  • Функция (чтобы из кода создать функцию, необходимо нажать P).
    Для большей части кода во время автоматического анализа IDA Pro сама распознает, где необходимо создать функции. Каким образом? За счет того, что если во время дизассемблирования встречается инструкция вызова (call), то анализатор однозначно может утверждать, что адрес, находящийся у этой инструкции в аргументе, — адрес начала функции.


Раз здесь спойлер, значит не все так просто

В реальности за счет использования различных техник защиты от реверса или просто из-за того, что мы грузим в IDA Pro прошивку и не знаем, где в ней начало кода, IDA Pro может начать дизассемблирование не с того адреса. Это приведет к тому, что будет получена инструкция call, хотя ее там и нет, а адрес «псевдоинструкции» call также не является началом никакой функции. Кроме того, какие-то данные, необходимые для работы программы, могут быть декодированы, как инструкции вызова — результат аналогичный.


0×33e Представление аргументов инструкций

Кроме представления каждого байта в виде различных вариантов указанных выше, можно по разному представить и аргументы большинства инструкций. Например, в инструкции записи числа в регистр mov eax, 0xFFFFFFFF второй аргумент может быть представлен, как в текущей записи, так и mov eax, -1. Для того, чтобы сменить вариант представления аргумента инструкции, необходимо нажать на него правой кнопкой мыши, и IDA Pro покажет возможные варианты представления.


0×33f Вспомогательные окна IDA Pro

В первую очередь стоит обратить внимание на Strings, Names, Functions, Hex Dump. Все эти окна можно открыть перейдя из строки меню View->Open Subviews.


0×40 Всего так много и как с этим работать?

; {EN}
; I think I found actual protocol parsing in function sub_401D3C
;
; But this function just chases bytes from corner to corner
; before actual parsing... we need to go deeper
; {EN}


0×41 Что же мы будем делать?

Теперь, когда мы знаем на какие кнопки надо нажимать и приблизительно представляем как «под капотом» работает IDA Pro, давайте попробуем вернуться к нашей задаче и найти ту часть бинарника, которая отвечает за разбор протокола обмена данными подопытного сервера.

Если внимательно посмотреть на вкладку с перечнем функций, можно увидеть, что IDA нашла в нём всего лишь 76 функций, то есть это очень маленькая программа. Реальные программы и, тем более прошивки, могут состоять из сотни тысяч функций. При этом никогда не ставится задача «втупую» восстановить исходный код программы на 100% (по крайней мере в моей практике никогда такого не было). Среди прочего от реверса бывает нужно:


  • Найти в протоколе ошибки. Для этого в программе необходимо искать места, связанные с получением данных извне;
  • Разобрать некий протокол взаимодействия, чтобы, например, создать свой API;
  • Пофиксить баг в чужой неподдерживаемой программной библиотеке (серьёзно, такое приходилось делать пару раз);
  • Что-то ещё…

Таким образом, главная задача, которая стоит перед исследователем, — найти некоторые интересующие места программы. Иначе говоря, реверсить всё целиком зачастую не имеет смысла, да и процесс этот слишком трудоёмкий. Не стоит забывать, что обратный анализ кода занимает времени больше, чем его прямая разработка.


0×42 Иголка в стоге байт

; {RU}
; Ищем функцию получения данных извне;
; Ищем на неё ссылки;
; Если не нашли, пытаемся искать по логам (повторяем пп.1-3);
; {RU}

Как же найти иголку в стоге сена? На самом деле, подходы практически такие же, как когда знакомишься с новым API или OpenSource-проектом, а вся документация в нем сделана в «doxygen»: пытаемся искать функции с вменяемыми именами или идём от API операционной системы.


Что не так с doxygen?

Наличие документации в doxygen — это отлично. Подразумевается, что это единственная документация, причем во время разработки программисты не всегда удосуживались написать комментарии к функции. То есть, все, что есть, — это HTML представление кода с именами функций и параметров (крайний случай).

Поскольку исходно сказано, что это TCP/IP-сервер, логично предположить, что данные будут «приезжать» в обработчик через recv, хотя в реальности могут быть использованы и другие функции. Например, recvfrom, или API более низкого уровня — самой ОСи (для Linux — read).

Заметка: кто хочет вспомнить/познакомиться с работой с сокетами в Си, тот читает Socket programming in c using TCP/IP.

udr58uxngkb0ntarrlf9hrawoy4.png

Как сделать это в IDA Pro? Сначала нам нужен перечень всех имен (строк и имен функций, вкомпилированных в программу и импортируемых из библиотек). Для этого служит сочетание клавиш SHIFT+F4. После нажатия откроется вкладка с именами (Names).

На вкладке с именами можно воспользоваться поиском, точнее фильтром (в широком смысле, это более сложная функция с возможностью фильтрации строчек с помощью регекс выражения). Для того, чтобы вызвать поиск, необходимо, находясь во вкладке с именами, нажать сочетание клавиш CTRL+F. После этого внизу вкладки откроется строка ввода (как показано на рисунке). В эту строку необходимо написать часть слова, которое мы хотим найти (в нашем случае это будет recv), список сократится, и в нем останутся только те строки, в которых встречается заданное ключевое слово (на рисунке не показано).

Во второй колонке выводится адрес соответствующего имени. Для перехода на этот адрес в листинге следует дважды кликнуть по нему (или ENTER).

Заметка: переход назад в листинге выполняется по горячей клавише ESC.

8nxqufiule2e6aphjdiiosshshi.png
Окно имен данного бинарника

И вот мы попадаем обратно в листинг. Теперь уже по адресу функции recv (вспоминаем, что recv в данном случае — библиотечная функция, и её код находится в динамической библиотеке).

Следующим шагом необходимо найти те места в программе, в которых происходит вызов функции recv. Для этого во время анализа IDA Pro создает перекрёстные ссылки между инструкцией вызова функции (или другим обращением к функции) и самой функцией. Чтобы посмотреть места, где используется функция, необходимо навести мышку на адрес (или имя), к которому мы хотим найти перекрёстные ссылки, и нажать кнопку X. Вслед за этим откроется окно, как на рисунке ниже. В окне будут перечислены все найденные перекрестные ссылки. Причем в колонке type используется следующая нотация:


  • p[rocedure] — перекрёстная ссылка «по вызову», то есть адрес (имя) используется в инструкции call;
  • r[ead] — перекрёстная ссылка на чтение; в этом месте программы происходит чтение из данного адреса (имени);
  • w[rite] — перекрёстная ссылка на запись; в этом месте программы происходит запись в данный адрес (имя).

В нашем случае ссылок всего две:


  1. Чтение адреса функции recv в регистр (тип r),
  2. Непосредственный вызов recv (тип p).
    Можно заметить, что реально прямой перекрёстной ссылки на recv в инструкции вызова нет. Листинг вызова выглядит следующим образом:
push    0
push    1000h   ; len
push    ebx     ; buf
push    edi     ; s
call    esi     ; recv   <----  Вызов recv здесь

Как видно из кода ассемблера, инструкция call выполняет переход по адресу из регистра esi. Во время автоматического анализа IDA отслеживает, какое значение было занесено в регистр esi, и делает вывод, что при выполнении call в регистре esi всегда будет адрес recv. Именно поэтому IDA создает перекрёстную ссылку на recv в этом адресе.

6d1w370mx79d5zmogje5t5tazrk.png
Перекрестные ссылки на recv

Выбираем из списка ссылку с типом p, и IDA перекидывает нас в граф (или листинг), где происходит вызов функции recv. Выше на экране — листинг IDA. Мы можем увидеть функцию, вызывающую recv: sub_401D3C.

Просто? Да. В данном случае. В реальности же может оказаться, что прямых ссылок нет, а вместо recv вызываются другие функции, или же данные сохраняются в какой-то буфер в структуре, и потом обрабатываются неизвестно где (но анализ всего этого — отдельная статья).


0×43 Делаем «заметки на полях»: sub_401D3C

; {EN} x_vserv_protocol {EN}


Что такое x_?

В нашей команде принято использовать префикс x_ для именования функций (от слова eXecutable), чтобы отличить поименованные вручную функции от автоматически поименнованных IDA Pro.
ax_ — префикс поименнованых функций скриптами (IDAPython);
v_ — префикс глобальных переменных (от слова Variable);
av_ — аналогично, но поименнованных скриптом (IDAPython).

Функция sub_401D3C в отличие от recv является частью данной программы. Поэтому можно исследовать, что происходит в этой части «подопытного».

Заметка: вообще исследование программы часто делится на два основных метода: статический и динамический анализ. Статика подразумевает, что весь анализ выполняется только на основе кода (без запуска программы), динамика — с учётом информации получаемой в дебаге.

В нашем случае проще было бы запустить подопытного в дебаге (англ. «debug» — отладка) и уже после этого начать изучать, что с ним происходит. Но, во-первых, чтобы поучиться мы проведем исследование чисто статикой: где-и-что делается в обработке сразу после получения данных из сокета. Во-вторых, прежде чем что-то запускать даже на виртуалке, я предпочитаю понять, чем это может закончиться

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

zgziqkawpljptoqlgi2du93vaii.png
Место вызова функции recv

Начнем делать «заметки на полях». Если окинуть взором функцию sub_401D3C, в которой мы очутились, можно увидеть в ней вызовы двух функций с неизвестными именами: sub_401CF0 и sub_401BFD. Кроме этого, мы видим и вызов функции puts — стандартная библиотечная функция из libc. Она выводит строку в стандартный поток вывода (stdout). Раз функция что-то печатает на экран, значит, должны быть и строки, из которых можно получить какую-то информацию!

ujuk7td6k0bfdtiisxf_lnenfx8.png

Заметка: в предыдущем разделе мы нашли интересующую нас функцию по библиотечной функции recv. Однако «золотой жилой» являются строки. Просто пробежавшись взглядом по строкам в окне Strings (SHIFT+F12) или поискав в нём различные ключевые слова, можно извлечь очень много дополнительной информации о работе программы или же найти места, в которых происходит что-то интересное для нас как для реверс-инженеров. Никогда не пренебрегайте возможностью посмотреть на строки, которые остались в программе.

Даже не особо разбираясь в ассемблере, можно легко понять, что выводит конкретно здесь puts. В блоке по адресу 0×00401D64 (чтобы перейти в этот блок, нужно нажать кнопку g и вставить в окно указанный адрес) будет выведена строка «Received failed», в блоке по адресу 0×00401D7A — «Client disconnected», а в блоке 0×00401D9C — «VSERV protocol error…». На основании этих строк можно сделать вывод, что данный сервер имеет внутреннее название VSERV (далее при именовании функций будем использовать такой идентификатор). Кроме этого нужно поименовать метки блоков по адресам:


  • 0×00401D76 как RECV_SUCCESS;
  • 0×00401D8C как CLIENT_NOT_DISCONNECTED.

Заметка: надо стараться именовать всё во время реверс-инжиниринга. Если вы натыкаетесь на функцию и у вас есть хотя бы малейшее предположение, что делает эта функция, — переименовывайте её. В будущем, когда натыкаетесь на эту же функцию, но при других обстоятельствах, вы будете помнить, что уже имели дело с ней, и она где-то была важна для вас. Также можете провести анализ её использования по нескольким случаям применения. Дальше по тексту уже не будет приводиться фраза: «надо переименовать», предполагается, что это рефлекс.

a3smji-z1irduackgnndpluxdm4.png

Далее видно, что адрес 0×00401D54 — начало цикла, в котором сервер постоянно «крутится» и получает данные от клиента. Этот адрес можно назвать «RECV_LOOP». Цикл в IDA Pro легко найти с помощью графового представления: стрелка перехода от нижнего блока (окончание цикла) к верхнему (начало цикла) выделяется жирным.

Хорошим вариантом для имени функции по адресу 0×00401D3C, в которой мы находимся, является, например, x_vserv_protocol. Видно, что в ней происходит приём данных от клиента, после чего вызываются две функции — в них будет либо полный разбор протокола, либо же предразбор (преобразование потока данных из TCP в «сообщения»). Из кода, который есть в функции x_vserv_protocol, невозможно сделать полноценный вывод, что же происходит внутри функций sub_401CF0 и sub_401BFD, поэтому давайте зайдем поочередно в каждую из них и попробуем понять их функциональное назначение (вернуться назад можно кнопкой ESC).

jztyawfpvn1ydci0xp5jemcbsay.png
Не забываем переименовывать метки и имена функций


0×44 Делаем «заметки на полях»: sub_401CF0

; {EN} x_vserv_parse_header {EN}

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


  • На вход получает только один аргумент (причем, скорее всего, с помощью функции recv данные из TCP-сокета);
  • Не имеет циклов и содержит одно ветвление (if-else);
  • Вызывает две библиотечные функции memcmp и atoi;
  • В одной из веток возвращается 0xFFFFFFFF (-1) в качестве результата;
  • Проверяет сигнатуру в пришедших данных.

vmx8r-chhl9nhifxb371kn2hon0.png
_Реверс функции sub_401CF0 она же x_vserv_parse_header_

Разберёмся по порядку, откуда что взялось.


0×44a Один аргумент и его назначение в функции sub_401CF0

Аргументы IDA пытается распознать сама (для этого она, точнее её конкретный процессорный модуль, использует знание о calling convention (соглашение о вызовах) и другие методы эвристики, но может ошибаться). Если аргумент передается через стек, а не через регистр (для x86 при соглашении о вызовах cdecl, которое используется чаще всего, это именно так), то такие смещения в стеке IDA Pro сама именует с префиксом arg_.

Заметка: передача аргуме

© Habrahabr.ru