Marylin
18.08.2021, 12:44
Всем привет!
Когда-тоя начал (https://forum.antichat.xyz/threads/575535/)разговор о том, как развлекалось наше старшее поколение, какие придумывали техники обхода дизассемблеров, и как противостояли отладчикам. Сейчас вот выбралось пару свободных деньков и решил продолжить эту тему, собрав в ней наиболее интересные на мой взгляд моменты. Не нужно сбрасывать со-счетов старые финты программирования – на их основе и сейчас создаются крепости, поскольку любые идеи имеют тенденцию заканчиваться, а ничего нового большинство из нас придумать не в состоянии.
1. Самомодификация кода
Система MS-DOS была колыбелью ныне могущественной империи Win. Она выгодно отличалась тем, что предоставляла программистам полную свободу действий, т.к. защита любого уровня в ней отсутствовала как сущность. Вплоть до Win2K это была поистине золотая эпоха, с благоприятной почвой для самовыражения. Вирусы и прочая нечисть плодилась с первой космической скоростью, а программисты закинув ноги на стол старались удивить друг-друга доселе невиданными идеями. (чего только стоит один червь Морриса, который в 88-году заразил чуть-ли не добрую половину сети ARPANET). Именно в то время разум гомосапиенса придумал первые стелс-вирусы, начал применять полиморфные движки, криптографию, и многое другое.
Отдельную нишу в этом котле занял и код, который модифицировал сам-себя. Несмотря на то, что техника требовала хороших знаний формата команд процессора, нервов и огромной самоотдачи – результат того стоил. Наблюдая как код мутирует прямо на глазах в реальном времени, антивирусы сходили с ума и полностью отказывались предпринимать какие-либо попытки анализа. Чтобы хоть как-то взять ситуацию под контроль, аверы стали применять эвристику, на которую хакеры тут-же нашли управу.
Самомодификация это то направление, зубрить которое не имеет смысла. Тут главное понять суть и предоставляемые процессором возможности, а дальше можно будет на автомате творить весьма интересные вещи. Поскольку тему невозможно охватить всю и сразу, то здесь мы возродим лишь парочку забытых идей.
https://forum.antichat.xyz/attachments/4910089/img_95f37ee16c.png
На рисунке выше представлен формат инструкций современных процессоров х86-32/64, где только поле "Код операции" является обязательным, а остальные могут отсутствовать в инструкциях. Размер опкода пляшет в диапазоне 1..3 байта, и в клинических случаях хвост его может торчать даже в соседнем поле "Mod-R/M". Как правило, 2..3-байтными опкодами кодируются инструкции SIMD, поэтому на данный момент они не интересны – нам будет достаточно взять на вооружение формат 1-байтного кода операции, на карту которого указывает красная стрелка.
Как видно из деталей битовой карты, младшая тетрада опкода достаточно творческая, и в ней закодировано аж 5 определений. В зависимости от типа инструкции, младший бит[0] задаёт размер операндов – если он сброшен в нуль, значит операнды размером 1-байт, и соответственно единичное его состояния указывает на 2 и более байтный операнд (word, dword, qword). Три младших бита[2:0] кодируют 8 регистров общего назначения РОН:
Код:
Инструкция Код HEX Опкод BIN
----------------|------------|-----------------
mov eax,ebx | 89.D8 | 89 = 1000.1001 ;<--- операнды размером DWORD, бит[0]=1
mov al,bl | 88.D8 | 88 = 1000.1000 ;<--- операнды размером BYTE, бит[0]=0
----------------+------------+-----------------
Если это инструкция условного перехода, то само условие определяется в битах[3:1], а бит[0] при этом обращает условие наоборот. Три выделенных под условие бита способны выбрать одно из 8-ми условий, типа
JZ, JS, JB
и т.д. Таким образом, изменив всего один младший бит, мы можем инвертировать условные переходы. Вот как это выглядит в отладчике:
C-подобный:
start
:
dw
0x9090
;
//<--- опкоды инструкции NOP
jz @f
;
// условие типа "Zero"
jnz @f
;
// ..(его инверсия)
js @f
;
// условие типа "Sign"
jns @f
;
// ..(его инверсия)
@@
:
dw
0x9090
https://forum.antichat.xyz/attachments/4910089/img_c0c6d312f8.png
Обратите внимание, как в условных инструкциях кодируется адрес перехода. Команды этого типа могут совершать прыжки только в пределах 127-байт, что кодируется всего одним байтом (в данном случае 06,04,02,00) –это адреса относительно текущего. Если переход осуществляется вперёд, под его адрес отводится младшая половина байта в диапазоне от 00h до 7Fh, а это как-раз макс.127. Если-же прыгаем назад, то для адресации используется уже старшая половина 80..FFh (байт со-знаком). В случае, когда нужен условный прыжок выше этого порога, придётся использовать безусловный JMP, который может отправить нас хоть в космос.
1.0. Бит направления
Однако более широкий интерес для самомодификации предоставляет поле "Направление" в опкоде – под него выделен бит[1]. Оно введено инженерами Intel для обозначения операнда-приёмника: нулевое значение присваивает результат правому операнду, а единица – левому. Так, манипулируя этим битом при помощи
OR/AND
, мы можем скрытно менять операнды местами. Причём этим операндом может быть как регистр, так и память, что более востребовано с практической точки зрения. Посмотрим на такой код:
C-подобный:
;
//----------
.
data
count dd
0
;
//<--- переменная для записи значения
;
//----------
section
'.code'
code executable writable
;
//<--- Отрываем секцию-кода на запись!
start
:
nop
mov eax
,
edx
;
// инструкция с двумя операндами
nop
or byte
[
@f
]
,
10
b
;
//<--- взводим бит[1] "Направление"
nop
@@
:
mov eax
,
edx
;
// эти регистры поменяются местами!
dw
0x9090
;
// Пример скрытой записи в память
;
//--------------------------------
mov eax
,
12345678
h
;
// Значение для записи
nop
or byte
[
@f
]
,
10
b
;
// <--- взводим бит[1] "Направление"
nop
@@
:
mov eax
,
[
count
]
;
// Теперь запись произойдёт не в регистр, а в память!
dw
0x9090
@exit
:
cinvoke _getch
cinvoke exit
,
0
https://forum.antichat.xyz/attachments/4910089/img_84a4307fe5.png
Основное условие при написание самомодифицируемого кода, это доступ к "секции-кода" на запись. Здесь нужно отметить, что в этом нет ничего криминального, и антивирусы никак не реагируют на это обстоятельство. В наше время софт разбух до таких размеров, что программисты сжимают и упаковывают все свои продукты, а чтобы при старте потом его распаковать, секции-кода по любому требуется доступ на запись. Если-бы аверы обращали на это внимание, то в природе не осталось-бы ни одного софта.
1.1. Бит направления при операнде с непосредственным значением
Ладно.. теперь мы знаем, что бит[1] направления, способен менять операнды местами.
Но рассмотрим другую ситуацию, когда один из операндов регистр (или память), а второй – непосредственное значение. Ясно, что это значение не может быть приёмником, например инструкция
ADD EAX,0x55
. Проблема в том, что процессор требует одинаковые размеры операндов. То-есть данное значение
55h
ассемблер переведёт в 4-байтное
0x00000055
, чтобы подогнать его под размер регистра EAX. Если брать в корень, то по правилам арифметики нули слева не играют никакой роли, но попробуйте это объяснить процессору. И нужно сказать, инженеры Intel смогли всё-таки обмануть проц, при помощи всё того-же бита[1] направления.
Когда операнд = значение, единичное состояние этого бита отсекает нули слева, в результате чего мы экономим как минимум 1-байт, а в лучшем случае целых 3-байта. Радует то, что все современные компиляторы осведомлены об этом нюансе, и по умолчанию взводят бит[1] в опкоде инструкции. Однако если его умышленно сбросить в нуль, то процессор не дополнит значение
55h
нулями, а заберёт из кода следующие за командой
ADD EAX,55h
три байта, посчитав их продолжением значения
55h
. То-есть он включит дурака и начнёт выравнивать операнды на 4-байта. Пользуясь этим глюком, можно прибавь к EAX например не
55h
, а заранее подготовив 3-следующих байта, преобразовать значение в
0x22334455
. Посмотрим на такой код:
C-подобный:
section
'.code'
code executable writable
start
:
nop
add eax
,
0x55
;
// текущая инструкция
nop
and byte
[
@f
]
,
11111101
b
;
//<--- сбрасываем бит направления!
nop
@@
:
add eax
,
0x55
;
// здесь получим "ADD EAX,0x22334455"
dw
0x3344
;
// подставные значения
db
0x22
;
// ^^^^^
nop
nop
@exit
:
cinvoke _getch
cinvoke exit
,
0
https://forum.antichat.xyz/attachments/4910089/img_70afa2ef09.png
Обратите внимание на опкод(83h) по адресу
0x0040200d
. После того-как инструкци
AND
сбрасывает бит[1], его значение получается
81h
и у инструкции
ADD
сразу-же срывает крышу, в результате чего она берёт в охапку следующие 3-байта
0x223344
считая их своими. Как показывают эти элементарные примеры, всего один бит может ввести в ступор начинающих реверсеров, особенно если они не освоили толком интерактивный отладчик.
2. Прячем код от дизассемблеров
В те далёкие время приличный дебагер был роскошью, и всё на что могли рассчитывать кодокопатели – это штатный debug.com, или в лучшем случае "TurboDebugger". Правда позже появился более приличный "AVP-Util" от аверов из лаборатории Касперского, но и он мог отлаживать только досовские приложения. Дела поправились лишь с приходом классики всех времён и народов "Soft-Ace". Таким образом, основным оружием были дизассемблеры типа "Hiew", которые не отлаживали приложения, а лишь предоставляли взору его код.
Одним из запоминающихся способов сокрытия исходника от посторонних глаз предложил вирус под названием CALL.243 (в младенчестве JUMP.466), который прятал полезную нагрузку в адресах переходов. Он был прост до неприличия, зато кроме инструкций
CALL
не содержал в своей тушке ничего – рассмотрим его реализацию.
Инструкция
CALL
предназначена для вызова процедур – компиляторы заменили её макросом-прототипом "Invoke". Соответственно в качестве операнда ей требуется адрес вызываемой процедуры, который на системах х32 имеет размер 4-байта (на х64 уже 8-байт). Под виндой, опкодом этой инструкции является 2-байтное значение
FF15h
, однако если заглянуть в таблицу инструкций, можно обнаружить и 1-байтный опкод
Е8h
, что демонстрирует скрин из Интеловской доки "Instruction Set Reference":
https://forum.antichat.xyz/attachments/4910089/img_5de7be5d7f.png
Теперь, если взять один реальный
CALL
и после него вставить фиктивный, то получим непрерывную цепочку этих инструкций. В этом случае, 4-байтный адрес перехода фиктивной инструкции мы можем использовать в своих целях, например вставить вместо него код расшифровки сообщения
XOR BYTE[EDI],77h + INC EDI
(как-раз получается 4-байта). Реальный-же
CALL
должен будет перепрыгивать фиктивный.
В примере ниже, в качестве адреса реального вызова я использую текущий($) + смещение, которое получается(6). Так осуществляется вызов следующей инструкции
CALL $+6
, и т.д. Вот пример такой махинации:
C-подобный:
@@
:
call $
+
6
;
// пропускаем нижеследующие 5 байт
db
0e8
h
;
// фиктивный опкод "call near"
xor byte
[
edi
]
,
77
h
;
// ..4 байта его адреса,
inc edi
;
// ..которые используем в своих целях
call $
+
6
;
// следующий реальный CALL
.
.
.
К сожалению, на системах х32 в таких "ямах" получить больше четырёх/халявных байт не получиться, а вот на х64 в нашем распоряжении уже 8-байт, внутри которых можно разместить слона. В демке ниже нужно предварительно зашифровать строку "Hello World!", а код её расшифрует рассмотренным способом:
C-подобный:
format pe console
include
'win32ax.inc'
;
entry start
;
//-----------
.
data
szText db
'Hello World!'
,
0
szCapt db
'Self-Mod'
,
0
;
//-----------
.
code
start
:
push szText
pop edi
;
// EDI = адрес строки
@@
:
call $
+
6
db
0e8
h
;
// call near opсode
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
add esp
,
14
*
4
;
//<---- выравнивание стека от адресов возврата
invoke MessageBox
,
0
,
szCapt
,
0
,
0
invoke ExitProcess
,
0
.
end start
https://forum.antichat.xyz/attachments/4910089/img_32e982d269.png
3. Заключение
Качество кода имеет различные метрики – в некоторых случаях нужно стремиться к его оптимизации, а в некоторых наоборот приправить так, чтобы не оставить взломщику никаких шансов. Старшее поколение программистов оставило нам огромный багаж знаний, а представленные здесь примеры лишь капля в этом океане. Однако опыт приобретается не сразу, а со-временем по таким вот мелким наякам. На сегодня это всё, о чём я хотел сказать, удачи пока!
Когда-тоя начал (https://forum.antichat.xyz/threads/575535/)разговор о том, как развлекалось наше старшее поколение, какие придумывали техники обхода дизассемблеров, и как противостояли отладчикам. Сейчас вот выбралось пару свободных деньков и решил продолжить эту тему, собрав в ней наиболее интересные на мой взгляд моменты. Не нужно сбрасывать со-счетов старые финты программирования – на их основе и сейчас создаются крепости, поскольку любые идеи имеют тенденцию заканчиваться, а ничего нового большинство из нас придумать не в состоянии.
1. Самомодификация кода
Система MS-DOS была колыбелью ныне могущественной империи Win. Она выгодно отличалась тем, что предоставляла программистам полную свободу действий, т.к. защита любого уровня в ней отсутствовала как сущность. Вплоть до Win2K это была поистине золотая эпоха, с благоприятной почвой для самовыражения. Вирусы и прочая нечисть плодилась с первой космической скоростью, а программисты закинув ноги на стол старались удивить друг-друга доселе невиданными идеями. (чего только стоит один червь Морриса, который в 88-году заразил чуть-ли не добрую половину сети ARPANET). Именно в то время разум гомосапиенса придумал первые стелс-вирусы, начал применять полиморфные движки, криптографию, и многое другое.
Отдельную нишу в этом котле занял и код, который модифицировал сам-себя. Несмотря на то, что техника требовала хороших знаний формата команд процессора, нервов и огромной самоотдачи – результат того стоил. Наблюдая как код мутирует прямо на глазах в реальном времени, антивирусы сходили с ума и полностью отказывались предпринимать какие-либо попытки анализа. Чтобы хоть как-то взять ситуацию под контроль, аверы стали применять эвристику, на которую хакеры тут-же нашли управу.
Самомодификация это то направление, зубрить которое не имеет смысла. Тут главное понять суть и предоставляемые процессором возможности, а дальше можно будет на автомате творить весьма интересные вещи. Поскольку тему невозможно охватить всю и сразу, то здесь мы возродим лишь парочку забытых идей.
https://forum.antichat.xyz/attachments/4910089/img_95f37ee16c.png
На рисунке выше представлен формат инструкций современных процессоров х86-32/64, где только поле "Код операции" является обязательным, а остальные могут отсутствовать в инструкциях. Размер опкода пляшет в диапазоне 1..3 байта, и в клинических случаях хвост его может торчать даже в соседнем поле "Mod-R/M". Как правило, 2..3-байтными опкодами кодируются инструкции SIMD, поэтому на данный момент они не интересны – нам будет достаточно взять на вооружение формат 1-байтного кода операции, на карту которого указывает красная стрелка.
Как видно из деталей битовой карты, младшая тетрада опкода достаточно творческая, и в ней закодировано аж 5 определений. В зависимости от типа инструкции, младший бит[0] задаёт размер операндов – если он сброшен в нуль, значит операнды размером 1-байт, и соответственно единичное его состояния указывает на 2 и более байтный операнд (word, dword, qword). Три младших бита[2:0] кодируют 8 регистров общего назначения РОН:
Код:
Инструкция Код HEX Опкод BIN
----------------|------------|-----------------
mov eax,ebx | 89.D8 | 89 = 1000.1001 ;<--- операнды размером DWORD, бит[0]=1
mov al,bl | 88.D8 | 88 = 1000.1000 ;<--- операнды размером BYTE, бит[0]=0
----------------+------------+-----------------
Если это инструкция условного перехода, то само условие определяется в битах[3:1], а бит[0] при этом обращает условие наоборот. Три выделенных под условие бита способны выбрать одно из 8-ми условий, типа
JZ, JS, JB
и т.д. Таким образом, изменив всего один младший бит, мы можем инвертировать условные переходы. Вот как это выглядит в отладчике:
C-подобный:
start
:
dw
0x9090
;
//<--- опкоды инструкции NOP
jz @f
;
// условие типа "Zero"
jnz @f
;
// ..(его инверсия)
js @f
;
// условие типа "Sign"
jns @f
;
// ..(его инверсия)
@@
:
dw
0x9090
https://forum.antichat.xyz/attachments/4910089/img_c0c6d312f8.png
Обратите внимание, как в условных инструкциях кодируется адрес перехода. Команды этого типа могут совершать прыжки только в пределах 127-байт, что кодируется всего одним байтом (в данном случае 06,04,02,00) –это адреса относительно текущего. Если переход осуществляется вперёд, под его адрес отводится младшая половина байта в диапазоне от 00h до 7Fh, а это как-раз макс.127. Если-же прыгаем назад, то для адресации используется уже старшая половина 80..FFh (байт со-знаком). В случае, когда нужен условный прыжок выше этого порога, придётся использовать безусловный JMP, который может отправить нас хоть в космос.
1.0. Бит направления
Однако более широкий интерес для самомодификации предоставляет поле "Направление" в опкоде – под него выделен бит[1]. Оно введено инженерами Intel для обозначения операнда-приёмника: нулевое значение присваивает результат правому операнду, а единица – левому. Так, манипулируя этим битом при помощи
OR/AND
, мы можем скрытно менять операнды местами. Причём этим операндом может быть как регистр, так и память, что более востребовано с практической точки зрения. Посмотрим на такой код:
C-подобный:
;
//----------
.
data
count dd
0
;
//<--- переменная для записи значения
;
//----------
section
'.code'
code executable writable
;
//<--- Отрываем секцию-кода на запись!
start
:
nop
mov eax
,
edx
;
// инструкция с двумя операндами
nop
or byte
[
@f
]
,
10
b
;
//<--- взводим бит[1] "Направление"
nop
@@
:
mov eax
,
edx
;
// эти регистры поменяются местами!
dw
0x9090
;
// Пример скрытой записи в память
;
//--------------------------------
mov eax
,
12345678
h
;
// Значение для записи
nop
or byte
[
@f
]
,
10
b
;
// <--- взводим бит[1] "Направление"
nop
@@
:
mov eax
,
[
count
]
;
// Теперь запись произойдёт не в регистр, а в память!
dw
0x9090
@exit
:
cinvoke _getch
cinvoke exit
,
0
https://forum.antichat.xyz/attachments/4910089/img_84a4307fe5.png
Основное условие при написание самомодифицируемого кода, это доступ к "секции-кода" на запись. Здесь нужно отметить, что в этом нет ничего криминального, и антивирусы никак не реагируют на это обстоятельство. В наше время софт разбух до таких размеров, что программисты сжимают и упаковывают все свои продукты, а чтобы при старте потом его распаковать, секции-кода по любому требуется доступ на запись. Если-бы аверы обращали на это внимание, то в природе не осталось-бы ни одного софта.
1.1. Бит направления при операнде с непосредственным значением
Ладно.. теперь мы знаем, что бит[1] направления, способен менять операнды местами.
Но рассмотрим другую ситуацию, когда один из операндов регистр (или память), а второй – непосредственное значение. Ясно, что это значение не может быть приёмником, например инструкция
ADD EAX,0x55
. Проблема в том, что процессор требует одинаковые размеры операндов. То-есть данное значение
55h
ассемблер переведёт в 4-байтное
0x00000055
, чтобы подогнать его под размер регистра EAX. Если брать в корень, то по правилам арифметики нули слева не играют никакой роли, но попробуйте это объяснить процессору. И нужно сказать, инженеры Intel смогли всё-таки обмануть проц, при помощи всё того-же бита[1] направления.
Когда операнд = значение, единичное состояние этого бита отсекает нули слева, в результате чего мы экономим как минимум 1-байт, а в лучшем случае целых 3-байта. Радует то, что все современные компиляторы осведомлены об этом нюансе, и по умолчанию взводят бит[1] в опкоде инструкции. Однако если его умышленно сбросить в нуль, то процессор не дополнит значение
55h
нулями, а заберёт из кода следующие за командой
ADD EAX,55h
три байта, посчитав их продолжением значения
55h
. То-есть он включит дурака и начнёт выравнивать операнды на 4-байта. Пользуясь этим глюком, можно прибавь к EAX например не
55h
, а заранее подготовив 3-следующих байта, преобразовать значение в
0x22334455
. Посмотрим на такой код:
C-подобный:
section
'.code'
code executable writable
start
:
nop
add eax
,
0x55
;
// текущая инструкция
nop
and byte
[
@f
]
,
11111101
b
;
//<--- сбрасываем бит направления!
nop
@@
:
add eax
,
0x55
;
// здесь получим "ADD EAX,0x22334455"
dw
0x3344
;
// подставные значения
db
0x22
;
// ^^^^^
nop
nop
@exit
:
cinvoke _getch
cinvoke exit
,
0
https://forum.antichat.xyz/attachments/4910089/img_70afa2ef09.png
Обратите внимание на опкод(83h) по адресу
0x0040200d
. После того-как инструкци
AND
сбрасывает бит[1], его значение получается
81h
и у инструкции
ADD
сразу-же срывает крышу, в результате чего она берёт в охапку следующие 3-байта
0x223344
считая их своими. Как показывают эти элементарные примеры, всего один бит может ввести в ступор начинающих реверсеров, особенно если они не освоили толком интерактивный отладчик.
2. Прячем код от дизассемблеров
В те далёкие время приличный дебагер был роскошью, и всё на что могли рассчитывать кодокопатели – это штатный debug.com, или в лучшем случае "TurboDebugger". Правда позже появился более приличный "AVP-Util" от аверов из лаборатории Касперского, но и он мог отлаживать только досовские приложения. Дела поправились лишь с приходом классики всех времён и народов "Soft-Ace". Таким образом, основным оружием были дизассемблеры типа "Hiew", которые не отлаживали приложения, а лишь предоставляли взору его код.
Одним из запоминающихся способов сокрытия исходника от посторонних глаз предложил вирус под названием CALL.243 (в младенчестве JUMP.466), который прятал полезную нагрузку в адресах переходов. Он был прост до неприличия, зато кроме инструкций
CALL
не содержал в своей тушке ничего – рассмотрим его реализацию.
Инструкция
CALL
предназначена для вызова процедур – компиляторы заменили её макросом-прототипом "Invoke". Соответственно в качестве операнда ей требуется адрес вызываемой процедуры, который на системах х32 имеет размер 4-байта (на х64 уже 8-байт). Под виндой, опкодом этой инструкции является 2-байтное значение
FF15h
, однако если заглянуть в таблицу инструкций, можно обнаружить и 1-байтный опкод
Е8h
, что демонстрирует скрин из Интеловской доки "Instruction Set Reference":
https://forum.antichat.xyz/attachments/4910089/img_5de7be5d7f.png
Теперь, если взять один реальный
CALL
и после него вставить фиктивный, то получим непрерывную цепочку этих инструкций. В этом случае, 4-байтный адрес перехода фиктивной инструкции мы можем использовать в своих целях, например вставить вместо него код расшифровки сообщения
XOR BYTE[EDI],77h + INC EDI
(как-раз получается 4-байта). Реальный-же
CALL
должен будет перепрыгивать фиктивный.
В примере ниже, в качестве адреса реального вызова я использую текущий($) + смещение, которое получается(6). Так осуществляется вызов следующей инструкции
CALL $+6
, и т.д. Вот пример такой махинации:
C-подобный:
@@
:
call $
+
6
;
// пропускаем нижеследующие 5 байт
db
0e8
h
;
// фиктивный опкод "call near"
xor byte
[
edi
]
,
77
h
;
// ..4 байта его адреса,
inc edi
;
// ..которые используем в своих целях
call $
+
6
;
// следующий реальный CALL
.
.
.
К сожалению, на системах х32 в таких "ямах" получить больше четырёх/халявных байт не получиться, а вот на х64 в нашем распоряжении уже 8-байт, внутри которых можно разместить слона. В демке ниже нужно предварительно зашифровать строку "Hello World!", а код её расшифрует рассмотренным способом:
C-подобный:
format pe console
include
'win32ax.inc'
;
entry start
;
//-----------
.
data
szText db
'Hello World!'
,
0
szCapt db
'Self-Mod'
,
0
;
//-----------
.
code
start
:
push szText
pop edi
;
// EDI = адрес строки
@@
:
call $
+
6
db
0e8
h
;
// call near opсode
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
call $
+
6
db
0e8
h
xor byte
[
edi
]
,
77
h
inc edi
add esp
,
14
*
4
;
//<---- выравнивание стека от адресов возврата
invoke MessageBox
,
0
,
szCapt
,
0
,
0
invoke ExitProcess
,
0
.
end start
https://forum.antichat.xyz/attachments/4910089/img_32e982d269.png
3. Заключение
Качество кода имеет различные метрики – в некоторых случаях нужно стремиться к его оптимизации, а в некоторых наоборот приправить так, чтобы не оставить взломщику никаких шансов. Старшее поколение программистов оставило нам огромный багаж знаний, а представленные здесь примеры лишь капля в этом океане. Однако опыт приобретается не сразу, а со-временем по таким вот мелким наякам. На сегодня это всё, о чём я хотел сказать, удачи пока!