mirror of
				https://github.com/Ylianst/MeshCentral.git
				synced 2025-03-09 15:40:18 +00:00 
			
		
		
		
	Started work on integrating Windows agent icon customization prior to agent signing.
This commit is contained in:
		
							parent
							
								
									c5315ba0fc
								
							
						
					
					
						commit
						48c6b42a0b
					
				
					 5 changed files with 109 additions and 49 deletions
				
			
		| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			@ -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." },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -308,6 +308,7 @@
 | 
			
		|||
        "fileName": "compagnyagent"
 | 
			
		||||
      },
 | 
			
		||||
      "_agentFileInfo": {
 | 
			
		||||
        "icon": "agent.ico",
 | 
			
		||||
        "filedescription": "sample_filedescription",
 | 
			
		||||
        "fileversion": "0.1.2.3",
 | 
			
		||||
        "internalname": "sample_internalname",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue