📜 ⬆️ ⬇️

Quine in assembler? It's simple

It is known that quine (quine) is a program whose result is the output of the text of the program itself. It is assumed that access to the file containing the program does not occur.

Quinees can be compiled in almost all programming languages ​​with the possible exception of some exotic ones. Consider the task of compiling a quine in assembly language, assuming that the program should be a Win32 application and should display its text in the window.

The same machine code can be represented in assembly language in different ways. Suppose you want to push the number 48 onto the stack. The corresponding two-byte instruction can be written as mnemonic
')
push 48 (or push 30h),

so with the use of the db data definition directive

db 06Ah, 030h (or db 106.48).

It is also possible option db 'j0'. In all the cases listed above, compiling produces the same machine code. It is difficult to write an assembler quine containing a mnemonic representation of instructions - apparently, thousands of lines of the program are required. Therefore, the main idea that will guide us is getting a quine with db directives in the form

Source 1

.386 .model flat,stdcall .code q: db 16-     .... db 16-     end q 


(MASM assembler is used). The difficulty is that it is almost impossible to write such a quine directly - this is actually programming in machine codes. However, you can start by writing the program using the usual assembler mnemonics - see source 2 below. It is important that when compiling source codes 1 and 2 you get the same machine code.

Source 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 


The principle of operation is clear from the comments given in the text. We only note that the program consists of three parts. In the first part, the Quine text (i.e., source code 1) is formed on the stack. The number of bytes defined by a single db directive (and therefore the width of the quine rows) is given by the constant bStr - it is set to 25. Since during compilation no API functions are imported, in the second part the addresses of a number of functions are dynamically determined. First, this is the address of the GetProcAddress function, obtained by tracing the export table of the kernel32.dll module. Then the addresses of the remaining functions obtained using GetProcAddress. Finally, in the third part, Quine is output to the MessageBox window. Since the default output text is static and cannot be copied anywhere, a hook is installed that allows you to copy text from the window. Other variants of the third part of the quine are possible - for example, by creating a message queue and writing a window function.

So, source code 2 (which is not quine) needs to be saved in the batch file quine.cmd and run it - the executable file quine.exe is obtained. Next, quine.exe is launched, and from the window that appears, select the text with the mouse and copy the text into the quine.asm file. The executable file can be deleted. Thus, in quine.asm, a full-fledged quine (see source 1). It is broadcast using

ml / c / coff quine.asm

and linked with

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

(in the code section should be allowed to write). The size of the len code section was 820 bytes.

I debugged the program I did turbo debugger, but at the same time I also included the line

invoke ExitProcess, 0

before the start entry point (you can take any other function), and the lines

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

Otherwise, the debugger hangs - apparently due to the lack of an import table in the executable file.

Source: https://habr.com/ru/post/217375/


All Articles