allasm.ru

    Меню

 

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

В прошлой главе я рассказал, как читать Книгу Двойных Слов. Теперь вы можете колдовать почти любой 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" указывает, что в сегмент кода можно писать. Мы модифицируем наш код очень простым методом: поверх имеющихся кодов записываем новые. После этого управление получает уже модифицированный код.

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

  [C] Aquila / WASM.RU