Квайн на Ассемблере? Это просто

от автора

Известно, что квайн (куайн) — программа, результатом работы которой является вывод текста самой программы. При этом предполагается, что обращения к файлу, содержащему программу, не происходит.

Квайны могут быть составлены практически на всех языках программирования за исключением, быть может, некоторых экзотических. Рассмотрим задачу составления квайна на ассемблере, предполагая, что программа должна быть Win32 приложением и должна выводить свой текст в окно.

Один и тот же машинный код может быть представлен на ассемблере по-разному. Допустим, что требуется затолкнуть в стек число 48. Соответствующая двухбайтовая инструкция может быть записана как мнемонически

push 48 (или же push 30h),

так и с использованием директивы определения данных db

db 06Ah,030h (или же db 106,48).

Возможен также вариант db ‘j0’. Во всех перечисленных случаях при компиляции получается один и тот же машинный код. Написать ассемблерный квайн, содержащий мнемоническое представление инструкций, затруднительно — по-видимому, потребуются тысячи строк программы. Поэтому основная идея, которой будем руководствоваться — получение квайна с директивами db в виде

Исходник 1

.386 .model flat,stdcall .code q: db 16-ричные коды инструкций и данных .... db 16-ричные коды инструкций и данных end q 

(используется ассемблер MASM). Трудность в том, что напрямую написать такой квайн почти невозможно — это фактически программирование в машинных кодах. Однако начать можно с записи программы с помощью обычной ассемблерной мнемоники — см. приводимый ниже исходник 2. Важно при этом, что при компиляции исходников 1 и 2 получается один и тот же машинный код.

Исходник 2

; @echo off  ; ml /c /coff quine.cmd ; pause ; if errorlevel 1 goto exit  ; link /subsystem:windows /section:.text,RW quine.obj ; pause ; del quine.obj ; goto exit  .386 .model flat,stdcall option casemap:none  include		\masm32\include\windows.inc  bStr		= 25						; кол-во байтов, определяемых в одной строке квайна qLen		= len * 5 + (len / bStr + 1) * 4 + len1 + 10	; размер исходника квайна stLen		= (qLen / 1000h + 1) * 1000h			; выделяемая в стеке память под исходник  .code  ; *** Часть 1 - формирование в стеке текста исходника ***  start:		cld 		mov edx,[esp]		; ссылка вглубь kernel32.dll - для части 2 		sub esp,stLen		; выделение области в стеке 		mov edi,esp 		mov stAddr,edi 		lea esi,text 		xor ecx,ecx 		mov cl,len1 rep 		movsb			; копирование преамбулы '.386 .model flat ...'  		lea esi,start 		mov bl,bStr cycl:		mov eax,ecx		; генерация текста. Конец строки? 		div bl 		or ah,ah 		jnz comma 		mov ax,0A0Dh		; да - перевод строки и вставка директивы db		 		stosw 		mov eax,'  bd' 		stosd 		dec edi 		jmp j1 comma:		mov al,','		; нет - вставка запятой, разделителя байтов 		stosb j1:		mov al,'h'		; формирование 16-ричного представления байта - суффикс h 		shl eax,16 		lodsb 		mov ah,al 		and al,0Fh 		cmp al,10 		sbb al,69h 		das			; младшая половина байта 		xchg al,ah 		shr al,4 		cmp al,10 		sbb al,69h 		das			; старшая половина 		shl eax,8 		mov al,'0'		; префикс 0 		stosd 		inc ecx 		cmp ecx,len 		jb cycl  		mov ax,0A0Dh		; концовка квайна 'end q' 		stosw 		mov eax,' dne' 		stosd 		mov eax,'q' 		stosd  ; *** Часть 2 - нахождение адресов API функций ***  		xor dx,dx c2:		cmp word ptr [edx],'ZM'	; MZ-заголовок модуля kernel32? 		je c1 c3:		sub edx,10000h 		jmp c2 c1:		mov ecx,[edx+3Ch] 		cmp dword ptr [edx+ecx],'EP' ; PE-заголовок? 		jne c3  		mov ecx,[edx+ecx+78h] 		add ecx,edx		; адрес таблицы экспорта kernel32 		push ecx 		mov eax,[ecx+20h] 		add eax,edx		; адрес таблицы адресов имен функций 		xor ebx,ebx 		xor ecx,ecx cycl2:		mov esi,[eax+4*ebx] 		add esi,edx 		lea edi,GPAname 		mov cl,len2 repe		cmpsb			; поиск строки GetProcAddress 		je found 		inc ebx 		jmp cycl2  found:		pop ecx 		mov eax,[ecx+24h] 		add eax,edx 		mov bx,[eax+2*ebx]	; ординал функции GetProcAddress 		mov eax,[ecx+1ch] 		add eax,edx 		mov ebx,[eax+4*ebx] 		add ebx,edx 		mov edi,edx		; EBX - адрес GetProcAddress 					; EDI - база модуля kernel32 (а затем - user32)   		xor ecx,ecx		; нахождение адресов остальных функций 		mov cl,tlen 		lea esi,tbl  cycl3:		cmp cl,tlen-tlen2	 		jne j2  		push ecx 		push offset DLLname 		call eax		; загрузка user32.dll с помощью вызова LoadLibrary 		pop ecx 		mov edi,eax  j2:		dec cl 		push ecx 		push [esi+4*ecx] 		push edi 		call ebx		; получение адресов функций с помощью GetProcAddress 		pop ecx 		mov [esi+4*ecx],eax  		or cl,cl 		jnz cycl3  ; *** Часть 3 - вывод исходника в окно MessageBox ***  		push NULL 		call dword ptr _GetModuleHandle 		mov hInst,eax  		call dword ptr _GetCurrentThreadId  		push eax 		push NULL 		push offset hProc 		push WH_CBT			; установка хука на системные окна 		call dword ptr _SetWindowsHookEx 		mov hook,eax  		push MB_OK 		push offset capt 		push stAddr 		push 0 		call dword ptr _MessageBox	; окно с текстом квайна  		push 0 		call dword ptr _ExitProcess  ; ***  hProc		proc code:dword,wParam:dword,lParam:dword	; обработчик хука 		local coord:RECT  		pusha 		cmp code,HCBT_ACTIVATE		; активация окна? 		jne exit  		push 0FFFFh 		push wParam 		call dword ptr _GetDlgItem 		mov ebx,eax			; хэндл текстового поля  		lea eax,coord 		push eax 		push ebx 		call dword ptr _GetClientRect	; координаты текстового поля 		add coord.right,20  		push NULL 		push NULL 		push WM_GETFONT 		push ebx 		mov edi,_SendMessage 		call edi			; текущий шрифт текста 		mov esi,eax  		push SW_HIDE 		push ebx 		call dword ptr _ShowWindow	; скрытие текста  		push NULL 		push hInst 		push 0FFFFh 		push wParam 		push coord.bottom 		push coord.right 		push coord.top 		push coord.left 		push WS_CHILD+WS_VISIBLE+ES_MULTILINE+ES_READONLY 		push stAddr 		push offset cname 		push WS_EX_WINDOWEDGE 		call dword ptr _CreateWindowEx	; окно EDIT вместо STATIC 						; для того, чтобы из него можно было копировать  		push NULL 		push esi 		push WM_SETFONT 		push eax 		call edi			; установка шрифта  		push hook 		call dword ptr _UnhookWindowsHookEx ; снятие хука 		popa 		xor eax,eax 		ret  exit:		popa 		push lParam 		push wParam 		push code 		push hook 		call dword ptr _CallNextHookEx	; на следующий обработчик 		ret hProc		endp  ; *** инициализированные данные  capt		db 'Quine',0			; заголовок окна GPAname		db 'GetProcAddress',0 len2		equ $ - GPAname  GDIname		db 'GetDlgItem',0		; имена используемых функций GCRname		db 'GetClientRect',0 SMname		db 'SendMessageA',0 SWname		db 'ShowWindow',0 CWEname		db 'CreateWindowExA',0 UWHEname	db 'UnhookWindowsHookEx',0 CNHEname	db 'CallNextHookEx',0 SWHEname	db 'SetWindowsHookExA',0 MBname		db 'MessageBoxA',0 LLname		db 'LoadLibraryA',0 GMHname		db 'GetModuleHandleA',0 GCTIname	db 'GetCurrentThreadId',0 EPname		db 'ExitProcess',0  DLLname		db 'user32.dll',0 cname		db 'EDIT',0			; класс окна  text		db '.386',13,10,'.model flat,stdcall',13,10,'.code',13,10,'q:' ; преамбула квайна len1		equ $ - text  tbl			label dword		; таблица смещений имен функций, динамически заменяемых на адреса  _GetDlgItem		dd offset GDIname	; функции из user32.dll _GetClientRect		dd offset GCRname _SendMessage		dd offset SMname _ShowWindow		dd offset SWname _CreateWindowEx		dd offset CWEname _UnhookWindowsHookEx	dd offset UWHEname _CallNextHookEx		dd offset CNHEname _SetWindowsHookEx	dd offset SWHEname _MessageBox		dd offset MBname  tbl2			label dword _LoadLibrary		dd offset LLname	; функции из kernel32.dll _GetModuleHandle	dd offset GMHname _GetCurrentThreadId	dd offset GCTIname _ExitProcess 		dd offset EPname tlen2			equ ($-tbl2) / 4  tlen			equ ($-tbl) / 4		; размер таблицы  len		equ $ - start			; размер кода  ; *** неинициализированные данные, в квайн не попадающие  hInst		dd ?				; хэндл модуля hook		dd ?				; хэндл хука stAddr		dd ?				; адрес текста квайна в стеке  end start  :exit 

Принцип работы ясен из приведенных в тексте комментариев. Отметим только, что программа состоит из трех частей. В первой части в стеке формируется текст квайна (т.е. исходник 1). Количество байтов, определяемых одной директивой db (следовательно, и ширина строк квайна), задается константой bStr — она установлена равной 25-и. Поскольку во время компиляции не импортируются какие-либо API функции, то во второй части динамически определяются адреса ряда функций. Вначале это адрес функции GetProcAddress, получаемый трассировкой таблицы экспорта модуля kernel32.dll. Затем — адреса остальных функций, получаемых с помощью GetProcAddress. Наконец, в третьей части осуществляется вывод квайна в окно MessageBox. Поскольку по умолчанию выводимый текст статический и его нельзя куда-либо скопировать, дополнительно устанавливается хук, позволяющий копировать текст из окна. Возможны другие варианты третьей части квайна — например, с помощью создания очереди сообщений и написания оконной функции.

Итак, исходник 2 (не являющийся квайном) необходимо сохранить в пакетном файле quine.cmd и запустить его — получается исполняемый файл quine.exe. Далее запускается quine.exe, и из появившегося окна следует, выделив мышью, скопировать текст в файл quine.asm. Исполняемый файл можно удалить. Таким образом, в quine.asm — полноценный квайн (см. исходник 1). Транслируется он с помощью

ml /c /coff quine.asm

а линкуется с помощью

link /subsystem:windows /section:.text,RW quine.obj

(в секцию кода должна быть разрешена запись). Размер len секции кода составил 820 байт.

Отладку программы я производил турбо дебаггером, но при этом дополнительно включал строку

invoke ExitProcess,0

перед точкой входа start (можно взять любую другую функцию), и строки

include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

Иначе дебаггер зависает — по-видимому, из-за отсутствия таблицы импорта в исполняемом файле.

ссылка на оригинал статьи http://habrahabr.ru/post/217375/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *