Заклинание кода: Бет — Архив WASM.RU

Все статьи

Заклинание кода: Бет — Архив WASM.RU

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

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

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

Одной из таких инструкций является NOP. Её опкод - 90h. У этой инструкции нет параметров, поэтому знания её опкода достаточно для заклинания. Вот пример программы:

format PE console
entry start

include '..\..\include\kernel.inc'
include '..\..\include\user.inc'
include '..\..\include\macro\stdcall.inc'
include '..\..\include\macro\import.inc'

section '.code' code executable readable

start:

        db      90h     ; nop

        invoke  ExitProcess,0

section '.idata' import data readable writeable

                library kernel32,'kernel32.dll'
kernel32:       import  ExitProcess,'ExitProcess'

Надеюсь, у читателей этой главы, новоявленных заклинателей, не возникнет вопроса, почему эта программа ничего не делает. А для тех, у кого возникнет, поясняю: инструкция NOP ничего не делает (отсюда и её мнемноника - No OPerations). Если быть более точным, то NOP имеет тот же опкод, что и инструкция XCHG EAX,EAX, однако результат (отсутствие изменений) от этого не меняется.

Но NOP - это не очень интересно. Безусловно, поначалу заклинание внушает, однако на практике оно не очень полезно. Было бы неплохо, если бы мы могли заклинать что-нибудь более полезное - например, увеличивать или уменьшать значение в eax на 1.

Это возможно. Для этого есть мнемоники INC (INCrement) и DEC (DECrement). Опкод INC EAX - 40h, опкод DEC EAX - 48h. Далее я приведу текст программы, в которой в eax кладётся число 10, затем содержимое eax увеличивается на 1 пять раз, потом - трижды уменьшается на 1. Используя абак несложно посчитать, что в результате должно получиться 12. Это число мы отобразим с помощью MessageBox.

format PE console
entry start

include '..\..\include\kernel.inc'
include '..\..\include\user.inc'
include '..\..\include\macro\stdcall.inc'
include '..\..\include\macro\import.inc'

section '.data' data writeable readable

_d      db '%d',0

section '.code' code executable readable

start:

        mov     eax,10
        db      40h     ; inc eax
        db      40h     ; inc eax
        db      40h     ; inc eax
        db      40h     ; inc eax
        db      40h     ; inc eax
        db      48h     ; dec eax
        db      48h     ; dec eax
        db      48h     ; dec eax

        push    eax
        push    _d
        call    [printf]
        add     esp,8

        invoke  ExitProcess,0

section '.idata' import data readable writeable

                library kernel32,'kernel32.dll',\
                        msvcrt,'msvcrt.dll'
kernel32:       import  ExitProcess,'ExitProcess'
msvcrt:         import  printf,'printf'

Скомпилировав и запустив программу можно убедиться, что в результате получается 12. Наше заклинание верно и ошибок нет. На досуге можете поэкспериментировать с PUSH EAX (опкод 50h) и POP EAX (опкод 58h).

Однако этого вам может показаться мало. Возможно, вы захотите сделать заклинание PUSH EBX или даже INC EDX - и это вполне вам по силам! Я дам вам общую формулу для этих инструкций, когда операндом является регистр (именно регистр, а не ячейка памяти, причём 32-х битный!). Общая формула такова:

Базовый опкод + номер регистра

Базовые опкоды:

  • INC - 40h
  • DEC - 48h
  • PUSH - 50h
  • POP - 58

Номера регистров:

  • EAX - 0
  • ECX - 1
  • EDX - 2
  • EBX - 3
  • ESP - 4
  • EBP - 5
  • ESI - 6
  • EDI - 7

Используя эту формулу легко вычислить, например, опкод PUSH EDX: 48h+2=4Ah. Я рекомендую потренироваться в составление простых заклинаний на основе приведённых выше.

Довольно практики, перейдём к теоретическим вопросам. Какой длины может быть опкод? Для процессоров семейств x86 он может быть длиной до 3 байт. Также, под опкод может использоваться часть байта ModR/M, о котором будет рассказано в следующих главах.

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

Также я хотел рассмотреть, зачем понадобилось делать отдельный мнемоник для XCHG EAX,EAX - NOP. Причина, несомненно, в метафизическом смысле числа 90h. В каком-то смысле девятка (как утроенная триада) символизирует Инь и Янь, а ноль среди своих прочих значений символизирует Абсолют. Внесение такого мощного магического слова в ассемблер укрепило положение x86 на астральном плане.

В следущей главе мы попытаемся поместить в EAX единицу. Более того, наша попытка окажется успешной! :)

2002-2013 (c) wasm.ru