Updated authenticode.js in order to support changing icons in Windows executables.

This commit is contained in:
Ylian Saint-Hilaire 2022-08-09 23:35:46 -07:00
parent 1257c4bc3a
commit c5315ba0fc

View File

@ -571,7 +571,7 @@ function createAuthenticodeHandler(path) {
if ((resSizeTotal % fileAlign) != 0) { resSizeTotal += (fileAlign - (resSizeTotal % fileAlign)); }
const resSectionBuffer = Buffer.alloc(resSizeTotal);
// Write the resource section, calling a recusrize method
// Write the resource section, calling a recursive method
const resPointers = { tables: 0, items: resSizes.tables, names: resSizes.tables + resSizes.items, data: resSizes.tables + resSizes.items + resSizes.names };
createResourceSection(resources, resSectionBuffer, resPointers);
//console.log('generateResourceSection', resPointers);
@ -720,6 +720,38 @@ function createAuthenticodeHandler(path) {
return pkcs7raw;
}
// Hash an object
obj.hashObject = function (obj) {
const hash = crypto.createHash('sha384');
hash.update(JSON.stringify(obj));
return hash.digest();
}
// Load a .ico file. This will load all icons in the file into a icon group object
obj.loadIcon = function (iconFile) {
var iconData = null;
try { iconData = fs.readFileSync(iconFile); } catch (ex) {}
if ((iconData == null) || (iconData.length < 6) || (iconData[0] != 0) || (iconData[1] != 0)) return null;
const r = { resType: iconData.readUInt16LE(2), resCount: iconData.readUInt16LE(4), icons: {} };
if (r.resType != 1) return null;
var ptr = 6;
for (var i = 1; i <= r.resCount; i++) {
var icon = {};
icon.width = iconData[ptr + 0];
icon.height = iconData[ptr + 1];
icon.colorCount = iconData[ptr + 2];
icon.planes = iconData.readUInt16LE(ptr + 4);
icon.bitCount = iconData.readUInt16LE(ptr + 6);
icon.bytesInRes = iconData.readUInt32LE(ptr + 8);
icon.iconCursorId = i;
const offset = iconData.readUInt32LE(ptr + 12);
icon.icon = iconData.slice(offset, offset + icon.bytesInRes);
r.icons[i] = icon;
ptr += 16;
}
return r;
}
// Get icon information from resource
obj.getIconInfo = function () {
const r = {}, ptr = obj.header.sections['.rsrc'].rawAddr;
@ -777,9 +809,81 @@ function createAuthenticodeHandler(path) {
return r;
}
// Set icon information
obj.setIconInfo = function (iconInfo) {
// Delete all icon and icon groups the the ressources
var resourcesEntries = [];
for (var i = 0; i < obj.resources.entries.length; i++) {
if ((obj.resources.entries[i].name != resourceDefaultNames.icon) && (obj.resources.entries[i].name != resourceDefaultNames.iconGroups)) {
resourcesEntries.push(obj.resources.entries[i]);
}
}
obj.resources.entries = resourcesEntries;
// count the icon groups
var iconGroupCount = 0;
for (var i in iconInfo) { iconGroupCount++; }
if (iconGroupCount == 0) return; // If there are no icon groups, we are done
// Add the new icons entry
const iconsEntry = { name: resourceDefaultNames.icon, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [] } };
for (var i in iconInfo) {
for (var j in iconInfo[i].icons) {
var name = j;
if (parseInt(j) == name) { name = parseInt(j); }
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 } }] } }
iconsEntry.table.entries.push(iconItemEntry);
}
}
obj.resources.entries.push(iconsEntry);
// Add the new icon group entry
const groupEntry = { name: resourceDefaultNames.iconGroups, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [] } };
for (var i in iconInfo) {
// Build icon group struct
var iconCount = 0, p = 6;
for (var j in iconInfo[i].icons) { iconCount++; }
const buf = Buffer.alloc(6 + (iconCount * 14));
buf.writeUInt16LE(iconInfo[i].resType, 2);
buf.writeUInt16LE(iconCount, 4);
for (var j in iconInfo[i].icons) {
buf[p] = iconInfo[i].icons[j].width;
buf[p + 1] = iconInfo[i].icons[j].height;
buf[p + 2] = iconInfo[i].icons[j].colorCount;
buf.writeUInt16LE(iconInfo[i].icons[j].planes, p + 4);
buf.writeUInt16LE(iconInfo[i].icons[j].bitCount, p + 6);
buf.writeUInt32LE(iconInfo[i].icons[j].bytesInRes, p + 8);
buf.writeUInt16LE(j, p + 12);
p += 14;
}
var name = i;
if (parseInt(i) == name) { name = parseInt(i); }
const groupItemEntry = { name: name, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [{ name: 1033, item: { buffer: buf, codePage: 0 } }] } }
groupEntry.table.entries.push(groupItemEntry);
}
obj.resources.entries.push(groupEntry);
// Sort the resources by name. This is required.
function resSort(a, b) {
if ((typeof a == 'string') && (typeof b == 'string')) { if (a < b) return -1; if (a > b) return 1; return 0; }
if ((typeof a == 'number') && (typeof b == 'number')) { return a - b; }
if ((typeof a == 'string') && (typeof b == 'number')) { return -1; }
return 1;
}
const names = [];
for (var i = 0; i < obj.resources.entries.length; i++) { names.push(obj.resources.entries[i].name); }
names.sort(resSort);
var newEntryOrder = [];
for (var i in names) {
for (var j = 0; j < obj.resources.entries.length; j++) {
if (obj.resources.entries[j].name == names[i]) { newEntryOrder.push(obj.resources.entries[j]); }
}
}
obj.resources.entries = newEntryOrder;
}
// Decode the version information from the resource
obj.getVersionInfo = function () {
//console.log('READ', getVersionInfoData().toString('hex'));
var r = {}, info = readVersionInfo(getVersionInfoData(), 0);
if ((info == null) || (info.stringFiles == null)) return null;
var StringFileInfo = null;
@ -1846,10 +1950,20 @@ function start() {
console.log(" --ou [value] Certificate organization unit name.");
console.log(" --serial [value] Certificate serial number.");
console.log(" timestamp: Add a signed timestamp to an already signed executable.");
console.log(" --exe [file] Required executable to sign.");
console.log(" --exe [file] Required executable to timestamp.");
console.log(" --out [file] Resulting signed executable.");
console.log(" --time [url] The time signing server URL.");
console.log(" --proxy [url] The HTTP proxy to use to contact the time signing server, must start with http://");
console.log(" icons: Show the icon resources in the executable.");
console.log(" --exe [file] Input executable.");
console.log(" saveicon: Save a single icon bitmap to a .ico file.");
console.log(" --exe [file] Input executable.");
console.log(" --out [file] Resulting .ico file.");
console.log(" --icon [number] Icon number to save to file.");
console.log(" saveicons: Save an icon group to a .ico file.");
console.log(" --exe [file] Input executable.");
console.log(" --out [file] Resulting .ico file.");
console.log(" --icongroup [groupNumber] Icon groupnumber to save to file.");
console.log("");
console.log("Note that certificate PEM files must first have the signing certificate,");
console.log("followed by all certificates that form the trust chain.");
@ -1865,11 +1979,13 @@ function start() {
console.log(" --originalfilename [value]");
console.log(" --productname [value]");
console.log(" --productversion [value]");
console.log(" --removeicongroup [number]");
console.log(" --icon [groupNumber],[filename.ico]");
return;
}
// Check that a valid command is passed in
if (['info', 'sign', 'unsign', 'createcert', 'icons', 'saveicon', 'header', 'timestamp', 'signblock'].indexOf(process.argv[2].toLowerCase()) == -1) {
if (['info', 'sign', 'unsign', 'createcert', 'icons', 'saveicon', 'saveicons', 'header', 'timestamp', 'signblock'].indexOf(process.argv[2].toLowerCase()) == -1) {
console.log("Invalid command: " + process.argv[2]);
console.log("Valid commands are: info, sign, unsign, createcert, timestamp");
return;
@ -1909,6 +2025,36 @@ function start() {
if (resChanges == true) { exe.setVersionInfo(versionStrings); }
}
// Parse the icon changes
resChanges = false;
var icons = null;
if (exe != null) {
icons = exe.getIconInfo();
if (typeof args['removeicongroup'] == 'string') { // If --removeicongroup is used, it's to remove an existing icon group
const groupsToRemove = args['removeicongroup'].split(',');
for (var i in groupsToRemove) { if (icons[groupsToRemove[i]] != null) { delete icons[groupsToRemove[i]]; resChanges = true; } }
} else if (typeof args['removeicongroup'] == 'number') {
if (icons[args['removeicongroup']] != null) { delete icons[args['removeicongroup']]; resChanges = true; }
}
if (typeof args['icon'] == 'string') { // If --icon is used, it's to add or replace an existing icon group
const iconToAddSplit = args['icon'].split(',');
if (iconToAddSplit.length != 2) { console.log("The --icon format is: --icon [number],[file]."); return; }
const iconName = parseInt(iconToAddSplit[0]);
const iconFile = iconToAddSplit[1];
const icon = exe.loadIcon(iconFile);
if (icon == null) { console.log("Unable to load icon: " + iconFile); return; }
if (icons[iconName] != null) {
const iconHash = exe.hashObject(icon); // Compute the new icon group hash
const iconHash2 = exe.hashObject(icons[iconName]); // Computer the old icon group hash
if (iconHash.toString('hex') != iconHash2.toString('hex')) { icons[iconName] = icon; resChanges = true; } // If different, replace the icon group
} else {
icons[iconName] = icon; // We are adding an icon group
resChanges = true;
}
}
if (resChanges == true) { exe.setIconInfo(icons); }
}
// Execute the command
var command = process.argv[2].toLowerCase();
if (command == 'info') { // Get signature information about an executable
@ -2045,6 +2191,48 @@ function start() {
fs.writeFileSync(args.out, Buffer.concat([buf, icon.icon]));
console.log("Done.");
}
if (command == 'saveicons') { // Save an icon group to file
if (exe == null) { console.log("Missing --exe [filename]"); return; }
if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
if (typeof args.icongroup != 'number') { console.log("Missing or incorrect --icongroup [number]"); return; }
const iconInfo = exe.getIconInfo();
const iconGroup = iconInfo[args.icongroup];
if (iconGroup == null) { console.log("Invalid or incorrect --icongroup [number]"); return; }
// Count the number of icons in the group
var iconCount = 0;
for (var i in iconGroup.icons) { iconCount++; }
// .ico header: https://en.wikipedia.org/wiki/ICO_(file_format)
const iconFileData = [];
const header = Buffer.alloc(6);
header.writeUInt16LE(1, 2); // 1 = Icon, 2 = Cursor
header.writeUInt16LE(iconCount, 4); // Icon Count, always 1 in our case
iconFileData.push(header);
// Store each icon header
var offsetPtr = 6 + (16 * iconCount);
for (var i in iconGroup.icons) {
const buf = Buffer.alloc(16);
buf[0] = iconGroup.icons[i].width; // Width (0 = 256)
buf[1] = iconGroup.icons[i].height; // Height (0 = 256)
buf[2] = iconGroup.icons[i].colorCount; // Colors
buf.writeUInt16LE(iconGroup.icons[i].planes, 4); // Color planes
buf.writeUInt16LE(iconGroup.icons[i].bitCount, 6); // Bits per pixel
buf.writeUInt32LE(iconGroup.icons[i].icon.length, 8); // Size
buf.writeUInt32LE(offsetPtr, 12); // Offset
offsetPtr += iconGroup.icons[i].icon.length;
iconFileData.push(buf);
}
// Store each icon
for (var i in iconGroup.icons) { iconFileData.push(iconGroup.icons[i].icon); }
// Write the .ico file
console.log("Writing to " + args.out);
fs.writeFileSync(args.out, Buffer.concat(iconFileData));
console.log("Done.");
}
if (command == 'signblock') { // Display the raw signature block of the executable in hex
if (exe == null) { console.log("Missing --exe [filename]"); return; }
var buf = exe.getRawSignatureBlock();