Квайны могут быть составлены практически на всех языках программирования за исключением, быть может, некоторых экзотических. Рассмотрим задачу составления квайна на ассемблере, предполагая, что программа должна быть 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/
Добавить комментарий