mirror of
https://github.com/Ylianst/MeshAgent
synced 2025-12-06 00:13:33 +00:00
448 lines
18 KiB
JavaScript
448 lines
18 KiB
JavaScript
/*
|
|
Copyright 2018 Intel Corporation
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
var fs = require('fs');
|
|
|
|
// Return information about this executable
|
|
function parse(exePath)
|
|
{
|
|
var retVal = {};
|
|
var fd = fs.openSync(exePath, 'rb');
|
|
var bytesRead;
|
|
var dosHeader = Buffer.alloc(64);
|
|
var ntHeader = Buffer.alloc(24);
|
|
var optHeader;
|
|
var z;
|
|
|
|
// Read the DOS header
|
|
bytesRead = fs.readSync(fd, dosHeader, 0, 64, 0);
|
|
if (dosHeader.readUInt16LE(0).toString(16).toUpperCase() != '5A4D')
|
|
{
|
|
throw ('unrecognized binary format');
|
|
}
|
|
|
|
// Read the NT header
|
|
bytesRead = fs.readSync(fd, ntHeader, 0, ntHeader.length, dosHeader.readUInt32LE(60));
|
|
if (ntHeader.slice(0, 4).toString('hex') != '50450000')
|
|
{
|
|
throw ('not a PE file');
|
|
}
|
|
switch (ntHeader.readUInt16LE(4).toString(16))
|
|
{
|
|
case '14c': // 32 bit
|
|
retVal.format = 'x86';
|
|
break;
|
|
case '8664': // 64 bit
|
|
retVal.format = 'x64';
|
|
break;
|
|
default: // Unknown
|
|
retVal.format = undefined;
|
|
break;
|
|
}
|
|
|
|
retVal.optionalHeaderSize = ntHeader.readUInt16LE(20);
|
|
retVal.optionalHeaderSizeAddress = dosHeader.readUInt32LE(60) + 24;
|
|
retVal.sectionHeadersAddress = retVal.optionalHeaderSizeAddress + retVal.optionalHeaderSize;
|
|
|
|
// Read the optional header
|
|
optHeader = Buffer.alloc(ntHeader.readUInt16LE(20));
|
|
bytesRead = fs.readSync(fd, optHeader, 0, optHeader.length, dosHeader.readUInt32LE(60) + 24);
|
|
var numRVA = undefined;
|
|
var rvaStart = 0;
|
|
retVal.CheckSumPos = dosHeader.readUInt32LE(60) + 24 + 64;
|
|
retVal.SizeOfCode = optHeader.readUInt32LE(4);
|
|
retVal.SizeOfInitializedData = optHeader.readUInt32LE(8);
|
|
retVal.SizeOfUnInitializedData = optHeader.readUInt32LE(12);
|
|
retVal.sections = {};
|
|
|
|
// read section headers
|
|
var sect = Buffer.alloc(40);
|
|
for (z = 0; z < 16; ++z)
|
|
{
|
|
fs.readSync(fd, sect, 0, sect.length, retVal.sectionHeadersAddress + (z * 40));
|
|
if (sect[0] != 46) { break; }
|
|
var s = {};
|
|
s.sectionName = sect.slice(0, 8).toString().trim('\0');
|
|
s.virtualSize = sect.readUInt32LE(8);
|
|
s.virtualAddr = sect.readUInt32LE(12);
|
|
s.rawSize = sect.readUInt32LE(16);
|
|
s.rawAddr = sect.readUInt32LE(20);
|
|
s.relocAddr = sect.readUInt32LE(24);
|
|
s.lineNumbers = sect.readUInt32LE(28);
|
|
s.relocNumber = sect.readUInt16LE(32);
|
|
s.lineNumbersNumber = sect.readUInt16LE(34);
|
|
s.characteristics = sect.readUInt32LE(36);
|
|
retVal.sections[s.sectionName] = s;
|
|
}
|
|
|
|
if (retVal.sections['.rsrc'] != null)
|
|
{
|
|
retVal.resources = readResourceTable(fd, retVal.sections['.rsrc'].rawAddr, 0); // Read all resources recursively
|
|
}
|
|
|
|
switch (optHeader.readUInt16LE(0).toString(16).toUpperCase())
|
|
{
|
|
case '10B': // 32 bit binary
|
|
numRVA = optHeader.readUInt32LE(92);
|
|
rvaStart = 96;
|
|
retVal.CertificateTableAddress = optHeader.readUInt32LE(128);
|
|
retVal.CertificateTableSize = optHeader.readUInt32LE(132);
|
|
retVal.CertificateTableSizePos = dosHeader.readUInt32LE(60) + 24 + 132;
|
|
retVal.rvaStartAddress = dosHeader.readUInt32LE(60) + 24 + 96;
|
|
break;
|
|
case '20B': // 64 bit binary
|
|
numRVA = optHeader.readUInt32LE(108);
|
|
rvaStart = 112;
|
|
retVal.CertificateTableAddress = optHeader.readUInt32LE(144);
|
|
retVal.CertificateTableSize = optHeader.readUInt32LE(148);
|
|
retVal.CertificateTableSizePos = dosHeader.readUInt32LE(60) + 24 + 148;
|
|
retVal.rvaStartAddress = dosHeader.readUInt32LE(60) + 24 + 112;
|
|
break;
|
|
default:
|
|
throw ('Unknown Value found for Optional Magic: ' + ntHeader.readUInt16LE(24).toString(16).toUpperCase());
|
|
break;
|
|
}
|
|
retVal.rvaCount = numRVA;
|
|
|
|
retVal.rva = [];
|
|
for (z = 0; z < retVal.rvaCount && z < 32; ++z)
|
|
{
|
|
retVal.rva.push({ virtualAddress: optHeader.readUInt32LE(rvaStart + (z * 8)), size: optHeader.readUInt32LE(rvaStart + 4 + (z * 8)) });
|
|
}
|
|
|
|
if (retVal.CertificateTableAddress)
|
|
{
|
|
// Read the authenticode certificate, only one cert (only the first entry)
|
|
var hdr = Buffer.alloc(8);
|
|
fs.readSync(fd, hdr, 0, hdr.length, retVal.CertificateTableAddress);
|
|
retVal.certificate = Buffer.alloc(hdr.readUInt32LE(0));
|
|
fs.readSync(fd, retVal.certificate, 0, retVal.certificate.length, retVal.CertificateTableAddress + hdr.length);
|
|
retVal.certificate = retVal.certificate.toString('base64');
|
|
retVal.certificateDwLength = hdr.readUInt32LE(0);
|
|
}
|
|
retVal.versionInfo = getVersionInfo(fd, retVal);
|
|
|
|
fs.closeSync(fd);
|
|
return (retVal);
|
|
}
|
|
|
|
// Read a unicode stting that starts with the string length as the first byte.
|
|
function readLenPrefixUnicodeString(fd, ptr)
|
|
{
|
|
var name = '';
|
|
var tmp = Buffer.alloc(1);
|
|
require('fs').readSync(fd, tmp, 0, 1, 0);
|
|
var nameLen = tmp[0];
|
|
|
|
var buf = Buffer.alloc(nameLen * 2);
|
|
require('fs').readSync(fd, buf, 0, buf.length, 1);
|
|
return (require('_GenericMarshal').CreateVariable(buf).Wide2UTF8);
|
|
}
|
|
// Read a resource item
|
|
// ptr: The pointer to the start of the resource section
|
|
// offset: The offset start of the resource item to read
|
|
function readResourceItem(fd, ptr, offset)
|
|
{
|
|
var buf = Buffer.alloc(16);
|
|
require('fs').readSync(fd, buf, 0, buf.length, ptr + offset);
|
|
var r = {};
|
|
r.offsetToData = buf.readUInt32LE(0);
|
|
r.size = buf.readUInt32LE(4);
|
|
r.codePage = buf.readUInt32LE(8);
|
|
r.reserved = buf.readUInt32LE(12);
|
|
return r;
|
|
}
|
|
function readResourceTable(fd, ptr, offset)
|
|
{
|
|
var buf = Buffer.alloc(16);
|
|
fs.readSync(fd, buf, 0, buf.length, ptr + offset);
|
|
var r = {};
|
|
r.characteristics = buf.readUInt32LE(0);
|
|
r.timeDateStamp = buf.readUInt32LE(4);
|
|
r.majorVersion = buf.readUInt16LE(8);
|
|
r.minorVersion = buf.readUInt16LE(10);
|
|
var numberOfNamedEntries = buf.readUInt16LE(12);
|
|
var numberofIdEntries = buf.readUInt16LE(14);
|
|
r.entries = [];
|
|
var totalResources = numberOfNamedEntries + numberofIdEntries;
|
|
for (var i = 0; i < totalResources; i++)
|
|
{
|
|
buf = Buffer.alloc(8);
|
|
fs.readSync(fd, buf, 0, buf.length, ptr + offset + 16 + (i * 8));
|
|
var resource = {};
|
|
resource.name = buf.readUInt32LE(0);
|
|
var offsetToData = buf.readUInt32LE(4);
|
|
if ((resource.name & 0x80000000) != 0) { resource.name = readLenPrefixUnicodeString(fd, ptr + (resource.name - 0x80000000)); }
|
|
if ((offsetToData & 0x80000000) != 0) { resource.table = readResourceTable(fd, ptr, offsetToData - 0x80000000); } else { resource.item = readResourceItem(fd, ptr, offsetToData); }
|
|
r.entries.push(resource);
|
|
}
|
|
return r;
|
|
}
|
|
// Return the version info data block
|
|
function getVersionInfoData(fd, header)
|
|
{
|
|
var ptr = header.sections['.rsrc'].rawAddr;
|
|
for (var i = 0; i < header.resources.entries.length; i++)
|
|
{
|
|
if (header.resources.entries[i].name == 16)
|
|
{
|
|
const verInfo = header.resources.entries[i].table.entries[0].table.entries[0].item;
|
|
const actualPtr = (verInfo.offsetToData - header.sections['.rsrc'].virtualAddr) + ptr;
|
|
var buffer = Buffer.alloc(verInfo.size);
|
|
require('fs').readSync(fd, buffer, 0, buffer.length, actualPtr);
|
|
header.resourcebuffer = buffer;
|
|
return (buffer);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// VS_FIXEDFILEINFO structure: https://docs.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
|
|
function readFixedFileInfoStruct(buf, ptr)
|
|
{
|
|
if (buf.length - ptr < 50) return null;
|
|
var r = {};
|
|
r.dwSignature = buf.readUInt32LE(ptr);
|
|
if (r.dwSignature != 0xFEEF04BD) return null;
|
|
r.dwStrucVersion = buf.readUInt32LE(ptr + 4);
|
|
r.dwFileVersionMS = buf.readUInt32LE(ptr + 8);
|
|
r.dwFileVersionLS = buf.readUInt32LE(ptr + 12);
|
|
r.dwProductVersionMS = buf.readUInt32LE(ptr + 16);
|
|
r.dwProductVersionLS = buf.readUInt32LE(ptr + 20);
|
|
r.dwFileFlagsMask = buf.readUInt32LE(ptr + 24);
|
|
r.dwFileFlags = buf.readUInt32LE(ptr + 28);
|
|
r.dwFileOS = buf.readUInt32LE(ptr + 32);
|
|
r.dwFileType = buf.readUInt32LE(ptr + 36);
|
|
r.dwFileSubtype = buf.readUInt32LE(ptr + 40);
|
|
r.dwFileDateMS = buf.readUInt32LE(ptr + 44);
|
|
r.dwFileDateLS = buf.readUInt32LE(ptr + 48);
|
|
return r;
|
|
}
|
|
|
|
// Trim a string at the first null character
|
|
function stringUntilNull(str)
|
|
{
|
|
if (str == null) return null;
|
|
const i = str.indexOf('\0');
|
|
if (i >= 0) return str.substring(0, i);
|
|
return str;
|
|
}
|
|
|
|
// StringFileInfo structure: https://docs.microsoft.com/en-us/windows/win32/menurc/stringfileinfo
|
|
function readStringFilesStruct(buf, ptr, len)
|
|
{
|
|
var t = [], startPtr = ptr;
|
|
while (ptr < (startPtr + len))
|
|
{
|
|
const r = {};
|
|
r.wLength = buf.readUInt16LE(ptr);
|
|
if (r.wLength == 0) return t;
|
|
r.wValueLength = buf.readUInt16LE(ptr + 2);
|
|
r.wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary
|
|
r.szKey = stringUntilNull(require('_GenericMarshal').CreateVariable(buf.slice(ptr + 6, ptr + 6 + (r.wLength - 6))).Wide2UTF8); // String value
|
|
//console.log('readStringFileStruct', r.wLength, r.wValueLength, r.wType, r.szKey.toString());
|
|
if (r.szKey == 'StringFileInfo') { r.stringTable = readStringTableStruct(buf, ptr + 36 + r.wValueLength); }
|
|
if (r.szKey == 'VarFileInfo$') { r.varFileInfo = {}; } // TODO
|
|
t.push(r);
|
|
ptr += r.wLength;
|
|
ptr = padPointer(ptr);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
// StringTable structure: https://docs.microsoft.com/en-us/windows/win32/menurc/stringtable
|
|
function readStringTableStruct(buf, ptr)
|
|
{
|
|
const r = {};
|
|
r.wLength = buf.readUInt16LE(ptr);
|
|
r.wValueLength = buf.readUInt16LE(ptr + 2);
|
|
r.wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary
|
|
r.szKey = require('_GenericMarshal').CreateVariable(buf.slice(ptr + 6, ptr + 6 + 16)).Wide2UTF8; // An 8-digit hexadecimal number stored as a Unicode string.
|
|
//console.log('readStringTableStruct', r.wLength, r.wValueLength, r.wType, r.szKey);
|
|
r.strings = readStringStructs(buf, ptr + 24 + r.wValueLength, r.wLength - 22);
|
|
console.info1('readStringTableStruct', JSON.stringify(r, null, 1));
|
|
return r;
|
|
}
|
|
|
|
// String structure: https://docs.microsoft.com/en-us/windows/win32/menurc/string-str
|
|
function readStringStructs(buf, ptr, len)
|
|
{
|
|
var t = [], startPtr = ptr;
|
|
while (ptr < (startPtr + len))
|
|
{
|
|
const r = {};
|
|
r.wLength = buf.readUInt16LE(ptr);
|
|
if (r.wLength == 0) return t;
|
|
r.wValueLength = buf.readUInt16LE(ptr + 2);
|
|
r.wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary
|
|
|
|
|
|
//console.log('tmp', tmp.toString('hex'));
|
|
r.key = require('_GenericMarshal').CreateVariable(buf.slice(ptr + 6, ptr + 6 + (r.wLength - 6))).Wide2UTF8; // String value
|
|
//console.log('keyLen: ' + r.key.length, 'wValueLength: ' + r.wValueLength);
|
|
r.value = require('_GenericMarshal').CreateVariable(buf.slice(ptr + r.wLength - (r.wValueLength*2), ptr + r.wLength)).Wide2UTF8;
|
|
t.push(r);
|
|
ptr += r.wLength;
|
|
ptr = padPointer(ptr);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
// Return the next 4 byte aligned number
|
|
function padPointer(ptr) { return ptr + (ptr % 4); }
|
|
|
|
// VS_VERSIONINFO structure: https://docs.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
|
|
function readVersionInfo(buf, ptr)
|
|
{
|
|
const r = {};
|
|
if (buf.length < 2) return null;
|
|
r.wLength = buf.readUInt16LE(ptr);
|
|
if (buf.length < r.wLength) return null;
|
|
r.wValueLength = buf.readUInt16LE(ptr + 2);
|
|
r.wType = buf.readUInt16LE(ptr + 4);
|
|
|
|
r.szKey = require('_GenericMarshal').CreateVariable(buf.slice(ptr + 6, ptr + 36)).Wide2UTF8;
|
|
if (r.szKey != 'VS_VERSION_INFO') return null;
|
|
////console.log('getVersionInfo', r.wLength, r.wValueLength, r.wType, r.szKey.toString());
|
|
if (r.wValueLength == 52)
|
|
{
|
|
r.fixedFileInfoBuffer = buf.slice(ptr + 40, ptr + 40 + 52);
|
|
r.fixedFileInfo = readFixedFileInfoStruct(buf, ptr + 40);
|
|
}
|
|
r.stringFiles = readStringFilesStruct(buf, ptr + 40 + r.wValueLength, r.wLength - 40 - r.wValueLength);
|
|
return r;
|
|
}
|
|
|
|
function getVersionInfo(fd, header, resources)
|
|
{
|
|
var r = {};
|
|
var b = getVersionInfoData(fd, header, resources);
|
|
var info = readVersionInfo(b, 0);
|
|
if ((info == null) || (info.stringFiles == null)) return null;
|
|
var StringFileInfo = null;
|
|
for (var i in info.stringFiles) { if (info.stringFiles[i].szKey == 'StringFileInfo') { StringFileInfo = info.stringFiles[i]; } }
|
|
|
|
if ((StringFileInfo == null) || (StringFileInfo.stringTable == null) || (StringFileInfo.stringTable.strings == null)) return null;
|
|
const strings = StringFileInfo.stringTable.strings;
|
|
|
|
for (var i in strings) { r[strings[i].key] = strings[i].value; }
|
|
return r;
|
|
}
|
|
|
|
function encodeVersionInfo(info)
|
|
{
|
|
console.log(JSON.stringify(info, null, 1));
|
|
|
|
var i;
|
|
var tableLen = 0;
|
|
for (i = 0; i < info.stringFiles[0].stringTable.strings.length; ++i)
|
|
{
|
|
// Update wValueLength fields
|
|
info.stringFiles[0].stringTable.strings[i].wValueLength = info.stringFiles[0].stringTable.strings[i].value.length + 1;
|
|
|
|
// Calculate Padding:
|
|
var p = 6 + (2 * (info.stringFiles[0].stringTable.strings[i].key.length + 1));
|
|
p = (4 - (p % 4)) % 4;
|
|
|
|
// Update wLength fields
|
|
info.stringFiles[0].stringTable.strings[i].wLength = p + (2*info.stringFiles[0].stringTable.strings[i].wValueLength) + (2*(info.stringFiles[0].stringTable.strings[i].key.length + 1)) + 6;
|
|
tableLen += info.stringFiles[0].stringTable.strings[i].wLength;
|
|
console.log(info.stringFiles[0].stringTable.strings[i]);
|
|
}
|
|
|
|
// Update stringTable wLength
|
|
info.stringFiles[0].stringTable.wLength = 6 + (2 * (info.stringFiles[0].stringTable.szKey.length)) + 1;
|
|
console.log('X=>' + info.stringFiles[0].stringTable.wLength);
|
|
info.stringFiles[0].stringTable.wLength += ((4 - (info.stringFiles[0].stringTable.wLength % 4)) % 4);
|
|
info.stringFiles[0].stringTable.wLength += tableLen;
|
|
|
|
// Update Wlength
|
|
info.stringFiles[0].wLength = 6 + (2 * info.stringFiles[0].szKey.length + 1);
|
|
info.stringFiles[0].wLength += ((4 - (info.stringFiles[0].wLength % 4)) % 4);
|
|
info.stringFiles[0].wLength += info.stringFiles[0].stringTable.wLength;
|
|
|
|
console.log(JSON.stringify(info.stringFiles, null, 1));
|
|
|
|
|
|
// Calculate Table Lengths:
|
|
var tableLengths = 0;
|
|
for(i=0;i<info.stringFiles.length;++i)
|
|
{
|
|
tableLengths += (info.stringFiles[i].wLength + ((4 - (info.stringFiles[i].wLength % 4)) % 4));
|
|
}
|
|
console.log('tableLengths: ' + tableLengths);
|
|
console.log(info.szKey);
|
|
|
|
var bufLen = 0;
|
|
var loc1, loc2;
|
|
bufLen = (info.szKey.length + 1) * 2;
|
|
bufLen += 6;
|
|
bufLen += ((4 - (bufLen % 4)) % 4);
|
|
loc1 = bufLen;
|
|
bufLen += info.wValueLength;
|
|
bufLen += ((4 - (bufLen % 4)) % 4);
|
|
loc2 = bufLen;
|
|
bufLen += tableLengths;
|
|
|
|
console.log('bufLen: ' + bufLen);
|
|
console.log('location ', loc1, loc2);
|
|
|
|
var out = Buffer.alloc(bufLen);
|
|
out.writeUInt16LE(bufLen);
|
|
out.writeUInt16LE(info.wValueLength, 2);
|
|
out.writeUInt16LE(info.wType, 4);
|
|
require('_GenericMarshal').CreateVariable(info.szKey, { wide: true }).toBuffer().copy(out, 6);
|
|
info.fixedFileInfoBuffer.copy(out, loc1);
|
|
for (i = 0; i < info.stringFiles.length; ++i)
|
|
{
|
|
out.writeUInt16LE(info.stringFiles[i].wLength, loc2); loc2 += 4; // Yes, incremented by 4, becuase the next DWORD is 0, and is already 0
|
|
out.writeUInt16LE(info.stringFiles[i].wType, loc2); loc2 += 2;
|
|
loc2 += require('_GenericMarshal').CreateVariable(info.stringFiles[i].szKey, { wide: true }).toBuffer().copy(out, loc2);
|
|
loc2 += ((4 - (loc2 % 4)) % 4);
|
|
console.log('Writing: ' + info.stringFiles[i].szKey);
|
|
if (info.stringFiles[i].stringTable == null) { continue; }
|
|
|
|
// Write String Table
|
|
out.writeUInt16LE(info.stringFiles[i].stringTable.wLength, loc2); loc2 += 2;
|
|
out.writeUInt16LE(info.stringFiles[i].stringTable.wValueLength, loc2); loc2 += 2;
|
|
out.writeUInt16LE(info.stringFiles[i].stringTable.wType, loc2); loc2 += 2;
|
|
loc2 += require('_GenericMarshal').CreateVariable(info.stringFiles[i].stringTable.szKey, { wide: true }).toBuffer().copy(out, loc2);
|
|
loc2 += ((4 - (loc2 % 4)) % 4);
|
|
|
|
for(var j=0;j<info.stringFiles[i].stringTable.strings.length;++j)
|
|
{
|
|
out.writeUInt16LE(info.stringFiles[i].stringTable.strings[j].wLength, loc2); loc2 += 2;
|
|
out.writeUInt16LE(info.stringFiles[i].stringTable.strings[j].wValueLength, loc2); loc2 += 2;
|
|
out.writeUInt16LE(info.stringFiles[i].stringTable.strings[j].wType, loc2); loc2 += 2;
|
|
loc2 += require('_GenericMarshal').CreateVariable(info.stringFiles[i].stringTable.strings[j].key, { wide: true }).toBuffer().copy(out, loc2);
|
|
loc2 += ((4 - (loc2 % 4)) % 4);
|
|
loc2 += require('_GenericMarshal').CreateVariable(info.stringFiles[i].stringTable.strings[j].value, { wide: true }).toBuffer().copy(out, loc2);
|
|
loc2 += ((4 - (loc2 % 4)) % 4);
|
|
}
|
|
|
|
//console.log(info.stringFiles[i]);
|
|
}
|
|
|
|
var tst = readVersionInfo(out, 0);
|
|
console.log('TEST');
|
|
console.log(JSON.stringify(tst, null, 1));
|
|
}
|
|
|
|
module.exports = parse;
|
|
module.exports.readVersionInfo = readVersionInfo;
|
|
module.exports.encodeVersionInfo = encodeVersionInfo;
|
|
|