Added bitmap list/remove/add/change support to authenticode.js.
Ylian Saint-Hilaire committed
Aug 11, 2022 at 16:30 UTC
e306af5fc9bcf75594f02572b27e7d185d644dff
1 file changed
+136
-5
authenticode.js
+136
-5
@@ -52,11 +52,20 @@ function createOutFile(args, filename) {
52
53
// Hash an object
54
function hashObject(obj) {
55
+ if (obj == null) { return null; }
56
const hash = crypto.createHash('sha384');
56
- hash.update(JSON.stringify(obj));
57
+ if (Buffer.isBuffer(obj)) { hash.update(obj); } else { hash.update(JSON.stringify(obj)); }
58
return hash.digest().toString('hex');
59
}
60
61
+// Load a .bmp file.
62
+function loadBitmap(bitmapFile) {
63
+ var bitmapData = null;
64
+ try { bitmapData = fs.readFileSync(bitmapFile); } catch (ex) { }
65
+ if ((bitmapData == null) || (bitmapData.length < 14) || (bitmapData[0] != 0x42) || (bitmapData[1] != 0x4D)) return null;
66
+ return bitmapData.slice(14);
67
+}
68
+
69
// Load a .ico file. This will load all icons in the file into a icon group object
70
function loadIcon(iconFile) {
71
var iconData = null;
@@ -753,6 +762,28 @@ function createAuthenticodeHandler(path) {
762
return pkcs7raw;
763
}
764
765
+
766
+ // Get bitmaps information from resource
767
+ obj.getBitmapInfo = function () {
768
+ const r = {}, ptr = obj.header.sections['.rsrc'].rawAddr;
769
+
770
+ // Find and parse each icon
771
+ const bitmaps = {}
772
+ for (var i = 0; i < obj.resources.entries.length; i++) {
773
+ if (obj.resources.entries[i].name == resourceDefaultNames.bitmaps) {
774
+ for (var j = 0; j < obj.resources.entries[i].table.entries.length; j++) {
775
+ const bitmapName = obj.resources.entries[i].table.entries[j].name;
776
+ const offsetToData = obj.resources.entries[i].table.entries[j].table.entries[0].item.offsetToData;
777
+ const size = obj.resources.entries[i].table.entries[j].table.entries[0].item.size;
778
+ const actualPtr = (offsetToData - obj.header.sections['.rsrc'].virtualAddr) + ptr;
779
+ bitmaps[bitmapName] = readFileSlice(actualPtr, size);
780
+ }
781
+ }
782
+ }
783
+
784
+ return bitmaps;
785
+ }
786
+
787
// Get icon information from resource
788
obj.getIconInfo = function () {
789
const r = {}, ptr = obj.header.sections['.rsrc'].rawAddr;
@@ -810,9 +841,49 @@ function createAuthenticodeHandler(path) {
841
return r;
842
}
843
844
+ // Set bitmap information
845
+ obj.setBitmapInfo = function (bitmapInfo) {
846
+ // Delete all bitmaps resources
847
+ var resourcesEntries = [];
848
+ for (var i = 0; i < obj.resources.entries.length; i++) {
849
+ if (obj.resources.entries[i].name != resourceDefaultNames.bitmaps) {
850
+ resourcesEntries.push(obj.resources.entries[i]);
851
+ }
852
+ }
853
+ obj.resources.entries = resourcesEntries;
854
+
855
+ // Add all bitmap entries
856
+ const bitmapEntry = { name: resourceDefaultNames.bitmaps, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [] } };
857
+ for (var i in bitmapInfo) {
858
+ var name = i;
859
+ if (parseInt(i) == name) { name = parseInt(i); }
860
+ const bitmapItemEntry = { name: name, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [{ name: 1033, item: { buffer: bitmapInfo[i], codePage: 0 } }] } }
861
+ bitmapEntry.table.entries.push(bitmapItemEntry);
862
+ }
863
+ obj.resources.entries.push(bitmapEntry);
864
+
865
+ // Sort the resources by name. This is required.
866
+ function resSort(a, b) {
867
+ if ((typeof a == 'string') && (typeof b == 'string')) { if (a < b) return -1; if (a > b) return 1; return 0; }
868
+ if ((typeof a == 'number') && (typeof b == 'number')) { return a - b; }
869
+ if ((typeof a == 'string') && (typeof b == 'number')) { return -1; }
870
+ return 1;
871
+ }
872
+ const names = [];
873
+ for (var i = 0; i < obj.resources.entries.length; i++) { names.push(obj.resources.entries[i].name); }
874
+ names.sort(resSort);
875
+ var newEntryOrder = [];
876
+ for (var i in names) {
877
+ for (var j = 0; j < obj.resources.entries.length; j++) {
878
+ if (obj.resources.entries[j].name == names[i]) { newEntryOrder.push(obj.resources.entries[j]); }
879
+ }
880
+ }
881
+ obj.resources.entries = newEntryOrder;
882
+ }
883
+
884
// Set icon information
885
obj.setIconInfo = function (iconInfo) {
815
- // Delete all icon and icon groups the the resources
886
+ // Delete all icon and icon groups resources
887
var resourcesEntries = [];
888
for (var i = 0; i < obj.resources.entries.length; i++) {
889
if ((obj.resources.entries[i].name != resourceDefaultNames.icon) && (obj.resources.entries[i].name != resourceDefaultNames.iconGroups)) {
@@ -2023,6 +2094,12 @@ function start() {
2094
console.log(" --out [file] Resulting signed executable.");
2095
console.log(" --time [url] The time signing server URL.");
2096
console.log(" --proxy [url] The HTTP proxy to use to contact the time signing server, must start with http://");
2097
+ console.log(" bitmaps: Show bitmap resources in the executable.");
2098
+ console.log(" --exe [file] Input executable.");
2099
+ console.log(" savebitmap: Save a single bitmap to a .bmp file.");
2100
+ console.log(" --exe [file] Input executable.");
2101
+ console.log(" --out [file] Resulting .ico file.");
2102
+ console.log(" --bitmap [number] Bitmap number to save to file.");
2103
console.log(" icons: Show the icon resources in the executable.");
2104
console.log(" --exe [file] Input executable.");
2105
console.log(" saveicon: Save a single icon bitmap to a .ico file.");
@@ -2049,12 +2126,14 @@ function start() {
2126
console.log(" --productname [value]");
2127
console.log(" --productversion [value]");
2128
console.log(" --removeicongroup [number]");
2129
+ console.log(" --removebitmap [number]");
2130
console.log(" --icon [groupNumber],[filename.ico]");
2131
+ console.log(" --bitmap [number],[filename.bmp]");
2132
return;
2133
}
2134
2135
// Check that a valid command is passed in
2057
- if (['info', 'sign', 'unsign', 'createcert', 'icons', 'saveicon', 'saveicons', 'header', 'sections', 'timestamp', 'signblock'].indexOf(process.argv[2].toLowerCase()) == -1) {
2136
+ if (['info', 'sign', 'unsign', 'createcert', 'icons', 'bitmaps', 'saveicon', 'saveicons', 'savebitmap', 'header', 'sections', 'timestamp', 'signblock'].indexOf(process.argv[2].toLowerCase()) == -1) {
2137
console.log("Invalid command: " + process.argv[2]);
2138
console.log("Valid commands are: info, sign, unsign, createcert, timestamp");
2139
return;
@@ -2096,15 +2175,22 @@ function start() {
2175
2176
// Parse the icon changes
2177
resChanges = false;
2099
- var icons = null;
2178
+ var icons = null, bitmaps = null;
2179
if (exe != null) {
2180
icons = exe.getIconInfo();
2181
+ bitmaps = exe.getBitmapInfo();
2182
if (typeof args['removeicongroup'] == 'string') { // If --removeicongroup is used, it's to remove an existing icon group
2183
const groupsToRemove = args['removeicongroup'].split(',');
2184
for (var i in groupsToRemove) { if (icons[groupsToRemove[i]] != null) { delete icons[groupsToRemove[i]]; resChanges = true; } }
2185
} else if (typeof args['removeicongroup'] == 'number') {
2186
if (icons[args['removeicongroup']] != null) { delete icons[args['removeicongroup']]; resChanges = true; }
2187
}
2188
+ if (typeof args['removebitmap'] == 'string') { // If --removebitmap is used
2189
+ const bitmapsToRemove = args['removebitmap'].split(',');
2190
+ for (var i in bitmapsToRemove) { if (bitmaps[bitmapsToRemove[i]] != null) { delete bitmaps[bitmapsToRemove[i]]; resChanges = true; } }
2191
+ } else if (typeof args['removebitmap'] == 'number') {
2192
+ if (bitmaps[args['removebitmap']] != null) { delete bitmaps[args['removebitmap']]; resChanges = true; }
2193
+ }
2194
if (typeof args['icon'] == 'string') { // If --icon is used, it's to add or replace an existing icon group
2195
const iconToAddSplit = args['icon'].split(',');
2196
if (iconToAddSplit.length != 2) { console.log("The --icon format is: --icon [number],[file]."); return; }
@@ -2121,7 +2207,26 @@ function start() {
2207
resChanges = true;
2208
}
2209
}
2124
- if (resChanges == true) { exe.setIconInfo(icons); }
2210
+ if (typeof args['bitmap'] == 'string') { // If --bitmap is used, it's to add or replace an existing bitmap
2211
+ const bitmapToAddSplit = args['bitmap'].split(',');
2212
+ if (bitmapToAddSplit.length != 2) { console.log("The --bitmap format is: --bitmap [number],[file]."); return; }
2213
+ const bitmapName = parseInt(bitmapToAddSplit[0]);
2214
+ const bitmapFile = bitmapToAddSplit[1];
2215
+ const bitmap = loadBitmap(bitmapFile);
2216
+ if (bitmap == null) { console.log("Unable to load bitmap: " + bitmapFile); return; }
2217
+ if (bitmaps[bitmapName] != null) {
2218
+ const bitmapHash = hashObject(bitmap); // Compute the new bitmap hash
2219
+ const bitmapHash2 = hashObject(bitmaps[bitmapName]); // Computer the old bitmap hash
2220
+ if (bitmapHash != bitmapHash2) { bitmaps[bitmapName] = bitmap; resChanges = true; } // If different, replace the new bitmap
2221
+ } else {
2222
+ bitmaps[bitmapName] = bitmap; // We are adding an new bitmap
2223
+ resChanges = true;
2224
+ }
2225
+ }
2226
+ if (resChanges == true) {
2227
+ exe.setIconInfo(icons);
2228
+ exe.setBitmapInfo(bitmaps);
2229
+ }
2230
}
2231
2232
// Execute the command
@@ -2241,6 +2346,32 @@ function start() {
2346
fs.writeFileSync(args.out, pki.certificateToPem(cert.cert) + '\r\n' + pki.privateKeyToPem(cert.key));
2347
console.log("Done.");
2348
}
2349
+ if (command == 'bitmaps') { // Show bitmaps in the executable
2350
+ if (exe == null) { console.log("Missing --exe [filename]"); return; }
2351
+ if (args.json) {
2352
+ var bitmapInfo = exe.getBitmapInfo();
2353
+ console.log(JSON.stringify(bitmapInfo, null, 2));
2354
+ } else {
2355
+ var bitmapInfo = exe.getBitmapInfo();
2356
+ if (bitmapInfo != null) {
2357
+ console.log("Bitmap Information:");
2358
+ for (var i in bitmapInfo) { console.log(' ' + i + ': ' + bitmapInfo[i].length + ' byte' + ((bitmapInfo[i].length > 1) ? 's' : '') + '.'); }
2359
+ }
2360
+ }
2361
+ }
2362
+ if (command == 'savebitmap') { // Save an bitmap to file
2363
+ if (exe == null) { console.log("Missing --exe [filename]"); return; }
2364
+ if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
2365
+ if (typeof args.bitmap != 'number') { console.log("Missing or incorrect --bitmap [number]"); return; }
2366
+ const bitmapInfo = exe.getBitmapInfo();
2367
+ if (bitmapInfo[args.bitmap] == null) { console.log("Unknown bitmap: " + args.bitmap); return; }
2368
+
2369
+ console.log("Writing to " + args.out);
2370
+ var bitmapHeader = Buffer.from('424D000000000000000036000000', 'hex');
2371
+ bitmapHeader.writeUInt32LE(14 + bitmapInfo[args.bitmap].length, 2); // Write the full size of the bitmap file
2372
+ fs.writeFileSync(args.out, Buffer.concat([bitmapHeader, bitmapInfo[args.bitmap]]));
2373
+ console.log("Done.");
2374
+ }
2375
if (command == 'icons') { // Show icons in the executable
2376
if (exe == null) { console.log("Missing --exe [filename]"); return; }
2377
if (args.json) {