From 34fdb39dcd830e44e0cd31a4b2dd87c5ac4d53ec Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 20 Jun 2022 14:31:28 -0700 Subject: [PATCH] Completed support for chaning windows executable file information resources. --- authenticode.js | 38 ++++++++++++++++++--- meshcentral-config-schema.json | 14 ++++++++ meshcentral.js | 60 ++++++++++++++++++++++------------ sample-config-advanced.json | 1 - 4 files changed, 86 insertions(+), 27 deletions(-) diff --git a/authenticode.js b/authenticode.js index 5f437b24..825652d4 100644 --- a/authenticode.js +++ b/authenticode.js @@ -1111,8 +1111,9 @@ function createAuthenticodeHandler(path) { //function padPointer(ptr) { return ptr + (ptr % 4); } // Hash the file using the selected hashing system + // This hash skips the executables CRC and code signing data and signing block obj.getHash = function(algo) { - var hash = crypto.createHash(algo); + const hash = crypto.createHash(algo); runHash(hash, 0, obj.header.peHeaderLocation + 88); runHash(hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16)); runHash(hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, obj.header.sigpos > 0 ? obj.header.sigpos : obj.filesize); @@ -1120,14 +1121,41 @@ function createAuthenticodeHandler(path) { } // Hash of an open file using the selected hashing system - obj.getHashOfFile = function (fd, algo, filesize) { - var hash = crypto.createHash(algo); + // This hash skips the executables CRC and code signing data and signing block + obj.getHashOfFile = function(fd, algo, filesize) { + const hash = crypto.createHash(algo); runHashOnFile(fd, hash, 0, obj.header.peHeaderLocation + 88); runHashOnFile(fd, hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16)); runHashOnFile(fd, hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, obj.header.sigpos > 0 ? obj.header.sigpos : filesize); return hash.digest(); } + // Hash the file using the selected hashing system skipping resource section + // This hash skips the executables CRC, sections table, resource section, code signing data and signing block + obj.getHashNoResources = function (algo) { + if (obj.header.sections['.rsrc'] == null) { return obj.getHash(algo); } // No resources in this executable, return a normal hash + + // Get the sections table start and size + const sectionHeaderPtr = obj.header.SectionHeadersPtr; + const sectionHeaderSize = obj.header.coff.numberOfSections * 40; + + // Get the resource section start and size + const resPtr = obj.header.sections['.rsrc'].rawAddr; + const resSize = obj.header.sections['.rsrc'].rawSize; + + // Get the end-of-file location + const eof = obj.header.sigpos > 0 ? obj.header.sigpos : obj.filesize; + + // Hash the remaining data + const hash = crypto.createHash(algo); + runHash(hash, 0, obj.header.peHeaderLocation + 88); + runHash(hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16)); + runHash(hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, sectionHeaderPtr); + runHash(hash, sectionHeaderPtr + sectionHeaderSize, resPtr); + runHash(hash, resPtr + resSize, eof); + return hash.digest(); + } + // Hash the file from start to end loading 64k chunks function runHash(hash, start, end) { var ptr = start; @@ -1137,8 +1165,8 @@ function createAuthenticodeHandler(path) { // Hash the open file loading 64k chunks // TODO: Do chunks on this!!! function runHashOnFile(fd, hash, start, end) { - var buf = Buffer.alloc(end - start); - var len = fs.readSync(fd, buf, 0, buf.length, start); + const buf = Buffer.alloc(end - start); + const len = fs.readSync(fd, buf, 0, buf.length, start); if (len != buf.length) { console.log('BAD runHashOnFile'); } hash.update(buf); } diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index 22001d21..d00bb162 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -543,6 +543,20 @@ "backgroundColor": { "type": "string", "default": null, "description": "Background color, valid values are RBG in format 0,0,0 to 255,255,255 or format #000000 to #FFFFFF." } } }, + "agentFileInfo": { + "type": "object", + "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": { + "fileDescription": { "type": "string", "description": "Executable file description." }, + "fileVersion": { "type": "string", "description": "Executable file version, generally in the form of 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." }, + "productCersion": { "type": "string", "description": "Executable product version, generally in the form of 1.2.3.4." } + } + }, "assistantCustomization": { "type": "object", "additionalProperties": false, diff --git a/meshcentral.js b/meshcentral.js index d717609a..a7ae60f4 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -2886,21 +2886,6 @@ function CreateMeshCentralServer(config, args) { if (args.agenttimestampserver === false) { timeStampUrl = null; } else if (typeof args.agenttimestampserver == 'string') { timeStampUrl = args.agenttimestampserver; } - // Setup agent signing arguments - const signingArguments = { desc: signDesc, url: signUrl, time: timeStampUrl }; - - // See if we have any resources we need to change in the agent - var resChanges = false; - if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object')) { - if (typeof domain.agentfileinfo.filedescription == 'string') { signingArguments.FileDescription = domain.agentfileinfo.filedescription; resChanges = true; } - if (typeof domain.agentfileinfo.fileversion == 'string') { signingArguments.FileVersion = domain.agentfileinfo.fileversion; resChanges = true; } - if (typeof domain.agentfileinfo.internalname == 'string') { signingArguments.InternalName = domain.agentfileinfo.internalname; resChanges = true; } - if (typeof domain.agentfileinfo.legalcopyright == 'string') { signingArguments.LegalCopyright = domain.agentfileinfo.legalcopyright; resChanges = true; } - if (typeof domain.agentfileinfo.originalfilename == 'string') { signingArguments.OriginalFilename = domain.agentfileinfo.originalfilename; resChanges = true; } - if (typeof domain.agentfileinfo.productname == 'string') { signingArguments.ProductName = domain.agentfileinfo.productname; resChanges = true; } - if (typeof domain.agentfileinfo.productversion == 'string') { signingArguments.ProductVersion = domain.agentfileinfo.productversion; resChanges = true; } - } - // Setup the pending operations counter var pendingOperations = 1; @@ -2929,11 +2914,33 @@ function CreateMeshCentralServer(config, args) { (destinationAgent != null) && (destinationAgent.fileHashSigned != null) && (Buffer.compare(destinationAgent.fileHashSigned, destinationAgent.fileHashActual) == 0) && - ((Buffer.compare(destinationAgent.fileHashSigned, originalAgent.getHash(destinationAgent.fileHashAlgo))) == 0) && (destinationAgent.signingAttribs.indexOf(signUrl) >= 0) && (destinationAgent.signingAttribs.indexOf(signDesc) >= 0) ); - if (destinationAgent != null) { destinationAgent.close(); } + + if (destinationAgent != null) { + // 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']; + 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. + } 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 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. + if ((destinationAgentOk == true) && (originalAgent.getHashNoResources('sha384').compare(destinationAgent.getHashNoResources('sha384')) != 0)) { destinationAgentOk = false; } + + // We are done comparing the destination agent, close it. + destinationAgent.close(); + } + if (destinationAgentOk == false) { // If not signed correctly, sign it. First, create the server signed agent folder if needed try { obj.fs.mkdirSync(serverSignedAgentsPath); } catch (ex) { } @@ -2952,17 +2959,28 @@ function CreateMeshCentralServer(config, args) { xagentSignedFunc.objx = objx; xagentSignedFunc.archid = archid; xagentSignedFunc.signeedagentpath = signeedagentpath; - const xsigningArguments = Object.assign({}, signingArguments); // Shallow clone - xsigningArguments.out = signeedagentpath; + // Parse the resources in the executable and make any required changes + var resChanges = false, versionStrings = null; + if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object')) { + versionStrings = originalAgent.getVersionInfo(); + var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion']; + 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; } + } + if (resChanges == true) { originalAgent.setVersionInfo(versionStrings); } + } + + const signingArguments = { out: signeedagentpath, desc: signDesc, url: signUrl, time: timeStampUrl }; // Shallow clone obj.debug('main', "Code signing agent with arguments: " + JSON.stringify(signingArguments)); if (resChanges == false) { // Sign the agent the simple way, without changing any resources. - originalAgent.sign(agentSignCertInfo, xsigningArguments, xagentSignedFunc); + originalAgent.sign(agentSignCertInfo, signingArguments, xagentSignedFunc); } else { // Change the agent resources and sign the agent, this is a much more involved process. // NOTE: This is experimental and could corupt the agent. - originalAgent.writeExecutable(xsigningArguments, agentSignCertInfo, xagentSignedFunc); + originalAgent.writeExecutable(signingArguments, agentSignCertInfo, xagentSignedFunc); } } else { // Signed agent is already ok, use it. diff --git a/sample-config-advanced.json b/sample-config-advanced.json index e316f4b5..6954c431 100644 --- a/sample-config-advanced.json +++ b/sample-config-advanced.json @@ -287,7 +287,6 @@ "fileName": "compagnyagent" }, "_agentFileInfo": { - "__COMMENT__": "This section is experimental", "_filedescription": "sample_filedescription", "_fileversion": "0.1.2.3", "_internalname": "sample_internalname",