Програмерский спецназ

Дата публикации 2 июл 2004

Програмерский спецназ — Архив WASM.RU

Уступая пожеланиям трудящихся :smile3:, я решил посмотреть, можно ли извлечь какой-нибудь практический смысл из использования Alt-кодов. Это пересеклось с другой давно меня занимавшей темой. Представьте необитаемый остров, пещеру, а в ней много "железа", но почти нет софта. Ситуация может оказаться не такой фантастической, как кажется на первый взгляд, начиная от случайно оставшейся в деревенском чулане "двойки" с допотопным модемом и кончая системами на основе NT с умышленно стертым debug'ом, не говоря о более продвинутых средствах разработки. В общем, мы оказываемся в ситуации некоего "виртуального экстрима", когда только от наших собственных способностей и умений, как и в случае экстрима реального, зависит, сможем ли мы "выжить" на этом необитаемом виртуальном острове.

Чтобы не было недоразумений, с самого начала подчеркну, что речь идет именно о "выживании". Никаких рафинированных систем с SDK, никаких уютных комфортных квартир - тайга, сырость, даже спичек нет - разжигай свой костер только подручными средствами. Любителям комфорта и собственных привычек тут делать совершенно нечего. Как и в настоящем спецназе, здесь могут пройти лишь парни с огрубевшими мозолистыми руками и обветренными лицами; их я и приглашаю совершить со мной эту прогулку.

Здесь мы сталкиваемся с интересным вопросом: а что, собственно, считать "подручными средствами"? Понятно, что компьютер уже должен быть :smile3:. Единственным его ПО "в комплекте" является BIOS. Поэтому можно было бы потребовать, что сначала нужно создать загрузочную дискету - но для этого опять нужен компьютер с уже запущенной, пусть самой примитивной, ОС! Замкнутый круг. В принципе, "задачей спецназа" можно было бы назвать подготовку загрузочной дискеты с использованием собранного из подручных средств электромагнита :smile3:. Или, скажем, с использованием тех же проводов и прочих подручных средств перепрограммировать 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), которые будут вставляться в файл.

Поэтому, в соответствии с нашим принципом выживания - максимум эффективности с минимумом усилий - нам жизненно необходимо создать инструмент для последующего создания бинарных файлов. В просторечии такой инструмент называют шестнадцатеричным редактором :smile3:.

В нашем случае это будет даже не редактор, а скорее конвертор, переводящий поток ASCII-символов из стандартного ввода в поток двоичных значений в стандартный вывод. Причем все символы, не попадающие в диапазон ASCII-символов 0..9A..Fa..f, просто игнорируются. Это позволяет форматировать вводимый текст для большего удобства восприятия (добавлять пробелы между отдельными байтами, а между строками вставлять перевод строки - CRLF). Алгоритм приведен на следующем рисунке.

Нам понадобятся три переменные размером в 1 байт: для хранения обрабатываемого байта (назовем ее place), для хранения предыдущего байта (place2) и указатель того, относится ли обрабатываемый байт к первой или второй тетраде (check). В целях экономии (нам нужно уложиться в 127 байтов!) эти переменные оставим за пределами com-файла. Тем не менее, в начале программы необходимо на всякий случай проинициализировать их нулями. Команда типа 'mov byte ptr [place],0' не годится, т.к. в этом случае в код попадает непосредственное значение 0, а это недопустимый символ. Поэтому используем косвенный способ с использованием регистра:

Код (Text):
  1. 100: 31C0                      xor    ax,ax
  2. 102: A3nnnn                    mov    [place],ax
  3. 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):
  1. 108: B43F                      mov    ah,3F
  2. 10A: 31DB                      xor    bx,bx
  3. 10C: 31C9                      xor    cx,cx
  4. 10E: 41                        inc    cx
  5. 10F: BAnnnn                    mov    dx,offset place
  6. 112: CD21                      int    21

Дальше начинаются проверки введенного значения, здесь же осуществляется и основное преобразование вводимых значений. Для начала проверим состояние флага переноса (CF) после функции файлового чтения - если он установлен, произошла ошибка чтения, выходим из программы:

Код (Text):
  1. 114: 72nn                  (1) jb     EXIT

В регистре AX возвращается число прочитанных байтов. Если прочитано 0 байт, файл закончился - также выходим.

Код (Text):
  1. 116: 85C0                      test   ax,ax
  2. 118: 74nn                  (2) jz     EXIT

Далее загружаем сохраненный в переменной 'place' байт в регистр AH для анализа.

Код (Text):
  1. 11A: 8A26nnnn                  mov    ah,byte ptr [place]
  2. 11E: 80FC30                    cmp    ah,30
  3. 121: 72E5                  (3) jb     108
  4. 123: 80FC39                    cmp    ah,39
  5. 126: 7705                  (4) jnbe   12D
  6. 128: 80EC30                    sub    ah,30
  7. 12B: EBnn                  (5) jmp    NUMBER
  8. 12D: 80FC41                    cmp    ah,41
  9. 130: 72D6                  (6) jb     108
  10. 132: 80FC46                    cmp    ah,46
  11. 135: 7705                  (7) jnbe   13C
  12. 137: 80EC37                    sub    ah,37
  13. 13A: EBnn                  (8) jmp    NUMBER
  14. 13C: 80FC61                    cmp    ah,61
  15. 13F: 72C7                  (9) jb     108
  16. 141: 80FC66                    cmp    ah,66
  17. 144: 77C2                      jnbe   108
  18. 146: 80EC57                    sub    ah,57

Логика исполнения должна быть ясна из рисунка. Небольшие ссылки вперед ("перескакивание через инструкцию") вычислены сразу; часто использующаяся ссылка назад - на ввод очередного символа, соответствующий код расположен по смещению 108h. Остальные ссылки вперед помечены метками NUMBER и FIRST (далее); они для удобства обозначены также и на рисунке. Начало следующего участка кода соответствует метке NUMBER: мы получили смещение (149h), теперь можно вычислить все ранее сделанные ссылки на этот адрес.

Код (Text):
  1. 149: 90                        nop
  2. 14A: 8A1Ennnn                  mov    bl,byte ptr [check]
  3. 14E: 84DB                      test   bl,bl
  4. 150: 74nn                      jz     FIRST
  5. 152: F6DC                      neg    ah
  6. 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):
  1. 158: B440                      mov    ah,40
  2. 15A: 31DB                      xor    bx,bx
  3. 15C: 43                        inc    bx
  4. 15D: 89D9                      mov    cx,bx
  5. 15F: BAnnnn                    mov    dx,offset place2
  6. 162: CD21                      int    21
  7. 164: 72nn                      jb     EXIT

Записав очередной байт, нужно указать в переменной check, что следующий вводимый байт ASCII-кода будет представлять первую тетраду выводимого байта; для этого обнуляем эту переменную. Как обычно, это делается опосредованно через регистр:

Код (Text):
  1. 166: 30C0                      xor    al,al
  2. 168: A2nnnn                    mov    [check],al
  3. 16B: EB9B                      jmp    108

В этом месте должен размещаться код для обработки случая первой тетрады (метка FIRST). Но сюда ссылается команда по адресу 150h, и если сделать ссылку на адрес 16Dh, получится инструкция '74 1B', а 1Bh у нас тоже "запрещен". Придется сослаться на следующий адрес 16Eh, а по адресу 16Dh опять поставить nop:

Код (Text):
  1. 16D: 90                        nop
  2. 16E: B104                      mov    cl,04
  3. 170: D2E4                      shl    ah,cl
  4. 172: 8826nnnn                  mov    byte ptr [place2],ah
  5. 176: FE0Ennnn                  dec    byte ptr [check]
  6. 17A: EB8C                      jmp    108

Инструкция по смещению 176 делает отметку в переменной check, что следующий вводимый байт будет соответствовать второй тетраде выходного байта. Вообще-то, check представляет собой простую логическую переменную, и решение делается на основе проверки ее на предмет 0 - не 0. Можно было бы использовать инструкцию 'inc byte ptr [check]', но, к несчастью, при этом в коде появляется запрещенный символ 06. Приходится вместо inc использовать dec; в нашем случае это совершенно не играет роли. Вот и все - здесь конец программы, инструкция возврата и метка EXIT:

Код (Text):
  1. 17C: C3                        ret

Программа заняла 7Dh = 125 байтов. Переменные, как мы уже говорили, можно разместить и за пределами файла, "виртуально"; т.е. можно было бы использовать адрес 17Dh. Но, как вы понимаете :smile3:, сочинение такого опуса и его подгонка заняла не один час, и для облегчения себе жизни, чтобы не пересчитывать каждый раз хотя бы адреса переменных, я отвел им места "с запасом" - для place, place2 и check соответственно 180h, 181h и 182h. Теперь можно вставить эти адреса, а также вычислить смещения и подставить их вместо 'nnnn' в инструкции. Окончательно мы получим такой код:

Код (Text):
  1. 31 C0 A3 80 ¦ 01 A3 82 01 ¦ B4 3F 31 DB ¦ 31 C9 41 BA
  2. 80 01 CD 21 ¦ 72 66 85 C0 ¦ 74 62 8A 26 ¦ 80 01 80 FC
  3. 30 72 E5 80 ¦ FC 39 77 05 ¦ 80 EC 30 EB ¦ 1D 80 FC 41
  4. 72 D6 80 FC ¦ 46 77 05 80 ¦ EC 37 EB 0E ¦ 80 FC 61 72
  5. C7 80 FC 66 ¦ 77 C2 80 EC ¦ 57 90 8A 1E ¦ 82 01 84 DB
  6. 74 1C F6 DC ¦ 28 26 81 01 ¦ B4 40 31 DB ¦ 43 89 D9 BA
  7. 81 01 CD 21 ¦ 72 16 30 C0 ¦ A2 82 01 EB ¦ 9B 90 B1 04
  8. D2 E4 88 26 ¦ 81 01 FE 0E ¦ 82 01 EB 8C ¦ C3

Но это еще не все - надо все это перевести в десятичную систему, и если вы проделаете это самостоятельно, то поймете, насколько ценный инструмент мы на самом деле создаем :smile3:. Но раз уж я проделал эту работу, не стану жадничать и приведу готовый вариант:

Код (Text):
  1. 049 192 163 128 | 001 163 130 001 | 180 063 049 219 | 049 201 065 186
  2. 128 001 205 033 | 114 102 133 192 | 116 098 138 038 | 128 001 128 252
  3. 048 114 229 128 | 252 057 119 005 | 128 236 048 235 | 029 128 252 065
  4. 114 214 128 252 | 070 119 005 128 | 236 055 235 014 | 128 252 097 114
  5. 199 128 252 102 | 119 194 128 236 | 087 144 138 030 | 130 001 132 219
  6. 116 028 246 220 | 040 038 129 001 | 180 064 049 219 | 067 137 217 186
  7. 129 001 205 033 | 114 022 048 192 | 162 130 001 235 | 155 144 177 004
  8. 210 228 136 038 | 129 001 254 014 | 130 001 235 140 | 195

Это не избавляет вас от необходимости вручную набивать числа. Если кто-то успел позабыть, как это делается ;), я напомню. В консольном окне (либо после загрузки с подготовленной дискеты) набирается команда:

Код (Text):
  1. copy con txt2bin.com

Затем вводятся все приведенные выше числа (десятичные), при вводе каждого числа нажимается и удерживается клавиша Alt. В случае ошибки можно просто стереть символ клавишей "Backspace" и ввести заново. Набрав все, нажимаем Ctrl+Z (или F6) и Enter - файл готов. Программа в принципе может работать и в интерактивном режиме, хотя это не очень удобно. Основное ее предназначение - использование в перенаправлениях, типа:

Код (Text):
  1. txt2bin < input.txt > output.bin

В качестве редактора для создания файла input.txt используется все та же внутренняя команда оболочки copy:

Код (Text):
  1. copy con input.txt

Только на этот раз мы набираем не Alt-коды, а нормальный текст, вернее, текстовое представление 16-ричного кода. Непременно нужно создать тестовый файл со всеми возможными 16-ричными значениями и подать его на вход нашего редактора, чтобы проверить, что все работает нормально:

Код (Text):
  1. 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
  2. . . . и т.д. до
  3. 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-кодов возражений со стороны интерпретатора не поступало :smile3:, а сама полученная программа успешно конвертировала тестовый файл со всеми возможными значениями в соответствующий бинарный файл. Кстати, если вы владеете слепой печатью на правом цифровом блоке, можно поспорить, каким образом программа набирается быстрее - с помощью Alt-кодов или традиционным образом, через написание исходного текста на ассемблере с последующим построением (если брать суммарное время, боюсь, сравнение окажется не в пользу ассемблера).

Еще одно замечание по поводу возможности ошибок, трудности программирования в машинных кодах и т.д. и т.п. В ходе подготовки материала я набирал этот числовой блок в Alt-кодах раз двадцать, и ни разу не сделал ни единой ошибки. Между тем в ассемблерных проектах, даже коротких, практически всегда хоть мелкая, но найдется какая-нибудь ошибка. Так что в мире программирования гуляет под видом истин множество предрассудков; развеять их могли бы люди, имеющие собственный опыт (например, создания больших проектов прямо в машинных кодах. Но у кого такой опыт есть? Ведь это считается безумием!) Но если бы мы ограничились тестированием лишь с небольшим пробным файлом, это было бы так, забавой, не более. Кто знает, может, там прячется какая-нибудь хитрая ошибка? Поэтому нужно преобразовать в ASCII-представление какой-нибудь рабочий бинарный файл, затем конвертировать его с помощью нашей утилиты и посмотреть, будет ли исходный файл работать после такого двойного преобразования. Я приведу готовый листинг утилиты, созданной с помощью MASM32, для преобразования бинарных файлов в текстовое 16-ричное представление. Не привожу полного описания, т.к. здесь нет ничего особенного; к тому же в исходном тексте есть комментарии.

Код (Text):
  1. .386
  2. .model flat,stdcall
  3. option casemap:none
  4. include \masm32\include\windows.inc
  5. include \masm32\include\user32.inc
  6. include \masm32\include\kernel32.inc
  7. include \masm32\include\comdlg32.inc
  8. includelib \masm32\lib\user32.lib
  9. includelib \masm32\lib\kernel32.lib
  10. includelib \masm32\lib\comdlg32.lib
  11.  
  12. .data
  13. f   OPENFILENAME <76,0,0,offset msk,0,0,1,offset file1,256,0,0,\
  14.         offset file2,offset src,\
  15.     OFN_FILEMUSTEXIST OR OFN_HIDEREADONLY OR OFN_PATHMUSTEXIST,\
  16.         0,0,0,0,0,0>
  17. ttl db "Bit2txt",0
  18. err1    db "Opening source file failed",0
  19. err2    db "File reading failed",0
  20. err3    db "File writing failed",0
  21. err4    db "Opening destination file failed",0
  22. msk db "All files",0,"*.*",0,0
  23. src db "Select source file",0
  24. dst db "Select destination file",0
  25. defext  db "txt",0
  26.  
  27. .data?
  28. hf1 dword ?     ; дескрипторы файлов
  29. hf2 dword ?
  30. read    dword ?     ; число прочитанных байтов
  31. wrtn    dword ?     ; число записанных байтов
  32. file1   db 256 dup(?)   ; буфер для имени и пути файла
  33. file2   db 256 dup(?)   ; каталог файла-источника
  34. file3   db 256 dup(?)   ; каталог файла-назначения
  35. buf db 16 dup(?)    ; буфер чтения
  36. buf2    db 50 dup(?)    ; буфер записи
  37.  
  38. .code
  39. start:
  40.     invoke GetWindowsDirectory,offset file2,256
  41.     invoke GetCurrentDirectory,256,offset file3
  42.     invoke GetOpenFileName,offset f
  43.     .if eax==0  ; Файл не выбран
  44.         jmp stop
  45.     .else
  46.         invoke CreateFile,offset file1,GENERIC_READ,0,0,\
  47.             OPEN_EXISTING,0,0
  48.         mov hf1,eax
  49.         .if eax==INVALID_HANDLE_VALUE
  50.             invoke MessageBox,0,offset err1,\
  51.                 offset ttl,MB_ICONERROR
  52.             jmp stop
  53.         .endif
  54. ; Первый (бинарный) файл открыт - выбираем второй (для записи ASCII)
  55.         lea eax,f
  56.         assume eax:ptr OPENFILENAME
  57.         mov [eax].lpstrTitle,offset dst
  58.         ; поместить в текущий каталог
  59.         mov [eax].lpstrInitialDir,offset file3 
  60.         ; расширение по умолчаню - txt
  61.         mov [eax].lpstrDefExt,offset defext
  62.         ; флаги для чтения и записи различны
  63.         mov [eax].Flags,OFN_OVERWRITEPROMPT OR OFN_HIDEREADONLY \
  64.             OR OFN_PATHMUSTEXIST OR OFN_EXTENSIONDIFFERENT
  65.         assume eax:nothing
  66.         invoke GetSaveFileName,offset f
  67.         .if eax!=0  ; Выбран или создан файл для записи
  68.             invoke CreateFile,offset file1,GENERIC_WRITE,\
  69.                 0,0,CREATE_ALWAYS,0,0
  70.             mov hf2,eax
  71.             .if eax!=INVALID_HANDLE_VALUE
  72.             ; успешно открыты оба файла;
  73.             ; начинаем цикл чтения-записи (bin->ASCII)
  74. m0:
  75.     invoke ReadFile,hf1,offset buf,16,offset read,0
  76.     ; каждый раз читаем строку в 16 байтов
  77.     .if eax==0  ; ошибка чтения - выход
  78.         invoke MessageBox,0,offset err2,offset ttl,MB_ICONERROR
  79.         jmp m4
  80.     .endif
  81.     mov ebx,read     
  82.     cmp ebx,0   ; конец файла (прочитано 0 байт) -
  83.     je m4       ; выход из цикла
  84.     inc ebx
  85.     shl ebx,1   ; в EBX вычисляем число байтов, которые нужно будет
  86.     add ebx,read ; записать = (число прочитанных байтов * 3 + 2)
  87.             ; (на каждый двоичный байт - 2 байта ASCII-кода +
  88.             ; 1 пробел + CRLF в конце строки)
  89.     cld
  90.     lea esi,buf ; буфер чтения <= 16
  91.     lea edi,buf2    ; буфер записи <= 50
  92.     mov cl,4    ; для сдвига
  93. m1:
  94.     lodsb
  95.     mov ah,al
  96.     and ah,0fh  ; младшая тетрада
  97.     cmp ah,10   ; преобразуем значение в ASCII-код:
  98.     jb m2       ; если < 10, добавить лишь 30h;
  99.     add ah,7    ; если >=10, добавить 37h (A-F)
  100. m2:
  101.     add ah,30h
  102.     shr al,cl   ; теперь старшая тетрада -
  103.     cmp al,10   ; все аналогично
  104.     jb m3
  105.     add al,7
  106. m3:
  107.     add al,30h
  108.     stosw       ; сохранить сразу 2 байта (AH+AL=AX)
  109.     mov al,20h  ; добавить пробел для форматирования
  110.     stosb
  111.     dec read    ; прочитанный буфер закончен?
  112.     jnz m1      ; нет - продолжить преобразование
  113.     mov ax,0a0dh    ; да - добавить CRLF для форматирования
  114.     stosw       ; (начинаем новую строку в 16 байтов)
  115.     invoke WriteFile,hf2,offset buf2,ebx,offset wrtn,0
  116.     ; записать подготовленный буфер buf2
  117.     .if eax==0 || ebx!=wrtn ; ошибка записи
  118.         invoke MessageBox,0,offset err3,offset ttl,MB_ICONERROR
  119.         jmp m4
  120.     .endif
  121.     jmp m0 ; прочитать очередные 16 (или сколько осталось) байтов
  122. m4:
  123.             .else   ; CreateFile==INVALID_HANDLE_VALUE
  124.     invoke MessageBox,0,offset err4,offset ttl,MB_ICONERROR
  125.             .endif  ; CreateFile
  126.             invoke CloseHandle,hf2
  127.         .endif  ; GetSaveFileName
  128.         invoke CloseHandle,hf1
  129.     .endif  ; GetOpenFileName
  130. stop:
  131.     invoke ExitProcess,0
  132. end start

Это небольшое приложение со стандартным графическим интерфейсом, если можно так его назвать: в первом стандартном диалоговом окне открытия файла надо указать исходный бинарный файл, во втором - указать имя нового (или существующего, если он будет переписываться) файла, в который будет осуществлена запись преобразованного в текстовое шестнадцатеричное представление содержимого первого файла.

Первой "жертвой" теста, как водится, стал calc.exe. После "двойного преобразования" он работал великолепно. Но интересно ведь попробовать какой-нибудь большой файл, этак в несколько мегов. После некоторых поисков мне удалось на своей системе найти такой файл, им оказался исполняемый модуль AcrobatReader 6.0 (AcroRd32.exe) в 7,3 Мб. Преобразованный в текстовый вид, он занял 22,8 Мб. Представляете, подать такое примитивной com-программке размером в 125 байт? Даже страшно как-то - вдруг задавит своей массой! Тем не менее, я проделал и это. Конечно, пришлось подождать пару-другую минут (что ж вы хотите :smile3:). И полученный после такого преобразования монстр запустился! Правда, отсутствовало меню и еще что-то по мелочи. Но это я отношу уже к тому, что файл был вырван из контекста сложной системы, перенесен в какой-то новый каталог, а там была куча записей в реестре и т.п. В общем, если бы была лажа с преобразованием, файл не запустился бы вообще.

Поэтому можно считать, что первый инструмент в борьбе за виртуальное выживание у нас уже есть. Я не думаю, что надо заучивать эти 125 байтов наизусть, как "Отче наш". Уловив общую суть, а главное, просмотрев этот путь и узнав, где какие подвохи попадаются, можно при необходимости его воспроизвести. А дальше уже дело собственных знаний. С помощью этого инструмента можно создавать все те вещи, которые создаются с помощью debug'а (и о которых я начал писать в статьях "Приложение Windows голыми руками" и "Dll в машинных кодах"). А это значит, что можно практически "с нуля" создавать любые нужные приложения (при необходимости используя наш принцип спецназа: используем простые утилиты для создания более сложных, а последние - для создания еще более сложных и т.д. - до выполнения боевой задачи). Фактически, это равносильно созданию этих утилит прямо в Alt-кодах. Более "голый" способ может быть разве что создание загрузочной дискеты с помощью самодельного электромагнита :smile3:. Лично я покопаюсь еще в этом направлении, уж больно интересные вещи можно здесь обнаружить.

© Roustem

0 1.378
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532