Assembler, компиляторы и туториалы Iczelion’а

Очередная ссылочная.

Компиляторы:

TASM/TASM32, компилятор для Windows x86 и DOS (Mega.NZ, ZIP, 4.6 Мб)
MASM for DOS (Mega.NZ, ZIP, 4.6 Мб)
MASM32, компилятор для Windows x86: Официальный сайт (копия на Mega.nz)
IMPORT32.LIB (7z, Mega.NZ) (копия на Google.Drive)

Туториалы Iczelion’а (RUS, перевод WASM.RU).

Довольно неплохое руководство по Win32 API для начинающих программистов на ассемблере. Начиная с простейших примеров и до довольно продвинутых программ, включая описание структур и функций Win32 API, структуры PE EXE-файла, простейших VxD-драйверов.

Перевод Aquila.

В комплекте исходники и бинарники примеров программ.

На Old-DOS
На mega.nz

Hello, world на 32-битном ассемблере (Windows x86).

Преамбула

Обещал сделать другу-школьнику, пусть тут описание валяется, не пропадать же.

Листинг

.386
.MODEL FLAT

	extrn	ExitProcess:proc
	extrn	MessageBoxA:proc
	
.DATA

	MSG_TITLE	DB	'Hello, world!',0
	MSG_MESSAGE	DB	'I am running!',0
	MB_INFORMATION	DD 40h

.CODE
Start:
	push	MB_INFORMATION		;Message box style (Icon - Information)
	push	offset MSG_TITLE	;Message box title
	push	offset MSG_MESSAGE	;Message box text
	push	0					;hwndOwner
	call	MessageBoxA
	
	push	0
	call	ExitProcess
end	Start

Что получилось

Начало

1. Прописываем директиву совместимого процессора .386 (больше и не надо), и модель памяти FLAT, стандартную для x86 PE исполняемых файлов.
2. Далее, экспортируем 2 функции WinAPI - ExitProcess, которая позволит программе корректно завершиться, и MessageBoxA, - функция вызовет стандартное диалоговое окно. Эти функции находятся в библиотеке IMPORT32.LIB (есть в комплекте TASM), так что они станут доступны программе на этапе линковки, а директива extrn показывает компилятору, что функции внешние, т.е. компилятор не будет ругаться, что не нашел их в исходнике при компиляции.

Примечание: Кроме функции WinAPI MessageBoxA, есть функция MessageBoxW, параметры у этой функции аналогичные, но используется она, если выводимый текст в кодировке UTF-16.

3. В секции данных (.DATA) определяем константы: MSG_TITLE и MSG_MESSAGE, содержащие, соответственно, строку заголовка и строку, содержащую текст в диалоговом окне. Строки должны оканчиваться символом с кодом 0 (,0)

Примечание: Не строковым символом "0", а нулевым байтом.

4. Также определяем четырехбайтовую (DD) константу, которая будет управлять поведением окна. В данном случае MB_INFORMATION, которой укажем значение 40h, что дополнит окно иконкой "Информация". Полный список констант, управляющих поведением окна, можно увидеть в источнике [1].

5. В секции кода (.CODE) ставим метку Start: (на самом деле, название может быть либо любым, либо зависеть от используемого компилятора, в TASM и MASM любое), это будет указывать компилятору на точку входа в нашу программу, т.е. говорить системе, откуда начинать выполнять код.
6. И ключевое слово end с именем той же метки, между этими конструкциями будет находиться код нашей программы. Поскольку, дополнительных внутренних функций в нашем HelloWorld'е не предполагается - этого хватит, описание функций выходит за рамки данного небольшого урока.

Вызов функции MessageBoxA

Описание функции есть в справочнике по WinAPI, где оно дано в C-подобном стиле:

int MessageBox(
  [in, optional] HWND    hWnd,
  [in, optional] LPCTSTR lpText,
  [in, optional] LPCTSTR lpCaption,
  [in]           UINT    uType
);

И во всех современных компиляторах ассемблера под Windows есть всякие удобняшки, типа готовых макросов, которые ускоряют написание кода, позволяют не париться с параметрами, не писать простыни кода, но, не позволяют осознать, как оно все на самом деле работает. Это или макросы в MASM или режим IDEAL в TASM. Впрочем, все нормальные ассемблеры должны уметь работать и с удобняшками, и без них. А поскольку, пример у нас маленький, то стоит как раз все показать и объяснить, без всяких удобняшек.

Функции WinAPI работают по единому стандартизированному принципу - они достают входные параметры из стека, а результат (конкретное значение или адрес, по которому следует взять данные) пишут в регистр EAX. Значение, возвращаемое функцией, нам в данном примере не понадобится, так что пока это опустим. Разберемся с параметрами.

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

Запись в стек осуществляется командой push, извлечение из стека - командой pop.

Ясно, что человеку такой способ записи параметров интуитивно непонятен, потому в языках высокого уровня, сделали так, чтоб было удобно. Если же писать на чистом ассемблере, мы должны положить параметры в стек в обратном порядке:

push	MB_INFORMATION ;Стиль Message box  (Добавляем иконку "Информация")
push	offset MSG_TITLE    ;Заголовок Message box
push	offset MSG_MESSAGE 	;Текст в Message box
push	0				;ID Вызывающего окна - его нет, устанавливаем в 0.

Далее вызываем саму функцию WinAPI:

call	MessageBoxA

Теперь вызываем функцию, необходимую для корректного завершения программы. На вход она принимает только один параметр - код возврата. Мы ничего не делаем, кроме вывода MessageBox'а, так что отдадим стандартный код нормального завершения - 0.

push 0
call ExitProcess

Под конец, о консольном HelloWorld

Его здесь не будет, потому что написание консольного приложения под Win32, связано с тем, что всегда в определенный момент возникает в ассемблере - "много мелких, суетливых движений", как сказал классик по другому поводу. Написание консольного приложения под Windows усложнено, алгоритм там примерно такой:

1. Получить дескриптор стандартного устройства ввода-вывода
2. Проверить, доступен ли он программе.
3. Если недоступен, значит нас вызвали не из консоли, а из GUI, например, щелчком мыши.
4. Если 3 - неверно
5. Вывести текст на консоль
6. Если 3 - верно
7. Создать новую консоль, вывести текст, закрыть/освободить консоль.

MASM, в отличии от TASM умеет прописывать на этапе линковки флаг IMAGE_SUBSYSTEM_WINDOWS_CUI (3) в заголовок PE-файла, это показывает ОС, что приложение расчитано на консольную подсистему, что, в свою очередь, избавляет программиста от необходимости вручную открывать консоль и устраивать дополнительные проверки. Система откроет консоль за нас. Но вернемся к этому в другой раз.

Ссылки

1. MessageBox function
2. Исходник и откомпилированная версия на GitHub

Компиляция из Notepad++

Преамбула

Точнее, конечно же, просто запуск стороннего компилятора, причем любого, имеющего поддержку командной строки (а они почти все такие, кроме совсем уж редких). Пока малому приятелю помогал написать Hello World на ассемблере, задолбался переключаться между редактором Far’а и батниками для сборки. MultiEdit или любимый FOTON, к которым можно привинтить компилятор, под Win7 не работают, но возможность запускать сторонние компиляторы оказалась у Notepad++

Установка плагина NppExec

Современные версии Notepad++ позволяют установить плагин из самого редактора.
Заходим в Плагины —> Управление Плагинами… и в появившемся окне вбиваем в строку поиска NppExec. Ставим напротив него галочку, и нажимаем ставшей активной кнопку Установить.

Notepad++ попросит перезагрузиться, скачает плагин и установит его.

После чего, в меню Плагины появится новое подменю NppExec.

В данном меню надо снять галочки напротив всех пунктов, выделенных по умолчанию, а поставить напротив пункта Follow $(CURRENT_DIRECTORY).

Написание скрипта

Осталось написать нужный скрипт для вызова компилятора и линковщика, покажу на примере TASM32.

Нажимаем F6 или в меню Плагины —> NppExec —> Execute NppExec Script…

Попадаем в пустое окно, где можно вводить команды скрипта:

Синтаксис скрипта мало отличается от BAT/CMD, за исключением того, что доступны внутренние команды Notepad++ и специальные внутренние переменные.

Для простейшего случая понадобится одна внутренняя команда и две специальных переменных. Сначала приведу пример скрипта, а потом опишу, что он делает.

npp_save
cd "$(CURRENT_DIRECTORY)"
N:\TASM\BIN\tasm32.exe /ml $(FILE_NAME)
if $(EXITCODE) !=0 goto exit

N:\TASM\BIN\tlink32 /Tpe /aa $(NAME_PART).obj,,,"N:\TASM\LIB\IMPORT32.LIB"

:exit

Скрипт выполняет компиляцию ассемблером TASM32 и линковку без запуска получившейся программы.

npp_save — внутренняя команда Notepad++, сохранить текущий файл.

Примечание: Для нового файла появится окно сохранения.

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

cd — аналогично команде BAT/CMD, перейти в указанную директорию.

"$(CURRENT_DIRECTORY)" — внутренняя переменная с текущей директорией. Текущей директорией считается та, где сохранен файл, открытый в активной вкладке редактора (т.е. файл, который редактируем).

N:\TASM\BIN\tasm32.exe /ml $(FILE_NAME) — вызов компилятора с нужными параметрами.

$(FILE_NAME) — полное имя файла без пути, например, hello.asm, если этот файл открыт на активной вкладке редактора.

if $(EXITCODE) !=0 goto exit

Команды if и goto работают почти также, как и в BAT-файлах. Некоторое различие в оформлении условий.

$(EXITCODE) — внутренняя переменная, получающая код возврата ранее вызванного приложения. Аналог %ERRORLEVEL% в BAT/CMD. Вызванные внешние компиляторы TASM/TASM32 завершаются с кодом 0, если компиляция прошла успешно.

Далее вызывается линковщик:

N:\TASM\BIN\tlink32 /Tpe /aa $(NAME_PART).obj,,,"N:\TASM\LIB\IMPORT32.LIB"

$(NAME_PART) — внутренняя переменная, содержащая имя файла без расширения и точки. Например, если на активной вкладке открыт файл hello.asm, переменная $(NAME_PART) содержит значение hello.

В конце скрипта ставим метку :exit — метка начинается с двоеточия (:).

Чтобы пользоваться скриптом постоянно, нужно его сохранить (кнопка Save…) и ввести имя, например TASM COMPILE:

Скрипт можно модифицировать так, чтобы после компиляции и линковки сразу выполнять программу:

npp_save
cd "$(CURRENT_DIRECTORY)"
N:\TASM\BIN\tasm32.exe /ml $(FILE_NAME)
if $(EXITCODE) !=0 goto exit

N:\TASM\BIN\tlink32 /Tpe /aa $(NAME_PART).obj,,,"N:\TASM\LIB\IMPORT32.LIB"
if $(EXITCODE) !=0 goto exit

$(NAME_PART).exe

:exit

Можно сохранить модифицированный скрипт под другим именем, например, TASM COMPILE AND RUN.

При запуске плагин запоминает последний выполненный скрипт, и при новом запуске по F6 он уже будет открыт в окне NppExec, что, конечно же, удобно.

При выполнении скрипта откроется окно консоли, в котором можно наблюдать за ходом его выполнения:

NPP_SAVE: N:\SimpleAsm\hello.asm
CD: N:\SimpleAsm
Current directory: N:\SimpleAsm
N:\TASM\BIN\tasm32.exe /ml hello.asm
Process started (PID=7964) >>>
Turbo Assembler Version 5.0 Copyright (c) 1988, 1996 Borland International

Assembling file: hello.asm
Error messages: None
Warning messages: None
Passes: 1

<<< Process finished (PID=7964). (Exit code 0)
IF: $(EXITCODE) !=0 goto exit
N:\TASM\BIN\tlink32 /Tpe /aa hello.obj,,,"N:\TASM\LIB\IMPORT32.LIB"
Process started (PID=2784) >>>
Turbo Link Version 1.6.71.0 Copyright (c) 1993,1996 Borland International
<<< Process finished (PID=2784). (Exit code 0)
IF: $(EXITCODE) !=0 goto exit
hello.exe
Process started (PID=6360) >>>
<<< Process finished (PID=6360). (Exit code 0)
================ READY ================

Бэкап скриптов

NppExec хранит скрипты в каталоге
C:\Users\<username>\Application Data\Notepad++\plugins\config\

где:
<username> — ваше имя пользователя.

В файлах:

npes_last.txt — хранится последний использованный скрипт.
npes_saved.txt — хранятся все скрипты, его можно забэкапить.

Источник

Компиляция программ с помощью Notepad++ — большая и несколько сумбурная статья на Habr.

NSIS: контрольная сумма (MD5), сравнение файлов.

Преамбула

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

Но одна из главных задач любого патча, перед началом работы проверить, а не пропатчен ли файл уже. Делается это обычно так — берется контрольная сумма MD5 или CRC32 оригинального файла и сравнивается с заранее известной суммой. Если совпадает — файл наш, можно патчить. Можно и наоборот — сравнить контрольную сумму оригинального файла с известной контрольной суммой пропатченного, если они равны — файл уже модифицирован, сообщаем пользователю и завершаем работу.

Получение MD5-суммы в NSIS.

Для получения MD5 в NSIS имеется плагин MD5 Plugin.

Скачиваем архив, копируем md5dll.dll из директории ANSI архива в директорию ANSI-плагинов NSIS (у меня C:\Program Files (x86)\NSIS\Plugins\x86-ansi\), а из директории UNICODE архива в директорию UNICODE-плагинов (C:\Program Files (x86)\NSIS\Plugins\x86-unicode). Понятно, что пути к вашим каталогам плагинов могут отличаться, т.к. зависят от того, куда установлен NSIS/

Примеры использования

Вычисление контрольной суммы строки:

#generate MD5sum of string
md5dll::GetMD5String "TestString"
Pop $0
DetailPrint "MD5 (string): [$0]"

Вывод:
MD5 (string): [5b56f40f8828701f97fa4511ddcd25fb]

Получение случайного хэша MD5:

#generate random MD5sum
md5dll::GetMD5Random
Pop $0
DetailPrint "MD5 (random): [$0]"

Вывод:

MD5 (random): [864fd151525efad56947f25fa3daeb75]

Получение контрольной суммы файла:

#generate MD5sum of file
DetailPrint "MD5 sum of file $EXEDIR\test\1.exe"
md5dll::GetMD5File "$EXEDIR\test\1.exe"
Pop $0
DetailPrint "MD5 (file): [$0]"

Вывод:

MD5 sum of file C:\!\Installers\MD5Sample\test\1.exe
MD5 (file): [03f3ba055d9f325c9abc90181f82657b]

Сравнение файлов

Для более красивого кода сравнения, рекомендую подключить инклюд с макросами логических операторов LogicLib.nsh (есть в комплекте NSIS):

!include LogicLib.nsh

Пример:

md5dll::GetMD5File "$EXEDIR\test\1.exe"
Pop $0
md5dll::GetMD5File "$EXEDIR\test\3.exe"
Pop $1
DetailPrint "Files $EXEDIR\test\1.exe"
DetailPrint "and   $EXEDIR\test\3.exe"
DetailPrint "MD5: $0" 
DetailPrint "and   $1"
${If} $0 == $1
	DetailPrint "IS IDENTICAL!"
${Else}
	DetailPrint "IS DIFFERENT!"
${EndIf}

Пример вывода (сравниваются два одинаковых файла, а потом два разных):

Files C:\!\Installers\MD5Sample\test\1.exe
and C:\!\Installers\MD5Sample\test\2.exe
MD5: 03f3ba055d9f325c9abc90181f82657b
and 03f3ba055d9f325c9abc90181f82657b
IS IDENTICAL!
Files C:\!\Installers\MD5Sample\test\1.exe
and C:\!\Installers\MD5Sample\test\3.exe
MD5: 03f3ba055d9f325c9abc90181f82657b
and 7baeee10b3cd6c210c4fcd61b92b8e1e
IS DIFFERENT!

Ошибки при работе библиотеки MD5

Если файл отсутствует, то функция md5dll::GetMD5File может выдать непредсказуемый результат, строку случайных символов, поэтому, перед использованием md5dll::GetMD5File следует проверять наличие и доступность файлов.

Ссылки

Библиотека:
— Описание на официальном сайте: MD5 plugin
Скачать с официального сайта
Зеркало
Копия (v 0.5) на Mega.nz

Пример из заметки целиком на GitHub