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) {