Added exactports description, #4104

Ylian Saint-Hilaire committed Jun 9, 2022 at 21:25 UTC cbc3ab49b6433dd7fabdbc40a98646536f9d451c
2 files changed +140 -37
authenticode.js
+139 -36
@@ -909,7 +909,7 @@ function createAuthenticodeHandler(path) {
909 const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary
910 //console.log('RStringTableStruct', buf.slice(ptr, ptr + wLength).toString('hex'));
911 r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + 6 + 16)); // An 8-digit hexadecimal number stored as a Unicode string.
912 - //console.log('readStringTableStruct', wLength, wValueLength, r.wType, r.szKey);
912 + //console.log('readStringTableStruct', wLength, wValueLength, wType, r.szKey);
913 r.strings = readStringStructs(buf, ptr + 24 + wValueLength, wLength - 22);
914 return r;
915 }
@@ -917,7 +917,7 @@ function createAuthenticodeHandler(path) {
917 // String structure: https://docs.microsoft.com/en-us/windows/win32/menurc/string-str
918 function readStringStructs(buf, ptr, len) {
919 var t = [], startPtr = ptr;
920 - while (ptr < (startPtr + len)) {
920 + while ((ptr + 6) < (startPtr + len)) {
921 const r = {};
922 const wLength = buf.readUInt16LE(ptr);
923 if (wLength == 0) return t;
@@ -953,12 +953,30 @@ function createAuthenticodeHandler(path) {
953 return hash.digest();
954 }
955
956 + // Hash of an open file using the selected hashing system
957 + obj.getHashOfFile = function (fd, algo, filesize) {
958 + var hash = crypto.createHash(algo);
959 + runHashOnFile(fd, hash, 0, obj.header.peHeaderLocation + 88);
960 + runHashOnFile(fd, hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
961 + runHashOnFile(fd, hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, obj.header.sigpos > 0 ? obj.header.sigpos : filesize);
962 + return hash.digest();
963 + }
964 +
965 // Hash the file from start to end loading 64k chunks
966 function runHash(hash, start, end) {
967 var ptr = start;
968 while (ptr < end) { const buf = readFileSlice(ptr, Math.min(65536, end - ptr)); hash.update(buf); ptr += buf.length; }
969 }
970
971 + // Hash the open file loading 64k chunks
972 + // TODO: Do chunks on this!!!
973 + function runHashOnFile(fd, hash, start, end) {
974 + var buf = Buffer.alloc(end - start);
975 + var len = fs.readSync(fd, buf, 0, buf.length, start);
976 + if (len != buf.length) { console.log('BAD runHashOnFile'); }
977 + hash.update(buf);
978 + }
979 +
980 // Checksum the file loading 64k chunks
981 function runChecksum() {
982 var ptr = 0, c = createChecksum(((obj.header.peOptionalHeaderLocation + 64) / 4));
@@ -1158,14 +1176,9 @@ function createAuthenticodeHandler(path) {
1176 }
1177
1178 // Save the executable
1161 - obj.writeExecutable = function (args) {
1162 - // Get version information from the resource
1163 - var versions = obj.getVersionInfo();
1164 - versions['FileDescription'] = 'This is a test';
1165 - obj.setVersionInfo(versions);
1166 -
1179 + obj.writeExecutable = function (args, cert) {
1180 // Open the file
1168 - var output = fs.openSync(args.out, 'w');
1181 + var output = fs.openSync(args.out, 'w+');
1182 var tmp, written = 0;
1183
1184 // Compute the size of the complete executable header up to after the sections header
@@ -1179,20 +1192,11 @@ function createAuthenticodeHandler(path) {
1192 var newResSize = obj.header.sections['.rsrc'].rawSize; // Testing 102400
1193 var resDeltaSize = newResSize - oldResSize;
1194
1182 - /*
1183 - console.log('fileAlign', fileAlign);
1184 - console.log('resPtr', resPtr);
1185 - console.log('oldResSize', oldResSize);
1186 - console.log('newResSize', newResSize);
1187 - console.log('resDeltaSize', resDeltaSize);
1188 - */
1189 -
1195 // Change PE optional header sizeOfInitializedData standard field
1196 fullHeader.writeUInt32LE(obj.header.peStandard.sizeOfInitializedData + resDeltaSize, obj.header.peOptionalHeaderLocation + 8);
1197 fullHeader.writeUInt32LE(obj.header.peWindows.sizeOfImage, obj.header.peOptionalHeaderLocation + 56); // TODO: resDeltaSize
1198
1194 - // Update the checksum, set to zero since it's not used
1195 - // TODO: Take a look at computing this correctly in the future
1199 + // Update the checksum to zero
1200 fullHeader.writeUInt32LE(0, obj.header.peOptionalHeaderLocation + 64);
1201
1202 // Make change to the data directories header to fix resource segment size and add/remove signature
@@ -1256,7 +1260,81 @@ function createAuthenticodeHandler(path) {
1260 }
1261
1262 // Write the signature if needed
1259 - // TODO
1263 + if (cert != null) {
1264 + //if (cert == null) { cert = createSelfSignedCert({ cn: 'Test' }); }
1265 +
1266 + // Set the hash algorithm hash OID
1267 + var hashOid = null, fileHash = null;
1268 + if (args.hash == null) { args.hash = 'sha384'; }
1269 + if (args.hash == 'sha256') { hashOid = forge.pki.oids.sha256; fileHash = obj.getHashOfFile(output, 'sha256', written); }
1270 + if (args.hash == 'sha384') { hashOid = forge.pki.oids.sha384; fileHash = obj.getHashOfFile(output, 'sha384', written); }
1271 + if (args.hash == 'sha512') { hashOid = forge.pki.oids.sha512; fileHash = obj.getHashOfFile(output, 'sha512', written); }
1272 + if (args.hash == 'sha224') { hashOid = forge.pki.oids.sha224; fileHash = obj.getHashOfFile(output, 'sha224', written); }
1273 + if (args.hash == 'md5') { hashOid = forge.pki.oids.md5; fileHash = obj.getHashOfFile(output, 'md5', written); }
1274 + if (hashOid == null) return false;
1275 +
1276 + // Create the signature block
1277 + var p7 = forge.pkcs7.createSignedData();
1278 + var content = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 6, 'constructed': false, 'composed': false, 'value': forge.asn1.oidToDer('1.3.6.1.4.1.311.2.1.15').data }, { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 3, 'constructed': false, 'composed': false, 'value': '\u0000', 'bitStringContents': '\u0000', 'original': { 'tagClass': 0, 'type': 3, 'constructed': false, 'composed': false, 'value': '\u0000' } }, { 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 2, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': '' }] }] }] }] }, { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 6, 'constructed': false, 'composed': false, 'value': forge.asn1.oidToDer(hashOid).data }, { 'tagClass': 0, 'type': 5, 'constructed': false, 'composed': false, 'value': '' }] }, { 'tagClass': 0, 'type': 4, 'constructed': false, 'composed': false, 'value': fileHash.toString('binary') }] }] };
1279 + p7.contentInfo = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, forge.asn1.oidToDer('1.3.6.1.4.1.311.2.1.4').getBytes())]);
1280 + p7.contentInfo.value.push(forge.asn1.create(forge.asn1.Class.CONTEXT_SPECIFIC, 0, true, [content]));
1281 + p7.content = {}; // We set .contentInfo and have .content empty to bypass node-forge limitation on the type of content it can sign.
1282 + p7.addCertificate(cert.cert);
1283 + if (cert.extraCerts) { for (var i = 0; i < cert.extraCerts.length; i++) { p7.addCertificate(cert.extraCerts[0]); } } // Add any extra certificates that form the cert chain
1284 +
1285 + // Build authenticated attributes
1286 + var authenticatedAttributes = [
1287 + { type: forge.pki.oids.contentType, value: forge.pki.oids.data },
1288 + { type: forge.pki.oids.messageDigest } // This value will populated at signing time by node-forge
1289 + ]
1290 + if ((typeof args.desc == 'string') || (typeof args.url == 'string')) {
1291 + var codeSigningAttributes = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [] };
1292 + if (args.desc != null) { // Encode description as big-endian unicode.
1293 + var desc = "", ucs = Buffer.from(args.desc, 'ucs2').toString()
1294 + for (var k = 0; k < ucs.length; k += 2) { desc += String.fromCharCode(ucs.charCodeAt(k + 1), ucs.charCodeAt(k)); }
1295 + codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': desc }] });
1296 + }
1297 + if (args.url != null) { codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 1, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': args.url }] }); }
1298 + authenticatedAttributes.push({ type: obj.Oids.SPC_SP_OPUS_INFO_OBJID, value: codeSigningAttributes });
1299 + }
1300 +
1301 + // Add the signer and sign
1302 + p7.addSigner({
1303 + key: cert.key,
1304 + certificate: cert.cert,
1305 + digestAlgorithm: forge.pki.oids.sha384,
1306 + authenticatedAttributes: authenticatedAttributes
1307 + });
1308 + p7.sign();
1309 + var p7signature = Buffer.from(forge.pkcs7.messageToPem(p7).split('-----BEGIN PKCS7-----')[1].split('-----END PKCS7-----')[0], 'base64');
1310 + //console.log('Signature', Buffer.from(p7signature, 'binary').toString('base64'));
1311 +
1312 + // Quad Align the results, adding padding if necessary
1313 + var len = written + p7signature.length;
1314 + var padding = (8 - ((len) % 8)) % 8;
1315 +
1316 + // Write the signature block header and signature
1317 + var win = Buffer.alloc(8); // WIN CERTIFICATE Structure
1318 + win.writeUInt32LE(p7signature.length + padding + 8); // DWORD length
1319 + win.writeUInt16LE(512, 4); // WORD revision
1320 + win.writeUInt16LE(2, 6); // WORD type
1321 + fs.writeSync(output, win);
1322 + fs.writeSync(output, p7signature);
1323 + if (padding > 0) { fs.writeSync(output, Buffer.alloc(padding, 0)); }
1324 +
1325 + // Write the signature header
1326 + var addresstable = Buffer.alloc(8);
1327 + addresstable.writeUInt32LE(written);
1328 + addresstable.writeUInt32LE(8 + p7signature.length + padding, 4);
1329 + var signatureHeaderLocation = (obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
1330 + fs.writeSync(output, addresstable, 0, 8, signatureHeaderLocation);
1331 + written += (p7signature.length + padding + 8); // Add the signature block to written counter
1332 +
1333 + // Compute the checksum and write it in the PE header checksum location
1334 + var tmp = Buffer.alloc(4);
1335 + tmp.writeUInt32LE(runChecksumOnFile(output, written, ((obj.header.peOptionalHeaderLocation + 64) / 4)));
1336 + fs.writeSync(output, tmp, 0, 4, obj.header.peOptionalHeaderLocation + 64);
1337 + }
1338
1339 // Close the file
1340 fs.closeSync(output);
@@ -1301,6 +1379,16 @@ function start() {
1379 console.log("");
1380 console.log("Note that certificate PEM files must first have the signing certificate,");
1381 console.log("followed by all certificates that form the trust chain.");
1382 + console.log("");
1383 + console.log("When doing sign/unsign, you can also change resource properties of the generated file.");
1384 + console.log("");
1385 + console.log(" --filedescription [value]");
1386 + console.log(" --fileversion [value]");
1387 + console.log(" --internalname [value]");
1388 + console.log(" --legalcopyright [value]");
1389 + console.log(" --originalfilename [value]");
1390 + console.log(" --productname [value]");
1391 + console.log(" --productversion [value]");
1392 return;
1393 }
1394
@@ -1321,6 +1409,15 @@ function start() {
1409 if (exe == null) { console.log("Unable to parse executable file: " + args.exe); return; }
1410 }
1411
1412 + // Parse the resources and make any required changes
1413 + var resChanges = false, versionStrings = exe.getVersionInfo();
1414 + var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion'];
1415 + for (var i in versionProperties) {
1416 + const prop = versionProperties[i], propl = prop.toLowerCase();
1417 + if (args[propl] && (args[propl] != versionStrings[prop])) { versionStrings[prop] = args[propl]; resChanges = true; }
1418 + }
1419 + if (resChanges == true) { exe.setVersionInfo(versionStrings); }
1420 +
1421 // Execute the command
1422 var command = process.argv[2].toLowerCase();
1423 if (command == 'info') { // Get signature information about an executable
@@ -1362,14 +1459,32 @@ function start() {
1459 if (typeof args.hash == 'string') { args.hash = args.hash.toLowerCase(); if (['md5', 'sha224', 'sha256', 'sha384', 'sha512'].indexOf(args.hash) == -1) { console.log("Invalid hash method, must be SHA256 or SHA384"); return; } }
1460 if (args.hash == null) { args.hash = 'sha384'; }
1461 createOutFile(args, args.exe);
1365 - const cert = loadCertificates(args.pem);
1366 - if (cert == null) { console.log("Unable to load certificate and/or private key, generating test certificate."); }
1367 - console.log("Signing to " + args.out); exe.sign(cert, args); console.log("Done.");
1462 + var cert = loadCertificates(args.pem);
1463 + if (cert == null) { console.log("Unable to load certificate and/or private key, generating test certificate."); cert = createSelfSignedCert({ cn: 'Test' }); }
1464 + if (resChanges == false) {
1465 + console.log("Signing to " + args.out);
1466 + exe.sign(cert, args); // Simple signing, copy most of the original file.
1467 + } else {
1468 + console.log("Changing resources and signing to " + args.out);
1469 + exe.writeExecutable(args, cert); // Signing with resources decoded and re-encoded.
1470 + }
1471 + console.log("Done.");
1472 }
1473 if (command == 'unsign') { // Unsign an executable
1474 if (typeof args.exe != 'string') { console.log("Missing --exe [filename]"); return; }
1475 createOutFile(args, args.exe);
1372 - if (exe.header.signed) { console.log("Unsigning to " + args.out); exe.unsign(args); console.log("Done."); } else { console.log("Executable is not signed."); }
1476 + if (resChanges == false) {
1477 + if (exe.header.signed) {
1478 + console.log("Unsigning to " + args.out);
1479 + exe.unsign(args); // Simple unsign, copy most of the original file.
1480 + console.log("Done.");
1481 + } else {
1482 + console.log("Executable is not signed.");
1483 + }
1484 + } else {
1485 + console.log("Changing resources and unsigning to " + args.out);
1486 + exe.writeExecutable(args, null); // Unsigning with resources decoded and re-encoded.
1487 + }
1488 }
1489 if (command == 'createcert') { // Create a code signing certificate and private key
1490 if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
@@ -1419,18 +1534,6 @@ function start() {
1534 fs.writeFileSync(args.out, Buffer.concat([buf, icon.icon]));
1535 console.log("Done.");
1536 }
1422 - if (command == 'test') { // Grow the resource segment by 100k
1423 - if (exe == null) { console.log("Missing --exe [filename]"); return; }
1424 - createOutFile(args, args.exe);
1425 - console.log("Writting to " + args.out);
1426 - exe.resourcesChanged = true; // Indicate the resources have changed
1427 - exe.writeExecutable(args);
1428 -
1429 - // Parse the output file
1430 - var exe2 = createAuthenticodeHandler(args.out);
1431 - if (exe2 == null) { console.log("Unable to parse output executable file: " + args.out); return; }
1432 - console.log('Output executable parsed correctly.');
1433 - }
1537
1538 // Close the file
1539 if (exe != null) { exe.close(); }
meshcentral-config-schema.json
+1 -1
@@ -102,7 +102,7 @@
102 "agentCoreDumpUsers": { "type": "array", "description": "List of non-administrator users that have access to mesh agent crash dumps." },
103 "agentSignLock": { "type": "boolean", "default": false, "description": "When code signing an agent using authenticode, lock the agent to only allow connection to this server. (This is in testing, the default value will change to true in the future)." },
104 "ignoreAgentHashCheck": { "type": [ "boolean", "string" ], "default": false, "description": "When true, the agent no longer checked the TLS certificate of the server. This should be used for debugging only. You can also set this to a comma seperated list of IP addresses to ignore, for example: \"192.168.2.100,192.168.1.0/24\"." },
105 - "exactPorts": { "type": "boolean", "default": false },
105 + "exactPorts": { "type": "boolean", "default": false, "description": "When set to true, MeshCentral will only grab the required TCP listening ports or fail. It will not try to use the next available port of it's busy." },
106 "allowLoginToken": { "type": "boolean", "default": false },
107 "StrictTransportSecurity": { "type": ["boolean", "string"], "default": null, "description": "Controls the Strict-Transport-Security header, default is 1 year. Set to false to remove, true to force enable, or string to set a custom value. If set to null, MeshCentral will enable if a trusted certificate is set." },
108 "allowFraming": { "type": "boolean", "default": false, "description": "When enabled, the MeshCentral web site can be embedded within another website's iframe." },