Completed support for chaning windows executable file information resources.
Ylian Saint-Hilaire committed
Jun 20, 2022 at 14:31 UTC
34fdb39dcd830e44e0cd31a4b2dd87c5ac4d53ec
4 files changed
+86
-27
authenticode.js
+33
-5
@@ -1111,8 +1111,9 @@ function createAuthenticodeHandler(path) {
1111
//function padPointer(ptr) { return ptr + (ptr % 4); }
1112
1113
// Hash the file using the selected hashing system
1114
+ // This hash skips the executables CRC and code signing data and signing block
1115
obj.getHash = function(algo) {
1115
- var hash = crypto.createHash(algo);
1116
+ const hash = crypto.createHash(algo);
1117
runHash(hash, 0, obj.header.peHeaderLocation + 88);
1118
runHash(hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
1119
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) {
1121
}
1122
1123
// Hash of an open file using the selected hashing system
1123
- obj.getHashOfFile = function (fd, algo, filesize) {
1124
- var hash = crypto.createHash(algo);
1124
+ // This hash skips the executables CRC and code signing data and signing block
1125
+ obj.getHashOfFile = function(fd, algo, filesize) {
1126
+ const hash = crypto.createHash(algo);
1127
runHashOnFile(fd, hash, 0, obj.header.peHeaderLocation + 88);
1128
runHashOnFile(fd, hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
1129
runHashOnFile(fd, hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, obj.header.sigpos > 0 ? obj.header.sigpos : filesize);
1130
return hash.digest();
1131
}
1132
1133
+ // Hash the file using the selected hashing system skipping resource section
1134
+ // This hash skips the executables CRC, sections table, resource section, code signing data and signing block
1135
+ obj.getHashNoResources = function (algo) {
1136
+ if (obj.header.sections['.rsrc'] == null) { return obj.getHash(algo); } // No resources in this executable, return a normal hash
1137
+
1138
+ // Get the sections table start and size
1139
+ const sectionHeaderPtr = obj.header.SectionHeadersPtr;
1140
+ const sectionHeaderSize = obj.header.coff.numberOfSections * 40;
1141
+
1142
+ // Get the resource section start and size
1143
+ const resPtr = obj.header.sections['.rsrc'].rawAddr;
1144
+ const resSize = obj.header.sections['.rsrc'].rawSize;
1145
+
1146
+ // Get the end-of-file location
1147
+ const eof = obj.header.sigpos > 0 ? obj.header.sigpos : obj.filesize;
1148
+
1149
+ // Hash the remaining data
1150
+ const hash = crypto.createHash(algo);
1151
+ runHash(hash, 0, obj.header.peHeaderLocation + 88);
1152
+ runHash(hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
1153
+ runHash(hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, sectionHeaderPtr);
1154
+ runHash(hash, sectionHeaderPtr + sectionHeaderSize, resPtr);
1155
+ runHash(hash, resPtr + resSize, eof);
1156
+ return hash.digest();
1157
+ }
1158
+
1159
// Hash the file from start to end loading 64k chunks
1160
function runHash(hash, start, end) {
1161
var ptr = start;
@@ -1137,8 +1165,8 @@ function createAuthenticodeHandler(path) {
1165
// Hash the open file loading 64k chunks
1166
// TODO: Do chunks on this!!!
1167
function runHashOnFile(fd, hash, start, end) {
1140
- var buf = Buffer.alloc(end - start);
1141
- var len = fs.readSync(fd, buf, 0, buf.length, start);
1168
+ const buf = Buffer.alloc(end - start);
1169
+ const len = fs.readSync(fd, buf, 0, buf.length, start);
1170
if (len != buf.length) { console.log('BAD runHashOnFile'); }
1171
hash.update(buf);
1172
}
meshcentral-config-schema.json
+14
@@ -543,6 +543,20 @@
543
"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." }
544
}
545
},
546
+ "agentFileInfo": {
547
+ "type": "object",
548
+ "additionalProperties": false,
549
+ "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.",
550
+ "properties": {
551
+ "fileDescription": { "type": "string", "description": "Executable file description." },
552
+ "fileVersion": { "type": "string", "description": "Executable file version, generally in the form of 1.2.3.4." },
553
+ "internalName": { "type": "string", "description": "Executable internal name." },
554
+ "legalCopyright": { "type": "string", "description": "Executable legal copyright." },
555
+ "originalFilename": { "type": "string", "description": "Executable original file name." },
556
+ "productName": { "type": "string", "description": "Executable product name." },
557
+ "productCersion": { "type": "string", "description": "Executable product version, generally in the form of 1.2.3.4." }
558
+ }
559
+ },
560
"assistantCustomization": {
561
"type": "object",
562
"additionalProperties": false,
meshcentral.js
+39
-21
@@ -2886,21 +2886,6 @@ function CreateMeshCentralServer(config, args) {
2886
if (args.agenttimestampserver === false) { timeStampUrl = null; }
2887
else if (typeof args.agenttimestampserver == 'string') { timeStampUrl = args.agenttimestampserver; }
2888
2889
- // Setup agent signing arguments
2890
- const signingArguments = { desc: signDesc, url: signUrl, time: timeStampUrl };
2891
-
2892
- // See if we have any resources we need to change in the agent
2893
- var resChanges = false;
2894
- if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object')) {
2895
- if (typeof domain.agentfileinfo.filedescription == 'string') { signingArguments.FileDescription = domain.agentfileinfo.filedescription; resChanges = true; }
2896
- if (typeof domain.agentfileinfo.fileversion == 'string') { signingArguments.FileVersion = domain.agentfileinfo.fileversion; resChanges = true; }
2897
- if (typeof domain.agentfileinfo.internalname == 'string') { signingArguments.InternalName = domain.agentfileinfo.internalname; resChanges = true; }
2898
- if (typeof domain.agentfileinfo.legalcopyright == 'string') { signingArguments.LegalCopyright = domain.agentfileinfo.legalcopyright; resChanges = true; }
2899
- if (typeof domain.agentfileinfo.originalfilename == 'string') { signingArguments.OriginalFilename = domain.agentfileinfo.originalfilename; resChanges = true; }
2900
- if (typeof domain.agentfileinfo.productname == 'string') { signingArguments.ProductName = domain.agentfileinfo.productname; resChanges = true; }
2901
- if (typeof domain.agentfileinfo.productversion == 'string') { signingArguments.ProductVersion = domain.agentfileinfo.productversion; resChanges = true; }
2902
- }
2903
-
2889
// Setup the pending operations counter
2890
var pendingOperations = 1;
2891
@@ -2929,11 +2914,33 @@ function CreateMeshCentralServer(config, args) {
2914
(destinationAgent != null) &&
2915
(destinationAgent.fileHashSigned != null) &&
2916
(Buffer.compare(destinationAgent.fileHashSigned, destinationAgent.fileHashActual) == 0) &&
2932
- ((Buffer.compare(destinationAgent.fileHashSigned, originalAgent.getHash(destinationAgent.fileHashAlgo))) == 0) &&
2917
(destinationAgent.signingAttribs.indexOf(signUrl) >= 0) &&
2918
(destinationAgent.signingAttribs.indexOf(signDesc) >= 0)
2919
);
2936
- if (destinationAgent != null) { destinationAgent.close(); }
2920
+
2921
+ if (destinationAgent != null) {
2922
+ // If the agent is signed correctly, look to see if the resources in the destination agent are correct
2923
+ var orgVersionStrings = originalAgent.getVersionInfo();
2924
+ if (destinationAgentOk == true) {
2925
+ var versionStrings = destinationAgent.getVersionInfo();
2926
+ var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion'];
2927
+ for (var i in versionProperties) {
2928
+ const prop = versionProperties[i], propl = prop.toLowerCase();
2929
+ if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo[propl] == 'string')) {
2930
+ 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.
2931
+ } else {
2932
+ 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.
2933
+ }
2934
+ }
2935
+ }
2936
+
2937
+ // 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.
2938
+ if ((destinationAgentOk == true) && (originalAgent.getHashNoResources('sha384').compare(destinationAgent.getHashNoResources('sha384')) != 0)) { destinationAgentOk = false; }
2939
+
2940
+ // We are done comparing the destination agent, close it.
2941
+ destinationAgent.close();
2942
+ }
2943
+
2944
if (destinationAgentOk == false) {
2945
// If not signed correctly, sign it. First, create the server signed agent folder if needed
2946
try { obj.fs.mkdirSync(serverSignedAgentsPath); } catch (ex) { }
@@ -2952,17 +2959,28 @@ function CreateMeshCentralServer(config, args) {
2959
xagentSignedFunc.objx = objx;
2960
xagentSignedFunc.archid = archid;
2961
xagentSignedFunc.signeedagentpath = signeedagentpath;
2955
- const xsigningArguments = Object.assign({}, signingArguments); // Shallow clone
2956
- xsigningArguments.out = signeedagentpath;
2962
2963
+ // Parse the resources in the executable and make any required changes
2964
+ var resChanges = false, versionStrings = null;
2965
+ if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object')) {
2966
+ versionStrings = originalAgent.getVersionInfo();
2967
+ var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion'];
2968
+ for (var i in versionProperties) {
2969
+ const prop = versionProperties[i], propl = prop.toLowerCase();
2970
+ if (domain.agentfileinfo[propl] && (domain.agentfileinfo[propl] != versionStrings[prop])) { versionStrings[prop] = domain.agentfileinfo[propl]; resChanges = true; }
2971
+ }
2972
+ if (resChanges == true) { originalAgent.setVersionInfo(versionStrings); }
2973
+ }
2974
+
2975
+ const signingArguments = { out: signeedagentpath, desc: signDesc, url: signUrl, time: timeStampUrl }; // Shallow clone
2976
obj.debug('main', "Code signing agent with arguments: " + JSON.stringify(signingArguments));
2977
if (resChanges == false) {
2978
// Sign the agent the simple way, without changing any resources.
2961
- originalAgent.sign(agentSignCertInfo, xsigningArguments, xagentSignedFunc);
2979
+ originalAgent.sign(agentSignCertInfo, signingArguments, xagentSignedFunc);
2980
} else {
2981
// Change the agent resources and sign the agent, this is a much more involved process.
2982
// NOTE: This is experimental and could corupt the agent.
2965
- originalAgent.writeExecutable(xsigningArguments, agentSignCertInfo, xagentSignedFunc);
2983
+ originalAgent.writeExecutable(signingArguments, agentSignCertInfo, xagentSignedFunc);
2984
}
2985
} else {
2986
// Signed agent is already ok, use it.
sample-config-advanced.json
-1
@@ -287,7 +287,6 @@
287
"fileName": "compagnyagent"
288
},
289
"_agentFileInfo": {
290
- "__COMMENT__": "This section is experimental",
290
"_filedescription": "sample_filedescription",
291
"_fileversion": "0.1.2.3",
292
"_internalname": "sample_internalname",