Last active
November 30, 2020 20:06
-
-
Save liath/c148ce9f72a64457150e16f2a880e7c4 to your computer and use it in GitHub Desktop.
Extracts FileVersion and other fun fields as seen in the Properties dialog for dll and exe files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment