Started work on integrating Windows agent icon customization prior to agent signing.
This commit is contained in:
parent
c5315ba0fc
commit
48c6b42a0b
|
@ -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…
Reference in New Issue