From cfd8521381222c5b5851b2e4788897119ea74d2a Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 9 Jun 2022 09:58:02 -0700 Subject: [PATCH] Fixed server exception on older NodeJS versions, #4102 --- authenticode.js | 330 ++++++++++++++++++++++++++++++++++++++++-------- webserver.js | 4 +- 2 files changed, 281 insertions(+), 53 deletions(-) diff --git a/authenticode.js b/authenticode.js index fc6cadbf..30482d17 100644 --- a/authenticode.js +++ b/authenticode.js @@ -404,7 +404,7 @@ function createAuthenticodeHandler(path) { r.size = buf.readUInt32LE(4); //console.log('readResourceData', r.offsetToData - obj.header.sections['.rsrc'].virtualAddr, r.size, r.offsetToData + r.size - obj.header.sections['.rsrc'].virtualAddr); r.codePage = buf.readUInt32LE(8); - r.reserved = buf.readUInt32LE(12); + //r.reserved = buf.readUInt32LE(12); return r; } @@ -449,9 +449,13 @@ function createAuthenticodeHandler(path) { if (resources.entries[i].table) { getResourceSectionSize(resources.entries[i].table, sizes); } else if (resources.entries[i].item) { sizes.items += 16; - var dataSize = resources.entries[i].item.size; - if ((dataSize % 8) != 0) { dataSize += (8 - (dataSize % 8)); } - sizes.data += dataSize; + if (resources.entries[i].item.buffer) { + sizes.data += resources.entries[i].item.buffer.length; + } else { + var dataSize = resources.entries[i].item.size; + if ((dataSize % 8) != 0) { dataSize += (8 - (dataSize % 8)); } + sizes.data += dataSize; + } } } } @@ -508,20 +512,29 @@ function createAuthenticodeHandler(path) { // This is a pointer to a data entry data = resPointers.items; + // Write the data + var entrySize = 0; + if (resources.entries[i].item.buffer) { + // Write the data from given buffer + resources.entries[i].item.buffer.copy(buf, resPointers.data, 0, resources.entries[i].item.buffer.length); + entrySize = resources.entries[i].item.buffer.length; + } else { + // Write the data from original file + const actualPtr = (resources.entries[i].item.offsetToData - obj.header.sections['.rsrc'].virtualAddr) + obj.header.sections['.rsrc'].rawAddr; + const tmp = readFileSlice(actualPtr, resources.entries[i].item.size); + tmp.copy(buf, resPointers.data, 0, tmp.length); + entrySize = resources.entries[i].item.size;; + } + // Write the item entry buf.writeUInt32LE(resPointers.data + obj.header.sections['.rsrc'].virtualAddr, resPointers.items); // Write the pointer relative to the virtual address - buf.writeUInt32LE(resources.entries[i].item.size, resPointers.items + 4); + buf.writeUInt32LE(entrySize, resPointers.items + 4); buf.writeUInt32LE(resources.entries[i].item.codePage, resPointers.items + 8); buf.writeUInt32LE(resources.entries[i].item.reserved, resPointers.items + 12); - // Write the data - const actualPtr = (resources.entries[i].item.offsetToData - obj.header.sections['.rsrc'].virtualAddr) + obj.header.sections['.rsrc'].rawAddr; - const tmp = readFileSlice(actualPtr, resources.entries[i].item.size); - tmp.copy(buf, resPointers.data, 0, tmp.length); - // Move items pointers forward resPointers.items += 16; - var dataSize = resources.entries[i].item.size; + var dataSize = entrySize; if ((dataSize % 8) != 0) { dataSize += (8 - (dataSize % 8)); } resPointers.data += dataSize; } @@ -531,17 +544,19 @@ function createAuthenticodeHandler(path) { // Convert a unicode buffer to a string function unicodeToString(buf) { - var r = ''; - for (var i = 0; i < (buf.length / 2) ; i++) { r += String.fromCharCode(buf.readUInt16LE(i * 2)); } + var r = '', c; + for (var i = 0; i < (buf.length / 2) ; i++) { + c = buf.readUInt16LE(i * 2); + if (c != 0) { r += String.fromCharCode(c); } else { return r; } + } return r; } - // Trim a string at teh 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; + // Convert a string to a unicode buffer + // Input is a string, a buffer to write to and the offset in the buffer (0 is default). + function stringToUnicode(str, buf, offset) { + if (offset == null) { offset = 0; } + for (var i = 0; i < str.length; i++) { buf.writeInt16LE(str.charCodeAt(i), offset + (i * 2)); } } var resourceDefaultNames = { @@ -612,6 +627,7 @@ function createAuthenticodeHandler(path) { // Decode the version information from the resource obj.getVersionInfo = function () { + console.log('READ', getVersionInfoData().toString('hex')); var r = {}, info = readVersionInfo(getVersionInfoData(), 0); if ((info == null) || (info.stringFiles == null)) return null; var StringFileInfo = null; @@ -622,6 +638,42 @@ function createAuthenticodeHandler(path) { return r; } + // Encode the version information to the resource + obj.setVersionInfo = function (versions) { + // Convert the version information into a string array + const stringArray = []; + for (var i in versions) { stringArray.push({ key: i, value: versions[i] }); } + + // Get the existing version data and switch the strings to the new strings + var r = {}, info = readVersionInfo(getVersionInfoData(), 0); + if ((info == null) || (info.stringFiles == null)) return; + 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; + StringFileInfo.stringTable.strings = stringArray; + + // Re-encode the version information into a buffer + var verInfoResBufArray = []; + writeVersionInfo(verInfoResBufArray, info); + var verInfoRes = Buffer.concat(verInfoResBufArray); + + // Display all buffers + //console.log('--WRITE BUF ARRAY START--'); + //for (var i in verInfoResBufArray) { console.log(verInfoResBufArray[i].toString('hex')); } + //console.log('--WRITE BUF ARRAY END--'); + + // Set the new buffer as part of the resources + for (var i = 0; i < obj.resources.entries.length; i++) { + if (obj.resources.entries[i].name == resourceDefaultNames.versionInfo) { + const verInfo = obj.resources.entries[i].table.entries[0].table.entries[0].item; + delete verInfo.size; + delete verInfo.offsetToData; + verInfo.buffer = verInfoRes; + obj.resources.entries[i].table.entries[0].table.entries[0].item = verInfo; + } + } + } + // Return the version info data block function getVersionInfoData() { if (obj.resources == null) return null; @@ -629,26 +681,175 @@ function createAuthenticodeHandler(path) { for (var i = 0; i < obj.resources.entries.length; i++) { if (obj.resources.entries[i].name == resourceDefaultNames.versionInfo) { const verInfo = obj.resources.entries[i].table.entries[0].table.entries[0].item; - const actualPtr = (verInfo.offsetToData - obj.header.sections['.rsrc'].virtualAddr) + ptr; - return readFileSlice(actualPtr, verInfo.size); + if (verInfo.buffer != null) { + return verInfo.buffer; + } else { + const actualPtr = (verInfo.offsetToData - obj.header.sections['.rsrc'].virtualAddr) + ptr; + return readFileSlice(actualPtr, verInfo.size); + } } } return null; } + // Create a VS_VERSIONINFO structure as a array of buffer that is ready to be placed in the resource section + // VS_VERSIONINFO structure: https://docs.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo + function writeVersionInfo(bufArray, info) { + const buf = Buffer.alloc(40); + buf.writeUInt16LE(0, 4); // wType + stringToUnicode('VS_VERSION_INFO', buf, 6); + bufArray.push(buf); + + var wLength = 40; + var wValueLength = 0; + if (info.fixedFileInfo != null) { + const buf2 = Buffer.alloc(52); + wLength += 52; + wValueLength += 52; + buf2.writeUInt32LE(info.fixedFileInfo.dwSignature, 0); // dwSignature + buf2.writeUInt32LE(info.fixedFileInfo.dwStrucVersion, 4); // dwStrucVersion + buf2.writeUInt32LE(info.fixedFileInfo.dwFileVersionMS, 8); // dwFileVersionMS + buf2.writeUInt32LE(info.fixedFileInfo.dwFileVersionLS, 12); // dwFileVersionLS + buf2.writeUInt32LE(info.fixedFileInfo.dwProductVersionMS, 16); // dwProductVersionMS + buf2.writeUInt32LE(info.fixedFileInfo.dwProductVersionLS, 20); // dwProductVersionLS + buf2.writeUInt32LE(info.fixedFileInfo.dwFileFlagsMask, 24); // dwFileFlagsMask + buf2.writeUInt32LE(info.fixedFileInfo.dwFileFlags, 28); // dwFileFlags + buf2.writeUInt32LE(info.fixedFileInfo.dwFileOS, 32); // dwFileOS + buf2.writeUInt32LE(info.fixedFileInfo.dwFileType, 36); // dwFileType + buf2.writeUInt32LE(info.fixedFileInfo.dwFileSubtype, 40); // dwFileSubtype + buf2.writeUInt32LE(info.fixedFileInfo.dwFileDateMS, 44); // dwFileDateMS + buf2.writeUInt32LE(info.fixedFileInfo.dwFileDateLS, 48); // dwFileDateLS + bufArray.push(buf2); + } + + if (info.stringFiles != null) { wLength += writeStringFileInfo(bufArray, info.stringFiles); } + + console.log('@@@@@@Z', wLength, Buffer.concat(bufArray).length); + + buf.writeUInt16LE(Buffer.concat(bufArray).length, 0); // wLength + buf.writeUInt16LE(wValueLength, 2); // wValueLength + return wLength; + } + + // StringFileInfo structure: https://docs.microsoft.com/en-us/windows/win32/menurc/stringfileinfo + function writeStringFileInfo(bufArray, stringFiles) { + //console.log('writeStringFileInfo', stringFiles); + var totalLen = 0; + for (var i in stringFiles) { + var l = 6 + (stringFiles[i].szKey.length * 2); + const buf2 = Buffer.alloc(padPointer(l)); + buf2.writeUInt16LE(1, 4); // wType + stringToUnicode(stringFiles[i].szKey, buf2, 6); + bufArray.push(buf2); + + var wLength = 0, wValueLength = 0; + + if (stringFiles[i].szKey == 'StringFileInfo') { wLength += writeStringTableStruct(bufArray, stringFiles[i].stringTable); } + if (stringFiles[i].szKey == 'VarFileInfo') { wLength += writeVarFileInfoStruct(bufArray, stringFiles[i].varFileInfo); } + + buf2.writeUInt16LE(l + wLength, 0); // wLength + buf2.writeUInt16LE(wValueLength, 2); // wValueLength + totalLen += buf2.length + wLength; + } + return totalLen; + } + + // VarFileInfo structure: https://docs.microsoft.com/en-us/windows/win32/menurc/var-str + function writeVarFileInfoStruct(bufArray, varFileInfo) { + console.log('*************writeVarFileInfoStruct', varFileInfo); + var l = 6 + (varFileInfo.szKey.length * 2); + const buf = Buffer.alloc(padPointer(l)); + buf.writeUInt16LE(0, 4); // wType + stringToUnicode(varFileInfo.szKey, buf, 6); + bufArray.push(buf); + + var wLength = 0; + var wValueLength = 0; + + if (varFileInfo.value) { + bufArray.push(varFileInfo.value); + wLength += varFileInfo.value.length; + } + buf.writeUInt16LE(l + wLength, 0); // wLength + buf.writeUInt16LE(wValueLength, 2); // wValueLength + + //console.log('WwriteVarFileInfoStruct', buf.toString('hex')); + return buf.length + wLength; + } + + // StringTable structure: https://docs.microsoft.com/en-us/windows/win32/menurc/stringtable + function writeStringTableStruct(bufArray, stringTable) { + //console.log('writeStringTableStruct', stringTable); + var l = 6 + (stringTable.szKey.length * 2); + const buf = Buffer.alloc(padPointer(l)); + buf.writeUInt16LE(1, 4); // wType + stringToUnicode(stringTable.szKey, buf, 6); + bufArray.push(buf); + + var wLength = 0; + var wValueLength = 0; + + if (stringTable.strings) { wLength += writeStringStructs(bufArray, stringTable.strings); } + buf.writeUInt16LE(l + wLength, 0); // wLength + buf.writeUInt16LE(wValueLength, 2); // wValueLength + + //console.log('WStringTableStruct', buf.toString('hex')); + return buf.length + wLength; + } + + // String structure: https://docs.microsoft.com/en-us/windows/win32/menurc/string-str + function writeStringStructs(bufArray, stringTable) { + //console.log('writeStringStructs', stringTable); + var totalLen = 0, bufadd = 0; + for (var i in stringTable) { + //console.log('writeStringStructs', stringTable[i]); + const buf = Buffer.alloc(padPointer(6 + ((stringTable[i].key.length + 1) * 2))); + var buf2, wLength = buf.length; + var wValueLength = 0; + stringToUnicode(stringTable[i].key, buf, 6); + bufArray.push(buf); + bufadd += buf.length; + if (typeof stringTable[i].value == 'string') { + // wType (string) + buf.writeUInt16LE(1, 4); + var l = (stringTable[i].value.length + 1) * 2; + buf2 = Buffer.alloc(padPointer(l)); + stringToUnicode(stringTable[i].value, buf2, 0); + bufArray.push(buf2); + bufadd += buf2.length; + wValueLength = stringTable[i].value.length + 1; + wLength += l; + } + if (typeof stringTable[i].value == 'object') { + // wType (binary) + buf.writeUInt16LE(2, 4); // TODO: PADDING + bufArray.push(stringTable[i].value); + bufadd += stringTable[i].value.length; + wValueLength = stringTable[i].value.length; + wLength += wValueLength; + } + buf.writeUInt16LE(wLength, 0); // wLength + buf.writeUInt16LE(wValueLength, 2); // wValueLength + //console.log('WStringStruct', buf.toString('hex'), buf2.toString('hex')); + totalLen += wLength; + } + //return totalLen; + return bufadd; + } + // 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); + const wLength = buf.readUInt16LE(ptr); + if (buf.length < wLength) return null; + const wValueLength = buf.readUInt16LE(ptr + 2); + const wType = buf.readUInt16LE(ptr + 4); r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + 36)); 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); + //console.log('getVersionInfo', wLength, wValueLength, wType, r.szKey.toString()); + if (wValueLength == 52) { r.fixedFileInfo = readFixedFileInfoStruct(buf, ptr + 40); } + r.stringFiles = readStringFilesStruct(buf, ptr + 40 + wValueLength, wLength - 40 - wValueLength); return r; } @@ -678,30 +879,43 @@ function createAuthenticodeHandler(path) { 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(unicodeToString(buf.slice(ptr + 6, ptr + 6 + (r.wLength - 6)))); // 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 + const wLength = buf.readUInt16LE(ptr); + if (wLength == 0) return t; + const wValueLength = buf.readUInt16LE(ptr + 2); + const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary + r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + 6 + (wLength - 6))); // String value + //console.log('readStringFileStruct', wLength, wValueLength, wType, r.szKey); + if (r.szKey == 'StringFileInfo') { r.stringTable = readStringTableStruct(buf, ptr + 36); } + if (r.szKey == 'VarFileInfo') { r.varFileInfo = readVarFileInfoStruct(buf, ptr + 32); } t.push(r); - ptr += r.wLength; + ptr += wLength; ptr = padPointer(ptr); } return t; } + // VarFileInfo structure: https://docs.microsoft.com/en-us/windows/win32/menurc/var-str + function readVarFileInfoStruct(buf, ptr) { + const r = {}; + const wLength = buf.readUInt16LE(ptr); + const wValueLength = buf.readUInt16LE(ptr + 2); + const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary + r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + wLength)); // "VarFileInfo" + r.value = buf.slice(ptr + wLength - wValueLength, ptr + wLength) + //console.log('readVarFileInfoStruct', wLength, wValueLength, wType, r.szKey, r.value.toString('hex')); + return r; + } + // 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 + const wLength = buf.readUInt16LE(ptr); + const wValueLength = buf.readUInt16LE(ptr + 2); + const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary + //console.log('RStringTableStruct', buf.slice(ptr, ptr + wLength).toString('hex')); r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + 6 + 16)); // 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.log('readStringTableStruct', wLength, wValueLength, r.wType, r.szKey); + r.strings = readStringStructs(buf, ptr + 24 + wValueLength, wLength - 22); return r; } @@ -710,22 +924,30 @@ function createAuthenticodeHandler(path) { 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.key = unicodeToString(buf.slice(ptr + 6, ptr + (r.wLength - (r.wValueLength * 2)))); // Key - r.value = unicodeToString(buf.slice(ptr + r.wLength - (r.wValueLength * 2), ptr + r.wLength)); // Value - //console.log('readStringStruct', r.wLength, r.wValueLength, r.wType, r.key, r.value); + const wLength = buf.readUInt16LE(ptr); + if (wLength == 0) return t; + + //console.log('RStringStruct', buf.slice(ptr, ptr + wLength).toString('hex')); + + const wValueLength = buf.readUInt16LE(ptr + 2); + const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary + + //console.log('R', buf.slice(ptr, ptr + wLength).toString('hex')); + + r.key = unicodeToString(buf.slice(ptr + 6, ptr + (wLength - (wValueLength * 2)) - 2)); // Key + if (wType == 1) { r.value = unicodeToString(buf.slice(ptr + wLength - (wValueLength * 2), ptr + wLength - 2)); } // String value + if (wType == 2) { r.value = buf.slice(ptr + wLength - (wValueLength * 2), ptr + wLength); } // Binary value + //console.log('readStringStruct', wLength, wValueLength, wType, r.key, r.value); t.push(r); - ptr += r.wLength; + ptr += wLength; ptr = padPointer(ptr); } return t; } // Return the next 4 byte aligned number - function padPointer(ptr) { return ptr + (ptr % 4); } + function padPointer(ptr) { return ptr + (((ptr % 4) == 0) ? 0 : (4 - (ptr % 4))); } + //function padPointer(ptr) { return ptr + (ptr % 4); } // Hash the file using the selected hashing system obj.getHash = function(algo) { @@ -942,6 +1164,12 @@ function createAuthenticodeHandler(path) { // Save the executable obj.writeExecutable = function (args) { + // Get version information from the resource + var versions = obj.getVersionInfo(); + //versions['FileDescription'] = 'Mesh Agent Service'; + obj.setVersionInfo(versions); + //var versions2 = obj.getVersionInfo(); + // Open the file var output = fs.openSync(args.out, 'w'); var tmp, written = 0; diff --git a/webserver.js b/webserver.js index 08fdea4d..b4b6be3e 100644 --- a/webserver.js +++ b/webserver.js @@ -7697,8 +7697,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF xargs.autocomplete = (domain.autocomplete === false)?'x':'autocomplete'; // This option allows autocomplete to be turned off on the login page. if (typeof domain.hide == 'number') { xargs.hide = domain.hide; } - // To mitigate any possible BREACH attack, we generate a random length string here. - xargs.randomlength = (args.webpagelengthrandomization !== false) ? parent.crypto.randomBytes(parent.crypto.randomInt(0, 256)).toString('base64') : ''; + // To mitigate any possible BREACH attack, we generate a random 0 to 255 bytes length string here. + xargs.randomlength = (args.webpagelengthrandomization !== false) ? parent.crypto.randomBytes(parent.crypto.randomBytes(1)[0]).toString('base64') : ''; return xargs; }