2Copyright 2018-2022 Intel Corporation
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
17/*xjslint node: true */
18/*xjslint plusplus: true */
19/*xjslint maxlen: 256 */
21/*jshint strict: false */
22/*jshint esversion: 6 */
25const exeJavaScriptGuid = 'B996015880544A19B7F7E9BE44914C18';
26const exeMeshPolicyGuid = 'B996015880544A19B7F7E9BE44914C19';
27const exeNullPolicyGuid = 'B996015880544A19B7F7E9BE44914C20';
30// Changes a Windows Executable to add JavaScript inside of it.
31// This method will write to destination stream and close it.
34// platform: 'win32' or 'linux',
35// sourceFileName: 'pathToBinary',
36// destinationStream: 'outputStream'
38// peinfo {} // Optional, if PE header already parsed place it here.
41module.exports.streamExeWithJavaScript = function (options) {
43 if (!options.platform) { throw ('platform not specified'); }
44 if (!options.destinationStream) { throw ('destination stream was not specified'); }
45 if (!options.sourceFileName) { throw ('source file not specified'); }
46 if (!options.js) { throw ('js content not specified'); }
48 // If a Windows binary, parse it if not already parsed
49 if ((options.platform == 'win32') && (!options.peinfo)) { options.peinfo = module.exports.parseWindowsExecutable(options.sourceFileName); }
51 // If unsigned Windows or Linux, we merge at the end with the GUID and no padding.
52 if (((options.platform == 'win32') && (options.peinfo.CertificateTableAddress == 0)) || (options.platform != 'win32')) {
53 // This is not a signed binary, so we can just send over the EXE then the MSH
54 options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r' });
55 options.destinationStream.sourceStream.options = options;
56 options.destinationStream.sourceStream.on('end', function () {
57 // Once the binary is streamed, write the msh + length + guid in that order.
58 this.options.destinationStream.write(this.options.js); // JS content
59 var sz = Buffer.alloc(4);
60 sz.writeUInt32BE(this.options.js.length, 0);
61 this.options.destinationStream.write(sz); // Length in small endian
62 this.options.destinationStream.end(Buffer.from(exeJavaScriptGuid, 'hex')); // GUID
64 // Pipe the entire source binary without ending the stream.
65 options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false });
67 throw ('streamExeWithJavaScript(): Cannot stream JavaScript with signed executable.');
72// Changes a Windows Executable to add the MSH inside of it.
73// This method will write to destination stream and close it.
76// platform: 'win32' or 'linux',
77// sourceFileName: 'pathToBinary',
78// destinationStream: 'outputStream'
80// randomPolicy: true, // Set is the MSH contains random data
81// peinfo {} // Optional, if PE header already parsed place it here.
84module.exports.streamExeWithMeshPolicy = function (options) {
86 if (!options.platform) { throw ('platform not specified'); }
87 if (!options.destinationStream) { throw ('destination stream was not specified'); }
88 if (!options.sourceFileName) { throw ('source file not specified'); }
89 if (!options.msh) { throw ('msh content not specified'); }
90 options.mshbuf = Buffer.from(options.msh, 'utf8');
92 // If a Windows binary, parse it if not already parsed
93 if ((options.platform == 'win32') && (!options.peinfo)) { options.peinfo = module.exports.parseWindowsExecutable(options.sourceFileName); }
95 // If unsigned Windows or Linux, we merge at the end with the GUID and no padding.
96 if ((options.platform == 'win32' && options.peinfo.CertificateTableAddress == 0) || options.platform != 'win32') {
97 // This is not a signed binary, so we can just send over the EXE then the MSH
98 options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r' });
99 options.destinationStream.sourceStream.options = options;
100 options.destinationStream.sourceStream.on('end', function () {
101 // Once the binary is streamed, write the msh + length + guid in that order.
102 this.options.destinationStream.write(this.options.mshbuf); // MSH
103 var sz = Buffer.alloc(4);
104 sz.writeUInt32BE(this.options.mshbuf.length, 0);
105 this.options.destinationStream.write(sz); // Length in small endian
106 this.options.destinationStream.end(Buffer.from((this.options.randomPolicy === true) ? exeNullPolicyGuid : exeMeshPolicyGuid, 'hex')); // Guid
108 // Pipe the entire source binary without ending the stream.
109 options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false });
110 } else if (options.platform == 'win32' && options.peinfo.CertificateTableAddress != 0) {
111 // Read up to the certificate table size and stream that out
112 options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: 0, end: options.peinfo.CertificateTableSizePos - 1 });
113 options.destinationStream.sourceStream.mshPadding = (8 - ((options.peinfo.certificateDwLength + options.mshbuf.length + 20) % 8)) % 8; // Compute the padding with quad-align
114 options.destinationStream.sourceStream.CertificateTableSize = (options.peinfo.CertificateTableSize + options.mshbuf.length + 20 + options.destinationStream.sourceStream.mshPadding); // Add to the certificate table size
115 options.destinationStream.sourceStream.certificateDwLength = (options.peinfo.certificateDwLength + options.mshbuf.length + 20 + options.destinationStream.sourceStream.mshPadding); // Add to the certificate size
116 options.destinationStream.sourceStream.options = options;
118 options.destinationStream.sourceStream.on('end', function () {
119 // We sent up to the CertificateTableSize, now we need to send the updated certificate table size
120 var sz = Buffer.alloc(4);
121 sz.writeUInt32LE(this.CertificateTableSize, 0);
122 this.options.destinationStream.write(sz); // New cert table size
124 // Stream everything up to the start of the certificate table entry
125 var source2 = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: this.options.peinfo.CertificateTableSizePos + 4, end: this.options.peinfo.CertificateTableAddress - 1 });
126 source2.options = this.options;
127 source2.mshPadding = this.mshPadding;
128 source2.certificateDwLength = this.certificateDwLength;
129 source2.on('end', function () {
130 // We've sent up to the Certificate DWLength, which we need to update
131 var sz = Buffer.alloc(4);
132 sz.writeUInt32LE(this.certificateDwLength, 0);
133 this.options.destinationStream.write(sz); // New certificate length
135 // Stream the entire binary until the end
136 var source3 = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: this.options.peinfo.CertificateTableAddress + 4 });
137 source3.options = this.options;
138 source3.mshPadding = this.mshPadding;
139 source3.on('end', function () {
140 // We've sent the entire binary... Now send: Padding + MSH + MSHLength + GUID
141 if (this.mshPadding > 0) { this.options.destinationStream.write(Buffer.alloc(this.mshPadding)); } // Padding
142 this.options.destinationStream.write(this.options.mshbuf); // MSH content
143 var sz = Buffer.alloc(4);
144 sz.writeUInt32BE(this.options.mshbuf.length, 0);
145 this.options.destinationStream.write(sz); // MSH Length, small-endian
146 this.options.destinationStream.end(Buffer.from((this.options.randomPolicy === true) ? exeNullPolicyGuid : exeMeshPolicyGuid, 'hex')); // Guid
148 source3.pipe(this.options.destinationStream, { end: false });
149 this.options.sourceStream = source3;
151 source2.pipe(this.options.destinationStream, { end: false });
152 this.options.destinationStream.sourceStream = source2;
154 options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false });
159// Return information about this executable
160// This works only on Windows binaries
161module.exports.parseWindowsExecutable = function (exePath) {
163 var fs = require('fs');
164 var fd = fs.openSync(exePath, 'r');
166 var dosHeader = Buffer.alloc(64);
167 var ntHeader = Buffer.alloc(24);
171 // Read the DOS header
172 bytesRead = fs.readSync(fd, dosHeader, 0, 64, 0);
173 if (dosHeader.readUInt16LE(0).toString(16).toUpperCase() != '5A4D') { throw ('unrecognized binary format'); }
175 // Read the NT header
176 bytesRead = fs.readSync(fd, ntHeader, 0, ntHeader.length, dosHeader.readUInt32LE(60));
177 if (ntHeader.slice(0, 4).toString('hex') != '50450000') {
178 throw ('not a PE file');
180 switch (ntHeader.readUInt16LE(4).toString(16)) {
181 case '14c': // 32 bit
182 retVal.format = 'x86';
184 case '8664': // 64 bit
185 retVal.format = 'x64';
188 retVal.format = undefined;
192 retVal.optionalHeaderSize = ntHeader.readUInt16LE(20);
193 retVal.optionalHeaderSizeAddress = dosHeader.readUInt32LE(60) + 20;
195 // Read the optional header
196 optHeader = Buffer.alloc(ntHeader.readUInt16LE(20));
197 bytesRead = fs.readSync(fd, optHeader, 0, optHeader.length, dosHeader.readUInt32LE(60) + 24);
199 retVal.CheckSumPos = dosHeader.readUInt32LE(60) + 24 + 64;
200 retVal.SizeOfCode = optHeader.readUInt32LE(4);
201 retVal.SizeOfInitializedData = optHeader.readUInt32LE(8);
202 retVal.SizeOfUnInitializedData = optHeader.readUInt32LE(12);
204 switch (optHeader.readUInt16LE(0).toString(16).toUpperCase()) {
205 case '10B': // 32 bit binary
206 numRVA = optHeader.readUInt32LE(92);
207 retVal.CertificateTableAddress = optHeader.readUInt32LE(128);
208 retVal.CertificateTableSize = optHeader.readUInt32LE(132);
209 retVal.CertificateTableSizePos = dosHeader.readUInt32LE(60) + 24 + 132;
210 retVal.rvaStartAddress = dosHeader.readUInt32LE(60) + 24 + 96;
212 case '20B': // 64 bit binary
213 numRVA = optHeader.readUInt32LE(108);
214 retVal.CertificateTableAddress = optHeader.readUInt32LE(144);
215 retVal.CertificateTableSize = optHeader.readUInt32LE(148);
216 retVal.CertificateTableSizePos = dosHeader.readUInt32LE(60) + 24 + 148;
217 retVal.rvaStartAddress = dosHeader.readUInt32LE(60) + 24 + 112;
220 throw ('Unknown Value found for Optional Magic: ' + ntHeader.readUInt16LE(24).toString(16).toUpperCase());
222 retVal.rvaCount = numRVA;
224 if (retVal.CertificateTableAddress) {
225 // Read the authenticode certificate, only one cert (only the first entry)
226 var hdr = Buffer.alloc(8);
227 fs.readSync(fd, hdr, 0, hdr.length, retVal.CertificateTableAddress);
228 retVal.certificate = Buffer.alloc(hdr.readUInt32LE(0));
229 fs.readSync(fd, retVal.certificate, 0, retVal.certificate.length, retVal.CertificateTableAddress + hdr.length);
230 retVal.certificate = retVal.certificate.toString('base64');
231 retVal.certificateDwLength = hdr.readUInt32LE(0);
239// Hash a executable file. Works on both Windows and Linux.
240// On Windows, will hash so that signature or .msh addition will not change the hash. Adding a .js on un-signed executable will change the hash.
243// sourcePath: <string> Executable Path
244// targetStream: <stream.writeable> Hashing Stream
245// platform: <string> Optional. Same value as process.platform ('win32' | 'linux' | 'darwin')
248module.exports.hashExecutableFile = function (options) {
249 if (!options.sourcePath || !options.targetStream) { throw ('Please specify sourcePath and targetStream'); }
250 var fs = require('fs');
252 // If not specified, try to determine platform type
253 if (!options.platform) {
255 // If we can parse the executable, we know it's windows.
256 options.peinfo = module.exports.parseWindowsExecutable(options.sourcePath);
257 options.platform = 'win32';
259 options.platform = 'other';
263 // Setup initial state
264 options.state = { endIndex: 0, checkSumIndex: 0, tableIndex: 0, stats: fs.statSync(options.sourcePath) };
266 if (options.platform == 'win32') {
267 if (options.peinfo.CertificateTableAddress != 0) { options.state.endIndex = options.peinfo.CertificateTableAddress; }
268 options.state.tableIndex = options.peinfo.CertificateTableSizePos - 4;
269 options.state.checkSumIndex = options.peinfo.CheckSumPos;
272 if (options.state.endIndex == 0) {
273 // We just need to check for Embedded MSH file
274 var fd = fs.openSync(options.sourcePath, 'r');
275 var guid = Buffer.alloc(16);
278 bytesRead = fs.readSync(fd, guid, 0, guid.length, options.state.stats.size - 16);
279 if (guid.toString('hex') == exeMeshPolicyGuid) {
280 bytesRead = fs.readSync(fd, guid, 0, 4, options.state.stats.size - 20);
281 options.state.endIndex = options.state.stats.size - 20 - guid.readUInt32LE(0);
283 options.state.endIndex = options.state.stats.size;
288 // Linux does not have a checksum
289 if (options.state.checkSumIndex != 0) {
291 options.state.source = fs.createReadStream(options.sourcePath, { flags: 'r', start: 0, end: options.state.checkSumIndex - 1 });
292 options.state.source.on('end', function () {
293 options.targetStream.write(Buffer.alloc(4));
294 var source = fs.createReadStream(options.sourcePath, { flags: 'r', start: options.state.checkSumIndex + 4, end: options.state.tableIndex - 1 });
295 source.on('end', function () {
296 options.targetStream.write(Buffer.alloc(8));
297 var source = fs.createReadStream(options.sourcePath, { flags: 'r', start: options.state.tableIndex + 8, end: options.state.endIndex - 1 });
298 options.state.source = source;
299 options.state.source.pipe(options.targetStream);
301 options.state.source = source;
302 options.state.source.pipe(options.targetStream, { end: false });
304 options.state.source.pipe(options.targetStream, { end: false });
307 options.state.source = fs.createReadStream(options.sourcePath, { flags: 'r', start: 0, end: options.state.endIndex - 1 });
308 options.state.source.pipe(options.targetStream);