const fs = require('fs'); const file = fs.readFileSync(process.argv[2]); let at = file.readUInt32LE(0x3c); if (file.slice(at, at + 0x4).toString('utf-8') !== 'PE\x00\x00') { // bail if not PE header console.error('Did not see PE magic constant'); process.exit(1); } const header = { sections: file.readUInt16LE(at + 0x6), sizeOfOptionalHeader: file.readUInt16LE(at + 0x14), resourcesVAddr: 0, resources: 0, }; // console.log({header}); for (let i = 0; i < header.sections; i += 1) { const offset = at + 0x18 + header.sizeOfOptionalHeader + (i * 0x28); const vaddr = file.readUInt32LE(offset + 0xc); const addr = file.readUInt32LE(offset + 0x14); const name = file.toString('utf-8', offset, offset + 8).split('\0').shift(); if (name === '.rsrc') { header.resourcesVAddr = vaddr; header.resources = addr; break; } } if (!header.resources) { console.error('Did not find resource table offset'); process.exit(1); } const parseResources = (offset) => { const namedEntriesCount = file.readUInt16LE(offset + 0xc); const idEntriesCount = file.readUInt16LE(offset + 0xe); const entries = []; for (let i = 0; i < namedEntriesCount + idEntriesCount; i += 1) { const cur = offset + 0x10 + (i * 0x8); const id = file.readUInt32LE(cur); const data = file.readUInt32LE(cur + 0x4); // true if high bit is set const isDir = !!(data >>> 31); // clear high bit const target = data & 0x7fffffff; entries.push({ id, isDir, target, }); } return entries; }; const versionDataEntry = { offset: parseResources( parseResources( parseResources(header.resources) .find((x) => x.id === 16 && x.isDir).target + header.resources, ) .find((x) => x.isDir).target + header.resources, ) .find((x) => !x.isDir).target + header.resources, }; versionDataEntry.rvaTarget = file.readUInt32LE(versionDataEntry.offset); versionDataEntry.sizeTarget = file.readUInt32LE(versionDataEntry.offset + 0x4); // set position to start of version data adjusted for virtual address at = versionDataEntry.rvaTarget - header.resourcesVAddr + header.resources; const readUTF16String = (offset) => { let count = 0; let i = 0; while (count < 2) { count += file[offset + i] === 0 ? 1 : -1; i += 1; } return file.toString('utf-16le', offset, offset + i - 1); }; const dwordAlign = (offset, base) => ((offset + base + 3) & 0xfffffffc) - (base & 0xfffffffc); const utf16len = (x) => 2 * (x.length + 1); if (readUTF16String(at + 0x6) !== 'VS_VERSION_INFO') { console.error('Failed to find version struct'); process.exit(1); } const parseStringFileInfo = (offset) => { const entrySize = file.readUInt16LE(offset + 0x2); const name = readUTF16String(offset + 0x6); const valueOffset = dwordAlign(offset + 0x6 + utf16len(name), at); return { totalSize: file.readUInt16LE(offset), name, value: entrySize ? readUTF16String(valueOffset) : null, }; }; let curStringFileInfo = dwordAlign(0x5a + at, at); while (curStringFileInfo < at + versionDataEntry.sizeTarget) { const stringFileInfo = parseStringFileInfo(curStringFileInfo); if (stringFileInfo.name === 'StringFileInfo') { let curStringTable = dwordAlign(curStringFileInfo + 0x6 + utf16len(stringFileInfo.name), at); while (curStringTable < curStringFileInfo + stringFileInfo.totalSize) { const stringTableInfo = parseStringFileInfo(curStringTable); let curStringTableEntry = dwordAlign(curStringTable + 0x6 + utf16len(stringTableInfo.name), at); while (curStringTableEntry < curStringTable + stringTableInfo.totalSize) { const stringEntryInfo = parseStringFileInfo(curStringTableEntry); console.log(`${stringEntryInfo.name}: ${stringEntryInfo.value}`); curStringTableEntry = dwordAlign(curStringTableEntry + stringEntryInfo.totalSize, at); } curStringTable = dwordAlign(curStringTable + stringTableInfo.totalSize, at); } } curStringFileInfo = dwordAlign(curStringFileInfo + stringFileInfo.totalSize, at); }