Сайт продаётся за 200 руб. Пишите в Skype аndrei54372 (не копируйте, а пишите).
Каталог статей
Меню сайта


Категории раздела
Мои статьи [3]


Наш опрос
Оцените мой сайт
Всего ответов: 113


Друзья сайта


Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0


Защита


Приветствую Вас, Гость · RSS 17.05.2024, 09:05
Главная » Статьи » Мои статьи

ассемблерные головоломки или может ли машина понимать естественный язык?
ассемблерные головоломки
или может ли машина понимать естественный язык?

крис касперски ака мыщъх, no-email

  машинные коды для непосвященных выглядят бессмысленной абракадаброй — это знаю все. но вот о том, что можно подобрать такую текстовую строку, воспринимаемую процессором как последовательность команд, делающих что-то полезное — догадываются немногие. практической пользы от этого, конечно, немного, зато какая гимнастика для мозгов!
введение
 Поиск текстовых строк, интерпретируемых как осмысленный код, — очень древнее увлечение, которым "болели" еще во времена "динозавров". В зависимости от структуры машинной команды, сложность решения задачи варьируются в очень широких пределах. Некоторые платформы вообще не позволяют написать ничего осмысленного, некоторые делают это настолько тривиальным, что пропадает весь интерес.
x86-процессоры занимают промежуточное положение. Гибкая система команд и множество способов адресации покрывают практически всю таблицу ASCII, однако, на поиск нужной комбинации могут уйди годы.
Никаких "официальных" правил в этой игре нет. Каждый волен назначать их сам. Код может быть как 16, так и 32-разрядным. Главное, чтобы он не вешал систему и не возбуждал никаких исключений. Теперь поговорим о прочих соглашениях. В 16-разрядном режиме обычно используется com-обрамление. При этом ASCII-строка помешается в текстовой файл, который затем переименовывается в com и передается на выполнение MS-DOS. Задача: вывести что-то на экран, причем, использовать прямой доступ к портам ввода/вывода и видеопамяти нежелательно, т. к. при прогоне программы под Windows NT это приводит к проблемам. Состояние регистров на момент запуска com-файла можно найти в таблице 1.
А вот другой вариант — текстовая строка оформляется в виде массива (например, char x[]="xxxxxx"), которому передается управление. Задача — прочитать входные аргументы и возвратить в регистре EAX результат вычислений.
Кодировка может быть любой — MS-DOS, WIN, KOI-8, но MS-DOS намного более популярна, хотя использование неанглийский символов алфавита в общем-то не приветствуется.
Для экспериментов нам понадобится: документация на ассемблер (предпочтительнее всего TECH HELP), отладчик (лучше avputil ничего не видел), HEX-редактор (например, HTE), пиво, вобла и некоторое количество свободного времени, а так же творческий настрой.

регистр значение
AX| == 00FFh, если 1-й аргумент командной строки начинается символами X:, где X соответствует букве несуществующего дисковода;
== FF00h, если 2-й аргумент командной строки начинается символами X:, где X соответствует букве несуществующего дисковода;
== FFFFh, если 1-й и 2-й аргументы командной строки ссылаются на несуществующие дисководы;
== 0000h, если 1-й и 2-й аргументы командной строки не ссылаются на несуществующие дисководы.
BX |0000
DX| ==DS
CX| 00FF
SI| 0100
IP| 0100
BP| 0000
DI |FFFE
SP |FFFE
CS| текущий сегмент
DS| текущий сегмент
SS |текущий сегмент
флаги| ODITSZAPC
001000000 == 7202
Таблица 1 начальное состояние регистров на момент загрузки com-файла
алфавит
Всякая письменность начинается с алфавита. Для кодирования в "текстовой" форме мы должны отчетливо представлять структуру машинной команды со всеми полями, префиксами и прочими превратностями судьбы, которые ее окружают. В этом нам поможет электронный справочник TECH HELP, который в частности можно найти на многих хакерских сайтах. Это настоящая библия программиста под MS-DOS в которой есть практически все!

Рисунок 1 внешний вид электронного помощника TECH HELP!
В первую очередь нас будет интересовать таблица опкодов (80x86/87 Opcodes), так же известная под именем Instruction Set Matrix или просто Матрица. На первый взгляд она выглядит ужасающее, но в действительности, пользоваться ей проще простого:

Рисунок 2 Матрица команд
Матрица представляет собой прямоугольную сетку, напичканную опокодами инструкций. По вертикали откладывается старший полубайт, а по горизонтали младший. Допустим, нас интересует какая инструкция соответствует машинной команде 41h. Откладываем по горизонтали 4x, откладываем по вертикали x1 и в точке их пересечения находит INC CX.
А теперь решим обратную задачу: по известной команде найдем соответствующей ей машинный код. Вот, например: PUSH SS. Находим такую инструкцию в таблице и видим, что она находится в клетке с координатами 1x:x6, значит, ее опкод 16h!
С однобайтовыми командами мы все понятно. Попробуем разобраться с остальными. В таблице видны сокращения: r/m, r8, r16. im8, im16. Что это? "im" это сокращения от "immediate", то есть "непосредственное значение" или "константа", а числа указывают на разрядность в битах. Вот, например, XOR AL,im8. Первый байт команды занимает опкод (34h), второй — непосредственное значение. В частности, XOR AL,69h будет выглядеть так: 34h 69h. А вот другой пример: ADD AX,im16h. Первый байт занимает опкод (05h), а два последних — непосредственное значение типа "слово", причем, младший байт располагается по меньшему адресу. Поэтому, ADD AX, 669h кодируется как 05h 69h 06h. Как видите, все предельно просто.
Сокращения r8 и r16 обозначают поля, кодирующие 8- и 16-разрядные регистры соответственно, а r/m ко всему прочему включает в себя еще и тип адресации, использующийся для доступа к памяти. Это довольно громоздка тема, даже поверхностное описание которой требует как минимум целой главы. И такая глава действительно включена в "Технику и философию хакерских атак", электронную версию которой можно найти на моем ftp-сервере (83.239.33.46). Она лежит в файле /pub/zq-disass.pdf. Добродушно настроенный The Svin проделал большую работу по поиску ошибок, которые водились там в большом числе и ходили косяками, за что ему большое спасибо. Список исправлений оформлен в виде независимого файла, который находится там же файле /pub/phck1.buglist.chm.
Подавляющая часть r/m и r8/16 сосредоточена в нечитабельных областях таблицы ASCII (т.е. имеет код либо меньше 20h, либо больше 7Fh), поэтому пользоваться ими нам практически не придется. Приятное исключение составляют команды типа: XXX [reg16],reg8/16 и XXX [BP+im8],reg8/16, да и то далеко не со всем набором регистров. Но об этом мы еще поговорим позже, а пока, уподобившись Кириллу и Мефодию, будет составлять Азбуку.
Все машинные команды можно разбить на три большие группы. К первой относятся однобайтовые команды, не имеющие никаких или практически никаких побочных эффектов. Они могут изменять значение регистров общего назначения или насиловать стек, но не должны лезть в порты, обращаться к памяти и т. д.
Вторую группу возглавляют двух или тех байтовые команды, один из операндов которых представляет собой непосредственное значение. Это очень важные команды, поскольку непосредственное значение позволяет кодировать те символы, которые не могут быть представлены командами первой группы. В частности, в нет символа пробела, без которого не обходится ни одна текстовая строка.
В третью группу попадают все остальные команды. Использовать их можно, но только с осторожностью. Короче говоря, первые две группы — это наш активный лексикон, а третья — заначка про запас.
Составленная нами азбука будет выглядеть так:

символ команда опкод
& \es:\ 26h
. \DDA\ 27h
.\ CS:\ 2Eh
/ \ DAS\ 2Fh
? \AAS\ 3Fh
@\ INC AX\ 40h
[\ POP BX\ 5Bh
\ \ POP SP\ 5Ch
]\ POP BP\ 5Dh
^\ POP SI\ 5Eh
_ \POP DI\ 5Fh
` PUSHA 60h
> DS: 3Eh
6 ss: 36h
7 AAA 37h
A INC CX 41h
a POPA 61h
B INC DX 42h
b BOUND 62h
C INC BX 43h
c ARPL 63h
D INC SP 44h
d FS: 64h
E INC BP 45h
e GS: 65h
F INC SI 46h
f size: 66h
G INC DI 47h
g addr: 67h
H DEC AX 48h
I DEC CX 49h
J DEC DX 5Ah
K DEC BX 4Bh
L DEC SP 4Ch
M DEC BP 4Dh
N DEC SI 4Eh
O DEC DI 4Fh
P PUSH AX 50h
Q PUSH CX 51h
R PUSH DX 52h
S PUSH BX 53h
T PUSH SP 54h
U PUSH BP 55h
V PUSH SI 56h
W PUSH DI 57h
X POP AX 58h
Y POP CX 59h
Z POP DX 5Ah
Таблица 2 однобайтовые команды первой группы
символ команда опкод
$ AND AL,im8 24h
% AND AX, im16 25h
4 XOR AL, im8 34h
5 XOR AX, im16 35h
, SUB AL, im8 2Ch
- SUB AX,im16 2Dh
< CMP AL, im8 2Ch
= CMP AX, im16 3Dh
Таблица 3 двух и трех байтовые команды второй группы
Смотрите! В первую группу попали все заглавные английские буквы, немного строчечный и значительная часть знаков препинания. То есть, закодировать можно практически все, что угодно, только бери и пиши! Компьютер не выбросит исключения и наш код будет вполне успешно исполнен. Правда, восклицательного знака здесь нет. А как же "HELLO,WORLD!". Ведь без восклицательного знака оно будет ущербным, если не сказать неполноценным. Во второй группе команд ничего подобного тоже не наблюдается. Все они начинаются с "посторонних" знаков и даже если передать восклицательный знак как непосредственное значение, получится полная ахинея. Например, AND AL,21h ("$!") или CMP AL,21h ("<!"). Выглядит отвратно. На самом деле, команда с опкодом 21h все-таки есть. Это, как подсказывает Матрица, AND r/m,r16. Правда, здесь возникает побочный эффект — обращение к памяти, поэтому приходится подбивать такую регистровую пару, которая бы не вызывала исключений, например, AND [SI],SP (21h 24h или "!$") в текстовом представлении. Только надо следить, чтобы SI указывал на память, не содержащую ничего интересного, иначе последствия себя не заставят ждать.
Кстати говоря, символ "$" нам очень пригодится, поскольку он служит завершителем MS-DOS строк. Это существенно отличает его от языка Си, в котором признаком конца строки является символ нуля.
Давайте для разминки наберем в hex-редакторе строку "HELLO,WORLD!$" и попробуем ее дизассемблировать:

00000000: 48 dec ax ; уменьшить регистр ax на единицу
00000001: 45 inc bp ; увеличить регистр bp на единицу
00000002: 4C dec sp ; уменьшить регистр sp на единицу
00000003: 4C dec sp ; уменьшить регистр sp на единицу
00000004: 4F dec di ; уменьшить регистр di на единицу
00000005: 2C 57 sub al,057 ; отнять от регистра al 57h
00000007: 4F dec di ; уменьшить регистр di на единицу
00000008: 52 push dx ; затолкать в стек регистр dx
00000009: 4C dec sp ; уменьшить регистр sp на единицу
0000000A: 44 inc sp ; увеличить регистр sp на единицу
0000000B: 2124 and [si],sp ; *si = sp

Листинг 1 дизассемблерный листинг "HELLO,WORLD!$" с моими комментариями
Как видно, программа тасует регистры и в хвост, и в гриву. При этом, на выходе стек оказывается несбалансированным. С одной стороны мы имеем три команды DEC SP и одну команду PUSH DX (которая уменьшает SP на 2), уменьшающие указатель вершины стека на 5 байт, а с другой — одну команду INC SP. Итого, счет 5:1! Стек оказывается опущенным на 4 байта. Следовательно, далеко не всякую текстовую строку можно непосредственно запихнуть в машинный код. В данном случае, для достижения баланса к тексту требуется добавить еще четыре буквы "D" или две команды POP reg16, которым соответствуют следующие символы: "X[YZ^_]". Например, это может быть "^HELLO,WORLD!$^". А что, выглядит вполне достойно!
Теперь, разобравшись с машинным кодом, перейдем к настоящим головоломкам.

Рисунок 3 строка "HELLO,WORLD!$" и ее машинное представление
извращения начинаются
Попробуем подобрать текстовую строку, выводящую заданный текст на экран. В каноническом варианте это выглядит так:

00000000: B4 09 MOV AH,009
00000002: BA 08 01 MOV DX,00108
00000005: CD 21 INT 021h
00000007: C3 RETN
00000008: 48 45 4C 4C-4F 2C "HELLO,
0000000E: 57 4F 52 4C 44 21-24 WORLD!$"

Листинг 2 ассемблерная программа, выводящая строку "HELLO,WORLD!" на экран и ее машинный код
Практически все символы этой программы нечитабельны, то есть не могут быть напрямую введены с клавиатуры и приходится хитрить. Начнем с "MOV AH, 09h", заносящую в регистр AH код сервисной функции, ответственный за телетайпный вывод. Заглянув в Матрицу, мы с огорчением наблюдаем, что все команды пересылки регистров MOV/LEA имеют опкод превышающий 7Fh, то есть "вылезающий" за американскую часть кодировки ASCII. Ладно, не дают нам MOV'а и не надо! Используем математические операции! В нашем распоряжении есть INC reg16/DEC reg16, SUB и XOR. Не такой уж и богатый выбор!
Поскольку, начальное значение регистра AX равно 0000h, для достижения задуманного, нам достаточно вычесть из него значение F700h, что равносильно сложением с 900h. В машинном представлении это будет выглядеть приблизительно так:

00000000: 2D 00 F7 sub ax,0F700
Листинг 3 подготовка регистра AH в работе (предварительный вариант)
Опс! Сразу два байта вылетают в штрафбат. Это 00h и F7h. Черт возьми! Как же быть? Надо подумать… А что если вычислить значение не все сразу, а по частям? Короче говоря, нужно разложить F700h на ряд слагаемых, каждое из которых находилось бы в заданном интервале. Точнее даже не интервале, а каждый байт, входящий в слово, удовлетворял бы условию 80h > x > 1Fh. Чем не головоломка? Любители математики легко найдут строгое решение, а всем остальным придется довольствоваться методом перебора. Вот, например, если от F700h шесть раз отнять по 292Ah, останется всего 4, которые можно накрутить обычным DEC AX (впрочем, в данном случае "крутить" совершенно необязательно, поскольку при AH == 9, значение регистра AL игнорируется). В общем, наш аналог MOV AX, 9 будет выглядеть так:

00000000: 2D 2A 29 sub ax,0292A
00000003: 2D 2A 29 sub ax,0292A
00000006: 2D 2A 29 sub ax,0292A
00000009: 2D 2A 29 sub ax,0292A
0000000C: 2D 2A 29 sub ax,0292A
0000000F: 2D 2A 29 sub ax,0292A
00000012: 48 dec ax
00000013: 48 dec ax
00000014: 48 dec ax
00000015: 48 dec ax

Листинг 4 подготовка регистра AH в работе (окончательный вариант)
А в текстовом виде: "-*)-*)-*)-*)-*)-*)HHHH". Для проверки работоспособности программы, запустим ее под отладчиком:

Рисунок 4 проверка работоспособности фрагменты программы под отладчиком
Ура! Получилось! Регистр AH послушно обратился в 09h и ни одного ASCII символа при этом не пострадало. Впрочем, это не единственный, и, к тому же не самый короткий, вариант. Можно, например, "подтянуть" регистр AL к 09h (в этом нам помогут команды INC AX), а затем переслать AL в AH. Стоп! Ведь команд пересылки у нас нет! Ни MOV, ни XCHG не работают! Но… зато в нашем распоряжении есть стек! А стек это могучая вещь! Команда PUSH reg16 забрасывает 16-разрядный регистр на верхушку, а POP reg16 стаскивает его оттуда. Команд для работы с 8-разярдными регистрами нет, а это, значит, что AL и AH мы никак не обменяем, во всяком случае если действовать в лобовую. Нет, тут нужен совсем другой подход! Что такое машинное слово? Совокупность двух байт, так? Причем, младший байт лежит по меньшему адресу, а за ним следует старший.
Немного медитации и… Постойте, но ведь если заслать в стек регистр AX, затем уменьшить указатель верхушки стека на единицу и извлечь регистр AX, то в AL попадет мусор, а в AH — младший байт оригинального регистра AX, в результате чего наша задача будет решена! Весь код угадывается в 0Bh байт, что на 0Ah байт короче, чем в прошлый раз. Это надо обмыть!

00000000: 40 inc ax
00000001: 40 inc ax
00000002: 40 inc ax
00000003: 40 inc ax
00000004: 40 inc ax
00000005: 40 inc ax
00000006: 40 inc ax
00000007: 40 inc ax
00000008: 40 inc ax
00000009: 50 push ax
0000000A: 4C dec sp
0000000B: 58 pop ax

Листинг 5 подготовка регистра AH в работе (улучшенный вариант)
С регистром DX мы разделываемся аналогичным образом (многократным вычитанием), а вот с "INT 21h" (CDh 21h) все обстоит значительно сложнее и без самомодифицирующегося код здесь просто никак. В нашем арсенале есть по меньшей мере две команды для работы с памятью: sub byte:[index_reg16],reg8 и sub byte:[BP+im8],reg8.
Естественно, для этого необходимо знать смещение команды "INT 21h" в машинном коде, а на данном этапе оно еще не известно, т. к. перед ним располагается самомодифицирующийся код, длину которого мы еще не готовы назвать. Хорошо, условимся считать, что "INT 21h" располагается по смещению 66h от начала файла, что соответствует 166h в памяти (базовый адрес загрузки для com-файлов равен 66h).
Начальное значение регистра SI равно 100h, что существенно упрощает нашу задачу. Остается разобраться с INT 21h (СDh 21h). Если закодировать эту команду как 23h 21, а затем отнять от нее 56h, мы добьемся того, что так долго искали. В машинном представлении это может выглядеть так:

00000000: 56 push si
00000001: 5D pop bp
00000002: 6A 56 push 056
00000004: 59 pop cx
00000005: 28 4E 66 sub [bp][00066],cl

00000066: 23 21

Листинг 6 формирование инструкции INT 21h с помощью самомодифицирующегося кода
Этому соответствует следующая текстовая строка: "V]jVY(Nf…#!". Не слишком литературно, конечно, но зато целиком из печатных символов! Команда "RETN" с опкодом C3h укрощается аналогично.
Короче говоря, первый этап ручного ассемблирования можно считать пройденным. Как и все готовые решения, он скрывает весь накал страстей и не передает треска мозговых извилин. Это только с виду кажется, что задачка решается просто. На самом деле, она требует нестандартного мышления, хорошего значения структуры машинных команд, медитации и длительных размышлений, пытающихся втиснуть заданную функциональность в ограниченный диапазон читабельных байт.
Впрочем, это всего лишь начало. Настоящее веселье наступает потом, когда хакер пытается превратить "читабельный" текст в осмысленную фразу. Очевидно, что наш первоначальный вариант (абракадабра в стиле "-*)-*)-*)-*)-*)-*)HHHH…V]jVY(Nf…#!") ничем подобным не является.
Приходится "разлагать" числа на слагаемые так, чтобы эти сами слагаемые представляли осмысленные комбинации букв, "разбавляемые" машинными командами из первой группы (см. таблицу 2), для ликвидации подобных эффектов от которых использовать противоположные им команды… В конечном счете образуется какая-то дикая текстовая строка, с кучей посторонних символов, но зато работающая!!! Чтобы не обламывать кайф, никаких законченных решений здесь не приводится. Первую "строку" создать всегда трудно. Главное — найти идею, понять общий принцип. В готовом виде все выглядит скучно и неинтересно.
На первых порах, можно смягчить условия и расширить доступный алфавит русскими буквами и символами псевдографики, а затем, по мере накопление опыта его постепенно ужесточать. Матерые хакеры, напротив, ограничиваются _только_ заглавными английскими буквами и к тому же соревнуются одновременно по размеру кода (в байтах), скорости его выполнения (в тактах старого доброго 8086) и времени решения задачи (в часах с точностью до минуты). До международных соревнований дело, конечно, не доходит, но в локальных схватках кровь кипит только так.
заключение
Когда я говорил, что составление таких строк не имеет никакого практического значения, я слегка привирал. Ведь это замечательный защитный трюк, сбивающий хакеров с толку. Если фрагмент программы выглядит как строка и передача управления на него тщательно замаскирована, далеко не каждый хакер с лету разберется что к чему. Как вариант, "машинные" текстовые строки можно использовать как серийные номера, расшифровывающие программу или добавляющие к ней недостающую функциональность (естественно, перед запуском на выполнение необходимо проверить их CRC, вдруг пользователь ошибся при вводе). Еще можно написать своего автономного рода, блуждающего по сети и управляемого посредством вот таких текстовых строк. Еще интереснее написать MS-DOS вируса, представляющего собой текстовое послание. В общем, как говорится, главное фантазию иметь, а области применения будут!
Категория: Мои статьи | Добавил: admin (27.06.2011)
Просмотров: 861 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Бесплатный конструктор сайтов - uCoz