Програмерский спецназ — Архив WASM.RU
Уступая пожеланиям трудящихся , я решил посмотреть, можно ли извлечь какой-нибудь практический смысл из использования Alt-кодов. Это пересеклось с другой давно меня занимавшей темой. Представьте необитаемый остров, пещеру, а в ней много "железа", но почти нет софта. Ситуация может оказаться не такой фантастической, как кажется на первый взгляд, начиная от случайно оставшейся в деревенском чулане "двойки" с допотопным модемом и кончая системами на основе NT с умышленно стертым debug'ом, не говоря о более продвинутых средствах разработки. В общем, мы оказываемся в ситуации некоего "виртуального экстрима", когда только от наших собственных способностей и умений, как и в случае экстрима реального, зависит, сможем ли мы "выжить" на этом необитаемом виртуальном острове.
Чтобы не было недоразумений, с самого начала подчеркну, что речь идет именно о "выживании". Никаких рафинированных систем с SDK, никаких уютных комфортных квартир - тайга, сырость, даже спичек нет - разжигай свой костер только подручными средствами. Любителям комфорта и собственных привычек тут делать совершенно нечего. Как и в настоящем спецназе, здесь могут пройти лишь парни с огрубевшими мозолистыми руками и обветренными лицами; их я и приглашаю совершить со мной эту прогулку.
Здесь мы сталкиваемся с интересным вопросом: а что, собственно, считать "подручными средствами"? Понятно, что компьютер уже должен быть . Единственным его ПО "в комплекте" является BIOS. Поэтому можно было бы потребовать, что сначала нужно создать загрузочную дискету - но для этого опять нужен компьютер с уже запущенной, пусть самой примитивной, ОС! Замкнутый круг. В принципе, "задачей спецназа" можно было бы назвать подготовку загрузочной дискеты с использованием собранного из подручных средств электромагнита . Или, скажем, с использованием тех же проводов и прочих подручных средств перепрограммировать BIOS так, чтобы можно было вводить бинарные значения в оперативную память прямо с клавиатуры. На самом деле, это весьма забавно; возможно, когда-нибудь мы этим даже и займемся, но все же это уводит нас в область железа. А у нас спецназ програмерский, а не аппаратный.
Поэтому примем следующий постулат: доступна загрузочная дискета с минимальными возможностями. Если такой дискеты нет, задача считается неосуществимой, а для ее решения требуется вызвать "аппаратный спецназ". Минимальный загрузочный набор является ориентированным на DOS (boot-сектор, io.sys, msdos.sys, coomand.com или их аналоги) - это наиболее распространенный вариант; вероятность того, что найдется загрузочная дискета или хотя бы "форматированная под загрузку" (format /s), относительно велика. Обратите внимание, что это никак не уменьшает наши возможности: мы получаем доступ к любому IBM-совместимому компьютеру (независимо от возможно установленной на нем ОС - будь то Windows XP, Windows 2003 Server, Linux, FreeBSD или т.п.), а через него - при наличии соответствующего коммуникационного железа - мы можем (в принципе!) получить доступ к любым другим системам.
Замечание для тех, кто любит все делать своими руками и хочет создать такую загрузочную дискету. При ее создании из-под Windows 98 с помощью команды 'sys a:' на дискету записывается фиктивный command.com размером 20 Кб; его нужно удалить, а вместо него вручную скопировать настоящий размером 92 Кб из каталога Windows. В Windows Me команда sys не работает для дискет, а format не имеет опции /s - для создания загрузочной дискеты необходимо воспользоваться вкладкой "Загрузочный диск" диалога "Установка и удаление программ" панели управления. Затем, для чистоты эксперимента, можно удалить все лишние файлы, кроме перечисленных выше. В линейке Windows NT традиционную загрузочную дискету создать нельзя; там создается аварийный набор из нескольких дискет, но это другое. В этом случае используются дискеты, созданные в DOS или Windows9*/Me, или же при уже запущенной ОС используется командный интерпретатор cmd.exe. Заметим также, что ничто не мешает работать с окном DOS в уже запущенном Windows9*/Me или с полноценным, а не урезанным DOS - дискета с указанными файлами является лишь минимальным необходимым набором.
Итак, нам удалось слегка оживить холодное железо, мы запустились и имеем теперь интерактивную командную оболочку с довольно ограниченными возможностями. Что дальше? Как "вытянуть" из наличного железа возможности, которые в нем могут быть запрятаны? Насколько далеко мы сможем продвинуться в этом предприятии, целиком зависит от наших собственных знаний и умений. Повторюсь еще раз, это не является нормальным программированием. К сожалению, термин "экстремальное программирование" уже используется в другом контексте - нам бы он очень подошел. Тем не менее, это и есть настоящий экстрим. Точно такой же, как лазание по вертикальным откосам скал и через таежные буреломы, несмотря на наличие ровных автострад и железных дорог - в реальном мире находятся любители таких путешествий, почему бы им не быть в мире виртуальном?
Следующим шагом, как и при выживании в реальном мире, нам нужно начать создавать простейшие, но жизненно необходимые в повседневном использовании инструменты - те, с помощью которых мы и должны выполнять свою задачу (будь то коммуникационная программа, какой-нибудь драйвер для железа или т.п.) Причем все подчинено одной главной цели - выжить и выполнить поставленную задачу (настоящий спецназ!) А это значит - ничего лишнего; экономить силы, экономить время, делать только абсолютно необходимое, выявляя самую суть и безжалостно отбрасывая все постороннее. Тут уже не до програмерских примочек, оптимизаций, шлифовки и даже (о ужас!) "правильного стиля". Все должно быть надежно и просто, как автомат Калашникова.
Как вы уже, наверное, догадались, такой инструмент мы и будем создавать с помощью так называемых Alt-кодов - встроенной в интерпретатор command.com (а также cmd.exe в Windows NT и последующих) возможности набирать бинарные значения, используя правый цифровой блок клавиатуры при нажатой клавише Alt. В самом деле, мы можем использовать команду 'copy con file.bin' в сочетании с Alt-кодами, чтобы создать двоичный файл file.bin. Хотя здесь есть и проблема.
Дело в том, что не все двоичные значения можно набрать таким способом - некоторые управляющие символы вызывают непосредственную реакцию системы, а в файл не записываются. К этим значениям относятся: 0, 3, 6, 8, 10 (0Ah), 13 (0Dh), 16 (10h), 19 (13h), 26 (1Ah), 27 (1Bh), 127 (7Fh). Причем эти значения относятся к command.com DOS (соответственно, и Windows9*). Cmd.exe от линейки Windows NT в этом отношении более "либеральна" - "запрещенными" являются лишь символы 8, 13 (0Dh), 26 (1Ah) и 127 (7Fh). К тому же, размер буфера строки в команде copy ограничен 127 байтами (в Windows 2k/Xp - 510), а это неприятно осложняет создание нормально функционирующих программ. Большие файлы набирать с помощью 'copy con' можно, но придется специально обрабатывать переходы через символы конца строки (CRLF), которые будут вставляться в файл.
Поэтому, в соответствии с нашим принципом выживания - максимум эффективности с минимумом усилий - нам жизненно необходимо создать инструмент для последующего создания бинарных файлов. В просторечии такой инструмент называют шестнадцатеричным редактором .
В нашем случае это будет даже не редактор, а скорее конвертор, переводящий поток ASCII-символов из стандартного ввода в поток двоичных значений в стандартный вывод. Причем все символы, не попадающие в диапазон ASCII-символов 0..9A..Fa..f, просто игнорируются. Это позволяет форматировать вводимый текст для большего удобства восприятия (добавлять пробелы между отдельными байтами, а между строками вставлять перевод строки - CRLF). Алгоритм приведен на следующем рисунке.
Нам понадобятся три переменные размером в 1 байт: для хранения обрабатываемого байта (назовем ее place), для хранения предыдущего байта (place2) и указатель того, относится ли обрабатываемый байт к первой или второй тетраде (check). В целях экономии (нам нужно уложиться в 127 байтов!) эти переменные оставим за пределами com-файла. Тем не менее, в начале программы необходимо на всякий случай проинициализировать их нулями. Команда типа 'mov byte ptr [place],0' не годится, т.к. в этом случае в код попадает непосредственное значение 0, а это недопустимый символ. Поэтому используем косвенный способ с использованием регистра:
Код (Text):
100: 31C0 xor ax,ax 102: A3nnnn mov [place],ax 105: A3nnnn mov [check],axВ листинге слева приведено смещение с начала com-файла (в hex со смещения 100h), далее - машинный код, а затем с отступом - ассемблерная мнемоника для удобства восприятия. Здесь мы инициализируем сразу по 2 байта. Вместо 'nnnn' мы потом подставим действительные адреса этих переменных.
Далее нам нужно ввести 1 байт данных из стандартного ввода. Для этого используется функция 3Fh чтения из файла прерывания DOS 21h. В регистре AH должен содержаться номер функции (3Fh), в регистре BX - дескриптор файла (для стандартного ввода - 0), в регистре CX - число читаемых байтов (в нашем случае 1), DS:DX адресует буфер, куда должны читаться данные. В данном случае, нельзя использовать инструкцию 'mov cx,1', т.к. получаем код 'B9 01 00' - появляется запрещенный нулевой байт.
Код (Text):
108: B43F mov ah,3F 10A: 31DB xor bx,bx 10C: 31C9 xor cx,cx 10E: 41 inc cx 10F: BAnnnn mov dx,offset place 112: CD21 int 21Дальше начинаются проверки введенного значения, здесь же осуществляется и основное преобразование вводимых значений. Для начала проверим состояние флага переноса (CF) после функции файлового чтения - если он установлен, произошла ошибка чтения, выходим из программы:
Код (Text):
114: 72nn (1) jb EXITВ регистре AX возвращается число прочитанных байтов. Если прочитано 0 байт, файл закончился - также выходим.
Код (Text):
116: 85C0 test ax,ax 118: 74nn (2) jz EXITДалее загружаем сохраненный в переменной 'place' байт в регистр AH для анализа.
Код (Text):
11A: 8A26nnnn mov ah,byte ptr [place] 11E: 80FC30 cmp ah,30 121: 72E5 (3) jb 108 123: 80FC39 cmp ah,39 126: 7705 (4) jnbe 12D 128: 80EC30 sub ah,30 12B: EBnn (5) jmp NUMBER 12D: 80FC41 cmp ah,41 130: 72D6 (6) jb 108 132: 80FC46 cmp ah,46 135: 7705 (7) jnbe 13C 137: 80EC37 sub ah,37 13A: EBnn (8) jmp NUMBER 13C: 80FC61 cmp ah,61 13F: 72C7 (9) jb 108 141: 80FC66 cmp ah,66 144: 77C2 jnbe 108 146: 80EC57 sub ah,57Логика исполнения должна быть ясна из рисунка. Небольшие ссылки вперед ("перескакивание через инструкцию") вычислены сразу; часто использующаяся ссылка назад - на ввод очередного символа, соответствующий код расположен по смещению 108h. Остальные ссылки вперед помечены метками NUMBER и FIRST (далее); они для удобства обозначены также и на рисунке. Начало следующего участка кода соответствует метке NUMBER: мы получили смещение (149h), теперь можно вычислить все ранее сделанные ссылки на этот адрес.
Код (Text):
149: 90 nop 14A: 8A1Ennnn mov bl,byte ptr [check] 14E: 84DB test bl,bl 150: 74nn jz FIRST 152: F6DC neg ah 154: 2826nnnn sub byte ptr [place2],ahЭтот код может показаться несколько странным. Зачем нужен nop, когда мы и так стараемся вместить код в ограниченные 127 байтов? Если вы посмотрите на сделанные ранее ссылки вперед и подсчитаете соответствующие смещения (в частности, для инструкции по адресу 13Ah), при ссылке на адрес 149h вы получите инструкцию 'EB 0D', а 0Dh у нас "запрещенный" символ. Поэтому ближайший адрес, на который мы можем ссылаться, - 14Ah, а по адресу 149h придется оставить пустую инструкцию. Далее, если введенный ASCII-символ соответствует первой тетраде выходного байта, в переменной check будет 0. Но использовать инструкцию типа 'cmp byte ptr [check],0' нельзя, поскольку в код опять попадает 0 в виде непосредственного значения. Поэтому используем косвенный метод через копирование значения в регистр BL и последующим test.
Вычисленное значение второй (в данном случае) тетрады необходимо добавить к переменной (place2), содержащей значение сохраненной первой тетрады. К несчастью, опкод инструкции add равен 0, поэтому нам придется вообще от нее отказаться, заменив ее двумя инструкциями neg и sub. Думаю, с этим разобрались.
Далее идет вывод одного байта в стандартный вывод. Используется функция 40h файлового вывода прерывания DOS 21h. В регистре AH, как обычно, должен быть номер функции (40h), в BX - дескриптор файла (на этот раз стандартный вывод, т.е. 1), в CX - число выводимых байтов (1), DS:DX адресует буфер для вывода (у нас - адрес переменной place2). Для избежания появления нулевых байтов в коде придется применить те же хитрости с регистрами и inc, которые мы использовали при вводе. В случае ошибки (установленный бит CF при возврате из функции) сразу завершаем программу, чтобы не нагородить лишнего.
Код (Text):
158: B440 mov ah,40 15A: 31DB xor bx,bx 15C: 43 inc bx 15D: 89D9 mov cx,bx 15F: BAnnnn mov dx,offset place2 162: CD21 int 21 164: 72nn jb EXITЗаписав очередной байт, нужно указать в переменной check, что следующий вводимый байт ASCII-кода будет представлять первую тетраду выводимого байта; для этого обнуляем эту переменную. Как обычно, это делается опосредованно через регистр:
Код (Text):
166: 30C0 xor al,al 168: A2nnnn mov [check],al 16B: EB9B jmp 108В этом месте должен размещаться код для обработки случая первой тетрады (метка FIRST). Но сюда ссылается команда по адресу 150h, и если сделать ссылку на адрес 16Dh, получится инструкция '74 1B', а 1Bh у нас тоже "запрещен". Придется сослаться на следующий адрес 16Eh, а по адресу 16Dh опять поставить nop:
Код (Text):
16D: 90 nop 16E: B104 mov cl,04 170: D2E4 shl ah,cl 172: 8826nnnn mov byte ptr [place2],ah 176: FE0Ennnn dec byte ptr [check] 17A: EB8C jmp 108Инструкция по смещению 176 делает отметку в переменной check, что следующий вводимый байт будет соответствовать второй тетраде выходного байта. Вообще-то, check представляет собой простую логическую переменную, и решение делается на основе проверки ее на предмет 0 - не 0. Можно было бы использовать инструкцию 'inc byte ptr [check]', но, к несчастью, при этом в коде появляется запрещенный символ 06. Приходится вместо inc использовать dec; в нашем случае это совершенно не играет роли. Вот и все - здесь конец программы, инструкция возврата и метка EXIT:
Код (Text):
17C: C3 retПрограмма заняла 7Dh = 125 байтов. Переменные, как мы уже говорили, можно разместить и за пределами файла, "виртуально"; т.е. можно было бы использовать адрес 17Dh. Но, как вы понимаете , сочинение такого опуса и его подгонка заняла не один час, и для облегчения себе жизни, чтобы не пересчитывать каждый раз хотя бы адреса переменных, я отвел им места "с запасом" - для place, place2 и check соответственно 180h, 181h и 182h. Теперь можно вставить эти адреса, а также вычислить смещения и подставить их вместо 'nnnn' в инструкции. Окончательно мы получим такой код:
Код (Text):
31 C0 A3 80 ¦ 01 A3 82 01 ¦ B4 3F 31 DB ¦ 31 C9 41 BA 80 01 CD 21 ¦ 72 66 85 C0 ¦ 74 62 8A 26 ¦ 80 01 80 FC 30 72 E5 80 ¦ FC 39 77 05 ¦ 80 EC 30 EB ¦ 1D 80 FC 41 72 D6 80 FC ¦ 46 77 05 80 ¦ EC 37 EB 0E ¦ 80 FC 61 72 C7 80 FC 66 ¦ 77 C2 80 EC ¦ 57 90 8A 1E ¦ 82 01 84 DB 74 1C F6 DC ¦ 28 26 81 01 ¦ B4 40 31 DB ¦ 43 89 D9 BA 81 01 CD 21 ¦ 72 16 30 C0 ¦ A2 82 01 EB ¦ 9B 90 B1 04 D2 E4 88 26 ¦ 81 01 FE 0E ¦ 82 01 EB 8C ¦ C3Но это еще не все - надо все это перевести в десятичную систему, и если вы проделаете это самостоятельно, то поймете, насколько ценный инструмент мы на самом деле создаем . Но раз уж я проделал эту работу, не стану жадничать и приведу готовый вариант:
Код (Text):
049 192 163 128 | 001 163 130 001 | 180 063 049 219 | 049 201 065 186 128 001 205 033 | 114 102 133 192 | 116 098 138 038 | 128 001 128 252 048 114 229 128 | 252 057 119 005 | 128 236 048 235 | 029 128 252 065 114 214 128 252 | 070 119 005 128 | 236 055 235 014 | 128 252 097 114 199 128 252 102 | 119 194 128 236 | 087 144 138 030 | 130 001 132 219 116 028 246 220 | 040 038 129 001 | 180 064 049 219 | 067 137 217 186 129 001 205 033 | 114 022 048 192 | 162 130 001 235 | 155 144 177 004 210 228 136 038 | 129 001 254 014 | 130 001 235 140 | 195Это не избавляет вас от необходимости вручную набивать числа. Если кто-то успел позабыть, как это делается ;), я напомню. В консольном окне (либо после загрузки с подготовленной дискеты) набирается команда:
Код (Text):
copy con txt2bin.comЗатем вводятся все приведенные выше числа (десятичные), при вводе каждого числа нажимается и удерживается клавиша Alt. В случае ошибки можно просто стереть символ клавишей "Backspace" и ввести заново. Набрав все, нажимаем Ctrl+Z (или F6) и Enter - файл готов. Программа в принципе может работать и в интерактивном режиме, хотя это не очень удобно. Основное ее предназначение - использование в перенаправлениях, типа:
Код (Text):
txt2bin < input.txt > output.binВ качестве редактора для создания файла input.txt используется все та же внутренняя команда оболочки copy:
Код (Text):
copy con input.txtТолько на этот раз мы набираем не Alt-коды, а нормальный текст, вернее, текстовое представление 16-ричного кода. Непременно нужно создать тестовый файл со всеми возможными 16-ричными значениями и подать его на вход нашего редактора, чтобы проверить, что все работает нормально:
Код (Text):
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f . . . и т.д. до f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ffЭта программа далеко не шедевр, возможно, в ней есть большие и малые ляпусы, тем не менее, она удовлетворяет главному требованию - она работает, и работает универсально. Я проверял ее для DOS 6.22, Windows 98 SE/Me/2000 Server/XP Pro/2003 Server, а также загрузочных дискет, созданных Windows 98 и Me. Во всех случаях при наборе Alt-кодов возражений со стороны интерпретатора не поступало , а сама полученная программа успешно конвертировала тестовый файл со всеми возможными значениями в соответствующий бинарный файл. Кстати, если вы владеете слепой печатью на правом цифровом блоке, можно поспорить, каким образом программа набирается быстрее - с помощью Alt-кодов или традиционным образом, через написание исходного текста на ассемблере с последующим построением (если брать суммарное время, боюсь, сравнение окажется не в пользу ассемблера).
Еще одно замечание по поводу возможности ошибок, трудности программирования в машинных кодах и т.д. и т.п. В ходе подготовки материала я набирал этот числовой блок в Alt-кодах раз двадцать, и ни разу не сделал ни единой ошибки. Между тем в ассемблерных проектах, даже коротких, практически всегда хоть мелкая, но найдется какая-нибудь ошибка. Так что в мире программирования гуляет под видом истин множество предрассудков; развеять их могли бы люди, имеющие собственный опыт (например, создания больших проектов прямо в машинных кодах. Но у кого такой опыт есть? Ведь это считается безумием!) Но если бы мы ограничились тестированием лишь с небольшим пробным файлом, это было бы так, забавой, не более. Кто знает, может, там прячется какая-нибудь хитрая ошибка? Поэтому нужно преобразовать в ASCII-представление какой-нибудь рабочий бинарный файл, затем конвертировать его с помощью нашей утилиты и посмотреть, будет ли исходный файл работать после такого двойного преобразования. Я приведу готовый листинг утилиты, созданной с помощью MASM32, для преобразования бинарных файлов в текстовое 16-ричное представление. Не привожу полного описания, т.к. здесь нет ничего особенного; к тому же в исходном тексте есть комментарии.
Код (Text):
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib .data f OPENFILENAME <76,0,0,offset msk,0,0,1,offset file1,256,0,0,\ offset file2,offset src,\ OFN_FILEMUSTEXIST OR OFN_HIDEREADONLY OR OFN_PATHMUSTEXIST,\ 0,0,0,0,0,0> ttl db "Bit2txt",0 err1 db "Opening source file failed",0 err2 db "File reading failed",0 err3 db "File writing failed",0 err4 db "Opening destination file failed",0 msk db "All files",0,"*.*",0,0 src db "Select source file",0 dst db "Select destination file",0 defext db "txt",0 .data? hf1 dword ? ; дескрипторы файлов hf2 dword ? read dword ? ; число прочитанных байтов wrtn dword ? ; число записанных байтов file1 db 256 dup(?) ; буфер для имени и пути файла file2 db 256 dup(?) ; каталог файла-источника file3 db 256 dup(?) ; каталог файла-назначения buf db 16 dup(?) ; буфер чтения buf2 db 50 dup(?) ; буфер записи .code start: invoke GetWindowsDirectory,offset file2,256 invoke GetCurrentDirectory,256,offset file3 invoke GetOpenFileName,offset f .if eax==0 ; Файл не выбран jmp stop .else invoke CreateFile,offset file1,GENERIC_READ,0,0,\ OPEN_EXISTING,0,0 mov hf1,eax .if eax==INVALID_HANDLE_VALUE invoke MessageBox,0,offset err1,\ offset ttl,MB_ICONERROR jmp stop .endif ; Первый (бинарный) файл открыт - выбираем второй (для записи ASCII) lea eax,f assume eax:ptr OPENFILENAME mov [eax].lpstrTitle,offset dst ; поместить в текущий каталог mov [eax].lpstrInitialDir,offset file3 ; расширение по умолчаню - txt mov [eax].lpstrDefExt,offset defext ; флаги для чтения и записи различны mov [eax].Flags,OFN_OVERWRITEPROMPT OR OFN_HIDEREADONLY \ OR OFN_PATHMUSTEXIST OR OFN_EXTENSIONDIFFERENT assume eax:nothing invoke GetSaveFileName,offset f .if eax!=0 ; Выбран или создан файл для записи invoke CreateFile,offset file1,GENERIC_WRITE,\ 0,0,CREATE_ALWAYS,0,0 mov hf2,eax .if eax!=INVALID_HANDLE_VALUE ; успешно открыты оба файла; ; начинаем цикл чтения-записи (bin->ASCII) m0: invoke ReadFile,hf1,offset buf,16,offset read,0 ; каждый раз читаем строку в 16 байтов .if eax==0 ; ошибка чтения - выход invoke MessageBox,0,offset err2,offset ttl,MB_ICONERROR jmp m4 .endif mov ebx,read cmp ebx,0 ; конец файла (прочитано 0 байт) - je m4 ; выход из цикла inc ebx shl ebx,1 ; в EBX вычисляем число байтов, которые нужно будет add ebx,read ; записать = (число прочитанных байтов * 3 + 2) ; (на каждый двоичный байт - 2 байта ASCII-кода + ; 1 пробел + CRLF в конце строки) cld lea esi,buf ; буфер чтения <= 16 lea edi,buf2 ; буфер записи <= 50 mov cl,4 ; для сдвига m1: lodsb mov ah,al and ah,0fh ; младшая тетрада cmp ah,10 ; преобразуем значение в ASCII-код: jb m2 ; если < 10, добавить лишь 30h; add ah,7 ; если >=10, добавить 37h (A-F) m2: add ah,30h shr al,cl ; теперь старшая тетрада - cmp al,10 ; все аналогично jb m3 add al,7 m3: add al,30h stosw ; сохранить сразу 2 байта (AH+AL=AX) mov al,20h ; добавить пробел для форматирования stosb dec read ; прочитанный буфер закончен? jnz m1 ; нет - продолжить преобразование mov ax,0a0dh ; да - добавить CRLF для форматирования stosw ; (начинаем новую строку в 16 байтов) invoke WriteFile,hf2,offset buf2,ebx,offset wrtn,0 ; записать подготовленный буфер buf2 .if eax==0 || ebx!=wrtn ; ошибка записи invoke MessageBox,0,offset err3,offset ttl,MB_ICONERROR jmp m4 .endif jmp m0 ; прочитать очередные 16 (или сколько осталось) байтов m4: .else ; CreateFile==INVALID_HANDLE_VALUE invoke MessageBox,0,offset err4,offset ttl,MB_ICONERROR .endif ; CreateFile invoke CloseHandle,hf2 .endif ; GetSaveFileName invoke CloseHandle,hf1 .endif ; GetOpenFileName stop: invoke ExitProcess,0 end startЭто небольшое приложение со стандартным графическим интерфейсом, если можно так его назвать: в первом стандартном диалоговом окне открытия файла надо указать исходный бинарный файл, во втором - указать имя нового (или существующего, если он будет переписываться) файла, в который будет осуществлена запись преобразованного в текстовое шестнадцатеричное представление содержимого первого файла.
Первой "жертвой" теста, как водится, стал calc.exe. После "двойного преобразования" он работал великолепно. Но интересно ведь попробовать какой-нибудь большой файл, этак в несколько мегов. После некоторых поисков мне удалось на своей системе найти такой файл, им оказался исполняемый модуль AcrobatReader 6.0 (AcroRd32.exe) в 7,3 Мб. Преобразованный в текстовый вид, он занял 22,8 Мб. Представляете, подать такое примитивной com-программке размером в 125 байт? Даже страшно как-то - вдруг задавит своей массой! Тем не менее, я проделал и это. Конечно, пришлось подождать пару-другую минут (что ж вы хотите ). И полученный после такого преобразования монстр запустился! Правда, отсутствовало меню и еще что-то по мелочи. Но это я отношу уже к тому, что файл был вырван из контекста сложной системы, перенесен в какой-то новый каталог, а там была куча записей в реестре и т.п. В общем, если бы была лажа с преобразованием, файл не запустился бы вообще.
Поэтому можно считать, что первый инструмент в борьбе за виртуальное выживание у нас уже есть. Я не думаю, что надо заучивать эти 125 байтов наизусть, как "Отче наш". Уловив общую суть, а главное, просмотрев этот путь и узнав, где какие подвохи попадаются, можно при необходимости его воспроизвести. А дальше уже дело собственных знаний. С помощью этого инструмента можно создавать все те вещи, которые создаются с помощью debug'а (и о которых я начал писать в статьях "Приложение Windows голыми руками" и "Dll в машинных кодах"). А это значит, что можно практически "с нуля" создавать любые нужные приложения (при необходимости используя наш принцип спецназа: используем простые утилиты для создания более сложных, а последние - для создания еще более сложных и т.д. - до выполнения боевой задачи). Фактически, это равносильно созданию этих утилит прямо в Alt-кодах. Более "голый" способ может быть разве что создание загрузочной дискеты с помощью самодельного электромагнита . Лично я покопаюсь еще в этом направлении, уж больно интересные вещи можно здесь обнаружить.
© Roustem
Програмерский спецназ
Дата публикации 2 июл 2004