Alexm
Intră

Folosește-ți e-mailul sau numele de utilizator și parola pentru a te identifica

Înscrie-te

Vei primi un e-mail cu un link de activare. Ultimul câmp este opțional


🠉

Crearea celui mai mic Portabil Executabil cu Assembly

Învățați structura unui executabil portabil codificând cea mai mică aplicație Hello World folosind doar limbaj de asamblare

A fost o vreme când aveam mult timp liber și nimic mai bun de făcut decât să mă apuc de învățat secretele fișierelor executabile, așa că m-am aprofundat în structura acestora până când am reușit să creez cea mai mică aplicație Windows posibilă folosind doar NASM și undeva la 400 de linii de cod.

A fost un exercițiu bun - și a luat ceva timp - care m-a ajutat să înțeleg structura unui executabil portabil, cunoștințe care mi-au venit la îndemână mai târziu, când am început să încerc marea cu degetul în domeniul ingineriei inverse sau când a trebuit să creez anumite instrumente de construcție.

În cele din urmă, m-am ales cu o bucățică de cod și poate excesiv de documentată, care arată un mesaj "Hello world"; binarul rezultat nu depășește 2,50 KB. Același lucru construit cu Microsoft Visual Studio (modul Release) are 9 KB. Desigur, acest lucru se datorează faptului că MSVC adaugă propriile instrucțiuni de pornire.

După cum am spus, codul este comentat la greu; unora s-ar putea să nu le placă. Am făcut asta ca să puteți înțelege ce face exact fiecare linie. Este un singur fișier, dar puteți muta constantele, definițiile și macrocomenzile în altul care poate fi inclus în sursa principală.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                                 ;;
;; EXECUTABIL PORTABIL IN ASSEMBLY                                                 ;;
;;                                                                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; CONSTANTE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Numărul de octeți din ultima pagină de 512 octeți care urmează să fie încărcată de 
; DOS, și anume dimensiunea întregului bloc DOS, care nu poate depăși 512 octeți
$LAST_PAGE_BYTES            equ     EXE_HEADER - DOS_HEADER
; Conținutul fiecărei secțiuni începe de la o adresă care este un multiplu al acestei valori.
; Pe sistemele x86 este egal cu dimensiunea unei pagini de memorie
$SECT_ALIGN          		equ     4096
; Fiecare secțiune de imagine este aliniată la această limită (o dimensiune a sectorului de disc)
$FILE_ALIGN                 equ     512
; Adresa absolută preferată unde aplicația va fi încărcată în memorie (4MB)
$PREFERRED_ADDRESS          equ     4194304
; Numărul de secțiuni în interiorul imaginii
$SECTIONS_COUNT             equ     4
; Dimensiunea reală a anteturilor
$HEADERS_SIZE               equ     HEADERS_END - DOS_HEADER
; Dimensiunea reală a secțiunii de cod
$CODE_SIZE                  equ     CODE_END - CODE
; Dimensiunea reală a secțiunii de date
$DATA_SIZE                  equ     DATA_END - INIT_DATA
; Dimensiunea reală a secțiunii de importări
$IMPORTS_SIZE               equ     IMPORTS_END - IMPORT_DATA
; Mărimea tabelului de elemente importate
$IMPORTS_TABLE_SIZE         equ     IMPORT_DATA_END - IMPORT_DATA_START
; Dimensiunea datelor neinițializate din memorie
$UDATA_SIZE                 equ     16

;; DEFINITII
; Alinierea la limita specificată
%define Round(Number, Boundary)     (Number + Boundary - 1)/Boundary * Boundary
; Împarte începutul secțiunii la aliniere
%define Divide(BaseAddress)			(BaseAddress - CODE)/$FILE_ALIGN
; Calculează adresa virtuală relativă la începutul secțiunilor
%define RVA(BaseAddress)            Divide(BaseAddress) * $SECT_ALIGN + $SECT_ALIGN
; Obține AVR-ul adresei de bază specificată
%define RVA(Adress, BaseAddress)    RVA(BaseAddress) + (Adress - BaseAddress)
; Mărimea imaginii în memorie (totul aliniat la $SECT_ALIGN)             
%define ImageSize                   RVA(IMAGE_END) + Round($UDATA_SIZE, $SECT_ALIGN)

;; MACROURI
; Adaugă valoarea primului argument de atâtea ori cât specifică al doilea argument
%macro Fill 2
	times %1 db %2
%endmacro

;; ANTETURI ALE EXECUTABILULUI
; Orice fișier PE valid va începe cu antetul DOS, pentru compatibilitate retroactivă
DOS_HEADER:
	; La început adăugăm semnătura MZ (Mark Zbikowski) pentru a reține compatibilitatea
	; cu MS-D0S (16 biți). Dacă aplicația rulează în DOS (:D), se va afișa
	; un mesaj despre faptul că aceasta nu este compatibilă decât cu Windows
	.MZSignature            db          "MZ"
	; Numărul de biți în ultima pagină de 512 biți pe care DOS o va încărca. Ultima
	; pagină de obicei nu conține exact 512 biți, așa că vom specifica mărimea
	; exactă aici, și anume mărimea antetului DOS plus porțiunea de cod caree va
        ; va fi executată în DOS pentru a afișa mesajul despre incompatibilitate
	.LastPageBytes          dw          $LAST_PAGE_BYTES
	; Paginile care vor fi încărcate în memoria DOS. Este vorba despre o singură
        ; pagină de unde se vor încărca doar biții specificați în LastPageBytes. 
        ; Mărimea modulului în DOS se calculează folosind următoarea formulă:
	; ((TotalPages * 512) - (DOSHeaderSize * 16)) - LastPageBytes
	.TotalPages             dw          1
	; Numărul de realocări de memorie. Nu există relocări, așa că numărul de
	; relocări este 0. În DOS putem specifica adresa exact pentru diferite segmente
	; ale fișierului dar în acest caz nu este nevoie
	.Relocations            dw          0
	; Mărime antet MZ în paragrafe (16 biți). Imediat după aceasta, următoare porțiune
	; este antetul DOS cu code pe 16-biți care rulează când aplicația se deschide în ... DOS
	.DOSHeaderSize          dw          4
	; Numărul minim de paragrafe ce trebuie încărcate în memorie pe lângă cod.
        ; Dacă în memorie nu există cel puțin atâtea paragrafe cât se specifică aici,
        programul nu va rula
	.MinimumParagraphs      dw          0
	; Numărul maxim de paragrafe care pot fi încărcate în memorie, în afară de cod.
        ; Sistemul va pune la dispoziție atâta memorie cât este necesar. Nu vom limita aceasta
	.MaximumParagraphs      dw          65535
	; Valoarea inițială a registrului care stochează segmentul stivei de memorie. Această
        ; valoarea este adăugată segmentului în care se încarcă programul iar rezultatul
        ; este stocat în registrul SS
	.SSRegister             dw          0
        ; Valorea inițială a registrului conținând indicatorul stivei (mărimea sa inițială)
	.SPRegister             dw          0x00B8
	; Sumă de verificare. Suma de verificare inversată a tuturor datelor din fișier.
	; Este în general ignorată de încărcătorul de program
	.Checksum               dw          0
	; Valoarea inițială a indicatorului de instrucțiuni
	.IPRegister             dw          0
	; Valoarea (relativă) inițială a segmentului de cod
	.CSRegister             dw          0
        ; Adresa tabelului de realocări (dacă există). Această adresă este relativă la
        ; începutul fișierului. Cum fișierul nu va rula în DOS, acesta nu are realocări.
        ; Specificăm aceasta folosind valoarea '40h'
	.RealocationsTable      dw          0x0040
	; Numărul de suprapuneri. Nu folosim suprapuneri. Acesta este singurul și
	; principalul program ce va fi încărcat în memorie
	.Overlays               dw          0
	; Umple cu 8 biți
	.RezervedWords          dq          0
	; Identificator OEM
	.OEMIdentifier          dw          0
	; Informații OEM
	.OEMInfo                dw          0
	; A daugă 10 biți nuli
	Fill                    20,         0
	; Adresa antetului PE, unde aplicația va rula în modul Windows
	.PEHeader               dd          EXE_HEADER

	; O porțiune de cod ce va afișa un mesaj despre incompatibilitatea în modul DOS
	DOS_PROGRAM:
		; Mută segmentul de cod în segmentul de date
		push               	cs
		; Stochează datele după cod, pentru a economisi spațiu
		pop                	ds
		; Încarcă indicatorul mesajului ce va fi afișat pe ecran
		mov                	dx,			DOSMessage - DOS_PROGRAM
		; Acesta este argumentul pentru operația de afișare a întrerupătorului 21h
		mov                	ah,         9
		; Apelează întrerupătorul și afișează mesajul
		int                	21h
		; Argument de ieșire pentru același întrerupător
		mov                	ax,         0x4C01
		; Ieșire
		int					21h

		; Mesaj de afișat în DOS când programul se execută
		DOSMessage:
			db				"Acest program nu poate rula pe acest sistem!"
			db				0Dh, 0Dh, 0Ah, '$'

		; Umple cu zero până la totalul de 64 biți ai acestei porțiuni DOS
		Fill               	64-$+DOS_PROGRAM, 0

; Aici vom defini antentul PE, care va valida fișierul ca Portabil Executabil
EXE_HEADER:
	; La fel ca în antetul DOS, vom începe cu o semnătură ce identifică acest fișier ca PE
	.PESignature            db          "PE", 0, 0
	; Tipul de procesor pentru acest program, în cazul nostru Intel i386
	.Microprocessor         dw          0x014C
	; Numărul de secțiuni în fișier
	.SectionsCount          dw          $SECTIONS_COUNT
	; Data și ora creării antetului
	.CreationDateHour       dd          __POSIX_TIME__
	; Indicator la tabelul de simboluri. Simbolurile vor ajuta compilatorul să
        ; identifice diferitele elemente din codul sursă. Nu este necesar acum
	.SymbolsTable           dd          0
	; Cât de multe simboluri există în tabel. Fără tabel de simboluri, nimic
	.SymbolsCount           dd          0
	; Mărimea antetelor adiționale sub acesta
	.OptionalHeaderSize     dw          SECTIONS_HEADER_TABLE - OPTIONAL_HEADER
	; Caracteristici fișier (a se vedea referința)
	.Characteristics        dw          0x0002|0x0004|0x0008|0x0100|0x0200

; Antent opțional (de fapt, obligatoriu) ce va defini structura executabilului
OPTIONAL_HEADER:
	; La fel ca înainte, vom începe cu numărul care identifică acest antent
	.OptHeaderSignature     dw          0x010B
	; Versiunea majoră a linker-ului. Acest program nu are așa ceva
	.LinkerMajorVersion     db          0
	; Versiunea minoră a linker-ului
	.LinkerMinorVersion     db          0
	; Mărimea secțiunilor de cod din imagine
	.CodeSize               dd          Round($CODE_SIZE, $SECT_ALIGN)
	; Mărimea secțiunilor de data inițializate
	.DataSize               dd          Round($DATA_SIZE, $SECT_ALIGN)
	; Mărimea secțiunilor de date neinițializate (BSS). Aceste secțiuni nu ocupă
	; nici un spațiu în fișier dar sistemul va aloca memoria necesară can va rula
	; programul
	.UdataSize              dd          Round($UDATA_SIZE, $SECT_ALIGN)
	; Adresa relativă a punctului de intrare al aplicației. Cum acesta se găsește
        ; în prima secțiune din memorie și încee la $SECT_ALIGN, punctul de intrase este
        ; același ca $SECT_ALIGN
	.EntryPoint             dd          RVA(CODE)
	; Adresa virtuală relativă (AVR) a secțiunii de cod
	.CodeBase               dd          RVA(CODE)
	; AVR a secțiunii de date
	.DataBase               dd          RVA(INIT_DATA)
	; Adresa preferată pentur a încărca imaginea. Pentru imaginea EXE, aceasta este
	; 0x00400000, pentru fișiere DLL 0x10000000. Deasupra acestei adrese sunt adăugate
        ; codul și datele de mai sus sunt adăugate
	.MemoryImageBase        dd          $PREFERRED_ADDRESS
	; Aliniere secțiune. Valoarea implicită este 0x1000 (4096). Fiecare secțiune începe
	; la o adresă virtuală multiplu al acestei valori
	.SectionAlignment       dd          $SECT_ALIGN
	; Aliniere fișier. Valoarea recomandată este 0x0200. Fiecare secțiune din fișier este
	; aliniată la această valoare
	.FileAlignment          dd          $FILE_ALIGN
	; Indică versiunea majoră a celui mai vechi sistem operativ acceptat. În cazul nostru,
	; acest cod poate rula pe Windows 95 și versiuni ulterioare
	.MajorOSVersion         dw          4
	; Versiunea minoră a sistemului operativ
	.MinorOSVersion         dw          0
	; Versiunea majoră a imaginii
	.MajorImageVersion      dw          0
	; Versiunea minoră a imaginii
	.MinorImageVersion      dw          0
	; Versiunea majoră a subsistemului. Valoarea minimă acceptată este 3
	.MajorSubsysVersion     dw          3
	; Versiunea minoră a subsistemului. Valoarea minimă acceptată este 10
	.MinorSubsysVersion     dw          10
	; Versiune WIN32, totdeauna 0
	.Win32Version           dd          0
	; Mărimea fișierului imaginii, incluzând anteturi aliniate la $SECT_ALIGN
	.ImgSize                dd          ImageSize
	; Mărimea tuturor anteturilor, incluzând anteturile secțiunilor, aliniate la $FILE_ALIGN
	.HeadersSize            dd          Round($HEADERS_SIZE, $FILE_ALIGN)
	; Sumă de verificare care validează integritatea imaginea. De obicei 0
	.Checksum               dd          0
	; Subsistemul folosit pentru interfața cu utilizatorul, în cazul nostru, consola
	.InterfaceSubsystem     dw          3
	; Caracteristici DLL. Niciun DLL, deci 0
	.DLLAttributes          dw          0
	; Totalul de memorie rezervată de sistem pentru stivă
	.ReservedStack          dd          4096
	; Totalul inițial de memorie rezervată de sistem pentru stiva locală
	.LocalStack             dd          4096
	; Mărimea inițială a memoriei libere
	.ReservedFreeMemory     dd          65536
	; Memoria inițială liberă pentru imagine
	.FreeMemory             dd          0
	; Indicator pentru depanare, obsolet
	.DebuggingPointer       dd          0
	; Numărul de intrări în the DATA_TABLE. 16 se folosește în cele mai multe cazuri
	.DataDirectories        dd          16
	; Adresa tabelului de elemente exportate. Acesta nu este un DLL, nu se exportă nimic
	.ExportTable            dd          0
	; Mărimea tabelului de elemente exportate
	.ExportTableSize        dd          0
	; Adresa tabelului de elemente importate, unde avem legături cu elementele
        ; altor module
	.ImportTable            dd          RVA(KERNEL32_ITABLE, IMPORT_DATA)
	; Mărimea tabelului de elemente importate
	.ImportTableSize        dd          $IMPORTS_TABLE_SIZE
	; 112 biți rezervați
	Fill                    112,        0

; Acest table conține anteturile secțiunilor acestei imagini, dar și atributele acestora
SECTIONS_HEADER_TABLE:
	; Antetul secțiunii de cod, unde codul executabil este localizat. De obicei, această
        ; secțiune se numește text, dar îi vom spune 'code'
	CODE_SECTION_HEADER:
		; Numele acestei secțiuni. Nu poate avea mai mult de 8 caractere
		.Name               db          ".code", 0, 0, 0
		; Mărimea virtuală (în memorie) a acestei secțiuni. Dacă este mai mare decât
                ; mărimea pe disc, va fi umplută cu 0
		.SectionSize        dd          Round($CODE_SIZE, $SECT_ALIGN)
		; AVR a acestei secțiuni
		.SectionStart       dd          RVA(CODE)
		; Mărimea reală aliniată a acestei secțiuni
		.RealSize           dd          Round($CODE_SIZE, $SECT_ALIGN)
		; Adresa reală de start
		.StartAddress       dd          CODE
		; Adresa tabelului de realocări
		.RelocationsTable   dd          0
		; Un indicator de fișier la începutul intrărilor de numere de linie pentru secțiune.
		; Dacă nu există numere de linie COFF, această valoare este zero
		.LineNumbers        dd          0
		; Numărul de realocări
		.RelocationsTotal   dw          0
		; Numărul de intrări de numere de rând pentru secțiune
		.LineNumbersTotal   dw          0
		; Caracteristici secțiune. Poate fi citită, scrisă și executată
		.Characteristics    dd          0x00000020|0x20000000|0x40000000|0x80000000

	; Antetul secțiunii de importări (vezi comentariile primului antet)
	IMPORT_SECTION_HEADER:
		.Name               db          ".idat", 0, 0, 0
		.SectionSize        dd          Round($IMPORTS_SIZE, $SECT_ALIGN)
		.SectionStart       dd          RVA(IMPORT_DATA)
		.RealSize           dd          Round($IMPORTS_SIZE, $FILE_ALIGN)
		.StartAddress       dd          IMPORT_DATA
		.RelocationsTable   dd          0
		.LineNumbers        dd          0
		.RelocationsTotal   dw          0
		.LineNumbersTotal   dw          0
		.Characteristics    dd          0x00000040|0x40000000|0x80000000

	; Antetul secțiunii de date inițializate (vezi comentariile primului antet)
	DATA_SECTION_HEADER:
		.Name               db          ".data", 0, 0, 0
		.SectionSize        dd          Round($DATA_SIZE, $SECT_ALIGN)
		.SectionStart       dd          RVA(INIT_DATA)
		.RealSize           dd          Round($DATA_SIZE, $FILE_ALIGN)
		.StartAddress       dd          INIT_DATA
		.RelocationsTable   dd          0
		.LineNumbers        dd          0
		.RelocationsTotal   dw          0
		.LineNumbersTotal   dw          0
		.Characteristics    dd          0x00000040|0x40000000|0x80000000

	; Antetul secțiunii de date neinițializate (vezi comentariile primului antet)
	NULL_DATA_HEADER:
		.Name               db          ".null", 0, 0, 0
		.SectionSize        dd          Round($UDATA_SIZE, $SECT_ALIGN)
		.SectionStart       dd          RVA(NULL_DATA)
		.RealSize           dd          0
		.StartAddress       dd          0
		.RelocationsTable   dd          0
		.LineNumbers        dd          0
		.RelocationsTotal   dw          0
		.LineNumbersTotal   dw          0
		.Characteristics    dd          0x00000080|0x40000000|0x80000000

	;; Aici se încheie declarațiile tuturor antetelor
	HEADERS_END:

;; Aliniere la mărimea paginii
align   $FILE_ALIGN

;; SECȚIUNE COD
; Pentru procesoare pe 32 de octeți
use32
; AVR al variabilelor globale neinițializate, din secțiunea BSS. Prima variabilă 
; va conține antetul dispozitivului de ieșire.
OutputHandler               equ RVA(NULL_DATA) + 0
; Aici se va stoca numărul de caractere generat de WriteConsoleW
WrittenChars                equ OutputHandler + 4

; Tot codul ce va fi executat se află aici
CODE:
	; În primul rând, vom avea nevoie de un manipulator la consolă
	call    GetOutputHandler
	; Adaugă mesajul de afișat în stivă
	push    dword $PREFERRED_ADDRESS + RVA(HelloMessage, INIT_DATA)
	; Numărul de caractere care trebuie afișate
	push    13
	; Apelarea funcției care va afișa textul
	call    ShowText

	; Închide aplicația în mod corect
	jmp     CloseApplication

	; Această porțiune de cod va obține manipulatorul consolei și-l va stoca în OutputHandler
	GetOutputHandler:
		; Argumentul care indică ceea ce vrem, și anume un dispozitiv de ieșire
		push                    -11
		; Acum, apelează funcția din Kernel32.dll
		call                    dword [$PREFERRED_ADDRESS + RVA(F_GetStdHandle, IMPORT_DATA)]
		; Dacă valoarea inversată este mai mică decât 1, avem o problemă și aplicația trebuie să iasă
		cmp                     eax,    1
		; Stocare registru BX pentru a-l restabili în caz de succes
		push                    ebx
		; Dacă există o eroare, vom forța sfârșitul procesului, iar codul de ieșire va fi 1
		mov                     ebx,    1
		; Salt la funcția care încheie aplicația
		jl                      CloseApplication
		; Restabiliți registrul BX, înainte de a primi codul de ieșire
		pop                     ebx
		; Salvare manipulator în BSS
		mov                     dword [$PREFERRED_ADDRESS + OutputHandler], eax
		; Revenire la locul din care a fost apelată această funcție
		ret						4

	; Această funcție va afișa un text pe consolă
	ShowText:
		; Salvează registrul EBP pentru restaurare posterioară
		push                    ebp
		; Mută ESP în EBP pentru a citi argumentele primite
		mov                     ebp,    esp
		; Argument rezervat NULL pentru funcția WriteConsoleW
		push                    0
		; Aici se vor salva numărul de caractere scrise
		push                    dword $PREFERRED_ADDRESS + WrittenChars
		; Numărul de caractere ce vor fi scrise, de la al doilea argument
		push                    dword [ebp + 8]
		; Indicatorul textului, de la primul argument din stivă
		push                    dword [ebp + 12]
		; Manipulatorul dispozitivului de ieșire
		push                    dword [$PREFERRED_ADDRESS + OutputHandler]
		; Apelează funcția ce va afișa textul
		call                    dword [$PREFERRED_ADDRESS + RVA(F_WriteConsoleW, IMPORT_DATA)]
		; Restaurare EBP
		pop                     ebp
		; Revino și în același timp restaurează stiva
		ret                     8

	; Această funcție va termina aplicația și va returna codul de ieșire din EBX
	CloseApplication:
		; Stochează valoarea lui BX în stivă, acesta este codul de ieșire
		push                    ebx
		; Apelează funcția care va ieși din aplicație cu codul din EBX
		call                    dword [$PREFERRED_ADDRESS + RVA(F_ExitProcess, IMPORT_DATA)]
		; Dacă nu a funcționat, stiva este restaurată și ne întoarcem
		pop                     ebx
		; Return
		ret

; Secțiunea de cod este aliniată la 512 octeți
align   $FILE_ALIGN
CODE_END:

; Aici avem toate datele și funcțiile importate din bibliotecile dinamice
IMPORT_DATA:
	; Aceasta este biblioteca principală din Windows și va oferi funcții esențiale
	KERNEL32_LIBRARY			db         'kernel32.dll', 0

	IMPORT_DATA_START:

	; Aici descriem caracteristicile tabelului de importări pentru biblioteca KERNEL32.DLL
	KERNEL32_ITABLE:
	    ; AVR al tabelului de căutări de importări
		.originalfthk		    dd         0
		; Dată și timp, vor fi actualizate după data și ora din DLL
		.timedate               dd         0
		; Indexul primei referințe a lanțului expeditorului
		.forwarder              dd         0
		; Numele bibliotecii de importat
		.name                   dd         RVA(KERNEL32_LIBRARY, IMPORT_DATA)
		; AVR al tabelul cu adrese de import
		.firstthunk             dd         RVA(KERNEL32_IMPORTED_FUNCTIONS, IMPORT_DATA)
		; Sfârșitul tabelului pentru biblioteca KERNEL32.DLL
		Fill                    20,        0

	; Aici avem adresele funcțiilor importate
	KERNEL32_IMPORTED_FUNCTIONS:
		; Obține un controlador pentru dispozitivul standard specificat
		F_GetStdHandle:         dd         RVA(I_GetStdHandle, IMPORT_DATA)
		; Salt la funcția care afișează un text pe consolă
		F_WriteConsoleW:        dd         RVA(I_WriteConsoleW, IMPORT_DATA)
		; Această funcție închide procesul și returnează un cod de ieșire
		F_ExitProcess           dd         RVA(I_ExitProcess, IMPORT_DATA)
		; Octet rezervat
		ReservedBytes           dd         0

	; Numele elementelor importate din librăria de mai sus
	KERNEL32_IMPORTED_ELEMENTS:
		I_GetStdHandle:
			dw                  0
			db                  'GetStdHandle',  0
			align               2
		I_WriteConsoleW:
			dw                  0
			db                  'WriteConsoleW', 0
			align               2
		I_ExitProcess:
			dw                  0
			db                  'ExitProcess',   0
			align				2

	IMPORT_DATA_END:

align   $FILE_ALIGN
IMPORTS_END:

; Date inițializate, de obicei constante precum mesajele de text statice
INIT_DATA:
	; Mesaj de salut de afișat
	HelloMessage:    			dw          __utf16__("Salut, lume!"), 0Ah
; Aliniere la 512 octeți
align   $FILE_ALIGN
DATA_END:

; Secțiunea BSS, cu date neinițializate
NULL_DATA:
; Sfârșitul imaginii
IMAGE_END:

Generarea fișierului binar este foarte simplă. Doar asigurați-vă că aveți NASM instalat și adăugat la calea de mediu. Executați următoarea comandă:

nasm main.asm -f bin -o app.exe

Aceasta va construi un executabil x86 pentru procesoarele pe 32 de biți. Într-o zi voi încerca să creez versiunea pe 64 de biți și vreau să adaug mai multe funcționalități (cum ar fi obținerea argumentelor din linia de comandă). Să vedem unde merge asta.

Puteți afla mai multe despre formatul PE aici.