1
0
mirror of https://github.com/Ylianst/MeshAgent synced 2025-12-06 00:13:33 +00:00
Files
MeshAgent/modules/PE_Parser.js
Bryan Roe 39150e1c27 1. Fixed -resetnodeid for Windows Service
2. Updated PE Parser
2022-06-03 01:19:47 -07:00

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;