diff --git a/authenticode.js b/authenticode.js index 37d39d50..39fa1c75 100644 --- a/authenticode.js +++ b/authenticode.js @@ -787,6 +787,8 @@ function createAuthenticodeHandler(path) { if ((StringFileInfo == null) || (StringFileInfo.stringTable == null) || (StringFileInfo.stringTable.strings == null)) return null; const strings = StringFileInfo.stringTable.strings; for (var i in strings) { r[strings[i].key] = strings[i].value; } + r['~FileVersion'] = (info.fixedFileInfo.dwFileVersionMS >> 16) + '.' + (info.fixedFileInfo.dwFileVersionMS & 0xFFFF) + '.' + (info.fixedFileInfo.dwFileVersionLS >> 16) + '.' + (info.fixedFileInfo.dwFileVersionLS & 0xFFFF); + r['~ProductVersion'] = (info.fixedFileInfo.dwProductVersionMS >> 16) + '.' + (info.fixedFileInfo.dwProductVersionMS & 0xFFFF) + '.' + (info.fixedFileInfo.dwProductVersionLS >> 16) + '.' + (info.fixedFileInfo.dwProductVersionLS & 0xFFFF); return r; } @@ -794,7 +796,7 @@ function createAuthenticodeHandler(path) { obj.setVersionInfo = function (versions) { // Convert the version information into a string array const stringArray = []; - for (var i in versions) { stringArray.push({ key: i, value: versions[i] }); } + for (var i in versions) { if (!i.startsWith('~')) { stringArray.push({ key: i, value: versions[i] }); } } // Get the existing version data and switch the strings to the new strings var r = {}, info = readVersionInfo(getVersionInfoData(), 0); @@ -804,6 +806,20 @@ function createAuthenticodeHandler(path) { if ((StringFileInfo == null) || (StringFileInfo.stringTable == null) || (StringFileInfo.stringTable.strings == null)) return; StringFileInfo.stringTable.strings = stringArray; + // Set the file version + if (versions['~FileVersion'] != null) { + const FileVersionSplit = versions['~FileVersion'].split('.'); + info.fixedFileInfo.dwFileVersionMS = (parseInt(FileVersionSplit[0]) << 16) + parseInt(FileVersionSplit[1]); + info.fixedFileInfo.dwFileVersionLS = (parseInt(FileVersionSplit[2]) << 16) + parseInt(FileVersionSplit[3]); + } + + // Set the product version + if (versions['~ProductVersion'] != null) { + const ProductVersionSplit = versions['~ProductVersion'].split('.'); + info.fixedFileInfo.dwProductVersionMS = (parseInt(ProductVersionSplit[0]) << 16) + parseInt(ProductVersionSplit[1]); + info.fixedFileInfo.dwProductVersionLS = (parseInt(ProductVersionSplit[2]) << 16) + parseInt(ProductVersionSplit[3]); + } + // Re-encode the version information into a buffer var verInfoResBufArray = []; writeVersionInfo(verInfoResBufArray, info); @@ -1840,6 +1856,8 @@ function start() { console.log(""); console.log("When doing sign/unsign, you can also change resource properties of the generated file."); console.log(""); + console.log(" --fileversionnumber n.n.n.n"); + console.log(" --productversionnumber n.n.n.n"); console.log(" --filedescription [value]"); console.log(" --fileversion [value]"); console.log(" --internalname [value]"); @@ -1867,7 +1885,7 @@ function start() { if (exe == null) { console.log("Unable to parse executable file: " + args.exe); return; } } - // Parse the resources and make any required changes + // Parse the string resources and make any required changes var resChanges = false, versionStrings = null; if (exe != null) { versionStrings = exe.getVersionInfo(); @@ -1876,6 +1894,18 @@ function start() { const prop = versionProperties[i], propl = prop.toLowerCase(); if (args[propl] && (args[propl] != versionStrings[prop])) { versionStrings[prop] = args[propl]; resChanges = true; } } + if (args['fileversionnumber'] != null) { + const fileVerSplit = args['fileversionnumber'].split('.'); + if (fileVerSplit.length != 4) { console.log("--fileversionnumber must be of format n.n.n.n, for example: 1.2.3.4"); return; } + for (var i in fileVerSplit) { var n = parseInt(fileVerSplit[i]); if ((n < 0) || (n > 65535)) { console.log("--fileversionnumber numbers must be between 0 and 65535."); return; } } + if (args['fileversionnumber'] != versionStrings['~FileVersion']) { versionStrings['~FileVersion'] = args['fileversionnumber']; resChanges = true; } + } + if (args['productversionnumber'] != null) { + const productVerSplit = args['productversionnumber'].split('.'); + if (productVerSplit.length != 4) { console.log("--productversionnumber must be of format n.n.n.n, for example: 1.2.3.4"); return; } + for (var i in productVerSplit) { var n = parseInt(productVerSplit[i]); if ((n < 0) || (n > 65535)) { console.log("--productversionnumber numbers must be between 0 and 65535."); return; } } + if (args['productversionnumber'] != versionStrings['~ProductVersion']) { versionStrings['~ProductVersion'] = args['productversionnumber']; resChanges = true; } + } if (resChanges == true) { exe.setVersionInfo(versionStrings); } } @@ -1884,8 +1914,12 @@ function start() { if (command == 'info') { // Get signature information about an executable if (exe == null) { console.log("Missing --exe [filename]"); return; } if (args.json) { - var r = {}, versionInfo = exe.getVersionInfo(); - if (versionInfo != null) { r.versionInfo = versionInfo; } + var r = {}, stringInfo = exe.getVersionInfo(); + if (stringInfo != null) { + r.versionInfo = {}; + r.stringInfo = {}; + for (var i in stringInfo) { if (i.startsWith('~')) { r.versionInfo[i.substring(1)] = stringInfo[i]; } else { r.stringInfo[i] = stringInfo[i]; } } + } if (exe.fileHashAlgo != null) { r.signture = {}; if (exe.fileHashAlgo != null) { r.signture.hashMethod = exe.fileHashAlgo; } @@ -1896,7 +1930,12 @@ function start() { console.log(JSON.stringify(r, null, 2)); } else { var versionInfo = exe.getVersionInfo(); - if (versionInfo != null) { console.log("Version Information:"); for (var i in versionInfo) { if (versionInfo[i] == null) { console.log(' ' + i + ': (Empty)'); } else { console.log(' ' + i + ': \"' + versionInfo[i] + '\"'); } } } + if (versionInfo != null) { + console.log("Version Information:"); + for (var i in versionInfo) { if (i.startsWith('~') == true) { console.log(' ' + i.substring(1) + ': ' + versionInfo[i] + ''); } } + console.log("String Information:"); + for (var i in versionInfo) { if (i.startsWith('~') == false) { if (versionInfo[i] == null) { console.log(' ' + i + ': (Empty)'); } else { console.log(' ' + i + ': \"' + versionInfo[i] + '\"'); } } } + } console.log("Checksum Information:"); console.log(" Header CheckSum: 0x" + exe.header.peWindows.checkSum.toString(16)); console.log(" Actual CheckSum: 0x" + exe.header.peWindows.checkSumActual.toString(16)); diff --git a/common.js b/common.js index d2c0c28b..5fe9ccd0 100644 --- a/common.js +++ b/common.js @@ -339,3 +339,16 @@ function validateObjectForMongoRec(obj, maxStrLen) { } return true; } + +// Parse a version string of the type n.n.n.n +module.exports.parseVersion = function (verstr) { + if (typeof verstr != 'string') return null; + const r = [], verstrsplit = verstr.split('.'); + if (verstrsplit.length != 4) return null; + for (var i in verstrsplit) { + var n = parseInt(verstrsplit[i]); + if (isNaN(n) || (n < 0) || (n > 65535)) return null; + r.push(n); + } + return r; +} diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index bdb6ae51..e8d0496c 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -575,12 +575,12 @@ "description": "Use this section to set resource metadata of the Windows agents prior to signing. In Windows, you can right-click and select properties to view these values.", "properties": { "fileDescription": { "type": "string", "description": "Executable file description." }, - "fileVersion": { "type": "string", "description": "Executable file version, generally in the form of 1.2.3.4." }, + "fileVersion": { "type": "string", "description": "Executable file version, in the form of 'n.n.n.n', for example: '1.2.3.4'." }, "internalName": { "type": "string", "description": "Executable internal name." }, "legalCopyright": { "type": "string", "description": "Executable legal copyright." }, "originalFilename": { "type": "string", "description": "Executable original file name." }, "productName": { "type": "string", "description": "Executable product name." }, - "productVersion": { "type": "string", "description": "Executable product version, generally in the form of 1.2.3.4." } + "productVersion": { "type": "string", "description": "Executable product version. Any string format will work, but a alphabetic character is required for this value to show correctly in the Windows property box. For example: 'v1.2.3.4' will work, but '1.2.3.4' will not." } } }, "assistantCustomization": { diff --git a/meshcentral.js b/meshcentral.js index 4df2ab69..a85881c8 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -1362,6 +1362,13 @@ function CreateMeshCentralServer(config, args) { } } } + + // Check agentfileinfo + if (typeof obj.config.domains[i].agentfileinfo == 'object') { + if ((obj.config.domains[i].agentfileinfo.fileversionnumber != null) && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.fileversionnumber) == null)) { delete obj.config.domains[i].agentfileinfo.fileversionnumber; } + if ((obj.config.domains[i].agentfileinfo.productversionnumber != null) && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.productversionnumber) == null)) { delete obj.config.domains[i].agentfileinfo.productversionnumber; } + if ((obj.config.domains[i].agentfileinfo.fileversionnumber == null) && (typeof obj.config.domains[i].agentfileinfo.fileversion == 'string') && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.fileversion) != null)) { obj.config.domains[i].agentfileinfo.fileversionnumber = obj.config.domains[i].agentfileinfo.fileversion; } + } } // Log passed arguments into Windows Service Log @@ -2939,8 +2946,8 @@ function CreateMeshCentralServer(config, args) { // If the agent is signed correctly, look to see if the resources in the destination agent are correct var orgVersionStrings = originalAgent.getVersionInfo(); if (destinationAgentOk == true) { - var versionStrings = destinationAgent.getVersionInfo(); - var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion']; + const versionStrings = destinationAgent.getVersionInfo(); + const versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion']; for (var i in versionProperties) { const prop = versionProperties[i], propl = prop.toLowerCase(); if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo[propl] == 'string')) { @@ -2949,6 +2956,20 @@ function CreateMeshCentralServer(config, args) { if (orgVersionStrings[prop] != versionStrings[prop]) { destinationAgentOk = false; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent. } } + + // Check file version number + if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo['fileversionnumber'] == 'string')) { + if (domain.agentfileinfo['fileversionnumber'] != versionStrings['~FileVersion']) { destinationAgentOk = false; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent. + } else { + if (orgVersionStrings['~FileVersion'] != versionStrings['~FileVersion']) { destinationAgentOk = false; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent. + } + + // Check product version number + if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo['productversionnumber'] == 'string')) { + if (domain.agentfileinfo['productversionnumber'] != versionStrings['~ProductVersion']) { destinationAgentOk = false; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent. + } else { + if (orgVersionStrings['~ProductVersion'] != versionStrings['~ProductVersion']) { destinationAgentOk = false; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent. + } } // If everything looks ok, runs a hash of the original and destination agent skipping the CRC, resource and signature blocks. If different, sign the agent again. @@ -2986,6 +3007,12 @@ function CreateMeshCentralServer(config, args) { const prop = versionProperties[i], propl = prop.toLowerCase(); if (domain.agentfileinfo[propl] && (domain.agentfileinfo[propl] != versionStrings[prop])) { versionStrings[prop] = domain.agentfileinfo[propl]; resChanges = true; } } + if (domain.agentfileinfo['fileversionnumber'] && (domain.agentfileinfo['fileversionnumber'] != versionStrings['~FileVersion'])) { + versionStrings['~FileVersion'] = domain.agentfileinfo['fileversionnumber']; resChanges = true; + } + if (domain.agentfileinfo['productversionnumber'] && (domain.agentfileinfo['productversionnumber'] != versionStrings['~ProductVersion'])) { + versionStrings['~ProductVersion'] = domain.agentfileinfo['productversionnumber']; resChanges = true; + } if (resChanges == true) { originalAgent.setVersionInfo(versionStrings); } } diff --git a/sample-config-advanced.json b/sample-config-advanced.json index ad5963fe..f50f20bc 100644 --- a/sample-config-advanced.json +++ b/sample-config-advanced.json @@ -308,13 +308,13 @@ "fileName": "compagnyagent" }, "_agentFileInfo": { - "_filedescription": "sample_filedescription", - "_fileversion": "0.1.2.3", - "_internalname": "sample_internalname", - "_legalcopyright": "sample_legalcopyright", - "_originalfilename": "sample_originalfilename", - "_productname": "sample_productname", - "_productversion": "0.1.2.3" + "filedescription": "sample_filedescription", + "fileversion": "0.1.2.3", + "internalname": "sample_internalname", + "legalcopyright": "sample_legalcopyright", + "originalfilename": "sample_originalfilename", + "productname": "sample_productname", + "productversion": "v0.1.2.3" }, "_assistantCustomization": { "title": "Company® Product™",