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

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

AnGel

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

AnGel

Администратор
Команда форума
27 Авг 2015
3,411
2,025
ИНСТРУКЦИИ
Как уже говорилось в прошлых частях, главной целью данного "Введения..." является объяснение теории, практика в OllyDbg, чтобы её усвоить, а в дальнейшем, после приобретения уверенности в своих навыках, перейти к возможностям, которые они дают.

Рассмотрев в OllyDbg почти все флаги, мы перейдём к рассмотрению наиболее важных инструкций. Если вы встретите в листинге инструкцию, которой не окажется здесь, вы можете проконсультироваться с каким-нибудь более полным руководством по ассемблеру.

NOP (NO OPERATION)

Это инструкция, которая при запуске не производит никаких изменений в регистрах, стеке или памяти, поэтому по-английски её название расшифровывается "NO OPERATION", то есть у неё нет никакого специального анзначения, и поэтому её можно использовать, например, если нужно заменить одну инструкцию на другую, более короткую. Чтобы процессор не столкнулся с ошибками, лишнее место заполняется NOP'ами.

Также она служит для полного уничтожения другой инструкции, для этого нужно заменить её соответствующим количество NOP'ов, или попросту заNOPать.

Снова откроем крэкми CrueHead'а.

29605f4067be7db75be4787d842e4772.jpg

В самом начале видим оригинальный код. Чтобы занопать первую инструкцию PUSH 0, которая имеет размер 2 байта, отмечаем соответствую строку листинга с помощью мыши, а затем нажимаем клавишу "пробел" или же правая кнопка мышь - ASSEMBLE.

ef61863204c1cb02e7ca3e20c113bced.jpg

Здесь видим, что действительно можно просто нажать "пробел", после чего откроется окошко для ввода желаемой инструкции.

4a3216a79c0cb42b35b15224b49d314e.jpg

Пишем NOP и нажимаем ASSEMBLE.

0827f8249fa948e9a05c62a01a73f1c1.jpg

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

Теперь на месте, где был "PUSH 0", находятся два NOP'а, которые при исполнении ничего не делают, в чём мы можем убедиться, нажав два раза F7, чтобы стала отмеченной инструкция CALL. Увидим, что изменился только регистр EIP, содержащий адрес инструкции, которая должна выполнится следующей, но ничего больше не изменило своего значения: ни другие регистры, ни стек, ни флаги, ни память.

Теперь нам нужно посмотреть эти два новых байта в DUMP, и для того, чтобы найти их там, нам нужен их адрес в памяти, и это 401000 и 401001.

89f70854a91e3bf1ec5c104fbcef7c76.jpg

Идём в окно DUMP, правая кнопка мыши - "Go to" - "Expression", после чего нужно будет ввести адрес, где располагаются требуемые нам байты.

21ebed0e97e6443235546b90dc73d938.jpg

Вводим 401000.

826ecfa0dfd019ccbc712f9011b62551.jpg

Видим, что:

bb27c0d1778ae458f5e8867c9e15e50e.jpg

Красным выделены изменённые Олли байты. Первым идут два 90, а затем E8, FF и все остальные байты, относящиеся к следующей за NOP'ами инструкцией, которой является CALL.

Может Олли вернуть обратно изменённые нами байты?

Хе-хе, да, может.

В любом из двух окон - DUMP или листинге, отмечаем оба байта.

c32ad293184c8c960fa68ac8a3d64158.jpg

Потом кликаем на правую кнопку мыши и выбираем "UNDO SELECTION"

56b9898a280d8a2313187e092d39e3d0.jpg

И затем снова появляется изначальный PUSH.

17b4ffb804a84316297ca9b2c66031d3.jpg

Также, если посмотрим в DUMP, увидим, что там находятся изначальные байты.

5f158a865ce28564840adaae7cfe73e3.jpg

Это всё, что касается инструкции NOP.



ИНСТРУКЦИИ СТЕКА
Мы уже говорили, что стек - это пачка писем, поверх которой последние помещаются или сверху которой они берутся.

Далее следуют инструкции для помещения или удаления "писем".

PUSH

Инструкция PUSH - типичная инструкция для помещения "письма" или значения на стек. Мы можем видеть, что первая инструкция в крэкми CrueHead'а - это PUSH.

a3b54672069fa35b40866d6d523fef0b.jpg

В данном случае это 'PUSH 0', и, будучи выполненной, данная инструкция поместит 0 на самый верх стека, а то, что было до этого наверху, окажется под этим значением.

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

77e462d87496663cece23b7a6cb48c00.jpg

Это стек на моей машине. Адрес 12FFC4 может отличеаться у вас, так как стек каждый раз может располагаться по разному адресу, и его начальное содержимое также может отличаться, то есть у вас может быть значение, отличное от 7c816d4f. По нажатию F7 на самый верх попадёт ноль, а всё остальное окажется внизу него. Нажмём F7.

719f9c35fda4ff3f5174a4232678b689.jpg

Видим, что по нажатию F7, ноль словно присоединился сверху того, что мы уже видели. Внизу по адресу 12ffc4 по-прежнему находится значение 7c816d4f, и ни одного из других значений в стеке также не изменилось.

Главным отличием является то, что самое верхнее значение стека находится теперь по адресу 12ffc0 (именно там и неаохдится ноль, который мы поместили с помощью инструкции PUSH), это действительно как положить поверх пачки писем новое, которое окажется на самом верху, а остальные - под ним, но ничего в них не изменится.

Также видим, что ESP, в котором находится адрес самого верхнего значения стека, теперь содержит 12FFc0.

c86f32748fe4bdc7bc6571b44bf65d5a.jpg

Конечно, инструкция PUSH имеет несколько вариантов, позволяющих помещать в стек не только числа:

PUSH EAX помещает значение EAX на верх стека. Подобным образом мы можем поместить в стек значение любого регистра, числа и т.п.

Также можно поместить значение, находящееся в памяти по определённому адресу:

PUSH [401008]
Обратите внимание, что это будет интерпретироватиться отличным образом от следующего:

PUSH 401008
(Без квадратных скобок)

Если выполним 'PUSH 401008', то в стек будет помещено число 401008.

480db4be1966ddd1bddf5757a3cc57ca.jpg

После выполнения имеем следующее:

44cc925f9da76f7e6b2b1e0b23ec0f1d.jpg

Если заменить на 'PUSH [401008]'

5652cf661a108c28acd51eb011100862.jpg

Квадратные скобки указывают на содержимое ячейки памяти по адресу 401008, то есть мы должны пойти в DUMP и посмотреть, что там содержится.

С помощью "GOTO EXPRESSION 401008" видим:

ced1736921636ba7379a7c9b0c169575.jpg

В этих четырёх байтах находятся CA 20 40 00. Запускаем PUSH с помощью F7.

12cd515c4181f6377d57fd58ac97a86e.jpg

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

Это одно из свойств процессора: при чтении или записи содержимого из/в память байты всегда переворачиваются. Жалуйтесь по этому поводу производителю процессора, хе-хе.

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

Теперь видим, что Олли, когда пишем 'PUSH [401000]' интерпретирует и отображает

a9d7490f5b3c3e66f346c1ebe3df5136.jpg

PUSH DWORD PTR DS:[401008]
Это потому, что если иное не задано специально, Олли считает, что нужно читать 4 байта из памяти, то есть DWORD. Остальные варианты мы рассмотрим в других инструкциях.

POP

Инструкция POP является обратной по отношению к PUSH: она достаёт первое письмо или первое значение из стека и помещает его в указанное параметром инструкции место назначения. Например, POP EAX берёт первое значение из стека и помещает его в EAX, после чего то значение, которое шло следующим, станосится верхним.

Видимо, что в начале крэкми CrueHead'а есть следующая инструкция:

bda2076a51826920e1275ffc8e6de7a0.jpg

Заменим данную инструкцию на 'POP EAX', отметив первую строку и нажав "пробел":

3882d1748fcf47086b9157893e08ebf8.jpg

743b347f5f7b741e2a14d1c19a251951.jpg

Вот что будет содержать стек до выполнения этой инструкции:

4440a930d791fd71520c73020f546c40.jpg

А ESP указывает на 12FFc4, что является адресом верхнего значения, содержащегося в стеке.

0d5b3bd21240fff18f7e28b219180509.jpg

И видим что в EAX содержится ноль (в моём случае).

Нажимаем F7.

5e5d5552c51ed7adae411fc0ba6cb73b.jpg

Видим, что в стеке исчезло наше первое "письмо", а ESP указывает на 12ffc8.

5789d2a8745df400364d6bca5bcf6293.jpg

Но куда делось наше "письмо"? Так как инструкция был 'POP EAX', то видим, что сейчас EAX содержит значение 7c816d4f - это в моём случае, а в вашем там будет то занчение, которое лежало сверху стека.

Таким же образом, если бы была инструкция 'POP ECX' то верхнее значение было бы помещено в ECX или иной регистр, который был бы указан.

Итак, мы рассмотрели инструкции, которые помещают или берут из стека значения по одному за раз.

PUSHAD

PUSHAD помещает содержимое регистров в стек в определённом порядке, то есть PUSHAD эквивалентно 'push EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI'.

Посмотрим, правда ли то, что нам говорит камрад CAOS REPTANTE в своём туториале про ассемблер, хе-хе.

Загрузим снова крэкми CrueHead'а, вызовем меню для замены инструкции и напишем PUSHAD.

cc16aef186684065261b78bb356101d6.jpg

50ad4c6641928220d37234f1d0f2fead.jpg

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

9ddf01bd7e460687c6fe334c03fdb860.jpg

Нажимаем F7 и видим, что теперь находится в стеке:

dd33db5b042f029214ed53dce063cd9f.jpg

Видим, что все регистры были подвергнуты PUSH'у. По адресу 12ffc4 находится значение, которое лежало сверху стека до выполнения инструкции, а сейчас после него лежит ноль (PUSH EAX), затем содержимое ECX, а в 12ffb0 - то, что находилось в ECX. Дальше находится содержимое регистров от ESP до EDI.

POPAD

Обратной по отношению к PUSHAD инструкцией является POPAD, которая берёт значения из стека и помещает их в соответствующие регистры. POPAD эквивалентна 'pop EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAX'.

Как и в предыдущем примере введём инструкцию POPAD:

93b4e49e016d38eb2c4a8154b80f4d8f.jpg
262fcc12f0b9ef16edc16df57d8f5690.jpg

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

e8ecff51f5053043e1697ff094d37edf.jpg

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

02f3d9a5999227868f0640f994e4f085.jpg

Комбинация PUSHAD-POPAD часто используется, когда нужно за раз сохранить содержимое всех регистров, потом совершить какие-то другие операции, меняющие значение регистра и стека, а затем восстановить их изначальное состояние с помощью POPAD.

Существуют также следующие варианты:

  • PUSHA эквивалентно 'push AX, CX, DX, BX, SP, BP, SI, DI'.
  • POPA эквивалентно 'pop DI, SI, BP, SP, BX, DX, CX, AX (восстанавливаемые значения, соответствующие ESP и SP не помещаются в эти регистры, а исключаются).
PUSHA и POPA похожи на своих сестёр PUSHAD и POPAD, не считая того, что используются в 16-ти битных программах, поэтому нас они не интересуют, так как OLLYDBG - это отладчик для 32-х битных программ.

ИНСТРУКЦИИ ДЛЯ ПЕРЕМЕЩЕНИЯ ДАННЫХ
MOV

Эта инструкция перемещает второй операнд в первый, например:

MOV EAX, EBX
Здесь идёт перемещение значения EBX в EAX. Смотрим в Олли и наше старое доброе крэкми от CrueHead'а.

6ec41457c4891a288c092a03b38915e6.jpg

Уже не буду повторять, как ввести нужную нам инструкцию, просто смотрим регистры:

28a84ff22b14fdff246c2d09481604b1.jpg

На моей машине EAX содержит 0, а ECX 7c91eb94. Эти начальные значения могут у вас отличаться, но важно то, что по нажатию F7 значение EBX переместиться в EAX, так что нажмём на эту клавишу.

dd0c07a864c7599e12436518b1f1e296.jpg

Понятно?

У MOV есть разные варианты, например:

MOV AL, CL
Это перемещает содержимое CL в AL. Напишем эту инструкцию в Олли.

5005c06397f660de5635f199a4fe1c26.jpg

Регистры:

597b8ed8a2b27c6c0eb5a81ea0458aa2.jpg

Помните, что AL - это две последних цифры EAX и CL - две последних цифры ECX. Нажмём F7.

93e3f97c1879b54324dd69a3a495786c.jpg

Видим, что было только скопировано B0 в AL без изменения остального содержимого EAX и ECX, то есть две последние цифры EAX.

Также можем переместить содержимое какого-либо регистра в нужную ячейку памяти или наоборот.

5e081661f42986cc14305387b7338606.jpg

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

Идём в DUMP и делаем GOTO EXPESSION 405000.

81cf523843b029367165e64d706aa775.jpg

b795537bc57399a32c9309885e65f137.jpg

Видим, что содержимое ячейки памяти - 00 10 00 00. Так как в памяти содержимое хранится в перевёрнутом виде, то в EAX попадёт 00 00 10 00. Нажимаем F7 и смотрим, что получилось.

3399553028e7a0d19f8239c32a4462c3.jpg

Вот значение 1000, которое было прочитано из памяти. Теперь если хотим записать значение по данному адресу:

MOV DWORD PTR DS:[400500],EAX
Введём эту инструкцию.

629bcbf922911f8529b808aa8e9f1a1f.jpg

С помощью DUMP видим по адресу 405000:

c01884d11d2459b1c75722539cc320af.jpg

Однако по нажатию на F7 происходит облом:

641b3b6b11ffeb07b1d39020254869ca.jpg Вылетает исключение, и это происходит из-за того, что хотим записать в секцию, в которую писать, то есть изменять в ней байты, запрещено.Ок, как менять права на работу с секциями мы узнаем потом, пока важно изучить инструкции. Очевидно, что если для перемещания 4 байтов испольется слово DWORD, то для перемещения двух байтов используем WORD, а слово BYTE - для перемещения одного.

Смотрим.

MOV AX,WORD PTR DS:[405008]
Здесь перемещаются два байта из памяти по адресу 405000 в AX. В данном случае мы не можем написать EAX, так как перемещаем только два байта, поэтому необходимо использовать 16-ти битный регистр.

С помощью DUMP смотрим, что находится по адресу 405000.

7e9a9b24f8f57330b8d32f4c5a3381c3.jpg

e6fd63e405d72f09b5f3c1b79529466d.jpg

По нажатию F7 должны переместить только эти два байта в AX. Проверяем:

79cd8310b42ff51efc15608c62b45ef1.jpg

И вот в AX в обратном порядке было считано содержимое памяти, а остальная часть EAX не изменилась.

Таким же образом испольуется BYTE.

MOV AL, BYTE PTR DS:[405008]
В данном случае в AL перемещается только последний байт, то есть 08.

e9e3f0b7a1e2e1314a2d6d2227bee1ef.jpg

MOVSX (Move with Sign-Extension)

Копирует содержимое второго операнда, который может быть регистром или адресом памяти, в первый (который должен быть в два раза больше, чем второй), заполняя остальные биты слева значением самого значимого бита второго операнда. Далее идёт пример.

Определение взято из туториала CAOS'а. Сейчас мы попробуем тестовый пример в OllyDbg, для чего используем нашего друга CrueHead'а.

3709628db448174b3d3997c88c377646.jpg

Не буду здесь много говорить, так как хочу, чтобы вы всё сделали сами и поискали значения операнда самостоятельно, хе-хе. В OllyDbg есть окошко с поясненениям прямо снизу листинга и сверху окна DUMP.

7e31b08ae3d44c93300f2aa8d3814b66.jpg

Тут видим, что окошко с пояснениями показывает нам значение операнда нашей инструкции. В моём случае BX содержит F000.

77f621a3a398383a6facd545213e4396.jpg

И также видим, что EAX содержит ноль, так что OllyDbg нам всегда помогает интерпретировать инструкцию, которая будет выполнена (надеюсь, что вы освоили концепцию того, где и что нужно искать, хе-хе).

Нажимаем F7.

ef94658605ac677d47e13976452a7cd9.jpg

Видим, что в AX скопировался BX, который содержал F000, и что остальное место заполнилось FFFF, так как F000 - отрицательное 16-ти битное число. Если бы BX содержал 1234, то EAX был бы равен 00001234, то есть левые байты были бы забиты нулями, так как 1234 - положительное 16-ти битное число.

Концепция положительных и отрицательных чисел в 16-ти битах работает так же как и для 32-х битных, только в интервале от 0000 до FFFF. Числа от 0000 до 7FFF являются положительными, а все, что выше до FFFF - отрицательными. Давайте посмотрим, что будет, если мы изменим BX на 7FFF, а EAX на ноль и снова выполним инструкцию.

2c4fe08885c6719b77f0b10597c24257.jpg

В AX было скопировано 7FFF, а остальное - забито нулями, так как 7FFF является положительным числом. Проделаем то же самое с BX=8000 (отрицательное).

cc86c42f384ec93f89746c280eac65fb.jpg

Выполним инструкцию по-новой с помощью F7.

97dd35a0cff09088a0020c33afefcab3.jpg

В BX было скопировано 8000, а в остальное FFFF, так как 8000 является отрицательным числом.

MOVZX (Move with Zero-Extend)

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

LEA (Load Effective Address)

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

Введём в OllyDbg следующую инструкцию:

000b1c33c6d02a47e9e262821d1c6033.jpg

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

В моём случае ECX равен 12FFb0.

564bd8d65968130d4fce0eaa6e9b9d69.jpg

И в это примере LEA прибавит к значению ECX 38 и полученное значение поместит в EAX.

В окне пояснений будут отображены оба операнда.

d41bd9f9964571609bb9462d84a7a8b8.jpg

Оно показывает, что операнд равен 12Ffe8, что является суммой ECX+38, а EAX до выполнения инструкции содержит ноль.

Нажимаем F7.

721f9dca43fef902b2fe8aa1c73bbc72.jpg

Указанный адрес был помещён в EAX, хотя квадратные скобки могли заставить нас подумать, что туда должно было быть помещено содержимое, находящееся по этому адресу, как бы это сделала инструкция MOV, но LEA просто поместила адрес в первый операнд, а не его содержимое.

XCHG (Exchange Register/Memory with Register)

Эта инструкция обменивает содержимое двух операндов, например:

XCHG EAX,ECX
Значение EAX будет помещено в ECX и наоборот. Проверим это в Олли.

6147e9d330db382d1a8e271dbf355855.jpg

На моей машине до выполнения EAX содержал ноль, а ECX - 12FFb0.

e863d9a835e1f088ce5bba9fc3fa12a8.jpg

Нажав F7, увидим, что они обменялись значениями.

11ee11f71a31f675b765b9b40a7eff08.jpg

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

3d3a157937d1e40549ab49932216620c.jpg

По нажатию на F7:

99bddea6257e5a32251e2fd114a8cd0f.jpg

Также как и в примере с MOV было сгенерировано исключение, так как у нас нет прав на запись.

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

О нас

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

    Dark-Time 2015 - 2022

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

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

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