Post

Reversing 'ELF x86 - 0 protection' from root-me.org

Без кучи слов и огромных вступлений сразу приступим к делу. В конце прикреплю полезные ссылки которыми сам пользуюсь. И так, скачав и распаковав наш архив, сразу обнаруживаем файл ch1.bin. Заходим в терминал, запускаем ch1 и видим перед собой это:

############################################################
##        Bienvennue dans ce challenge de cracking        ##
############################################################
 
Veuillez entrer le mot de passe :

Воспользуемся беспроигрышным вариантом и введем в качестве пароля 12345:

############################################################
##        Bienvennue dans ce challenge de cracking        ##
############################################################
 
Veuillez entrer le mot de passe : 12345
Dommage, essaye encore une fois.

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

  • Первый вариант: strings (лёгкий) Прибегнем к команде “strings file_name” - она ищет строковые литералы в нашем бинарном файле. Пролистав все строки, можно найти имена библиотек, функций, секций и интуитивно их отбросить. First

    В нашей ситуации, посмотрев в начало вывода, мы можем обнаружить строки, которые очень похожи на ввод того, кто писал эту программу. К примеру мы можем увидеть приветствие, которое выводит программа, приглашение ввести пароль и т.д. Замечаем среди этой каши интересную строку “123456789”. При запуске программы я ее не видел, выглядит подозрительно, поэтому пробуем ее в качестве нашего пароля.

  ############################################################
  ##        Bienvennue dans ce challenge de cracking        ##
  ############################################################
 
  Veuillez entrer le mot de passe : 123456789
  Bien joue, vous pouvez valider l'epreuve avec le pass : 123456789!
  

Браво! Мы подобрали пароль! (Хоть strings и хорошая утилита, но к сожалению с помощью неё одной можно решать только такие простенькие задачки.)

  • Второй вариант: decompile Открываем наш дизассемблер с декомпилятором, в моем случае это Ghidra, и загружаем туда наш файл. Соглашаемся на анализ, который предлагает наш инструмент сразу после открытия бинарника и оставялем все галочки по умолчанию.

    Сразу попадаем на функцию main.
    В любом случае можно залезть во вкладку Functions и найти нужную нам функцию.
    Для Ghidra: (По умолчанию - левый нижний угол) Symbol Tree -> Functions
    Для IDA PRO: (По умолчанию - левый верхни угол) Functions Second

    Закрываем наш ассемблерный листинг, ибо он нам не понадобися и сразу же переходим к нашему декомпилятору.
    Предупреждаю, опираться только на декомпилятор, минуя чтение листинга на ассемблере, - это плохая идея. Но это легкое задание, без огромных ветвлений и условий, с функциями стандартных библиотек, так что можно прибегнуть к нему.
    Если вкладки с декомпилятором нет, откройте её так:
    Для Ghidra: CTRL + E / Window -> Decompile: function_name.
    Для IDA PRO: F5 / View -> Open Subviews -> Generate pseudocode.
    P.S. Symbol Tree в Ghidra тоже можно открыть через вкладку Window!

    Наша декомпилированная функция main сначала выводит 4 строчки на экран - приветcтвие (см. 1).
    Далее используется функция getString (см. 2) - мы не анализировали ее, и не будем этого делать, но в данном контексте будем просто опираться на её поведение, исходя из её названия, т.е. она принимает строку от пользователя.
    Потом следует функция strcmp (см. 3) - проверяет две строчки, если они равны, то возвращает 0. Смотрим на параметры функции: первым параметром передается наша введенная строчка, а вторым - литеральная константа “123456789”.
    Если строчки равны, то на экран выводится похвала и пароль, если строчки не равны, - “Попробуйте еще раз” (см. 4). Third

    Вводим наш пароль 123456789 и радуемся.

Третий вариант: disassembly

  • 1. Закрываем вкладку декомпилятора и открываем листинг дизассемблера. (по умолчанию он прямо по центру, но мы его с вами закрыли)
    для Ghidra: Window -> Listing: programm_name.
    для IDA PRO: View -> Open subviews -> Disassembly.

  • 2. В самом начале у нас выделяется место под стек, под наши локальные переменные (см. 1). После того как стек подготовлен, следующая инструкция помещает значение “123456789” в нашу локальную переменную - local_10 (см. 2).
    Далее, на верхушку стека (ESP - это регистр который указывает на верхушку стека) помещаются строки, которые нужно вывести, и вызываются соответствующие функции - puts() и printf(), которые берут параметр с верхушки(см. 3). Four

  • 3. Второй пункт был не очень интересен, ибо он выводит только приветствие и приглашение к вводу пароля, а вот следующие инструкции очень важно рассмотреть детальнее. В регистр EAX заносится адрес некой переменной local_14 (см. 1), который затем помещается на верхушку стека и передается в функцию getString (см. 2). Получается local_14 - это переменная в которой будет хранится наш введенный пароль. Six
    P.S. Чтобы было немного понятнее - функции возвращают значение через регистр EAX.

  • 4. Затем у нас идет некоторое перемещение локальных переменных, выглядит оно примерно так (см. 3):

local_10 (“123456789”) -> EAX (промежуточный регистр) -> local_2c (эта переменная находится прямо перед верхушкой стека, подробнее о том как устроен стек можно прочесть по ссылке в конце статьи)

local_14 (наш введенный пароль) -> EAX (промежуточный регистр) -> ESP (верхушка стека)

И сразу же после таких махинаций у нас вызывается функция strcmp, которая как мы знаем принимает 2 параметра и сравнивает их, если они эквивалентны, то функция возвращает ноль. Она берет 2 аргумента с верхушки стека и переводя на язык C, выглядит это вот так:

strcmp(our_password, "123456789")
  • 5. Далее, встречаем инструкцию TEST. Она выполняет побитовое AND с двумя операндами и выставляет некоторые флаги. Результат выражения TEST EAX, EAX будет равен EAX. (можете подробнее прочитать про битовые операции, будет очень полезно).
    Сразу после этой инструкции следует инструкция условного перехода - JNZ, которая расшифровывается как Jump if Not Zero. Все инструкции такого типа (JNZ, JZ, JA, JNB и т.д. ) в этих небольших статьях мы будем называть общим термином - JZZ (Conditional Jump).
    Чтобы долго не топтаться на месте, кратко разберем что происходит у нас при выполнении. Инструкции типа JZZ делают переход исходя из флага, если условие верное -> выполняется переход, если ложно -> процессор просто линейно продолжает выполнять инструкции, минуя прыжок.
    В этом контексте нам интересен только флаг ZF (Zero Flag), который устанавливается после инструкции, если в результате она даёт 0.

Пример: 10-10 -> ZF = 1
5+5+5+5 -> ZF = 0
TEST 0,0 -> ZF = 1

  • 6. Вспомним пункт 4 - strcmp возвращает 0 если строки эквивалентны, возвращаемое значение хранится в регистре EAX и получается - TEST EAX, EAX = 0. Zero Flag устанавливается и инструкция JNZ не выполняется, ибо она выполняется только в том случае, когда ZF не выставлен. Если бы строки были не равны -> ZF = 0 -> процессор выполнил прыжок и мы попали бы на LAB_0804871e (см. 6) и увидели строку “Попробуйте еще раз”.

Пропускаем инструкцию JNZ, следуем дальше по коду (см. 5) и мы с вами уже видим как строка с сообщением об успехе и правильный пароль помещаются на стек. Следовательно чтобы увидеть долгожданное сообщение об успехе, наш пароль должен быть равен “123456789”. Вводим и чувствуем себя хакером :)


Обратную связь, пожелание или сообщение об ошибки можно отправить вот сюда: Telegram.

  1. О том как устроен стек: тык.
  2. Лучший курс по ассемблеру (и не только) который я только находил. Настоятельно рекомендую ознакомиться: тык.
  3. x86 и amd64 комманды ассемблера: тык.
This post is licensed under CC BY 4.0 by the author.