diff --git a/modules/PE_Parser.js b/modules/PE_Parser.js index ef460c9..879ed20 100644 --- a/modules/PE_Parser.js +++ b/modules/PE_Parser.js @@ -14,16 +14,18 @@ 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 fs = require('fs'); 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); @@ -52,22 +54,50 @@ function parse(exePath) } retVal.optionalHeaderSize = ntHeader.readUInt16LE(20); - retVal.optionalHeaderSizeAddress = dosHeader.readUInt32LE(60) + 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; @@ -75,6 +105,7 @@ function parse(exePath) 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; @@ -86,6 +117,12 @@ function parse(exePath) } 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) @@ -96,10 +133,207 @@ function parse(exePath) 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); + 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); + 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.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; +} module.exports = parse;