Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

articles:asm_text [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== asm_text ======
 +<​sub>​{{asm_text.odt|Original file}}</​sub>​
 +
 +====== ассемблерные головоломки\\ или может ли машина понимать естественный язык? ======
 +
 +**крис касперски ака мыщъх, ****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-файла
 +
 +===== алфавит =====
 +
 +Всякая письменность начинается с алфавита. Для кодирования в "​текстовой"​ форме мы должны отчетливо представлять структуру машинной команды со всеми полями,​ префиксами и прочими превратностями судьбы,​ которые ее окружают. В этом нам поможет электронный справочник TECHHELP, который в частности можно найти на многих хакерских сайтах. Это настоящая библия программиста под MS-DOS в которой есть практически все!
 +
 +{{asm_text_Image_0.png?​553}}
 +
 +Рисунок 1 внешний вид электронного помощника TECHHELP!
 +
 +В первую очередь нас будет интересовать таблица опкодов (80x86/87 Opcodes), так же известная под именем Instruction Set Matrix или просто Матрица. На первый взгляд она выглядит ужасающее,​ но в действительности,​ пользоваться ей проще простого:​
 +
 +{{asm_text_Image_1.png?​552}}
 +
 +Рисунок 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. Добродушно настроенный TheSvin проделал большую работу по поиску ошибок,​ которые водились там в большом числе и ходили косяками,​ за что ему большое спасибо. Список исправлений оформлен в виде независимого файла, который находится там же файле /​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!$^"​. А что, выглядит вполне достойно!
 +
 +Теперь,​ разобравшись с машинным кодом, перейдем к настоящим головоломкам.
 +
 +{{asm_text_Image_2.png?​552}}
 +
 +Рисунок 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"​. Для проверки работоспособности программы,​ запустим ее под отладчиком:​
 +
 +{{asm_text_Image_3.png?​552}}
 +
 +Рисунок 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:​ 6A56   ​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 вируса,​ представляющего собой текстовое послание. В общем, как говорится,​ главное фантазию иметь, а области применения будут!
 +
 +