diff --git a/authenticode.js b/authenticode.js index cb966fdc..4dc495a6 100644 --- a/authenticode.js +++ b/authenticode.js @@ -50,6 +50,38 @@ function createOutFile(args, filename) { args.out = outputFileName.join('.'); } +// Hash an object +function hashObject(obj) { + const hash = crypto.createHash('sha384'); + hash.update(JSON.stringify(obj)); + return hash.digest().toString('hex'); +} + +// Load a .ico file. This will load all icons in the file into a icon group object +function loadIcon(iconFile) { + var iconData = null; + try { iconData = fs.readFileSync(iconFile); } catch (ex) { } + if ((iconData == null) || (iconData.length < 6) || (iconData[0] != 0) || (iconData[1] != 0)) return null; + const r = { resType: iconData.readUInt16LE(2), resCount: iconData.readUInt16LE(4), icons: {} }; + if (r.resType != 1) return null; + var ptr = 6; + for (var i = 1; i <= r.resCount; i++) { + var icon = {}; + icon.width = iconData[ptr + 0]; + icon.height = iconData[ptr + 1]; + icon.colorCount = iconData[ptr + 2]; + icon.planes = iconData.readUInt16LE(ptr + 4); + icon.bitCount = iconData.readUInt16LE(ptr + 6); + icon.bytesInRes = iconData.readUInt32LE(ptr + 8); + icon.iconCursorId = i; + const offset = iconData.readUInt32LE(ptr + 12); + icon.icon = iconData.slice(offset, offset + icon.bytesInRes); + r.icons[i] = icon; + ptr += 16; + } + return r; +} + // Load certificates and private key from PEM files function loadCertificates(pemFileNames) { var certs = [], keys = []; @@ -720,38 +752,6 @@ function createAuthenticodeHandler(path) { return pkcs7raw; } - // Hash an object - obj.hashObject = function (obj) { - const hash = crypto.createHash('sha384'); - hash.update(JSON.stringify(obj)); - return hash.digest(); - } - - // Load a .ico file. This will load all icons in the file into a icon group object - obj.loadIcon = function (iconFile) { - var iconData = null; - try { iconData = fs.readFileSync(iconFile); } catch (ex) {} - if ((iconData == null) || (iconData.length < 6) || (iconData[0] != 0) || (iconData[1] != 0)) return null; - const r = { resType: iconData.readUInt16LE(2), resCount: iconData.readUInt16LE(4), icons: {} }; - if (r.resType != 1) return null; - var ptr = 6; - for (var i = 1; i <= r.resCount; i++) { - var icon = {}; - icon.width = iconData[ptr + 0]; - icon.height = iconData[ptr + 1]; - icon.colorCount = iconData[ptr + 2]; - icon.planes = iconData.readUInt16LE(ptr + 4); - icon.bitCount = iconData.readUInt16LE(ptr + 6); - icon.bytesInRes = iconData.readUInt32LE(ptr + 8); - icon.iconCursorId = i; - const offset = iconData.readUInt32LE(ptr + 12); - icon.icon = iconData.slice(offset, offset + icon.bytesInRes); - r.icons[i] = icon; - ptr += 16; - } - return r; - } - // Get icon information from resource obj.getIconInfo = function () { const r = {}, ptr = obj.header.sections['.rsrc'].rawAddr; @@ -1661,11 +1661,15 @@ function createAuthenticodeHandler(path) { var fullHeaderLen = obj.header.SectionHeadersPtr + (obj.header.coff.numberOfSections * 40); var fullHeader = readFileSlice(written, fullHeaderLen); + // Compute the size of the resource segment + //const resSizes = { tables: 0, items: 0, names: 0, data: 0 }; + //getResourceSectionSize(obj.resources, resSizes); + // Calculate the location and original and new size of the resource segment var fileAlign = obj.header.peWindows.fileAlignment var resPtr = obj.header.sections['.rsrc'].rawAddr; var oldResSize = obj.header.sections['.rsrc'].rawSize; - var newResSize = obj.header.sections['.rsrc'].rawSize; // Testing 102400 + var newResSize = obj.header.sections['.rsrc'].rawSize; // TODO: resSizes.data; var resDeltaSize = newResSize - oldResSize; // Change PE optional header sizeOfInitializedData standard field @@ -2041,12 +2045,12 @@ function start() { if (iconToAddSplit.length != 2) { console.log("The --icon format is: --icon [number],[file]."); return; } const iconName = parseInt(iconToAddSplit[0]); const iconFile = iconToAddSplit[1]; - const icon = exe.loadIcon(iconFile); + const icon = loadIcon(iconFile); if (icon == null) { console.log("Unable to load icon: " + iconFile); return; } if (icons[iconName] != null) { - const iconHash = exe.hashObject(icon); // Compute the new icon group hash - const iconHash2 = exe.hashObject(icons[iconName]); // Computer the old icon group hash - if (iconHash.toString('hex') != iconHash2.toString('hex')) { icons[iconName] = icon; resChanges = true; } // If different, replace the icon group + const iconHash = hashObject(icon); // Compute the new icon group hash + const iconHash2 = hashObject(icons[iconName]); // Computer the old icon group hash + if (iconHash != iconHash2) { icons[iconName] = icon; resChanges = true; } // If different, replace the icon group } else { icons[iconName] = icon; // We are adding an icon group resChanges = true; @@ -2261,4 +2265,5 @@ if (require.main === module) { start(); } // Exports module.exports.createAuthenticodeHandler = createAuthenticodeHandler; module.exports.loadCertificates = loadCertificates; - +module.exports.loadIcon = loadIcon; +module.exports.hashObject = hashObject; \ No newline at end of file diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index e8d0496c..83cfc549 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -574,6 +574,7 @@ "additionalProperties": false, "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": { + "icon": { "type": "string", "default": null, "description": "DO NOT USE. THIS FEATURE DOES NOT WORK YET. Sets the agent icon, this is the name of a .ico file with the file placed in the meshcentral-data folder." }, "fileDescription": { "type": "string", "description": "Executable file description." }, "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." }, diff --git a/meshcentral.js b/meshcentral.js index a85881c8..57726277 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -1368,6 +1368,25 @@ function CreateMeshCentralServer(config, args) { 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; } + if (typeof obj.config.domains[i].agentfileinfo.icon == 'string') { + // Load the agent .ico file + var icon = null; + try { icon = require('./authenticode.js').loadIcon(obj.path.join(obj.datapath, obj.config.domains[i].agentfileinfo.icon)); } catch (ex) { } + if (icon != null) { + // The icon file was correctly loaded + obj.config.domains[i].agentfileinfo.icon = icon; + obj.config.domains[i].agentfileinfo.iconhash = require('./authenticode.js').hashObject(icon); + } else { + // Failed to load the icon file, display a server warning + addServerWarning("Unable to load agent icon file: " + obj.config.domains[i].agentfileinfo.icon + ".", 23, [obj.config.domains[i].agentfileinfo.icon]); + delete obj.config.domains[i].agentfileinfo.icon; + delete obj.config.domains[i].agentfileinfo.iconhash; + } + } else { + // Invalid icon file path + delete obj.config.domains[i].agentfileinfo.icon; + delete obj.config.domains[i].agentfileinfo.iconhash; + } } } @@ -2951,24 +2970,41 @@ function CreateMeshCentralServer(config, args) { 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')) { - if (domain.agentfileinfo[propl] != versionStrings[prop]) { destinationAgentOk = false; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent. + if (domain.agentfileinfo[propl] != versionStrings[prop]) { destinationAgentOk = false; break; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent. } else { - 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. + if (orgVersionStrings[prop] != versionStrings[prop]) { destinationAgentOk = false; break; } // 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. + if (destinationAgentOk == true) { + 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 (destinationAgentOk == true) { + 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. + } + } + + // Check the agent icon + if ((destinationAgentOk == true) && (domain.agentfileinfo != null) && (domain.agentfileinfo.iconhash != null)) { + const agentIconGroups = destinationAgent.getIconInfo(); + if (agentIconGroups != null) { + const agentIconGroupNames = Object.keys(agentIconGroups); + if (agentIconGroupNames.length > 0) { + const agentMainIconGroupName = agentIconGroupNames[0]; + const agentMainIconGroupHash = require('./authenticode.js').hashObject(agentIconGroups[agentMainIconGroupName]); + if (agentMainIconGroupHash != domain.agentfileinfo.iconhash) { destinationAgentOk = false; } // If the existing agent icon does not match the desired icon, we need to re-sign the agent. + } + } } } @@ -3003,17 +3039,33 @@ function CreateMeshCentralServer(config, args) { if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object')) { versionStrings = originalAgent.getVersionInfo(); var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion']; + // Change the agent string properties for (var i in versionProperties) { const prop = versionProperties[i], propl = prop.toLowerCase(); if (domain.agentfileinfo[propl] && (domain.agentfileinfo[propl] != versionStrings[prop])) { versionStrings[prop] = domain.agentfileinfo[propl]; resChanges = true; } } + // Change the agent file version if (domain.agentfileinfo['fileversionnumber'] && (domain.agentfileinfo['fileversionnumber'] != versionStrings['~FileVersion'])) { versionStrings['~FileVersion'] = domain.agentfileinfo['fileversionnumber']; resChanges = true; } + // Change the agent product version if (domain.agentfileinfo['productversionnumber'] && (domain.agentfileinfo['productversionnumber'] != versionStrings['~ProductVersion'])) { versionStrings['~ProductVersion'] = domain.agentfileinfo['productversionnumber']; resChanges = true; } if (resChanges == true) { originalAgent.setVersionInfo(versionStrings); } + + // Change the agent icon + if (domain.agentfileinfo.icon != null) { + const agentIconGroups = originalAgent.getIconInfo(); + if (agentIconGroups != null) { + const agentIconGroupNames = Object.keys(agentIconGroups); + if (agentIconGroupNames.length > 0) { + const agentMainIconGroupName = agentIconGroupNames[0]; + agentIconGroups[agentIconGroupNames[0]] = domain.agentfileinfo.icon; + originalAgent.setIconInfo(agentIconGroups); + } + } + } } const signingArguments = { out: signeedagentpath, desc: signDesc, url: signUrl, time: timeStampUrl, proxy: timeStampProxy }; // Shallow clone diff --git a/sample-config-advanced.json b/sample-config-advanced.json index f50f20bc..e3420d30 100644 --- a/sample-config-advanced.json +++ b/sample-config-advanced.json @@ -308,6 +308,7 @@ "fileName": "compagnyagent" }, "_agentFileInfo": { + "icon": "agent.ico", "filedescription": "sample_filedescription", "fileversion": "0.1.2.3", "internalname": "sample_internalname", diff --git a/views/default.handlebars b/views/default.handlebars index 2bc05d12..2e105332 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -2328,7 +2328,8 @@ 19: "SMS gateway has limited use in LAN mode.", 20: "Invalid \"LoginCookieEncryptionKey\" in config.json.", 21: "Backup path can't be set within meshcentral-data folder, backup settings ignored.", - 22: "Failed to sign agent {0}: {1}" + 22: "Failed to sign agent {0}: {1}", + 23: "Unable to load agent icon file: {0}." }; var x = ''; for (var i in message.warnings) {