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." },