Skip to content

Instantly share code, notes, and snippets.

@liath
Last active November 30, 2020 20:06
Show Gist options
  • Save liath/c148ce9f72a64457150e16f2a880e7c4 to your computer and use it in GitHub Desktop.
Save liath/c148ce9f72a64457150e16f2a880e7c4 to your computer and use it in GitHub Desktop.

Revisions

  1. liath revised this gist Dec 1, 2020. 1 changed file with 6 additions and 5 deletions.
    11 changes: 6 additions & 5 deletions version-extract.js
    Original file line number Diff line number Diff line change
    @@ -19,8 +19,8 @@ const 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 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') {
    @@ -94,13 +94,14 @@ if (readUTF16String(at + 0x6) !== 'VS_VERSION_INFO') {
    }

    const parseStringFileInfo = (offset) => {
    const entrySize = file.readUint16LE(offset + 0x2);
    const entrySize = file.readUInt16LE(offset + 0x2);
    const name = readUTF16String(offset + 0x6);
    const valueOffset = dwordAlign(offset + 0x6 + utf16len(name), at);

    return {
    totalSize: file.readUint16LE(offset),
    totalSize: file.readUInt16LE(offset),
    name,
    value: entrySize ? readUTF16String(offset + 0x6 + utf16len(name)) : null,
    value: entrySize ? readUTF16String(valueOffset) : null,
    };
    };

  2. liath created this gist Nov 26, 2020.
    131 changes: 131 additions & 0 deletions version-extract.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,131 @@
    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);

    return {
    totalSize: file.readUint16LE(offset),
    name,
    value: entrySize ? readUTF16String(offset + 0x6 + utf16len(name)) : 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);
    }