Updated authenticode.js in order to support changing icons in Windows executables.
Ylian Saint-Hilaire committed
Aug 9, 2022 at 23:35 UTC
c5315ba0fcdb7bdf19484331aec1db46a02edadc
1 file changed
+216
-28
authenticode.js
+216
-28
@@ -571,7 +571,7 @@ function createAuthenticodeHandler(path) {
571
if ((resSizeTotal % fileAlign) != 0) { resSizeTotal += (fileAlign - (resSizeTotal % fileAlign)); }
572
const resSectionBuffer = Buffer.alloc(resSizeTotal);
573
574
- // Write the resource section, calling a recusrize method
574
+ // Write the resource section, calling a recursive method
575
const resPointers = { tables: 0, items: resSizes.tables, names: resSizes.tables + resSizes.items, data: resSizes.tables + resSizes.items + resSizes.names };
576
createResourceSection(resources, resSectionBuffer, resPointers);
577
//console.log('generateResourceSection', resPointers);
@@ -720,6 +720,38 @@ function createAuthenticodeHandler(path) {
720
return pkcs7raw;
721
}
722
723
+ // Hash an object
724
+ obj.hashObject = function (obj) {
725
+ const hash = crypto.createHash('sha384');
726
+ hash.update(JSON.stringify(obj));
727
+ return hash.digest();
728
+ }
729
+
730
+ // Load a .ico file. This will load all icons in the file into a icon group object
731
+ obj.loadIcon = function (iconFile) {
732
+ var iconData = null;
733
+ try { iconData = fs.readFileSync(iconFile); } catch (ex) {}
734
+ if ((iconData == null) || (iconData.length < 6) || (iconData[0] != 0) || (iconData[1] != 0)) return null;
735
+ const r = { resType: iconData.readUInt16LE(2), resCount: iconData.readUInt16LE(4), icons: {} };
736
+ if (r.resType != 1) return null;
737
+ var ptr = 6;
738
+ for (var i = 1; i <= r.resCount; i++) {
739
+ var icon = {};
740
+ icon.width = iconData[ptr + 0];
741
+ icon.height = iconData[ptr + 1];
742
+ icon.colorCount = iconData[ptr + 2];
743
+ icon.planes = iconData.readUInt16LE(ptr + 4);
744
+ icon.bitCount = iconData.readUInt16LE(ptr + 6);
745
+ icon.bytesInRes = iconData.readUInt32LE(ptr + 8);
746
+ icon.iconCursorId = i;
747
+ const offset = iconData.readUInt32LE(ptr + 12);
748
+ icon.icon = iconData.slice(offset, offset + icon.bytesInRes);
749
+ r.icons[i] = icon;
750
+ ptr += 16;
751
+ }
752
+ return r;
753
+ }
754
+
755
// Get icon information from resource
756
obj.getIconInfo = function () {
757
const r = {}, ptr = obj.header.sections['.rsrc'].rawAddr;
@@ -777,9 +809,81 @@ function createAuthenticodeHandler(path) {
809
return r;
810
}
811
812
+ // Set icon information
813
+ obj.setIconInfo = function (iconInfo) {
814
+ // Delete all icon and icon groups the the ressources
815
+ var resourcesEntries = [];
816
+ for (var i = 0; i < obj.resources.entries.length; i++) {
817
+ if ((obj.resources.entries[i].name != resourceDefaultNames.icon) && (obj.resources.entries[i].name != resourceDefaultNames.iconGroups)) {
818
+ resourcesEntries.push(obj.resources.entries[i]);
819
+ }
820
+ }
821
+ obj.resources.entries = resourcesEntries;
822
+
823
+ // count the icon groups
824
+ var iconGroupCount = 0;
825
+ for (var i in iconInfo) { iconGroupCount++; }
826
+ if (iconGroupCount == 0) return; // If there are no icon groups, we are done
827
+
828
+ // Add the new icons entry
829
+ const iconsEntry = { name: resourceDefaultNames.icon, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [] } };
830
+ for (var i in iconInfo) {
831
+ for (var j in iconInfo[i].icons) {
832
+ var name = j;
833
+ if (parseInt(j) == name) { name = parseInt(j); }
834
+ const iconItemEntry = { name: name, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [{ name: 1033, item: { buffer: iconInfo[i].icons[j].icon, codePage: 0 } }] } }
835
+ iconsEntry.table.entries.push(iconItemEntry);
836
+ }
837
+ }
838
+ obj.resources.entries.push(iconsEntry);
839
+
840
+ // Add the new icon group entry
841
+ const groupEntry = { name: resourceDefaultNames.iconGroups, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [] } };
842
+ for (var i in iconInfo) {
843
+ // Build icon group struct
844
+ var iconCount = 0, p = 6;
845
+ for (var j in iconInfo[i].icons) { iconCount++; }
846
+ const buf = Buffer.alloc(6 + (iconCount * 14));
847
+ buf.writeUInt16LE(iconInfo[i].resType, 2);
848
+ buf.writeUInt16LE(iconCount, 4);
849
+ for (var j in iconInfo[i].icons) {
850
+ buf[p] = iconInfo[i].icons[j].width;
851
+ buf[p + 1] = iconInfo[i].icons[j].height;
852
+ buf[p + 2] = iconInfo[i].icons[j].colorCount;
853
+ buf.writeUInt16LE(iconInfo[i].icons[j].planes, p + 4);
854
+ buf.writeUInt16LE(iconInfo[i].icons[j].bitCount, p + 6);
855
+ buf.writeUInt32LE(iconInfo[i].icons[j].bytesInRes, p + 8);
856
+ buf.writeUInt16LE(j, p + 12);
857
+ p += 14;
858
+ }
859
+ var name = i;
860
+ if (parseInt(i) == name) { name = parseInt(i); }
861
+ const groupItemEntry = { name: name, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [{ name: 1033, item: { buffer: buf, codePage: 0 } }] } }
862
+ groupEntry.table.entries.push(groupItemEntry);
863
+ }
864
+ obj.resources.entries.push(groupEntry);
865
+
866
+ // Sort the resources by name. This is required.
867
+ function resSort(a, b) {
868
+ if ((typeof a == 'string') && (typeof b == 'string')) { if (a < b) return -1; if (a > b) return 1; return 0; }
869
+ if ((typeof a == 'number') && (typeof b == 'number')) { return a - b; }
870
+ if ((typeof a == 'string') && (typeof b == 'number')) { return -1; }
871
+ return 1;
872
+ }
873
+ const names = [];
874
+ for (var i = 0; i < obj.resources.entries.length; i++) { names.push(obj.resources.entries[i].name); }
875
+ names.sort(resSort);
876
+ var newEntryOrder = [];
877
+ for (var i in names) {
878
+ for (var j = 0; j < obj.resources.entries.length; j++) {
879
+ if (obj.resources.entries[j].name == names[i]) { newEntryOrder.push(obj.resources.entries[j]); }
880
+ }
881
+ }
882
+ obj.resources.entries = newEntryOrder;
883
+ }
884
+
885
// Decode the version information from the resource
886
obj.getVersionInfo = function () {
782
- //console.log('READ', getVersionInfoData().toString('hex'));
887
var r = {}, info = readVersionInfo(getVersionInfoData(), 0);
888
if ((info == null) || (info.stringFiles == null)) return null;
889
var StringFileInfo = null;
@@ -1822,34 +1926,44 @@ function start() {
1926
console.log(" node authenticode.js [command] [options]");
1927
console.log("Commands:");
1928
console.log(" info: Show information about an executable.");
1825
- console.log(" --exe [file] Required executable to view information.");
1826
- console.log(" --json Show information in JSON format.");
1929
+ console.log(" --exe [file] Required executable to view information.");
1930
+ console.log(" --json Show information in JSON format.");
1931
console.log(" sign: Sign an executable.");
1828
- console.log(" --exe [file] Required executable to sign.");
1829
- console.log(" --out [file] Resulting signed executable.");
1830
- console.log(" --pem [pemfile] Certificate & private key to sign the executable with.");
1831
- console.log(" --desc [description] Description string to embbed into signature.");
1832
- console.log(" --url [url] URL to embbed into signature.");
1833
- console.log(" --hash [method] Default is SHA384, possible value: MD5, SHA224, SHA256, SHA384 or SHA512.");
1834
- console.log(" --time [url] The time signing server URL.");
1835
- console.log(" --proxy [url] The HTTP proxy to use to contact the time signing server, must start with http://");
1932
+ console.log(" --exe [file] Required executable to sign.");
1933
+ console.log(" --out [file] Resulting signed executable.");
1934
+ console.log(" --pem [pemfile] Certificate & private key to sign the executable with.");
1935
+ console.log(" --desc [description] Description string to embbed into signature.");
1936
+ console.log(" --url [url] URL to embbed into signature.");
1937
+ console.log(" --hash [method] Default is SHA384, possible value: MD5, SHA224, SHA256, SHA384 or SHA512.");
1938
+ console.log(" --time [url] The time signing server URL.");
1939
+ console.log(" --proxy [url] The HTTP proxy to use to contact the time signing server, must start with http://");
1940
console.log(" unsign: Remove the signature from the executable.");
1837
- console.log(" --exe [file] Required executable to un-sign.");
1838
- console.log(" --out [file] Resulting executable with signature removed.");
1941
+ console.log(" --exe [file] Required executable to un-sign.");
1942
+ console.log(" --out [file] Resulting executable with signature removed.");
1943
console.log(" createcert: Create a code signging self-signed certificate and key.");
1840
- console.log(" --out [pemfile] Required certificate file to create.");
1841
- console.log(" --cn [value] Required certificate common name.");
1842
- console.log(" --country [value] Certificate country name.");
1843
- console.log(" --state [value] Certificate state name.");
1844
- console.log(" --locality [value] Certificate locality name.");
1845
- console.log(" --org [value] Certificate organization name.");
1846
- console.log(" --ou [value] Certificate organization unit name.");
1847
- console.log(" --serial [value] Certificate serial number.");
1944
+ console.log(" --out [pemfile] Required certificate file to create.");
1945
+ console.log(" --cn [value] Required certificate common name.");
1946
+ console.log(" --country [value] Certificate country name.");
1947
+ console.log(" --state [value] Certificate state name.");
1948
+ console.log(" --locality [value] Certificate locality name.");
1949
+ console.log(" --org [value] Certificate organization name.");
1950
+ console.log(" --ou [value] Certificate organization unit name.");
1951
+ console.log(" --serial [value] Certificate serial number.");
1952
console.log(" timestamp: Add a signed timestamp to an already signed executable.");
1849
- console.log(" --exe [file] Required executable to sign.");
1850
- console.log(" --out [file] Resulting signed executable.");
1851
- console.log(" --time [url] The time signing server URL.");
1852
- console.log(" --proxy [url] The HTTP proxy to use to contact the time signing server, must start with http://");
1953
+ console.log(" --exe [file] Required executable to timestamp.");
1954
+ console.log(" --out [file] Resulting signed executable.");
1955
+ console.log(" --time [url] The time signing server URL.");
1956
+ console.log(" --proxy [url] The HTTP proxy to use to contact the time signing server, must start with http://");
1957
+ console.log(" icons: Show the icon resources in the executable.");
1958
+ console.log(" --exe [file] Input executable.");
1959
+ console.log(" saveicon: Save a single icon bitmap to a .ico file.");
1960
+ console.log(" --exe [file] Input executable.");
1961
+ console.log(" --out [file] Resulting .ico file.");
1962
+ console.log(" --icon [number] Icon number to save to file.");
1963
+ console.log(" saveicons: Save an icon group to a .ico file.");
1964
+ console.log(" --exe [file] Input executable.");
1965
+ console.log(" --out [file] Resulting .ico file.");
1966
+ console.log(" --icongroup [groupNumber] Icon groupnumber to save to file.");
1967
console.log("");
1968
console.log("Note that certificate PEM files must first have the signing certificate,");
1969
console.log("followed by all certificates that form the trust chain.");
@@ -1865,11 +1979,13 @@ function start() {
1979
console.log(" --originalfilename [value]");
1980
console.log(" --productname [value]");
1981
console.log(" --productversion [value]");
1982
+ console.log(" --removeicongroup [number]");
1983
+ console.log(" --icon [groupNumber],[filename.ico]");
1984
return;
1985
}
1986
1987
// Check that a valid command is passed in
1872
- if (['info', 'sign', 'unsign', 'createcert', 'icons', 'saveicon', 'header', 'timestamp', 'signblock'].indexOf(process.argv[2].toLowerCase()) == -1) {
1988
+ if (['info', 'sign', 'unsign', 'createcert', 'icons', 'saveicon', 'saveicons', 'header', 'timestamp', 'signblock'].indexOf(process.argv[2].toLowerCase()) == -1) {
1989
console.log("Invalid command: " + process.argv[2]);
1990
console.log("Valid commands are: info, sign, unsign, createcert, timestamp");
1991
return;
@@ -1909,6 +2025,36 @@ function start() {
2025
if (resChanges == true) { exe.setVersionInfo(versionStrings); }
2026
}
2027
2028
+ // Parse the icon changes
2029
+ resChanges = false;
2030
+ var icons = null;
2031
+ if (exe != null) {
2032
+ icons = exe.getIconInfo();
2033
+ if (typeof args['removeicongroup'] == 'string') { // If --removeicongroup is used, it's to remove an existing icon group
2034
+ const groupsToRemove = args['removeicongroup'].split(',');
2035
+ for (var i in groupsToRemove) { if (icons[groupsToRemove[i]] != null) { delete icons[groupsToRemove[i]]; resChanges = true; } }
2036
+ } else if (typeof args['removeicongroup'] == 'number') {
2037
+ if (icons[args['removeicongroup']] != null) { delete icons[args['removeicongroup']]; resChanges = true; }
2038
+ }
2039
+ if (typeof args['icon'] == 'string') { // If --icon is used, it's to add or replace an existing icon group
2040
+ const iconToAddSplit = args['icon'].split(',');
2041
+ if (iconToAddSplit.length != 2) { console.log("The --icon format is: --icon [number],[file]."); return; }
2042
+ const iconName = parseInt(iconToAddSplit[0]);
2043
+ const iconFile = iconToAddSplit[1];
2044
+ const icon = exe.loadIcon(iconFile);
2045
+ if (icon == null) { console.log("Unable to load icon: " + iconFile); return; }
2046
+ if (icons[iconName] != null) {
2047
+ const iconHash = exe.hashObject(icon); // Compute the new icon group hash
2048
+ const iconHash2 = exe.hashObject(icons[iconName]); // Computer the old icon group hash
2049
+ if (iconHash.toString('hex') != iconHash2.toString('hex')) { icons[iconName] = icon; resChanges = true; } // If different, replace the icon group
2050
+ } else {
2051
+ icons[iconName] = icon; // We are adding an icon group
2052
+ resChanges = true;
2053
+ }
2054
+ }
2055
+ if (resChanges == true) { exe.setIconInfo(icons); }
2056
+ }
2057
+
2058
// Execute the command
2059
var command = process.argv[2].toLowerCase();
2060
if (command == 'info') { // Get signature information about an executable
@@ -1983,7 +2129,7 @@ function start() {
2129
if (resChanges == false) {
2130
if (exe.header.signed) {
2131
console.log("Unsigning to " + args.out);
1986
- exe.unsign(args); // Simple unsign, copy most of the original file.
2132
+ exe.unsign(args); // Simple unsign, copy most of the original file.
2133
console.log("Done.");
2134
} else {
2135
console.log("Executable is not signed.");
@@ -2045,6 +2191,48 @@ function start() {
2191
fs.writeFileSync(args.out, Buffer.concat([buf, icon.icon]));
2192
console.log("Done.");
2193
}
2194
+ if (command == 'saveicons') { // Save an icon group to file
2195
+ if (exe == null) { console.log("Missing --exe [filename]"); return; }
2196
+ if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
2197
+ if (typeof args.icongroup != 'number') { console.log("Missing or incorrect --icongroup [number]"); return; }
2198
+ const iconInfo = exe.getIconInfo();
2199
+ const iconGroup = iconInfo[args.icongroup];
2200
+ if (iconGroup == null) { console.log("Invalid or incorrect --icongroup [number]"); return; }
2201
+
2202
+ // Count the number of icons in the group
2203
+ var iconCount = 0;
2204
+ for (var i in iconGroup.icons) { iconCount++; }
2205
+
2206
+ // .ico header: https://en.wikipedia.org/wiki/ICO_(file_format)
2207
+ const iconFileData = [];
2208
+ const header = Buffer.alloc(6);
2209
+ header.writeUInt16LE(1, 2); // 1 = Icon, 2 = Cursor
2210
+ header.writeUInt16LE(iconCount, 4); // Icon Count, always 1 in our case
2211
+ iconFileData.push(header);
2212
+
2213
+ // Store each icon header
2214
+ var offsetPtr = 6 + (16 * iconCount);
2215
+ for (var i in iconGroup.icons) {
2216
+ const buf = Buffer.alloc(16);
2217
+ buf[0] = iconGroup.icons[i].width; // Width (0 = 256)
2218
+ buf[1] = iconGroup.icons[i].height; // Height (0 = 256)
2219
+ buf[2] = iconGroup.icons[i].colorCount; // Colors
2220
+ buf.writeUInt16LE(iconGroup.icons[i].planes, 4); // Color planes
2221
+ buf.writeUInt16LE(iconGroup.icons[i].bitCount, 6); // Bits per pixel
2222
+ buf.writeUInt32LE(iconGroup.icons[i].icon.length, 8); // Size
2223
+ buf.writeUInt32LE(offsetPtr, 12); // Offset
2224
+ offsetPtr += iconGroup.icons[i].icon.length;
2225
+ iconFileData.push(buf);
2226
+ }
2227
+
2228
+ // Store each icon
2229
+ for (var i in iconGroup.icons) { iconFileData.push(iconGroup.icons[i].icon); }
2230
+
2231
+ // Write the .ico file
2232
+ console.log("Writing to " + args.out);
2233
+ fs.writeFileSync(args.out, Buffer.concat(iconFileData));
2234
+ console.log("Done.");
2235
+ }
2236
if (command == 'signblock') { // Display the raw signature block of the executable in hex
2237
if (exe == null) { console.log("Missing --exe [filename]"); return; }
2238
var buf = exe.getRawSignatureBlock();