Введение в крэкинг с нуля, используя OllyDbg - Глава 6

  • На форуме работает ручное одобрение пользователей. Это значит, что, если Ваша причина регистрации не соответствует тематике форума, а также Вы используете временную почту, Ваша учётная запись будет отклонена без возможности повторной регистрации. В дальнейшем - пожизненная блокировка обоих аккаунтов за создание мультиаккаунта.
  • Мы обновили Tor зеркало до v3!
    Для входа используйте следующий url: darkv3nw2...bzad.onion/
  • Мы вновь вернули telegram чат форуму, вступайте, общайтесь, задавайте любые вопросы как администрации, так и пользователям!
    Ссылка: https://t.me/chat_dark_time

AnGel

Администратор
Команда форума

AnGel

Администратор
Команда форума
27 Авг 2015
3,411
2,025
СРАВНЕНИЯ И УСЛОВНЫЕ ПЕРЕХОДЫ

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

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

Далее мы рассмотрим подробно, как работают сравнение и переход.

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

CMP

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

Рассмотрим пример:

CMP EAX, ECX
Пусть EAX и ECX равны, они будут вычтены друг из друга, значение их не изменится, но результатом операции станет то, что активируется флаг Z. Рассмотрим пример в OllyDbg.

972bac53be998ecde6ccdd61f42c4994.png

Вводим инструкцию и модифицируем EAX и ECX так, чтобы они были равны.

299923a41ef66bfd8e9961cbaf08882d.png

Нажимаем F7 и видим, что значения EAX и ECX не изменились, но зато стал активным флаг Z, так как результатом вычитания одного операнда от другого был ноль, хотя мы и не можем увидеть его напрямую или сохранить куда-либо.

b5d882d3e1c33744f7d7ceddff125953.png

В реальности нам не важен точный результат вычитания, а только равен ли EAX ECX’у или нет (что чаще).

Хотя мы ещё и не дошли до самих условных переходов, есть две возможности: согласно значению флагов решить, делать переход или не делать. Самый понятный пример, который работает в сочетании с предыдущим – это инструкция JZ, которая совершает переход, если флаг Z активен, т.е. равен ЕДИНИЦЕ, и не совершает, если он не активен, т.е. равен НУЛЮ.

Таким же образом программа может вынести решение относительно двух серийных номеров, один из которых интересует вас на предмет регистрации программы и содержится, скажем, в EAX, а второй находится в EAX и является правильным серийным номером. Программа может определить с помощью CMP, являются ли они равными, и если да (флаг Z будет активным), совершить переход с помощью JZ в ту область, где происходит регистрация. А если введённый вами серийный номер, находящийся в EAX, и правильный в ECX окажутся не равны, то флаг Z будет содержать НОЛЬ, и, соответственно, никакого перехода не произойдёт.

Давайте рассмотрим более конкретные примеры условных переходов.

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

Рассмотрим пример.

Повторим «CMP EAX, ECX», но в этот раз сделаем EAX больше ECX.

86c5cc83e42eb4f4e77491e20899e85b.png

Если нажмём F7:

778482d9fcca96b75a7ef8ccacff12ad.png

Видим, что флаг Z равен нулю, потому что, как мы уже знаем, значения не равны, а исходя из того, что флаг S тоже равен нулю, можно опеределить, что остаток от EAX-ECX является положительным, то есть EAX больше ECX.

Таким же образом повторим предыдущую операцию, но EAX меньшим чем ECX.

bbb6206999c92af18efefeaec4293eac.png

И нажимаем F7.

bf5238f71eb7dc210da475859d9615ca.png

Здесь видим, что остаток от вычитания ECX от EAX отрицателен, то есть значение в ECX больше, что активизирует флаг S.

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

a0ecba39226a37b0f88d729a76e42144.png

Здесь сравниваем EAX с содержимым 405000, и, как обычно, окошко с пояснениями нам даёт значение каждого из операндов. В моём случае они следующие:

b259e021f7f041d705520e99caefdaf3.png

EAX в данном примере меньше чем содержимое ячейки памяти по адресу 405000, которое равно 1000, что приведёт к отрицательному остатку и активирует флаг S.

5111cafc01ff6a75b669b6c39a0bd6bc.png

Существуют похожие формы

CMP AX,WORD PTR DS:[405000]
и

CMP AL,BYTE PTR DS:[405000]
В этих случая сравниваются 2 или 1 байт содержимого памяти соответственно.

TEST (Логическое сравнение)

Эта инструкция работает похожим, до некоторой степени, образом, что и CMP – производится определённая операция над двумя значениями, и результат её не сохраняется, однако могут измениться состояния определённых флагов (в данном случае SF, ZF и PF), в следствии чего программа может определить, следует или нет совершать переход. Отличие от предыдущей инструкции заключается в том, что пресловутой операцией является AND.

Это нам рассказал наш друг CAOS в своё туториале по ассемблеру. Рассмотрим несколько примеров, чтобы уяснить данное определение:

TEST EAX,EAX
Вы спрашиваете зачем сравнивать с самим собой EAX или любой другой регистр? Для того, чтобы определить равен ли EAX нулю. Как это работает?

Пишем в Олли:

TEST EAX,EAX
a178f6084baecc197d69356dec72cb6f.png

Таблица результатов для операции AND следующая:

Результать равен 1, если оба операнда равны 1, и 0 во всех остальных случаях.

1 and 1 = 1
1 and 0 = 0
0 and 1 = 0
0 and 0 = 0
Видим, что единственный случай, когда результат равен нулю, это если оба операнда равны нулю (нас не интересуют случаи, когда у операндов разные значения, так как мы сравниваем EAX с самим собой, поэтому они всегда будут равны), соответственно, если у EAX в двоичной форме какой-нибудь из битов равен 1, то результатом операции никак не может быть ноль.

Сделаем EAX равным НУЛЮ:

45d9461bbf57acc5855fb30b3c81c4de.png

В OllyDbg это делается очень легко правым кликом мыши на нужном регистре и вводом НУЛЯ.

91d47ceb9af2f4799e6ed6a67afcb559.png

Теперь нажимаем F7.

9690c5ad258eb1fa94e4d8e64b1aeabc.png

Видим, что активен флаг Z, то есть знаем, что результатом операции AND был НОЛЬ, в виду чего он и активировался.

Если повторим эту операцию на EAX со значением, отличающимся от нуля:

e9c62f8872e6b091ef166908b0fb5916.png

Нажимем F7.

3d69bf1a9f40c9c82c2230b3fdb399b4.png

И флаг Z не является активным, так как результат отличается от нуля.

Если используем калькулятор, то можем вычислить, что результатом 390 AND 390 будет 390.

В двоичной форме 390 – это 1110010000.

5fa7d3c5f63e9b9793f090b50fca3a74.png

Так как операция AND, если оба операнда равны НУЛЮ в качестве результата также выдаст НОЛЬ, а если они оба равны ЕДИНИЦЕ, то результатом будет НОЛЬ, то видим, что в случае с 390 результат будет 390 и флаг Z не станет активным.

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

ПЕРЕХОДЫ

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

  • JMP – переход
  • JE, JZ – переход, если равно нулю
  • JNE, JNZ – переход, если не равно нулю
  • JS – переход, если знак отрицателен
  • JNS – переход, если знак не отрицателен
  • JP, JPE – переход, если чётно
  • JNP, JOP – переход, если нечётно
  • JO – переход, если произошло переполнение
  • JNO – переход, если переполнения не произошло
  • JB, JNAE – переход, если ниже
  • JNB, JAE – переход, если выше или равно
  • JBE, JNA – переход, если ниже или равно
  • JNBE, JA – переход, если выше
  • JL, JNGE – переход, если меньше
  • JNL, JGE – переход, если больше или равно
  • JLE, JNG – переход, если меньше или равно
  • JNLE, JG – переход, если больше
JMP

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

fb8fabf4e8fba7accfd13f004197d86b.png

При выполнении этой инструкции программа перейдёт в 401031 и продолжит выполнение уже оттуда.

В Олли есть пара опций, которые позволяют сделать переходы более наглядными.

Если зайдём в OPTIONS-DEBUGGING OPTIONS:

e4c21b914bd84b014f05668b8f82921e.png

А там во вкладку CPU:

ff2e5a8721dde3c0bc0041363d31b104.png

Ставим следующие три галочки:

8d99a3781bfecf8ac5daf420f715c38f.png

Видим, что предоставляемая информация стала гораздо нагляднее.

b479ed5900a70bfa2d21d01be3d2a706.png

Видим, что теперь OllyDbg показывает нам красной линией откуда и куда совершается переход, в данном случае в 401031.

Если запустим с помощью F7:

c775139d48e005ebc7d78d6c9e36874b.png

Совершается переход и EIP становится равным 401031.

14ed164269321d3a66925840973a906c.png

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

JE или JZ

И то и другое являются одним и тем же условным переходом, который можно записать двумя разными образами. JZ «прыгает» тогда, когда активен флаг Z.

23ae59dcc8aa0c9d76f9caef08898ccf.png

Напишем в OllyDbg две инструкции, которые делают сравнение и совершают переход, если сравнение оказалось удачным.

Делаем EAX и ECX одинаковыми.

5694114a504620bd897db4558363e234.png

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

134508f379b9ca755842b83964dfe865.png

Следующая инструкция – это условный переход:

260dd2d59db1c1343968187bffc28109.png

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

eed85c7ff1f8e45e9e44cad7e0a91503.png

Видим, что был совершён переход и EIP сейчас равен 401031.

Повторим пример с EAX, отличным от ECX.

b20452adfcdeb73f39f50abc5ac5256f.png

При нажатии на F7, поскольку результат первой инструкции отличен от нуля, флаг Z остаётся неактивным.

55a38fb0a51e4b0ea59e85b07daca3ad.png

И идём в Олли:

bfd487f49bd2af53a16830eb874a10e5.png

Поскольку перехода не произойдёт, то линия перехода отображается серым цветом. Ещё раз нажимаем F7.

5e0ee3bd01306b888608548cdc348730.png

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

Если повторим предыдущий пример до шага с переходом, но не будем его совершать:

1ff32e1c5602ae5c1401f9f0b1d6f2d3.png

Мы знаем, что флаг Z равен нулю и перехода не будет. Теперь что случится, если кликнем два раза на флаге Z и изменим его значение на 1?

f20ffb62cace91d69e376f1464d829a3.png

7285f4389d3b1102cee746630ba6a0df.png Видим, что линия стала красного цвета, а переход будет совершён, независимо от результата сравнения. Хотя программа уже приняла решение о переходе, его можно изменить, напрямую манипулируя значением флага.

Рассмотрим коротко другие флаги на наглядных примерах.

JNE или JNZ

Эта инструкция противоположна предыдущей: переход совершается, если флаг Z неактивен, то есть если результат операции отличен от нуля.

0873990f7f2b41130fa1edd4c2dd33b6.png

Здесь пишем сравнение и JNZ.

Если EAX и ECX равны, то флаг Z будет активным.

a40d6a8b6de4d50e135c13a90171b99e.png

f7e15f14ff237bfceb6b3051f7185ec7.png

И в отличии от инструкции JZ, совершавшей переход, когда флаг Z был активен, эта, напротив, переходит тогда, когда флаг Z равен нулю, то есть неактивен.

Как можно понять, если EAX отличен от ECX, то результат операции также будет отличен от нуля, что сделает флаг Z неактивным и, в свою очередь, приведёт к переходу.

JS

Как видно из таблицы выше, этот переход выполняется, если результат сравнения отрицателен, то есть если в предыдущем примере EAX меньше чем ECX.

70c56c5518eee9dbe7e959975e087ef2.png

259df3a7f0608751987507eb9f9d2419.png

Нажимаем F7:

7a909d1962bb411abd5028e48dba7773.png

Флаг S равен 1, поэтому происходит переход.

542edcb72f0cb02c2de011f2f80b996b.png

Видим, что красная линия указывает нам, куда произойдёт переход. В случае, если EAX больше ECX, флаг S будет неактивным и переход JS не сработает.

JNS

Этот переход противоположен предыдущему. Переход совершается, когда флаг S равен нулю, то есть, если рассматривать предыдущий пример, когда EAX больше ECX.

JP или JPE

Условный переход JP срабатывает, когда активен флаг P, а это происходит, когда результат сравнения является чётным (понимая её так, как это мы рассматривали раньше).

f317b375e2846b03103fafbc1ee6625d.png

5383724a8eb447892be51297e80e8c8d.png

Имеем 20 в EAX и 18 в ECX. Нажимаем F7:

acbe694e304e3744fc67aa0f27bb3f94.png

Разность между EAX и ECX равна 2, что в двоичной системе отображается как 10. В этом числе всего лишь одна единица, то есть нечётное их количество, поэтому флаг P неактивен и перехода JPE не происходит.

18466df1a6212d4fad862cc4e923511a.png

Теперь заменим значение в ECX на 17 и снова нажмём F7:

828dc7429c458a5989ccdd993263d438.png

f438fad3cd90a227a394e936a79cf344.png

Видим, что результат равен 3, что в двоичной системе выглядит как 11 – в этом числе чётное количество единиц, флаг P активен, поэтому переход JPE срабатывает.

af1c2aab9c79f15a2bf09b611af599e5.png

JNP или JNPE

Этот переход противоположен предыдущему и срабатывает, когда флаг P равен нулю, то есть количество единиц в числе нечётно. В рассмотренном только что примере переход произошёл бы, когда результат был равен 2, и не сработал бы, когда он равнялся 3, то есть обратно действию JP.

JO

Срабатывает, когда происходит переполнение, в виду чего активируется флаг O.

0fee091f9b7a8f287c0617a1bc294591.png

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

8d5fc134a5f9be053b23acfa52f5400f.png

Нажимем F7:

94fd2044723d78fa7df9c0cb1d869b47.png

d2ccbe8c29bbeb9a3f37938c72f814e6.png

И срабатывает переход JO, так как из-за произошедшего переполнения активировался флаг O.

JNO

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

JB

Переход происходит, если первый операнд ниже чем второй. Смотрим пример.

6d0ec7ba0f22444243d98ff0f0c68c12.png

c7e6aca288f09997cf0d1f9a80e3bf8d.png

Видим, что EAX ниже чем ECX, то есть должен сработать переход. Нажимем F7:

21705d11a0cdc1c07ff43974cb63f13f.png

Флаг C активен, так как разность между этими двумя операндами является отрицательным числом, то есть установлен самый значимый бит, на основании чего и делается вывод, что EAX меньше ECX.

JNB

Противоположен предыдущему – срабатывает, если флаг C равено нулю, то есть результат сравнения был положителен. В предыдущем примере это произошло бы, если EAX был бы больше ECX.

JBE

Это переход если ниже или равно, то есть проверяются два флага – активен ли флаг C, и тогда переход срабатывает, а также активен ли флаг Z, и тогда он тоже срабатывает, то есть если EAX равен ECX или меньше.

d00dd3d019769b3cab5b3fb652b77ac2.png

Сделаем EAX равными друг другу.

17fa4b58eea96c875706312b9228d6a9.png

Нажимаем F7.

559e528c87b463452d86a6be597cb4ef.png

Видим, что флаг Z активен.

14940a31229a2958c6b1dc55e9e8edfe.png

Если EAX меньше ECX:

8946927f93ffd0672c7217776a9fb75a.png

Нажимаем F7.

6decac068a5316be6ac4e01f3cf8e0ea.png

В данном случае активируется флаг C, так как результат отрицателен (установлен самый значимый бит), то есть EAX меньше ECX.

В последнем примере EAX больше ECX. Нажимаем F7.

4e51b545431d3d0b57a27f8632137dc8.png

Оба флага Z и C равны нулю, поэтому перехода не происходит.

8a03a9f3673eb40bae66231e752957cc.png

То есть, JBE срабатывает, если в нашем примере EAX ниже или равен ECX.

JNBE

Этот переход противоположен предыдущему и срабатывает, если оба флага Z и C равны нулю. В предыдущем примере переход произошёл бы только в последнем случае.

JL

JL – это переход «если меньше», но в несколько другой форме. Здесь проверяется, отличается ли флаг S от флага O, и в этом случае и совершается переход.

Рассмотрим пример, когда EAX и ECX положительны и EAX больше ECX.

b6cbe9df68ff6118924e3c6693cc103e.png

20307411ea94e3ab6f24fd541b12d912.png

При нажатии на F7 перехода не произойдёт, так как EAX больше ECX и результат положителен, поэтому ни флаг O, ни флаг S не будут активны.

89709589a8d0e5ffc1b4e859401a77a1.png

Повторим пример при EAX меньше ECX, но оба по-прежнему положительны:

3a723b1ea130585d5e53e52acb4881bf.png

Нажмём F7:

0f7b1591f3e75e6179f63c6c3ad52f45.png

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

a8d62e1675c23a3e48b90ec24d6a7f53.png

В данном случае EAX меньше ECX, так как первый является отрицательным. Смотрим, что произойдёт.

Переход совершается прекрасно, а теперь попробуем эти же два значения с переходом JB.

2a85ce593031aac2fe52ef6cdcc0c255.png

Нажимаем F7.

b463d3d040268c502a7065f57dc60988.png

Видим, что JB не срабатывает, так как JB сравнивает оба значения как если бы они были положительными, то есть считает их беззнаковыми, поэтому если переход должен учитывать знак, то нужно использовать JL – это и есть основное различие между ними.

bd0c83cf2b4dcc6451b8216a1ed2330d.png

Как видно, эти условные переходы делятся на две категории: те, в которых знаки операндов учитываются, и те, в которых нет.

Видим, что JA, JB, JBE И JAE считают оба операнда положительными, в то время как JG, JL, JLE и JGE рассматривают их как числа со знаком. JE и JNE работают в обоих случаях.

Думаю, что тема условных переходов и сравнений достаточно хорошо раскрыта, в дальнейшем увидим, как они используются в программах.

Следующая часть будет также посвящена ассемблеру, в ней будут рассматриваться вызовы (call) и возвраты (ret), а также режимы адресации. Потерпите ещё чуть-чуть, хе-хе.
 
  • Лайк
Reactions: Gl.Korolb

О нас

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

    Dark-Time 2015 - 2022

    При поддержке: XenForo.Info

Быстрая навигация

Меню пользователя