1+ // Minimal Windows x64 PE emitter
2+ // Generates a PE32+ executable that calls ExitProcess(exitCode)
3+
4+ export interface PEEmitOptions {
5+ exitCode ?: number ;
6+ imageBase ?: number ; // default 0x140000000
7+ }
8+
9+ function align ( value : number , alignment : number ) : number {
10+ return Math . ceil ( value / alignment ) * alignment ;
11+ }
12+
13+ function writeUint16LE ( buf : Uint8Array , offset : number , value : number ) {
14+ buf [ offset ] = value & 0xff ;
15+ buf [ offset + 1 ] = ( value >>> 8 ) & 0xff ;
16+ }
17+ function writeUint32LE ( buf : Uint8Array , offset : number , value : number ) {
18+ buf [ offset ] = value & 0xff ;
19+ buf [ offset + 1 ] = ( value >>> 8 ) & 0xff ;
20+ buf [ offset + 2 ] = ( value >>> 16 ) & 0xff ;
21+ buf [ offset + 3 ] = ( value >>> 24 ) & 0xff ;
22+ }
23+ function writeUint64LE ( buf : Uint8Array , offset : number , value : number ) {
24+ const low = value >>> 0 ;
25+ const high = Math . floor ( value / 0x100000000 ) >>> 0 ;
26+ writeUint32LE ( buf , offset , low ) ;
27+ writeUint32LE ( buf , offset + 4 , high ) ;
28+ }
29+
30+ class Section {
31+ name : string ;
32+ data : Uint8Array ;
33+ virtualSize : number ;
34+ virtualAddress : number = 0 ; // RVA
35+ sizeOfRawData : number = 0 ;
36+ pointerToRawData : number = 0 ;
37+
38+ constructor ( name : string , data : Uint8Array , virtualSize ?: number ) {
39+ this . name = name ;
40+ this . data = data ;
41+ this . virtualSize = virtualSize ?? data . length ;
42+ }
43+ }
44+
45+ export function emitPE ( options : PEEmitOptions = { } ) : Uint8Array {
46+ const exitCode = options . exitCode ?? 0 ;
47+ const imageBase = options . imageBase ?? 0x140000000 ; // PE32+
48+ const fileAlignment = 0x200 ; // 512
49+ const sectionAlignment = 0x1000 ; // 4096
50+
51+ // Build .rdata with import table for kernel32!ExitProcess
52+ const dllName = Buffer . from ( "KERNEL32.dll\0" , "ascii" ) ;
53+ const funcName = Buffer . from ( "ExitProcess\0" , "ascii" ) ;
54+ const hintName = new Uint8Array ( 2 + funcName . length ) ; // hint(2) + name
55+ hintName [ 0 ] = 0 ; hintName [ 1 ] = 0 ;
56+ hintName . set ( funcName , 2 ) ;
57+
58+ // Layout inside .rdata:
59+ // [Import Descriptor][null desc][INT (OriginalFirstThunk)][IAT (FirstThunk)][DLL Name][Hint/Name]
60+ const offDesc = 0 ;
61+ const offNullDesc = offDesc + 20 ; // IMAGE_IMPORT_DESCRIPTOR size (5 * uint32)
62+ const offINT = offNullDesc + 20 ; // one 8-byte pointer + terminator
63+ const offIAT = offINT + 16 ; // one 8-byte slot + terminator
64+ const offName = offIAT + 16 ; // dll string
65+ const offHintName = offName + dllName . length ; // hint/name for ExitProcess
66+ const rdataSize = align ( offHintName + hintName . length , 8 ) ;
67+ const rdata = new Uint8Array ( rdataSize ) ;
68+
69+ // We will fill descriptor fields later when RVAs are known
70+ rdata . set ( dllName , offName ) ;
71+ rdata . set ( hintName , offHintName ) ;
72+ // INT: pointer to hint/name
73+ // IAT: two entries: first is filled by loader, second is zero terminator
74+ // Leave IAT zeros now; loader fills it
75+
76+ const rdataSection = new Section ( ".rdata" , rdata ) ;
77+
78+ // Build .text code: mov ecx, imm32; call [rip + rel32]; ret
79+ const text : number [ ] = [ ] ;
80+ // mov ecx, imm32
81+ text . push ( 0xB9 ) ;
82+ text . push ( exitCode & 0xff , ( exitCode >>> 8 ) & 0xff , ( exitCode >>> 16 ) & 0xff , ( exitCode >>> 24 ) & 0xff ) ;
83+ // call qword ptr [rip+disp32] => FF 15 disp32
84+ text . push ( 0xFF , 0x15 , 0x00 , 0x00 , 0x00 , 0x00 ) ; // placeholder disp32
85+ // ret
86+ text . push ( 0xC3 ) ;
87+ const textSection = new Section ( ".text" , new Uint8Array ( text ) ) ;
88+
89+ // Headers sizes
90+ const dosStubSize = 0x80 ; // e_lfanew points here
91+ const peSigSize = 4 ;
92+ const coffHeaderSize = 20 ;
93+ const optionalHeaderSize = 0xF0 ; // PE32+ optional header
94+ const sectionHeaderSize = 40 ;
95+ const numberOfSections = 2 ;
96+ const headersSize = align ( dosStubSize + peSigSize + coffHeaderSize + optionalHeaderSize + sectionHeaderSize * numberOfSections , fileAlignment ) ;
97+
98+ // Assign section RVAs and raw pointers
99+ let currentVA = align ( headersSize , sectionAlignment ) ;
100+ let currentRaw = headersSize ;
101+
102+ // .text
103+ textSection . virtualAddress = currentVA ;
104+ textSection . sizeOfRawData = align ( textSection . data . length , fileAlignment ) ;
105+ textSection . pointerToRawData = currentRaw ;
106+ currentVA = align ( currentVA + align ( textSection . virtualSize , sectionAlignment ) , sectionAlignment ) ;
107+ currentRaw += textSection . sizeOfRawData ;
108+
109+ // .rdata
110+ rdataSection . virtualAddress = currentVA ;
111+ rdataSection . sizeOfRawData = align ( rdataSection . data . length , fileAlignment ) ;
112+ rdataSection . pointerToRawData = currentRaw ;
113+ currentVA = align ( currentVA + align ( rdataSection . virtualSize , sectionAlignment ) , sectionAlignment ) ;
114+ currentRaw += rdataSection . sizeOfRawData ;
115+
116+ const sizeOfImage = currentVA ;
117+
118+ // Now fill import structures with actual RVAs
119+ const importDescriptorRVA = rdataSection . virtualAddress + offDesc ;
120+ const nameRVA = rdataSection . virtualAddress + offName ;
121+ const hintNameRVA = rdataSection . virtualAddress + offHintName ;
122+ const intRVA = rdataSection . virtualAddress + offINT ;
123+ const iatRVA = rdataSection . virtualAddress + offIAT ;
124+
125+ // INT[0] -> RVA of hint/name, INT[1] = 0
126+ writeUint64LE ( rdataSection . data , offINT , hintNameRVA ) ;
127+ writeUint64LE ( rdataSection . data , offINT + 8 , 0 ) ;
128+
129+ // IMAGE_IMPORT_DESCRIPTOR fields
130+ // OriginalFirstThunk (INT)
131+ writeUint32LE ( rdataSection . data , offDesc + 0 , intRVA ) ;
132+ // TimeDateStamp
133+ writeUint32LE ( rdataSection . data , offDesc + 4 , 0 ) ;
134+ // ForwarderChain
135+ writeUint32LE ( rdataSection . data , offDesc + 8 , 0 ) ;
136+ // Name
137+ writeUint32LE ( rdataSection . data , offDesc + 12 , nameRVA ) ;
138+ // FirstThunk (IAT)
139+ writeUint32LE ( rdataSection . data , offDesc + 16 , iatRVA ) ;
140+ // Null descriptor already zeros
141+
142+ // Patch call RIP-relative displacement in .text
143+ const callOffsetInText = 1 + 4 ; // after mov ecx,imm32 (1+4 bytes)
144+ const nextInstrRVA = textSection . virtualAddress + callOffsetInText + 6 ; // call is 6 bytes
145+ const disp32 = ( iatRVA - nextInstrRVA ) | 0 ; // signed
146+ writeUint32LE ( textSection . data , callOffsetInText + 2 , disp32 >>> 0 ) ;
147+
148+ // Build final buffer
149+ const totalSize = currentRaw ;
150+ const buf = new Uint8Array ( totalSize ) ;
151+
152+ // DOS header & stub
153+ buf [ 0 ] = 0x4D ; // 'M'
154+ buf [ 1 ] = 0x5A ; // 'Z'
155+ // e_lfanew at 0x3C
156+ writeUint32LE ( buf , 0x3C , dosStubSize ) ;
157+ // simple DOS stub text (optional)
158+ const stubText = Buffer . from ( "This program cannot be run in DOS mode.\r\n$" , "ascii" ) ;
159+ buf . set ( stubText . subarray ( 0 , Math . min ( stubText . length , dosStubSize - 64 ) ) , 64 ) ;
160+
161+ // PE signature
162+ buf . set ( Buffer . from ( "PE\0\0" , "ascii" ) , dosStubSize ) ;
163+
164+ // COFF header
165+ const coffStart = dosStubSize + peSigSize ;
166+ writeUint16LE ( buf , coffStart + 0 , 0x8664 ) ; // Machine AMD64
167+ writeUint16LE ( buf , coffStart + 2 , numberOfSections ) ;
168+ writeUint32LE ( buf , coffStart + 4 , Math . floor ( Date . now ( ) / 1000 ) ) ; // timestamp
169+ writeUint32LE ( buf , coffStart + 8 , 0 ) ; // ptr to symbols
170+ writeUint32LE ( buf , coffStart + 12 , 0 ) ; // number of symbols
171+ writeUint16LE ( buf , coffStart + 16 , optionalHeaderSize ) ;
172+ writeUint16LE ( buf , coffStart + 18 , 0x0002 ) ; // characteristics: executable
173+
174+ // Optional header (PE32+)
175+ const optStart = coffStart + coffHeaderSize ;
176+ writeUint16LE ( buf , optStart + 0 , 0x20B ) ; // Magic PE32+
177+ buf [ optStart + 2 ] = 14 ; // MajorLinkerVersion
178+ buf [ optStart + 3 ] = 0 ; // MinorLinkerVersion
179+ writeUint32LE ( buf , optStart + 4 , align ( textSection . data . length , fileAlignment ) ) ; // SizeOfCode
180+ writeUint32LE ( buf , optStart + 8 , align ( rdataSection . data . length , fileAlignment ) ) ; // SizeOfInitializedData
181+ const entryRVA = textSection . virtualAddress ; // entry at start of .text
182+ writeUint32LE ( buf , optStart + 12 , entryRVA ) ; // AddressOfEntryPoint
183+ writeUint32LE ( buf , optStart + 16 , textSection . virtualAddress ) ; // BaseOfCode
184+ writeUint64LE ( buf , optStart + 24 , imageBase ) ;
185+ writeUint32LE ( buf , optStart + 32 , sectionAlignment ) ;
186+ writeUint32LE ( buf , optStart + 36 , fileAlignment ) ;
187+ writeUint16LE ( buf , optStart + 40 , 6 ) ; // MajorOSVersion
188+ writeUint16LE ( buf , optStart + 42 , 0 ) ; // MinorOSVersion
189+ writeUint16LE ( buf , optStart + 44 , 0 ) ; // MajorImageVersion
190+ writeUint16LE ( buf , optStart + 46 , 0 ) ; // MinorImageVersion
191+ writeUint16LE ( buf , optStart + 48 , 6 ) ; // MajorSubsystemVersion
192+ writeUint16LE ( buf , optStart + 50 , 0 ) ; // MinorSubsystemVersion
193+ writeUint32LE ( buf , optStart + 52 , 0 ) ; // Win32VersionValue
194+ writeUint32LE ( buf , optStart + 56 , sizeOfImage ) ;
195+ writeUint32LE ( buf , optStart + 60 , headersSize ) ;
196+ writeUint32LE ( buf , optStart + 64 , 0 ) ; // CheckSum (optional)
197+ writeUint16LE ( buf , optStart + 68 , 3 ) ; // Subsystem: Windows CUI
198+ writeUint16LE ( buf , optStart + 70 , 0 ) ; // DllCharacteristics
199+ writeUint64LE ( buf , optStart + 72 , 0x400000 ) ; // SizeOfStackReserve
200+ writeUint64LE ( buf , optStart + 80 , 0x4000 ) ; // SizeOfStackCommit
201+ writeUint64LE ( buf , optStart + 88 , 0x100000 ) ; // SizeOfHeapReserve
202+ writeUint64LE ( buf , optStart + 96 , 0x2000 ) ; // SizeOfHeapCommit
203+ writeUint32LE ( buf , optStart + 104 , 0 ) ; // LoaderFlags
204+ writeUint32LE ( buf , optStart + 108 , 16 ) ; // NumberOfRvaAndSizes
205+
206+ // Data directories (set import table and IAT)
207+ const ddStart = optStart + 112 ;
208+ // Export
209+ writeUint32LE ( buf , ddStart + 0 , 0 ) ; writeUint32LE ( buf , ddStart + 4 , 0 ) ;
210+ // Import
211+ writeUint32LE ( buf , ddStart + 8 , importDescriptorRVA ) ; writeUint32LE ( buf , ddStart + 12 , 40 ) ; // approx size
212+ // Resource
213+ writeUint32LE ( buf , ddStart + 16 , 0 ) ; writeUint32LE ( buf , ddStart + 20 , 0 ) ;
214+ // Exception
215+ writeUint32LE ( buf , ddStart + 24 , 0 ) ; writeUint32LE ( buf , ddStart + 28 , 0 ) ;
216+ // Security
217+ writeUint32LE ( buf , ddStart + 32 , 0 ) ; writeUint32LE ( buf , ddStart + 36 , 0 ) ;
218+ // Relocation
219+ writeUint32LE ( buf , ddStart + 40 , 0 ) ; writeUint32LE ( buf , ddStart + 44 , 0 ) ;
220+ // Debug
221+ writeUint32LE ( buf , ddStart + 48 , 0 ) ; writeUint32LE ( buf , ddStart + 52 , 0 ) ;
222+ // Architecture
223+ writeUint32LE ( buf , ddStart + 56 , 0 ) ; writeUint32LE ( buf , ddStart + 60 , 0 ) ;
224+ // GlobalPtr
225+ writeUint32LE ( buf , ddStart + 64 , 0 ) ; writeUint32LE ( buf , ddStart + 68 , 0 ) ;
226+ // TLS
227+ writeUint32LE ( buf , ddStart + 72 , 0 ) ; writeUint32LE ( buf , ddStart + 76 , 0 ) ;
228+ // LoadConfig
229+ writeUint32LE ( buf , ddStart + 80 , 0 ) ; writeUint32LE ( buf , ddStart + 84 , 0 ) ;
230+ // BoundImport
231+ writeUint32LE ( buf , ddStart + 88 , 0 ) ; writeUint32LE ( buf , ddStart + 92 , 0 ) ;
232+ // IAT (index 12)
233+ writeUint32LE ( buf , ddStart + 96 , iatRVA ) ; writeUint32LE ( buf , ddStart + 100 , 16 ) ;
234+ // DelayImport
235+ writeUint32LE ( buf , ddStart + 104 , 0 ) ; writeUint32LE ( buf , ddStart + 108 , 0 ) ;
236+ // COM Descriptor
237+ writeUint32LE ( buf , ddStart + 112 , 0 ) ; writeUint32LE ( buf , ddStart + 116 , 0 ) ;
238+ // Reserved
239+ writeUint32LE ( buf , ddStart + 120 , 0 ) ; writeUint32LE ( buf , ddStart + 124 , 0 ) ;
240+
241+ // Section headers
242+ const secStart = optStart + optionalHeaderSize ;
243+
244+ function writeSectionHeader ( idx : number , s : Section , characteristics : number ) {
245+ const off = secStart + idx * sectionHeaderSize ;
246+ // Name (8 bytes)
247+ const nameBuf = Buffer . from ( s . name , "ascii" ) ;
248+ for ( let i = 0 ; i < 8 ; i ++ ) buf [ off + i ] = i < nameBuf . length ? nameBuf [ i ] : 0 ;
249+ writeUint32LE ( buf , off + 8 , s . virtualSize ) ;
250+ writeUint32LE ( buf , off + 12 , s . virtualAddress ) ;
251+ writeUint32LE ( buf , off + 16 , s . sizeOfRawData ) ;
252+ writeUint32LE ( buf , off + 20 , s . pointerToRawData ) ;
253+ writeUint32LE ( buf , off + 24 , 0 ) ; // PointerToRelocations
254+ writeUint32LE ( buf , off + 28 , 0 ) ; // PointerToLinenumbers
255+ writeUint16LE ( buf , off + 32 , 0 ) ; // NumberOfRelocations
256+ writeUint16LE ( buf , off + 34 , 0 ) ; // NumberOfLinenumbers
257+ writeUint32LE ( buf , off + 36 , characteristics ) ;
258+ }
259+
260+ // Characteristics
261+ const IMAGE_SCN_CNT_CODE = 0x00000020 ;
262+ const IMAGE_SCN_MEM_EXECUTE = 0x20000000 ;
263+ const IMAGE_SCN_MEM_READ = 0x40000000 ;
264+ const IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 ;
265+
266+ writeSectionHeader ( 0 , textSection , IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ ) ;
267+ writeSectionHeader ( 1 , rdataSection , IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ ) ;
268+
269+ // Write section raw data
270+ buf . set ( textSection . data , textSection . pointerToRawData ) ;
271+ buf . set ( rdataSection . data , rdataSection . pointerToRawData ) ;
272+
273+ return buf ;
274+ }
0 commit comments