EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
exeHandler.js
Go to the documentation of this file.
1/*
2Copyright 2018-2022 Intel Corporation
3
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
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
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.
15*/
16
17/*xjslint node: true */
18/*xjslint plusplus: true */
19/*xjslint maxlen: 256 */
20/*jshint node: true */
21/*jshint strict: false */
22/*jshint esversion: 6 */
23"use strict";
24
25const exeJavaScriptGuid = 'B996015880544A19B7F7E9BE44914C18';
26const exeMeshPolicyGuid = 'B996015880544A19B7F7E9BE44914C19';
27const exeNullPolicyGuid = 'B996015880544A19B7F7E9BE44914C20';
28
29
30// Changes a Windows Executable to add JavaScript inside of it.
31// This method will write to destination stream and close it.
32//
33// options = {
34// platform: 'win32' or 'linux',
35// sourceFileName: 'pathToBinary',
36// destinationStream: 'outputStream'
37// js: 'jsContent',
38// peinfo {} // Optional, if PE header already parsed place it here.
39// }
40//
41module.exports.streamExeWithJavaScript = function (options) {
42 // Check all inputs
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'); }
47
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); }
50
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
63 });
64 // Pipe the entire source binary without ending the stream.
65 options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false });
66 } else {
67 throw ('streamExeWithJavaScript(): Cannot stream JavaScript with signed executable.');
68 }
69};
70
71
72// Changes a Windows Executable to add the MSH inside of it.
73// This method will write to destination stream and close it.
74//
75// options = {
76// platform: 'win32' or 'linux',
77// sourceFileName: 'pathToBinary',
78// destinationStream: 'outputStream'
79// msh: 'mshContent',
80// randomPolicy: true, // Set is the MSH contains random data
81// peinfo {} // Optional, if PE header already parsed place it here.
82// }
83//
84module.exports.streamExeWithMeshPolicy = function (options) {
85 // Check all inputs
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');
91
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); }
94
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
107 });
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;
117
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
123
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
134
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
147 });
148 source3.pipe(this.options.destinationStream, { end: false });
149 this.options.sourceStream = source3;
150 });
151 source2.pipe(this.options.destinationStream, { end: false });
152 this.options.destinationStream.sourceStream = source2;
153 });
154 options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false });
155 }
156};
157
158
159// Return information about this executable
160// This works only on Windows binaries
161module.exports.parseWindowsExecutable = function (exePath) {
162 var retVal = {};
163 var fs = require('fs');
164 var fd = fs.openSync(exePath, 'r');
165 var bytesRead;
166 var dosHeader = Buffer.alloc(64);
167 var ntHeader = Buffer.alloc(24);
168 var optHeader;
169 var numRVA;
170
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'); }
174
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');
179 }
180 switch (ntHeader.readUInt16LE(4).toString(16)) {
181 case '14c': // 32 bit
182 retVal.format = 'x86';
183 break;
184 case '8664': // 64 bit
185 retVal.format = 'x64';
186 break;
187 default: // Unknown
188 retVal.format = undefined;
189 break;
190 }
191
192 retVal.optionalHeaderSize = ntHeader.readUInt16LE(20);
193 retVal.optionalHeaderSizeAddress = dosHeader.readUInt32LE(60) + 20;
194
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);
198
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);
203
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;
211 break;
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;
218 break;
219 default:
220 throw ('Unknown Value found for Optional Magic: ' + ntHeader.readUInt16LE(24).toString(16).toUpperCase());
221 }
222 retVal.rvaCount = numRVA;
223
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);
232 }
233 fs.closeSync(fd);
234 return (retVal);
235};
236
237
238//
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.
241//
242// options = {
243// sourcePath: <string> Executable Path
244// targetStream: <stream.writeable> Hashing Stream
245// platform: <string> Optional. Same value as process.platform ('win32' | 'linux' | 'darwin')
246// }
247//
248module.exports.hashExecutableFile = function (options) {
249 if (!options.sourcePath || !options.targetStream) { throw ('Please specify sourcePath and targetStream'); }
250 var fs = require('fs');
251
252 // If not specified, try to determine platform type
253 if (!options.platform) {
254 try {
255 // If we can parse the executable, we know it's windows.
256 options.peinfo = module.exports.parseWindowsExecutable(options.sourcePath);
257 options.platform = 'win32';
258 } catch (e) {
259 options.platform = 'other';
260 }
261 }
262
263 // Setup initial state
264 options.state = { endIndex: 0, checkSumIndex: 0, tableIndex: 0, stats: fs.statSync(options.sourcePath) };
265
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;
270 }
271
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);
276 var bytesRead;
277
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);
282 } else {
283 options.state.endIndex = options.state.stats.size;
284 }
285 fs.closeSync(fd);
286 }
287
288 // Linux does not have a checksum
289 if (options.state.checkSumIndex != 0) {
290 // Windows
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);
300 });
301 options.state.source = source;
302 options.state.source.pipe(options.targetStream, { end: false });
303 });
304 options.state.source.pipe(options.targetStream, { end: false });
305 } else {
306 // Linux
307 options.state.source = fs.createReadStream(options.sourcePath, { flags: 'r', start: 0, end: options.state.endIndex - 1 });
308 options.state.source.pipe(options.targetStream);
309 }
310};