123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- /*
- Copyright 2018-2022 Intel Corporation
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- /*xjslint node: true */
- /*xjslint plusplus: true */
- /*xjslint maxlen: 256 */
- /*jshint node: true */
- /*jshint strict: false */
- /*jshint esversion: 6 */
- "use strict";
- const exeJavaScriptGuid = 'B996015880544A19B7F7E9BE44914C18';
- const exeMeshPolicyGuid = 'B996015880544A19B7F7E9BE44914C19';
- const exeNullPolicyGuid = 'B996015880544A19B7F7E9BE44914C20';
- // Changes a Windows Executable to add JavaScript inside of it.
- // This method will write to destination stream and close it.
- //
- // options = {
- // platform: 'win32' or 'linux',
- // sourceFileName: 'pathToBinary',
- // destinationStream: 'outputStream'
- // js: 'jsContent',
- // peinfo {} // Optional, if PE header already parsed place it here.
- // }
- //
- module.exports.streamExeWithJavaScript = function (options) {
- // Check all inputs
- if (!options.platform) { throw ('platform not specified'); }
- if (!options.destinationStream) { throw ('destination stream was not specified'); }
- if (!options.sourceFileName) { throw ('source file not specified'); }
- if (!options.js) { throw ('js content not specified'); }
- // If a Windows binary, parse it if not already parsed
- if ((options.platform == 'win32') && (!options.peinfo)) { options.peinfo = module.exports.parseWindowsExecutable(options.sourceFileName); }
- // If unsigned Windows or Linux, we merge at the end with the GUID and no padding.
- if (((options.platform == 'win32') && (options.peinfo.CertificateTableAddress == 0)) || (options.platform != 'win32')) {
- // This is not a signed binary, so we can just send over the EXE then the MSH
- options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r' });
- options.destinationStream.sourceStream.options = options;
- options.destinationStream.sourceStream.on('end', function () {
- // Once the binary is streamed, write the msh + length + guid in that order.
- this.options.destinationStream.write(this.options.js); // JS content
- var sz = Buffer.alloc(4);
- sz.writeUInt32BE(this.options.js.length, 0);
- this.options.destinationStream.write(sz); // Length in small endian
- this.options.destinationStream.end(Buffer.from(exeJavaScriptGuid, 'hex')); // GUID
- });
- // Pipe the entire source binary without ending the stream.
- options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false });
- } else {
- throw ('streamExeWithJavaScript(): Cannot stream JavaScript with signed executable.');
- }
- };
- // Changes a Windows Executable to add the MSH inside of it.
- // This method will write to destination stream and close it.
- //
- // options = {
- // platform: 'win32' or 'linux',
- // sourceFileName: 'pathToBinary',
- // destinationStream: 'outputStream'
- // msh: 'mshContent',
- // randomPolicy: true, // Set is the MSH contains random data
- // peinfo {} // Optional, if PE header already parsed place it here.
- // }
- //
- module.exports.streamExeWithMeshPolicy = function (options) {
- // Check all inputs
- if (!options.platform) { throw ('platform not specified'); }
- if (!options.destinationStream) { throw ('destination stream was not specified'); }
- if (!options.sourceFileName) { throw ('source file not specified'); }
- if (!options.msh) { throw ('msh content not specified'); }
- options.mshbuf = Buffer.from(options.msh, 'utf8');
- // If a Windows binary, parse it if not already parsed
- if ((options.platform == 'win32') && (!options.peinfo)) { options.peinfo = module.exports.parseWindowsExecutable(options.sourceFileName); }
- // If unsigned Windows or Linux, we merge at the end with the GUID and no padding.
- if ((options.platform == 'win32' && options.peinfo.CertificateTableAddress == 0) || options.platform != 'win32') {
- // This is not a signed binary, so we can just send over the EXE then the MSH
- options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r' });
- options.destinationStream.sourceStream.options = options;
- options.destinationStream.sourceStream.on('end', function () {
- // Once the binary is streamed, write the msh + length + guid in that order.
- this.options.destinationStream.write(this.options.mshbuf); // MSH
- var sz = Buffer.alloc(4);
- sz.writeUInt32BE(this.options.mshbuf.length, 0);
- this.options.destinationStream.write(sz); // Length in small endian
- this.options.destinationStream.end(Buffer.from((this.options.randomPolicy === true) ? exeNullPolicyGuid : exeMeshPolicyGuid, 'hex')); // Guid
- });
- // Pipe the entire source binary without ending the stream.
- options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false });
- } else if (options.platform == 'win32' && options.peinfo.CertificateTableAddress != 0) {
- // Read up to the certificate table size and stream that out
- options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: 0, end: options.peinfo.CertificateTableSizePos - 1 });
- options.destinationStream.sourceStream.mshPadding = (8 - ((options.peinfo.certificateDwLength + options.mshbuf.length + 20) % 8)) % 8; // Compute the padding with quad-align
- options.destinationStream.sourceStream.CertificateTableSize = (options.peinfo.CertificateTableSize + options.mshbuf.length + 20 + options.destinationStream.sourceStream.mshPadding); // Add to the certificate table size
- options.destinationStream.sourceStream.certificateDwLength = (options.peinfo.certificateDwLength + options.mshbuf.length + 20 + options.destinationStream.sourceStream.mshPadding); // Add to the certificate size
- options.destinationStream.sourceStream.options = options;
- options.destinationStream.sourceStream.on('end', function () {
- // We sent up to the CertificateTableSize, now we need to send the updated certificate table size
- var sz = Buffer.alloc(4);
- sz.writeUInt32LE(this.CertificateTableSize, 0);
- this.options.destinationStream.write(sz); // New cert table size
- // Stream everything up to the start of the certificate table entry
- var source2 = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: this.options.peinfo.CertificateTableSizePos + 4, end: this.options.peinfo.CertificateTableAddress - 1 });
- source2.options = this.options;
- source2.mshPadding = this.mshPadding;
- source2.certificateDwLength = this.certificateDwLength;
- source2.on('end', function () {
- // We've sent up to the Certificate DWLength, which we need to update
- var sz = Buffer.alloc(4);
- sz.writeUInt32LE(this.certificateDwLength, 0);
- this.options.destinationStream.write(sz); // New certificate length
- // Stream the entire binary until the end
- var source3 = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: this.options.peinfo.CertificateTableAddress + 4 });
- source3.options = this.options;
- source3.mshPadding = this.mshPadding;
- source3.on('end', function () {
- // We've sent the entire binary... Now send: Padding + MSH + MSHLength + GUID
- if (this.mshPadding > 0) { this.options.destinationStream.write(Buffer.alloc(this.mshPadding)); } // Padding
- this.options.destinationStream.write(this.options.mshbuf); // MSH content
- var sz = Buffer.alloc(4);
- sz.writeUInt32BE(this.options.mshbuf.length, 0);
- this.options.destinationStream.write(sz); // MSH Length, small-endian
- this.options.destinationStream.end(Buffer.from((this.options.randomPolicy === true) ? exeNullPolicyGuid : exeMeshPolicyGuid, 'hex')); // Guid
- });
- source3.pipe(this.options.destinationStream, { end: false });
- this.options.sourceStream = source3;
- });
- source2.pipe(this.options.destinationStream, { end: false });
- this.options.destinationStream.sourceStream = source2;
- });
- options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false });
- }
- };
- // Return information about this executable
- // This works only on Windows binaries
- module.exports.parseWindowsExecutable = function (exePath) {
- var retVal = {};
- var fs = require('fs');
- var fd = fs.openSync(exePath, 'r');
- var bytesRead;
- var dosHeader = Buffer.alloc(64);
- var ntHeader = Buffer.alloc(24);
- var optHeader;
- var numRVA;
- // Read the DOS header
- bytesRead = fs.readSync(fd, dosHeader, 0, 64, 0);
- if (dosHeader.readUInt16LE(0).toString(16).toUpperCase() != '5A4D') { throw ('unrecognized binary format'); }
- // Read the NT header
- bytesRead = fs.readSync(fd, ntHeader, 0, ntHeader.length, dosHeader.readUInt32LE(60));
- if (ntHeader.slice(0, 4).toString('hex') != '50450000') {
- throw ('not a PE file');
- }
- switch (ntHeader.readUInt16LE(4).toString(16)) {
- case '14c': // 32 bit
- retVal.format = 'x86';
- break;
- case '8664': // 64 bit
- retVal.format = 'x64';
- break;
- default: // Unknown
- retVal.format = undefined;
- break;
- }
- retVal.optionalHeaderSize = ntHeader.readUInt16LE(20);
- retVal.optionalHeaderSizeAddress = dosHeader.readUInt32LE(60) + 20;
- // Read the optional header
- optHeader = Buffer.alloc(ntHeader.readUInt16LE(20));
- bytesRead = fs.readSync(fd, optHeader, 0, optHeader.length, dosHeader.readUInt32LE(60) + 24);
- retVal.CheckSumPos = dosHeader.readUInt32LE(60) + 24 + 64;
- retVal.SizeOfCode = optHeader.readUInt32LE(4);
- retVal.SizeOfInitializedData = optHeader.readUInt32LE(8);
- retVal.SizeOfUnInitializedData = optHeader.readUInt32LE(12);
- switch (optHeader.readUInt16LE(0).toString(16).toUpperCase()) {
- case '10B': // 32 bit binary
- numRVA = optHeader.readUInt32LE(92);
- retVal.CertificateTableAddress = optHeader.readUInt32LE(128);
- retVal.CertificateTableSize = optHeader.readUInt32LE(132);
- retVal.CertificateTableSizePos = dosHeader.readUInt32LE(60) + 24 + 132;
- retVal.rvaStartAddress = dosHeader.readUInt32LE(60) + 24 + 96;
- break;
- case '20B': // 64 bit binary
- numRVA = optHeader.readUInt32LE(108);
- retVal.CertificateTableAddress = optHeader.readUInt32LE(144);
- retVal.CertificateTableSize = optHeader.readUInt32LE(148);
- retVal.CertificateTableSizePos = dosHeader.readUInt32LE(60) + 24 + 148;
- retVal.rvaStartAddress = dosHeader.readUInt32LE(60) + 24 + 112;
- break;
- default:
- throw ('Unknown Value found for Optional Magic: ' + ntHeader.readUInt16LE(24).toString(16).toUpperCase());
- }
- retVal.rvaCount = numRVA;
- if (retVal.CertificateTableAddress) {
- // Read the authenticode certificate, only one cert (only the first entry)
- var hdr = Buffer.alloc(8);
- fs.readSync(fd, hdr, 0, hdr.length, retVal.CertificateTableAddress);
- retVal.certificate = Buffer.alloc(hdr.readUInt32LE(0));
- fs.readSync(fd, retVal.certificate, 0, retVal.certificate.length, retVal.CertificateTableAddress + hdr.length);
- retVal.certificate = retVal.certificate.toString('base64');
- retVal.certificateDwLength = hdr.readUInt32LE(0);
- }
- fs.closeSync(fd);
- return (retVal);
- };
- //
- // Hash a executable file. Works on both Windows and Linux.
- // 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.
- //
- // options = {
- // sourcePath: <string> Executable Path
- // targetStream: <stream.writeable> Hashing Stream
- // platform: <string> Optional. Same value as process.platform ('win32' | 'linux' | 'darwin')
- // }
- //
- module.exports.hashExecutableFile = function (options) {
- if (!options.sourcePath || !options.targetStream) { throw ('Please specify sourcePath and targetStream'); }
- var fs = require('fs');
- // If not specified, try to determine platform type
- if (!options.platform) {
- try {
- // If we can parse the executable, we know it's windows.
- options.peinfo = module.exports.parseWindowsExecutable(options.sourcePath);
- options.platform = 'win32';
- } catch (e) {
- options.platform = 'other';
- }
- }
- // Setup initial state
- options.state = { endIndex: 0, checkSumIndex: 0, tableIndex: 0, stats: fs.statSync(options.sourcePath) };
- if (options.platform == 'win32') {
- if (options.peinfo.CertificateTableAddress != 0) { options.state.endIndex = options.peinfo.CertificateTableAddress; }
- options.state.tableIndex = options.peinfo.CertificateTableSizePos - 4;
- options.state.checkSumIndex = options.peinfo.CheckSumPos;
- }
- if (options.state.endIndex == 0) {
- // We just need to check for Embedded MSH file
- var fd = fs.openSync(options.sourcePath, 'r');
- var guid = Buffer.alloc(16);
- var bytesRead;
- bytesRead = fs.readSync(fd, guid, 0, guid.length, options.state.stats.size - 16);
- if (guid.toString('hex') == exeMeshPolicyGuid) {
- bytesRead = fs.readSync(fd, guid, 0, 4, options.state.stats.size - 20);
- options.state.endIndex = options.state.stats.size - 20 - guid.readUInt32LE(0);
- } else {
- options.state.endIndex = options.state.stats.size;
- }
- fs.closeSync(fd);
- }
- // Linux does not have a checksum
- if (options.state.checkSumIndex != 0) {
- // Windows
- options.state.source = fs.createReadStream(options.sourcePath, { flags: 'r', start: 0, end: options.state.checkSumIndex - 1 });
- options.state.source.on('end', function () {
- options.targetStream.write(Buffer.alloc(4));
- var source = fs.createReadStream(options.sourcePath, { flags: 'r', start: options.state.checkSumIndex + 4, end: options.state.tableIndex - 1 });
- source.on('end', function () {
- options.targetStream.write(Buffer.alloc(8));
- var source = fs.createReadStream(options.sourcePath, { flags: 'r', start: options.state.tableIndex + 8, end: options.state.endIndex - 1 });
- options.state.source = source;
- options.state.source.pipe(options.targetStream);
- });
- options.state.source = source;
- options.state.source.pipe(options.targetStream, { end: false });
- });
- options.state.source.pipe(options.targetStream, { end: false });
- } else {
- // Linux
- options.state.source = fs.createReadStream(options.sourcePath, { flags: 'r', start: 0, end: options.state.endIndex - 1 });
- options.state.source.pipe(options.targetStream);
- }
- };
|