Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Низкоуровневое программирование для дZeнствующих


Информационный Канал Subscribe.Ru

Subscribe.Ru : Низкоуровневое программирование для дZeнствующих

Низкоуровневое программирование для дZенствующих, #40

Бредисловие

Как известно, среди всего разнообразия компьютерной мысли отдельным, жирным параграфом стоитъ так называемый вопрос о длине шворца. Извечные споры о том, что круче: Windows или Linux, Delphi или Visual C++, Clarion или dBASE, Excel или QuatroPro, Балтика-с-лимоном или Балтика-с-вишней - болезнь настолько заразная, что порой даже некоторые ассемблерщики (люди, как известно, дZенствующие) позволяют себе предать забвению основные аксиомы Дао программирования, и, потрясая сырцами, начинают доказывать, что MASM круче TASM'а, TASM круче NASM'а, а NASM круче MASM'а...

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

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

СОДЕРЖАНИЕ

Новости WASM.RU

В этом месяце на сервере WASM.RU появилось немного новых материалов, исходников и инструментов, поэтому настоятельно рекомендуется зайти и все посмотреть самим. Особенно хочется отметить, что Four-F продолжает свой цикл, посвященный программированию драйверов под Win2k, а Aquila начал новый цикл статей (первые из которых публикуются в данной рассылке) под названием "Заклинание кода".

Заклинание кода: Алеф

Aquila / HI-TECH

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

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

Заклинание кода - одна из немногих наук, которые дают своим адептам истинное могущество и власть над битами и байтами, составляющим основы виртуального мироздания. В данном фолианте я попытался рассказать об этой чудесной дисциплине. У иного нетерпеливого читателя может возникнуть вопрос: "Что же такое заклинание кода?", который он и поспешит задать. В былые времена мастер просто хорошенько бы двинул наглеца 2х скоростным внешним CD-ROM'ом по лбу, но времена меняются, и сейчас правильнее дать на поставленный вопрос прямой ответ.

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

Чтобы научиться заклинать код, нам понадобятся три вещи - ассемблер, Книга Двойных Слов и собственное намерение. Без последнего ничего не выйдет, даже если будут первые два в сколь угодно большом количестве.

В настоящее время существует несколько ассемблеров. Многие пользуются TASM (любимый ассемблер заклинателей демонов), многие - MASM (хотя на нём и стоит печать Баала). Но мы остановим свой взор на FASM, так как он до сих пор поддерживается и развивается, к тому же он компактен и быстр, не в пример раздутым чудовищам современности. Найти FASM можно в пристанище его автора в Междусети (http://fasm.sf.net).

Я надеюсь, что вы незамедлительно скачаете этот замечательный ассемблер и установите его туда, куда вам будет удобно (и хорошо). Его синтаксис несколько отличается от синтаксиса TASM и MASM и больше похож на язык NASM, который, к сожалению, уже не развивается в наши дни, хотя баалоподобные языки плодятся как воины Язона, после того, как последний засеял поле зубами дракона. Важное отличие языка FASM от языков TASM и MASM в том, что использование метки или имени переменной в инструкциях означает использование адреса, а для обращения к значению, находящемуся по этому адресу в памяти, необходимо заключить имя в квадратные скобки - тогда как в MASM, и в TASM в режиме совместимости с MASM, имя переменной (но не имени константы!) без скобок также означает обращение к памяти, а для получения адреса необходимо использовать слово offset. Пример:

        mov eax,var

В MASM в eax после выполнения данной инструкции окажется значение переменной var, а в FASM в eax будет помещён адрес этой переменной. А чтобы в eax оказалось значение переменной var, нужно написать

        mov eax,[var]

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

Книга Двойных Слов так же известна как "IA-32 Intel' Architecture Software Developer's Manual" и представляет собой три огромных фолианта от 400 до 1000 страниц каждый. Все три тома свободно доступны в пристанище интеловских гномов в Междусети (http://www.intel.com) в разделе для разработчиков.

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

Наконец, намерение вы можете обрести только в самих себе. Однако, чтобы вам было проще это сделать, я дам вам адреса некоторых пристанищ, в которых можно найти необходимые знания:

http://www.mageguild.by.ru
http://www.dreamhackers.narod.ru
http://www.magictower.ru

В следующей главе "Бет" я расскажу вам о простейшем заклинании - заклинании инструкции NOP.

Заклинание кода: Бет

Aquila / HI-TECH

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

В прошлой главе данного фолианта я обещал рассказать вам о том, как заклинать инструкцию 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 единицу. Более того, наша попытка окажется успешной! :)

Заклинание кода: Гимель

Aquila / HI-TECH

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

К примеру, мы хотим поместить в EAX число 1. Сделать это просто, нужный опкод в данном случае 0B8h. А как задаётся число, которое надо поместить в регистр? Над этой проблемой билось не одно поколение заклинателей кода, но затем один из них, расшифровав письмена в Книге Двойных Слов, рассказал, что нужное число просто помещается сразу после опкода. То есть заклинание выглядит следующим образом:

        db      0B8h    ; mov eax,1
        dd      1

Обратите внимание: единица вставлена с помощью директивы dd, а в результате это будет последовательность байт 1,0,0,0. Также обратите внимание, что в последовательности байт единица идёт вначале. Это потому, что в архитектуре процессоров семейства x86 байты слов и двойных слов располагаются от младшего к старшему, а единица, безусловно, находится в младшем байте. Таким образом, я могу переписать вышеприведённое заклинание так:

        db      0B8h    ; mov eax, 1
        db      1,0,0,0

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

А что делать, если нужно поместить какое-либо число не в EAX, а в EDI? Данный опкод повинуется правилу, о котором было рассказано в предыдущей главе: к базовому опкоду (0B8h) следует прибавить номер регистра (номер EDI - 7). Это даёт 0B8h+7=0BFh. Давайте проверим это на примере простого заклинания:

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:

        db      0BFh    ; mov edi,100
        dd      100

        push    edi
        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'

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

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

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

Начнём с простого случая, с MOV EAX,EDX. Как нам составить заклинание? Базовый опкод инструкции "MOV регистр,регистр" - 08Bh, но правило, которое мы применяли в прошлые разы, здесь не поможет, ведь здесь два операнда, а не один.

Чтобы составить требуемое заклинание, нам придётся обратиться к помощи дополнительного байта, применяемого как раз в таких случаях. Этот байт называется ModR/M, и, если он есть, то располагается сразу после опкода. Сам ModR/M делится на три поля следующим образом:

  • Биты 6-7 - Mod
  • Биты 3-5 - Reg/Opcode (пока что я буду называть это поле просто Reg)
  • Биты 0-2 - R/M

Здесь поля Reg и R/M обычно служат для указания операндов. Помня вышеизложенное, можно легко составить заклинание - в Reg помещаем номер первого регистра, в R/M - номер второго. А в Mod помещаем 3 (почему, пока говорить не буду, ибо вы можете не выдержать обрушившегося на вас знания и цифровые духи будут пировать на ваших костях).

Для удобства ниже даны номера регистров в двоичном формате:

  • EAX - 000b
  • ECX - 001b
  • EDX - 010b
  • EBX - 011b
  • ESP - 100b
  • EBP - 101b
  • ESI - 110b
  • EDI - 111b

А 3, которое помещается в поле Mod, в двоичной системе счисления равна 11b. Вот и само заклинание для MOV EAX,EDX:

        db      08Bh,11000010b

Первый байт (08Bh) - это опкод. Второй байт можно логически разделить так: 11(Mod)-000(Reg=EAX)-010(R/M=EDX).

Теперь рассмотрим инструкцию "ADD регистр,регистр". Её базовый опкод равен 03h и она повинуется только что рассмотренному правилу, поэтому мы можем легко составить заклинание, например, для ADD ECX,EBX:

        db      03h,11001011b ; 03h(Опкод), 11(Mod)-001(Reg=ECX)-011(R/M=EBX)

Теперь давайте составим заклинание для следующей программы:

        mov     eax,10
        mov     edx,15
        add     eax,edx

В результате в eax должно получиться 25. Вот текст такой программы для тех, кто пока не сумел ввести его сам:

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:

        db      0B8h            ; mov eax, 10
        dd      10
        db      0BAh            ; mov edx, 15
        dd      15
        db      3,11000010b     ; add eax, edx

        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'

Если вы внимательно читали то, о чём говорилось в предыдущей главе, то вспомните, что PUSH EAX можно заменить на 50h. А как заменить PUSH _d? _d - это имя (оно же адрес) переменной, то есть 32-битное число. Опкод "PUSH число" 68h, поэтому соответствующее заклинание такое:

        db      68h
        dd      _d

ADD ESP,8 заколдовать тоже несложно. Базовый опкод "ADD регистр,число" 81h. Однако для этой инструкции не применимы заклинания, которые мы творили ранее, ведь здесь операндами выступают не два регистра, а только один регистр и число-константа. Как же быть? Очень просто: в Reg записывается 0, а в R/M - номер регистра. В Mod, как и ранее, помещаем 11b, а число должно идти сразу после опкода и байта ModR/M. В итоге получается следующая программа:

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:

        db      0B8h            ; mov eax, 10
        dd      10
        db      0BAh            ; mov edx, 15
        dd      15
        db      3,11000010b     ; add eax, edx

        db      50h             ; push eax
        db      68h             ; push _d
        dd      _d

        call    [printf]

        db      81h,11000100b   ; add esp, 8
        dd      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'

Надеюсь, вы усвоили материал данной главы. При необходимости проведите курс медитаций или (в крайнем случае) пишите мне по адресу aquila@wasm.ru.

В следующей главе я окончу свой рассказ о байте ModR/M и расскажу о секретах еще одного байта - SIB.

Заклинание кода: Далет

Aquila / HI-TECH

Говорят, что Баал, помимо написания своей книги, которая так и называется "Книга Баала", создаёт собственные операционные системы. Поистине с диавольской хитростью он смог сделать так, что почти каждый из нас пользуется одной из них, попадая таким образом в тень Баала, в сумерках которой рыщут его дети в поисках подати, которую не брезгуют взимать чем угодно, но преимущественно зеленью. Когда кто-нибудь, набравшись смелости, замечает Баалу, что поведение его детей слишком вызывающе и даже разрушительно для окружающей среди, он отвечает: "Но разве не достоин я некоторого количества денег от пользователей моих операционных систем? И разве не нужно моим кобольдам из индийских пределов есть, пить и отдыхать? Разве не работают они в поте лица своего, чтобы принести Человечеству лучшие операционные системы? Разве не должны вы отдать мне все свои деньги?". На последнем Баал обычно замолкает, понимая, что ляпнул что-то не то. На сем мы вернёмся к теме данного фолианта: к заклинанию кода.

В прошлой главе я рассказал, что такое байт ModR/M и показал некоторые из его возможностей, но далеко не все. Пришло время поговорить о поле Mod. Но прежде я хотел бы напомнить о том, какие режимы адресации есть в современных процессорах семейства x86.

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

        mov eax,[edx]

В данном примере в EAX копируется значение ячейки памяти, адрес которой находится в EDX. Также вам должно быть известно, что в ассемблерах процессоров семейства x86 возможны и такие финты:

        mov eax,[edx+ecx*4+1000]

Здесь в EAX копируется содержимое ячейки памяти, адрес которой вычисляется следующим образом: содержимое ECX умножается 4, прибавляется к содержимому EDX, а к полученному результату добавляется 1000. А ещё вам должно быть известно, что косвенная адресация может быть только в одном операнде, но не в двух. Поэтому инструкции, подобные следующей, недопустимы, а при попытке их трансляции будет выдана ошибка:

        mov [eax],[edx]

Возможно, у вас возникли вопросы: "А где можно взять полный список возможных режимов адресации? И насколько можно умножать регистры? На 4 можно - а на 10?". Ответ на эти вопросы можно получить с помощью магической формулы, данной интеловскими гномами в Книге Двойных Слов. Вот она:

        [регистр1+регистр2*множитель+смещение]

Регистр1 - это один из 8 регистров общего назначения. Регистр2 - тоже один из регистров общего назначения, за исключением ESP (почему - объяснено ниже). Множителем может быть число 1, 2, 4 или 8, то есть умножить на 10 или 100 не получится (а жаль). Смещение может быть 8-, 16- или 32-битным (последние два - в зависимости от того, в каком режиме работает программа, в 16- или 32-битном).

Теперь я научу вас составлять заклинания, в которых используется косвенная адресация, если множитель равен 1. Другие множители требуют использования ещё одного байта (SIB), но о нём будет рассказано несколько позднее.

Помните поле Mod в ModR/M? Как могли догадаться самые прозорливые, название этого поля происходит от слова MODe, которое означает "режим" (но не как "правящий режим", а как "режим работы"). Это потому, что поле Mod задаёт, что именно кодируется в R/M. Оказывается, в сочетании с Mod, R/M может кодировать не только регистры, но и различные режимы косвенной адресации. Отмечу, что назначение поля Reg не изменяется - в нём всегда кодируется номер регистра (либо расширение опкода, но об этом в другой главе).

Мы помещали в поле Mod 11b. Это означало, что в R/M находится номер регистра. А что представляют из себя другие режимы? Вот как интерпретируется значение поля R/M в режиме 00b.

  • 000b - [EAX]
  • 001b - [ECX]
  • 010b - [EDX]
  • 011b - [EBX]
  • 100b - за байтом ModR/M следует байт SIB
  • 101b - за ModR/M следует 32-битное смещение (адрес памяти)
  • 110b - [ESI]
  • 111b - [EDI]

Теперь давайте составим заклинание для следующего нехитрого действия:

        mov eax,[edx]

Думаю, взглянув на вышеприведённый список и зная, что опкод операции "MOV регистр,регистр/память" (где "регистр/память" означает, что параметр - это либо регистр, либо адрес ячейки памяти) 8Bh, вы легко догадаетесь, что получится:

        db 8Bh,00000010b ; 08Bh(Опкод), 00(Mod)-000(Reg=EAX)-010(R/M=[EDX])

Теперь предположим, что у нас есть переменная var, содержимое которой нужно скопировать в ESI. В FASM это записывается следующим образом:

        mov esi,[var] ; в MASM можно записать mov esi,var

Чтобы составить правильное заклинание, поместим, согласно списку выше, в поле R/M 101b, а после байта ModR/M поместим адрес переменной:

        db 8Bh,00110101b
        dd var

Чувствуете Силу? С каждым заклинанием вы становитесь все могущественнее и могущественнее. Но успокаиваться рано. Внимательный читатель может заметить, что в списке выше отсутствуют регистры EBP и ESP, хотя, как легко убедиться, инструкции MOV EAX,[EBP] и MOV EAX,[ESP] поддерживаются. Значит, должен быть какой-то другой путь, чтобы их закодировать? Безусловно, он есть, ведь мы ещё не рассмотрели другие возможные значения поля Mod, а посему перейдём к следующему значению - 01b. В этом случае поле R/M интерпретируется следующим образом:

  • 000b - [EAX] + 8-битное смещение
  • 001b - [ECX] + 8-битное смещение
  • 010b - [EDX] + 8-битное смещение
  • 011b - [EBX] + 8-битное смещение
  • 100b - за ModR/M следует SIB, а далее 8-битное смещение
  • 101b - [EBP] + 8-битное смещение
  • 110b - [ESI] + 8-битное смещение
  • 111b - [EDI] + 8-битное смещение

Из этого списка можно получить ответ, как же заколдовать инструкцию MOV EDI,[EBP]. Фактически, у нас получается MOV EDI,[EBP+0], а заклинание будет следующим:

        db 8Bh,01111101b,0 ; 08Bh(Опкод),
                           ; 01(Mod)-111(Reg=EDI)-101(R/M=[EBP+смещ8b]),
                           ; 0(8-битное смещение)

Если вы поняли, как работает режим 01b, то и режим 01b для вас не будет сложным.

  • 000b - [EAX] + 32-битное смещение
  • 001b - [ECX] + 32-битное смещение
  • 010b - [EDX] + 32-битное смещение
  • 011b - [EBX] + 32-битное смещение
  • 100b - за ModR/M следует SIB, а далее 32-битное смещение
  • 101b - [EBP] + 32-битное смещение
  • 110b - [ESI] + 32-битное смещение
  • 111b - [EDI] + 32-битное смещение

Давайте заколдуем инструкцию ADD ECX,[EBX+100000]:

        db 3,10001011b  ; 03h(Опкод)
                        ; 10(Mod)-001(Reg=ECX)-011(R/M=[EBX+смещ32b])
        dd 100000       ; 32-битное смещение

Остался последний режим (11b), но думаю, что здесь уже и так всё ясно, ведь мы неоднократно пользовались им в прошлой главе. Вот как интерпретируется поле R/M в режиме 11b:

  • 000b - EAX
  • 001b - ECX
  • 010b - EDX
  • 011b - EBX
  • 100b - ESP
  • 101b - EBP
  • 110b - ESI
  • 111b - EDI

Итак, теперь вы можете с лёгкостью заколдовывать инструкции, в которых есть косвенная адресация вида [регистр+смещение] или просто адрес переменной. Но как же окончательно обрести власть над инструкциями ассемблера и овладеть искусством заклинания всех режимов адресации? Ответ прост - читайте дальше! Ибо я собираюсь поведать вам о следующем поле, которое и применяется для этих целей - SIB.

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

Но вернёмся к делу. С помощью байта SIB можно задавать выражения вида [регистр1+регистр2*множитель+смещение]. Структура байта SIB такова:

  • Биты 6-7: множитель
    • 00b - 1
    • 01b - 2
    • 10b - 4
    • 11b - 8
    Биты 3-5: регистр2
    • 000b - EAX
    • 001b - ECX
    • 010b - EDX
    • 011b - EBX
    • 100b - регистр2 не используется
    • 101b - EBP
    • 110b - ESI
    • 111b - EDI
    Биты 0-2: регистр1
    • 000b - EAX
    • 001b - ECX
    • 010b - EDX
    • 011b - EBX
    • 100b - ESP
    • 101b - 32-битное смещение, если Mod в ModR/M равен 00, в противном случае EBP
    • 110b - ESI
    • 111b - EDI

Попробуйте следующее упражнение: глядя на приведённую таблицу произносите "Ом" на выдохе, а "Ам" на вдохе и одновременно чертите левой рукой круг, а правой - квадрат. Через несколько часов вы, без всякого сомнения, познаете Дао SIB, а я постараюсь вам в этом помочь своими мудрыми наставлениями и замечаниями. Давайте разомнем ваши косточки и немного потренируемся.

Начнём с MOV ECX,[EDI+ESI*4]. Разложим ModR/M: Mod должно быть равно 00, Reg - 001b (ECX), R/M - 100b (потому что далее следует SIB). Теперь разложим SIB: в поле множитель должно быть 10b (что указывает на значение 4), в регистр2 (который должен быть умножен на 4) - 110b (ESI), в регистр1 - 111b (EDI). В итоге получается следующее прекрасно работающее заклинание:

        db 8Bh,00001100b,10110111b

Вернёмся к вопросу о том, как заколдовать MOV EAX,[ESP]. Теперь вы можете это сделать, поскльку это довольно просто. В самом деле: Mod=00b, Reg=000b (EAX), R/M=100b (нам понадобится поле SIB). SIB: множитель=00b (*1), регистр2=100b (не используется), регистр1=100b (ESP).

        db 8Bh,00000100b,00100100b

Обратите внимание на следующее. Во-первых, регистр2 не может быть ESP, ведь значение 100b, которое должно было символизировать ESP, указывает, что регистр2 не используется. Соответственно, заколдовать инструкцию MOV EAX,[EDX+ESP*4] не получится (хотя MOV EAX,[ESP+EDX] или MOV EAX,[ESP+4*EDX] - вполне).

Во-вторых, если в Mod 00b, то 101b в регистр1 означает, что далее следует 32-битное смещение. Поэтому, чтобы заколдовать MOV EAX,[EBP+EDX], нужен описанный выше трюк: в Mod помещается 01b, а после SIB идет нулевое 8-битное смещение.

На досуге попытайтесь составить заклинания для различных инструкций.

В следующей главе речь пойдёт о том, как читать Книгу Двойных Слов.

Заклинание кода: Хей

Aquila / HI-TECH

В прошлом веке магия кода пользовалась почтением, а ремесленный подход к программированию, которым грешат многие нынешние адепты, был не так распространён. В те времена заклинание кода было популярно, особенно среди заклинателей демонов. Таинственные маги древности обладали невероятными на взгляд современного обывателя способностями: практически из ничего они вызывали грозных духов, ужасных привидений и призраков давно умерших программ, которые, как армия Франкенштейнов, собирали свою черную жатву. Однако разрушительное влияние Баала коснулось и магов. Со временем клан заклинателей демонов измельчал, старые герои сошли со сцены, на их место пришли новые маги. Но большинство из них отдало предпочтение бааловым лженаукам и дельфовщине. Они также создавали демонов, но их творения были убоги - так же, как и их создатели, и как инструменты, которые они использовали для своей псевдомагии. И поскольку судьба этих заклинателей не слишком завидна, перейдём к чтению Книги Двойных Слов. Да отступит тьма и прольётся на нас благословенный свет!

Книгу Двойных Слов написали интеловские гномы для того, чтобы можно было создавать заклинания для их процессоров. В ней три тома. Первый том посвящён основам архитектуры интеловских процессоров и о них излагается на протяжении более чем 400 страниц. Во втором томе рассказывается о формате инструкций процессора и приводится их подробное описание, что заняло почти 1000 страниц. Наконец, третий том предназначен брухо, которые хотят поселить в ЭВМ собственную операционную систему, посрамив тем самым Баала.

Нам понадобится второй том Книги Двойных Слов. Если вы до сих пор не получили свою копию в пристанище интеловских гномов в Междусети, самое время сделать это сейчас.

Теперь откроем том на 472 странице. Это описание знакомой нам инструкции MOV. Здесь можно видеть, что оно состоит из двух частей: краткий обзор (в квадратной рамке) и подробный (все остальное). Сейчас нас интересует то, что находится в квадратной рамке:

Opcode      Instruction        Description
88 /r       MOV r/m8,r8        Move r8 to r/m8
89 /r       MOV r/m16,r16      Move r16 to r/m16
89 /r       MOV r/m32,r32      Move r32 to r/m32
8A /r       MOV r8,r/m8        Move r/m8 to r8
8B /r       MOV r16,r/m16      Move r/m16 to r16
8B /r       MOV r32,r/m32      Move r/m32 to r32
8C /r       MOV r/m16,Sreg**   Move segment register to r/m16
8E /r       MOV Sreg,r/m16**   Move r/m16 to segment register
A0          MOV AL,moffs8*     Move byte at (seg:offset) to AL
A1          MOV AX,moffs16*    Move word at (seg:offset) to AX
A1          MOV EAX,moffs32*   Move doubleword at (seg:offset) to EAX
A2          MOV moffs8*,AL     Move AL to (seg:offset)
A3          MOV moffs16*,AX    Move AX to (seg:offset)
A3          MOV moffs32*,EAX   Move EAX to (seg:offset)
B0+ rb      MOV r8,imm8        Move imm8 to r8
B8+ rw      MOV r16,imm16      Move imm16 to r16
B8+ rd      MOV r32,imm32      Move imm32 to r32
C6 /0       MOV r/m8,imm8      Move imm8 to r/m8
C7 /0       MOV r/m16,imm16    Move imm16 to r/m16
C7 /0       MOV r/m32,imm32    Move imm32 to r/m32

Opcode - это опкод, Instruction - инструкция, а Description - описание. Посмотрите внимательно. Очевидно, что для колдования инструкции MOV есть несколько опкодов (довольно много, если быть точным). Какие же и когда применять? И по каким правилам?

Правила задаются в графе Instruction, рядом находится краткое описание. Всё, что нужно - это уметь читать условные обозначения ("/r", "r8" и т.п.). Далее приведён полный список обозначений, используемых интеловскими гномами. Практически, это перевод одной из глав Книги Двойных Слов. Нижеприведенные описания не обязательно читать подробно сразу - они пригодятся при разборе конкретных опкодов и выполняют справочную роль.

-= Начались даоподобные слова из Книги Двойных слов =-

Обозначения в колонке Opcode

/цифра - в Reg/Opcode всегда находится это число. Своёго рода расширение опкода за счёт поля Reg в ModR/M. Также это значит, что ModR/M задаёт только один операнд (r/m - регистр/память).

/r - ModR/M задаёт оба операнда (регистр и r/m).

eb, cw, cd, cp - однобайтное (eb), двухбайтное (cw), четырехбайтное (cd) или шестибайтное (cp) значение. Следует за опкодом и, возможно, задаёт новое значение регистра.

ib, iw, id - однобайтное (ib), двухбайтное (iw) или четырёхбайтное (id) непосредственное значение (число). Следует за опкодом, ModR/M и SIB (если таковые есть). Со знаком или без знака это значение - определяется опкодом.

+rb, +rw, +rd - номер регистра от нуля до 7. Добавляется к байту слева от знака "+". В результате получается окончательный опкод. Ниже даны номера регистров.

        rb      rb      rd
        --      --      --
  0     AL      AX      EAX
  1     CL      CX      ECX
  2     DL      DX      EDX
  3     BL      BX      EBX
  4     AH      SP      ESP
  5     CH      BP      EBP
  6     DH      SI      ESI
  7     BH      DI      EDI

+i - число от 0 до 7. Добавляемое к байту опкода слева от знака "+". Это число используется в инструкциях FPU, когда одним из операндов является ST(i) (FPU-регистр).

Обозначения в колонке Instruction

rel8 - относительный адрес (смещение). Задаёт смещение от 128 байт перед концом инструкции до 127 байт после конца инструкции. Иными словами, смещение от -128 до 127 байт относительно адреса после конца инструкции.

rel16 и rel32 - относительный адрес в пределах того же сегмента кода, в котором находится инструкция. rel16 относится к инструкциям с 16-битным операндом, rel32 - к инструкциям с 32-битным операндом.

ptr16:16 и ptr16:32 - дальний (far) указатель. "16:16" говорит о том, что значение указателя состоит из двух частей. Слева - 16-битное значение для сегментного регистра, справа - смещение внутри сегмента. ptr16:16 относится к инструкциям с 16-битным операндом, ptr16:32 - к 32-битным.

r8 - один из однобайтных регистров общего назначения (AL, CL, DL, BL, AH, CH, DH или BH).

r16 - один из двухбайтных регистров общего назначения (AX, CX, DX, BX, SP, BP, SI или DI).

r32 - один из четырехбайтных регистров общего назначения (EAX, ECX, EDX, EBX, ESP, EBP, ESI или EDI).

imm8 - непосредственное однобайтное значение. Это число в диапазоне от -128 до +127. В инструкциях, где imm8 скомбинировано с 16- или 32-битным операндом, непосредственное значение расширяется до размера операнда с сохранением знака (знаковое расширение: старшие байты операнда заполняются старшим битом непосредственного значения).

imm16 - непосредственное двухбайтное значение. Это число в диапазоне от -32768 до +32767. Используется для инструкций с 16-битным операндом.

imm32 - непосредственное четырёхбайтное значение. Это число в диапазоне от -2,147,483,648 до +2,147,483,647. Используется для инструкций с 32-битным операндом.

r/m8 - однобайтный операнд. Является либо регистром общего назначения (AL, BL, CL, DL, AH, BH, CH и DH), либо адресом байта памяти.

r/m16 - двухбайтный регистр общего назначения (AX, BX, CX, DX, SP, BP, SI или DI), либо адрес ячейки памяти. Используется для инструкций с 16-битным операндом.

r/m32 - четырехбайтный регистр общего назначения (EAX, EBX, ECX, EDX, ESP, EBP, ESI или EDI), либо адрес ячейки памяти. Используемая для инструкций с 32-битным операндом.

m - адрес 16- или 32-битной ячейки памяти.

m8 - адрес однобайтной ячейки памяти в регистре (E)SI или (E)DI. Используется только со строковыми инструкциями.

m16 - адрес двухбайтной ячейки памяти в регистре (E)SI или (E)DI. Используется только со строковыми инструкциями.

m32 - адрес четырёхбайтной ячейки памяти в регистре (E)SI или (E)DI. Используется только со строковыми инструкциями.

m64 - адрес 64-битной (учетверённое слово) ячейки памяти. Используется только с инструкцией CMPXCHG8B.

m128 - адрес 128-битной (увосьмерённое слово) ячейки памяти. Используется только с инструкциями SSE и SSE2.

m16:16, m16:32 - адрес ячейки памяти, содержащей дальний указатель.

m16&32, m16&16, m32&32 - адрес ячейки памяти, состоящей из нескольких элементов, чьи размеры задаются справа и слева от амперсанда. Доступны все режимы адресации памяти. Операнды m16&16 и m32&32 используются инструкцией BOUND (в них задаются верние и нижние границы массива). Операнд m16&32 используется LIDT и LGDT.

moffs8, moffs16, moffs32 - непосредственный адрес (смещение) байта, слова или двойного слова в памяти (число в moffs показывает размер ячейки памяти); байт ModR/M не представлен. Используется некоторыми вариантами инструкции MOV.

Sreg - сегментный регистр. Номера сегментных регистров в поле Reg байта ModR/M: 0=ES, 1=CS, 2=SS, 3=DS, 4=FS и 5=GS.

m32fp, m64fp, m80fp - адрес в памяти операнда с плавающей точкой одинарной точности, двойной точности или расширенной точности. Используются инструкциями FPU.

m16int, m32int, m64int - адрес в памяти целочисленного операнда размером в слово, двойное слово или учетверённое слово.

ST или ST(0) - верхний элемент стека регистров FPU.

ST(i) - i-тый элемент (от 0 до 7) от вершины стека регистров FPU.

mm - 64-битный MMX-регистр (от MM0 до MM7).

mm/m32 - младшие 32 бита MMX-регистра (от MM0 до MM7) или адрес 32-битной ячейки памяти.

mm/m64 - MMX-регистр (от MM0 до MM7) или адрес 64-битной ячейки памяти.

xmm - 128-битный XMM-регистр (от XMM0 до XMM7).

xmm/m32 - XMM-регистр (от XMM0 до XMM7) или адрес 32-битной ячейки памяти.

xmm/m64 - XMM-регистр (от XMM0 до XMM7) или адрес 64-битной ячейки памяти.

xmm/m128 - XMM-регистр (от XMM0 до XMM7) или адрес 128-битной ячейки памяти.

-= Закончились даоподобные слова из Книги Двойных слов =-

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

Обратимся к инструкции INC. Вот её описание:

Opcode       Instruction        Description
FE /0        INC r/m8           Increment r/m byte by 1
FF /0        INC r/m16          Increment r/m word by 1
FF /0        INC r/m32          Increment r/m doubleword by 1
40+ rw       INC r16            Increment word register by 1
40+ rd       INC r32            Increment doubleword register by 1

Мы увеличивали EAX на единицу с помощью опкода 40h, к которому прибавлялся номер регистра. Проверим, соответствует ли это описанию из Книги Двойных Слов. Нам нужна последняя строчка:

40+ rd       INC r32            Increment doubleword register by 1

В колонке Opcode указано "40+ rd". Согласно Книге Двойных Слов, "+rd" означает, что номер регистра прибавляется к байту слева от знака "+". Слева - 40h. Всё верно, это соответствует формуле "базовый опкод + номер регистра", которая вы использовали раньше, опираясь на мои наставления. Теперь вы можете опираться на Книгу Двойных Слов.

А что, если нужно увеличить на 1 не регистр, а ячейку памяти, на которую этот регистр указывает (например [eax])? Ищем нужную строчку в описании инструкции INC и находим:

FF /0        INC r/m32          Increment r/m doubleword by 1

Начнём с Opcode. "/0" говорит о том, что поле Reg в ModR/M всегда равно 0. Это неудивительно, поскольку здесь всего один операнд. "r/m32" задаёт либо регистр, либо ячейку памяти размером в двойное слово, используя поля ModR/M и, возможно, SIB. Зная это, можно легко составить заклинание:

        db 0FFh,00000000b ; FFh(Опкод), 00(Mod)-000(Reg="/0")-000(R/M=[EAX])

Обратите внимание: с помощью опкода "FF /0" можно заколдовать также и "INC EAX". В самом деле:

        db 0FFh,11000000b ; FFh(Опкод), 11(Mod)-000(Reg="/0")-000(R/M=EAX)

Не верите? Вот работающий код:

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
var     dd 1024

section '.code' code executable readable

start:

        mov     eax,5           ; Помещаем в eax 5
        db      40h             ; inc eax
        db      0FFh,11000000b  ; inc eax
                        ; FFh(Опкод), 11(Mod)-000(Reg="/0")-000(R/M=EAX)

        push    eax             ; В eax - 7
        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'

Чудеса? Именно так. Интеловские гномы даровали нам много чудес и необъяснимых явлений. Похожая ситуация обстоит и с MOV. Для того, чтобы заколдовать "MOV ECX,EDX", можно использовать как

8B /r MOV r32,r/m32 Move r/m32 to r32

так и

89 /r MOV r/m32,r32 Move r32 to r/m32

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

Заклинание кода: Вав

Aquila / HI-TECH

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

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

Предположим, в нашей программе есть следующий код:

        mov esi,50
        mov edi,20
        imul esi,5
        add esi,edi
        mov eax,esi

Очевидно, этот код вычисляет следующее значение в eax: esi*5+edi=50*5+20=270. А теперь мы хотим, чтобы наша программа модифицировала сама себя так, чтобы приведенный код превратился в:

        mov ecx,50
        mov edx,20
        imul edx,5
        sub edx,ecx
        mov eax,edx

Это можно записать как edx*5-ecx=20*5-50=50. "Но возможно ли, чтобы программа без нашего участия сама себя изменила?", - может спросить иной читатель. Да, возможно. Разумеется, если мы правильно её запрограммируем и будем следовать канонам алхимии, а именно - смешивать всё в правильных пропорциях и давать свои зелья на пробу другим, прежде чем пить их самим.

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

        db 0BEh ; mov ecx, 50
        dd 50
        db 0BFh ; mov edx, 20
        dd 20

Чтобы выяснить заклинание для третьей инструкции, следует обратиться к Книге Двойных Слов. Из всех опкодов нам подходит следующий:

6B /r ib         IMUL r32,r/m32,imm8

Согласно описанию, в поле Reg задается регистр назначения, в R/M - регистр или адрес ячейки памяти с умножаемым значением, а за опкодом следует байт с множителем. Таким образом, у нас получается следующее:

        db 6Bh,11110110b,8

С операцией сложения регистров мы также уже имели дело:

        db 3,11110111b

Как вы должны помнить из предыдущей главы, "MOV регистр,регистр" можно закодировать двумя способами. Как выяснилось, FASM заколдовал нужную нам инструкцию сложения так:

01 /r    ADD r/m32,r32     Add r32 to r/m32

А мы в предыдущих главах использовали следующую инструкцию:

03 /r    ADD r32,r/m32     Add r/m32 to r32

Не страшно. Это просто результат того, что набор инструкций избыточный и к одной и той же цели ведут разные пути. Главное - аккуратно определить заклинание:

        db 1,11111110b ; 03(Опкод), 11(Mod)-111(Reg=EDI)-110(R/M=ESI)

Теперь перейдем к самомодифицированию программы. Вот рабочий текст:

format PE console
entry start

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

section '.data' data readable writeable

_d      db '%d', 0
var     dd 50

section '.code' code executable readable writeable

start:

                mov     [.mov_esi_imm],byte 0B9h
                mov     [.mov_edi_imm],byte 0BAh
                mov     [.imul_esi_imm+1],byte 11010010b
                mov     [.add_esi_edi],byte 02Bh
                mov     [.add_esi_edi+1],byte 11010001b
                mov     [.mov_eax_esi+1],byte 11010000b

                ; Модифицируемый код
 .mov_esi_imm:   mov     esi,50
 .mov_edi_imm:   mov     edi,20
 .imul_esi_imm:  imul    esi,5
 .add_esi_edi:   add     esi,edi
 .mov_eax_esi:   mov     eax,esi

                ; Проверка модификации
                push    edx
                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'

Строчка "section '.code' code executable readable writeable" указывает, что в сегмент кода можно писать. Мы модифицируем наш код очень простым методом: поверх имеющихся кодов записываем новые. После этого управление получает уже модифицированный код.

Как видите, самомодифицируемый код - это не так сложно: у вас есть код, и он сам себя модифицирует.

Заключение

А вообще, народная мудрость свидетельствует о том, что "синдром шворца" - явление целиком и полностью надуманное. Потому что известно издревна: сколько волка не корми - а у слона шворц все равно толще.

И да пребудет с вами сила, братья по безумию!



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное