|
| 1 | +; |
| 2 | +; hh1.nasm: 664-byte, tiny hello-world Win32 PE .exe |
| 3 | +; by [email protected] at Sat Jan 13 11:53:58 CET 2018 |
| 4 | +; |
| 5 | +; How to compile hh1.exe: |
| 6 | +; |
| 7 | +; $ nasm -f bin -o hh1.exe hh1.nasm |
| 8 | +; $ chmod 755 hh1.exe # For QEMU Samba server. |
| 9 | +; $ ndisasm -b 32 -e 0x200 -o 0x403000 hh1.exe |
| 10 | +; |
| 11 | +; hh1.asm was inspired by the 268-byte .exe on |
| 12 | +; https://www.codejuggle.dj/creating-the-smallest-possible-windows-executable-using-assembly-language/ |
| 13 | +; . The fundamental difference is that hh1.exe works on Windows XP ... Windows |
| 14 | +; 10, while the program above doesn't work on Windows XP. |
| 15 | +; |
| 16 | +; The generated hh1.exe works on: |
| 17 | +; |
| 18 | +; * Wine 1.6.2 on Linux. |
| 19 | +; * Windows XP SP3, 32-bit: Microsoft Windows XP [Version 5.1.2600] |
| 20 | +; * Windows 10 64-bit: Microsoft Windows [Version 10.0.16299.192] |
| 21 | +; |
| 22 | +; Output .exe file size in bytes (approximately): |
| 23 | +; |
| 24 | +; len(text_bytes) + len(data_bytes) + len(rodata_bytes) + |
| 25 | +; + 384 |
| 26 | +; + sum(len(name) for name in imported_names) + 2 * len(imported_names) - 1 |
| 27 | +; + 8 * len(imported_names) + 6 |
| 28 | +; + sum(len(name) for name in library_names) + len(library_names) |
| 29 | +; + 20 * len(library_names) |
| 30 | +; |
| 31 | +; Assumptions: |
| 32 | +; |
| 33 | +; * len(imported_names) >= 1: ['ExitProcess'] |
| 34 | +; * len(library_names) >= 1: ['kernel32'] |
| 35 | +; |
| 36 | +bits 32 |
| 37 | +imagebase equ 0x400000 ; Default base since Windows 95. |
| 38 | +textbase equ imagebase + 0x3000 |
| 39 | +file_alignment equ 0x200 |
| 40 | +bits 32 |
| 41 | +org 0 ; Can be anything, this file doesn't depend on it. |
| 42 | + |
| 43 | +_filestart: |
| 44 | +;_text: |
| 45 | + |
| 46 | +IMAGE_DOS_HEADER: ; Truncated, breaks file(1) etc. |
| 47 | +db 'MZ' |
| 48 | +times 10 db 'x' |
| 49 | + |
| 50 | +IMAGE_NT_HEADERS: |
| 51 | +Signature: dw 'PE', 0 |
| 52 | + |
| 53 | +IMAGE_FILE_HEADER: |
| 54 | +Machine: dw 0x14c ; IMAGE_FILE_MACHINE_I386 |
| 55 | +NumberOfSections: dw (_headers_end - _sechead) / 40 ; Windows XP needs >= 3. |
| 56 | +TimeDateStamp: dd 0x00000000 |
| 57 | +PointerToSymbolTable: dd 0x00000000 |
| 58 | +NumberOfSymbols: dd 0x00000000 |
| 59 | +SizeOfOptionalHeader: dw _datadir_end - _opthd ; Windows XP needs >= 0x78. |
| 60 | +Characteristics: dw 0x030f |
| 61 | +_opthd: |
| 62 | +IMAGE_OPTIONAL_HEADER32: |
| 63 | +Magic: dw 0x10b ; IMAGE_NT_OPTIONAL_HDR32_MAGIC |
| 64 | +MajorLinkerVersion: db 0 |
| 65 | +MinorLinkerVersion: db 0 |
| 66 | +SizeOfCode: dd 0x00000000 |
| 67 | +SizeOfInitializedData: dd 0x00000000 |
| 68 | +SizeOfUninitializedData: dd 0x00000000 |
| 69 | +AddressOfEntryPoint: dd (textbase - imagebase) + (_entry - _text) |
| 70 | +BaseOfCode: dd 0x00000000 |
| 71 | +BaseOfData: dd (IMAGE_NT_HEADERS - _filestart) ; Overlaps with: IMAGE_DOS_HEADER.e_lfanew. |
| 72 | +ImageBase: dd imagebase |
| 73 | +SectionAlignment: dd 0x1000 ; Minimum value for Windows XP. |
| 74 | +%if file_alignment == 0 || file_alignment & (file_alignment - 1) |
| 75 | +%fatal Invalid file_alignment, must be a power of 2. |
| 76 | +%endif |
| 77 | +%if file_alignment < 0x200 |
| 78 | +%fatal Windows XP needs file_alignment >= 0x200 |
| 79 | +%endif |
| 80 | +FileAlignment: dd file_alignment ; Minimum value for Windows XP. |
| 81 | +MajorOperatingSystemVersion: dw 4 |
| 82 | +MinorOperatingSystemVersion: dw 0 |
| 83 | +MajorImageVersion: dw 1 |
| 84 | +MinorImageVersion: dw 0 |
| 85 | +MajorSubsystemVersion: dw 4 |
| 86 | +MinorSubsystemVersion: dw 0 |
| 87 | +Win32VersionValue: dd 0 |
| 88 | +SizeOfImage: dd (textbase - imagebase) + (_eof + bss_size - _text) ; Wine rounds it up to a multiple of 0x1000, and loads and maps that much. |
| 89 | +SizeOfHeaders: dd _headers_end - _filestart ; Windows XP needs > 0. |
| 90 | +CheckSum: dd 0 |
| 91 | +Subsystem: dw 3 ; IMAGE_SUBSYSTEM_WINDOWS_CUI; gcc -mconsole |
| 92 | +DllCharacteristics: dw 0 |
| 93 | +SizeOfStackReserve: dd 0x00100000 |
| 94 | +SizeOfStackCommit: dd 0x00001000 |
| 95 | +SizeOfHeapReserve: dd 0 |
| 96 | +SizeOfHeapCommit: dd 0 |
| 97 | +LoaderFlags: dd 0 |
| 98 | +; If we hardcode 2 here, on Windows XP we can put arbitrary bytes to |
| 99 | +; IMAGE_DIRECTORY_ENTRY_RESOURCE.VirtualAddress and .Size. If we put |
| 100 | +; 3 here (autogenerated), then the values must be 0. |
| 101 | +;NumberOfRvaAndSizes: dd (_datadir_end - _datadir) / 8 ; Number of IMAGE_DATA_DIRECTORY entries below. |
| 102 | +NumberOfRvaAndSizes: dd 2 |
| 103 | + |
| 104 | +_datadir: |
| 105 | +DataDirectory: |
| 106 | +IMAGE_DIRECTORY_ENTRY_EXPORT: |
| 107 | +.VirtualAddress: dd 0x00000000 |
| 108 | +.Size: dd 0x00000000 |
| 109 | +IMAGE_DIRECTORY_ENTRY_IMPORT: |
| 110 | +.VirtualAddress: dd (textbase - imagebase) + (_idescs - _text) |
| 111 | +.Size: dd _idata_data_end - _idata |
| 112 | +IMAGE_DIRECTORY_ENTRY_RESOURCE: |
| 113 | +.VirtualAddress_AndSize: db 'tiny.exe' |
| 114 | +%if 0 |
| 115 | +; Changing all 0x78787878 to 0 below may fix startup errors. |
| 116 | +IMAGE_DIRECTORY_ENTRY_EXCEPTION: |
| 117 | +.VirtualAddress: dd 0x78787878 |
| 118 | +.Size: dd 0x78787878 |
| 119 | +IMAGE_DIRECTORY_ENTRY_SECURITY: |
| 120 | +.VirtualAddress: dd 0x78787878 |
| 121 | +.Size: dd 0x78787878 |
| 122 | +IMAGE_DIRECTORY_ENTRY_BASERELOC: |
| 123 | +.VirtualAddress: dd 0x78787878 |
| 124 | +.Size: dd 0x78787878 |
| 125 | +IMAGE_DIRECTORY_ENTRY_DEBUG: |
| 126 | +.VirtualAddress: dd 0x78787878 |
| 127 | +.Size: dd 0x00000000 |
| 128 | +IMAGE_DIRECTORY_ENTRY_ARCHITECTURE: |
| 129 | +.VirtualAddress: dd 0x00000000 |
| 130 | +.Size: dd 0x00000000 |
| 131 | +IMAGE_DIRECTORY_ENTRY_GLOBALPTR: |
| 132 | +.VirtualAddress: dd 0x00000000 |
| 133 | +.Size: dd 0x78787878 |
| 134 | +IMAGE_DIRECTORY_ENTRY_TLS: |
| 135 | +.VirtualAddress: dd 0x78787878 |
| 136 | +.Size: dd 0x78787878 |
| 137 | +IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: |
| 138 | +.VirtualAddress: dd 0x78787878 |
| 139 | +.Size: dd 0x78787878 |
| 140 | +IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: |
| 141 | +.VirtualAddress: dd 0x78787878 |
| 142 | +.Size: dd 0x78787878 |
| 143 | +IMAGE_DIRECTORY_ENTRY_IAT: |
| 144 | +.VirtualAddress: dd 0x78787878 |
| 145 | +.Size: dd 0x78787878 |
| 146 | +IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: |
| 147 | +.VirtualAddress: dd 0x78787878 |
| 148 | +.Size: dd 0x78787878 |
| 149 | + Missing: |
| 150 | +IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: |
| 151 | +.VirtualAddress: dd 0x78787878 |
| 152 | +.Size: dd 0x78787878 |
| 153 | +IMAGE_DIRECTORY_ENTRY_RESERVED: |
| 154 | +.VirtualAddress: dd 0x78787878 |
| 155 | +.Size: dd 0x78787878 |
| 156 | +%endif |
| 157 | +_datadir_end: |
| 158 | + |
| 159 | +_sechead: |
| 160 | + |
| 161 | +IMAGE_SECTION_HEADER__0: |
| 162 | +.Name: db '.dummy1', 0 |
| 163 | +.VirtualSize: dd 0x000000001 ; Must be positive for Windows XP. |
| 164 | +.VirtualAddress: dd 0x1000 ; Must be positive and divisible by 0x1000 for Windows XP. |
| 165 | +.SizeOfRawData: dd 0x00000000 |
| 166 | +.PointerToRawData: dd 0x00000000 |
| 167 | +.PointerToRelocations: dd 0 |
| 168 | +.PointerToLineNumbers: dd 0 |
| 169 | +.NumberOfRelocations: dw 0 |
| 170 | +.NumberOfLineNumbers: dw 0 |
| 171 | +.Characteristics: dd 0xc0300040 |
| 172 | + |
| 173 | +IMAGE_SECTION_HEADER__1: |
| 174 | +.Name: db '.dummy2', 0 |
| 175 | +.VirtualSize: dd 0x00000001 ; Must be positive for Windows XP. |
| 176 | +.VirtualAddress: dd 0x2000 ; Must be positive, divisible by 0x1000, and larger then the prev .VirtualAddress for Windows XP. |
| 177 | +.SizeOfRawData: dd 0x00000000 |
| 178 | +.PointerToRawData: dd 0x00000000 |
| 179 | +.PointerToRelocations: dd 0 |
| 180 | +.PointerToLineNumbers: dd 0 |
| 181 | +.NumberOfRelocations: dw 0 |
| 182 | +.NumberOfLineNumbers: dw 0 |
| 183 | +.Characteristics: dd 0xc0300040 |
| 184 | + |
| 185 | +IMAGE_SECTION_HEADER__2: |
| 186 | +.Name: db '.text', 0, 0, 0 |
| 187 | +.VirtualSize: dd (_eof - _text) + bss_size |
| 188 | +%if (textbase - imagebase) & 0xfff |
| 189 | +%fatal _text doesn't start at page boundary, needed by Windows XP. |
| 190 | +%endif |
| 191 | +%if (textbase - imagebase) <= 0x2000 |
| 192 | +%fatal _text doesn't start later than the previous sections, needed by Windows XP. |
| 193 | +%endif |
| 194 | +.VirtualAddress: dd textbase - imagebase |
| 195 | +.SizeOfRawData: dd _eof - _text |
| 196 | +.PointerToRawData: dd _text - _filestart |
| 197 | +.PointerToRelocations: dd 0 |
| 198 | +.PointerToLineNumbers: dd 0 |
| 199 | +.NumberOfRelocations: dw 0 |
| 200 | +.NumberOfLineNumbers: dw 0 |
| 201 | +.Characteristics: dd 0xe0300020 |
| 202 | + |
| 203 | +_headers_end: |
| 204 | +; We can check it only this late, when _headers_end is defined. |
| 205 | +%if (_headers_end - _sechead) % 40 != 0 |
| 206 | +%fatal Multiples of IMAGE_SECTION_HEADER needed. |
| 207 | +%endif |
| 208 | +%if (_headers_end - _sechead) / 40 < 3 |
| 209 | +%fatal Windows XP needs at least 3 sections. |
| 210 | +%endif |
| 211 | + |
| 212 | +times 0x200 - ($-$$) db 'x' |
| 213 | + |
| 214 | +;times 0x100 db 'y' ; Doesn't work, _text is not aligned properly. |
| 215 | +;times 0x200 db 'y' ; Works, making the .exe larger. |
| 216 | + |
| 217 | +_text: |
| 218 | + |
| 219 | +_entry: |
| 220 | +; Arguments pushed in reverse order, popped by the callee. |
| 221 | +; WINBASEAPI HANDLE WINAPI GetStdHandle (DWORD nStdHandle); |
| 222 | +; HANDLE hfile = GetStdHandle(STD_OUTPUT_HANDLE); |
| 223 | +push byte -11 ; STD_OUTPUT_HANDLE |
| 224 | +call [textbase + (__imp__GetStdHandle@4 - _text)] |
| 225 | +; Arguments pushed in reverse order, popped by the callee. |
| 226 | +; WINBASEAPI WINBOOL WINAPI WriteFile (HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped); |
| 227 | +; DWORD bw; |
| 228 | +push eax ; Value does't matter. |
| 229 | +mov ecx, esp |
| 230 | +push byte 0 ; lpOverlapped |
| 231 | +push ecx ; lpNumberOfBytesWritten = &dw |
| 232 | +push byte (_msg_end - _msg) ; nNumberOfBytesToWrite |
| 233 | +push textbase + (_msg - _text) ; lpBuffer |
| 234 | +push eax ; hFile = hfile |
| 235 | +call [textbase + (__imp__WriteFile@20 - _text)] |
| 236 | +;pop eax ; This would pop dw. Needed for cleanup. |
| 237 | +; Arguments pushed in reverse order, popped by the callee. |
| 238 | +; WINBASEAPI DECLSPEC_NORETURN VOID WINAPI ExitProcess(UINT uExitCode); |
| 239 | +push byte 0 ; uExitCode |
| 240 | +call [textbase + (__imp__ExitProcess@4 - _text)] |
| 241 | + |
| 242 | +_data: |
| 243 | +_msg: |
| 244 | +db 'Hello, World!', 13, 10 |
| 245 | +_msg_end: |
| 246 | + |
| 247 | +; This can be before of after _entry, it doesn't matter. |
| 248 | +_idata: ; Relocations, IMAGE_DIRECTORY_ENTRY_IMPORT data. |
| 249 | +_hintnames: |
| 250 | +dd (textbase - imagebase) + (IMAGE_IMPORT_BY_NAME_ExitProcess - _text) |
| 251 | +dd (textbase - imagebase) + (IMAGE_IMPORT_BY_NAME_GetStdHandle - _text) |
| 252 | +dd (textbase - imagebase) + (IMAGE_IMPORT_BY_NAME_WriteFile - _text) |
| 253 | +dd 0 ; Marks end-of-list. |
| 254 | +_iat: ; Modified by the PE loader before jumping to _entry. |
| 255 | +__imp__ExitProcess@4: dd (textbase - imagebase) + (IMAGE_IMPORT_BY_NAME_ExitProcess - _text) |
| 256 | +__imp__GetStdHandle@4: dd (textbase - imagebase) + (IMAGE_IMPORT_BY_NAME_GetStdHandle - _text) |
| 257 | +__imp__WriteFile@20: dd (textbase - imagebase) + (IMAGE_IMPORT_BY_NAME_WriteFile - _text) |
| 258 | +dd 0 ; Marks end-of-list. |
| 259 | +IMAGE_IMPORT_BY_NAME_ExitProcess: |
| 260 | +.Hint: dw 0 |
| 261 | +.Name: db 'ExitProcess' ; Terminated by the subsequent .Hint. |
| 262 | +IMAGE_IMPORT_BY_NAME_GetStdHandle: |
| 263 | +.Hint: dw 0 |
| 264 | +.Name: db 'GetStdHandle' ; Terminated by the subsequent .Hint. |
| 265 | +IMAGE_IMPORT_BY_NAME_WriteFile: |
| 266 | +.Hint: dw 0 |
| 267 | +.Name: db 'WriteFile' ; Terminated below. |
| 268 | +db 0 ; Terminates last .Name. |
| 269 | + |
| 270 | +_KERNEL32_str: db 'kernel32', 0 ; 'KERNEL32' and 'KERNEL32.dll' also work. |
| 271 | +_idescs: |
| 272 | +IMAGE_IMPORT_DESCRIPTOR__0: |
| 273 | +.OriginalFirstThunk: dd (textbase - imagebase) + (_hintnames - _text) |
| 274 | +.TimeDateStamp: dd 0 |
| 275 | +.ForwarderChain: dd 0 |
| 276 | +.Name: dd (textbase - imagebase) + (_KERNEL32_str - _text) |
| 277 | +.FirstThunk: dd (textbase - imagebase) + (_iat - _text) |
| 278 | + |
| 279 | +_idata_data_end: |
| 280 | +_eof: |
| 281 | +;bss_size equ 0 |
| 282 | +;IMAGE_IMPORT_DESCRIPTOR__1: ; Empty, marks end-of-list. |
| 283 | +;.OriginalFirstThunk: dd 0 |
| 284 | +;.TimeDateStamp: dd 0 |
| 285 | +;.ForwarderChain: dd 0 |
| 286 | +;.Name: dd 0 |
| 287 | +;.FirstThunk: dd 0 |
| 288 | +;_idata_end: |
| 289 | +bss_size equ 20 ; _idata_end - _eof |
| 290 | + |
| 291 | +%if (_text - _filestart) & (file_alignment - 1) |
| 292 | +%fatal _text is not aligned to file_alignment, needed by Windows XP. |
| 293 | +%endif |
| 294 | + |
0 commit comments