import ImageDataDirectoryArray from './format/ImageDataDirectoryArray.js'; import ImageDirectoryEntry from './format/ImageDirectoryEntry.js'; import ImageDosHeader from './format/ImageDosHeader.js'; import ImageNtHeaders from './format/ImageNtHeaders.js'; import ImageSectionHeaderArray from './format/ImageSectionHeaderArray.js'; import { allocatePartialBinary, calculateCheckSumForPE, cloneObject, cloneToArrayBuffer, roundUp, } from './util/functions.js'; import { makeEmptyNtExecutableBinary } from './util/generate.js'; var NtExecutable = /** @class */ (function () { function NtExecutable(_headers, _sections, _ex) { this._headers = _headers; this._sections = _sections; this._ex = _ex; var dh = ImageDosHeader.from(_headers); var nh = ImageNtHeaders.from(_headers, dh.newHeaderAddress); this._dh = dh; this._nh = nh; this._dda = nh.optionalHeaderDataDirectory; _sections.sort(function (a, b) { var ra = a.info.pointerToRawData; var rb = a.info.pointerToRawData; if (ra !== rb) { return ra - rb; } var va = a.info.virtualAddress; var vb = b.info.virtualAddress; if (va === vb) { return a.info.virtualSize - b.info.virtualSize; } return va - vb; }); } /** * Creates an NtExecutable instance with an 'empty' executable binary. * @param is32Bit set true if the binary is for 32-bit (default: false) * @param isDLL set true if the binary is DLL (default: true) * @return NtExecutable instance */ NtExecutable.createEmpty = function (is32Bit, isDLL) { if (is32Bit === void 0) { is32Bit = false; } if (isDLL === void 0) { isDLL = true; } return this.from(makeEmptyNtExecutableBinary(is32Bit, isDLL)); }; /** * Parse the binary and create NtExecutable instance. * An error will be thrown if the binary data is invalid * @param bin binary data * @param options additional option for parsing * @return NtExecutable instance */ NtExecutable.from = function (bin, options) { var dh = ImageDosHeader.from(bin); var nh = ImageNtHeaders.from(bin, dh.newHeaderAddress); if (!dh.isValid() || !nh.isValid()) { throw new TypeError('Invalid binary format'); } if (nh.fileHeader.numberOfSymbols > 0) { throw new Error('Binary with symbols is not supported now'); } var fileAlignment = nh.optionalHeader.fileAlignment; var securityEntry = nh.optionalHeaderDataDirectory.get(ImageDirectoryEntry.Certificate); if (securityEntry.size > 0) { // Signed executables should be parsed only when `ignoreCert` is true if (!(options === null || options === void 0 ? void 0 : options.ignoreCert)) { throw new Error('Parsing signed executable binary is not allowed by default.'); } } var secOff = dh.newHeaderAddress + nh.getSectionHeaderOffset(); var secCount = nh.fileHeader.numberOfSections; var sections = []; var tempSectionHeaderBinary = allocatePartialBinary(bin, secOff, secCount * ImageSectionHeaderArray.itemSize); var secArray = ImageSectionHeaderArray.from(tempSectionHeaderBinary, secCount, 0); var lastOffset = roundUp(secOff + secCount * ImageSectionHeaderArray.itemSize, fileAlignment); // console.log(`from data size 0x${bin.byteLength.toString(16)}:`); secArray.forEach(function (info) { if (!info.pointerToRawData || !info.sizeOfRawData) { info.pointerToRawData = 0; info.sizeOfRawData = 0; sections.push({ info: info, data: null, }); } else { // console.log(` section ${info.name}: 0x${info.pointerToRawData.toString(16)}, size = 0x${info.sizeOfRawData.toString(16)}`); var secBin = allocatePartialBinary(bin, info.pointerToRawData, info.sizeOfRawData); sections.push({ info: info, data: secBin, }); var secEndOffset = roundUp(info.pointerToRawData + info.sizeOfRawData, fileAlignment); if (secEndOffset > lastOffset) { lastOffset = secEndOffset; } } }); // the size of DOS and NT headers is equal to section offset var headers = allocatePartialBinary(bin, 0, secOff); // extra data var exData = null; var lastExDataOffset = bin.byteLength; // It may contain that both extra data and certificate data are available. // In this case the extra data is followed by the certificate data. if (securityEntry.size > 0) { lastExDataOffset = securityEntry.virtualAddress; } if (lastOffset < lastExDataOffset) { exData = allocatePartialBinary(bin, lastOffset, lastExDataOffset - lastOffset); } return new NtExecutable(headers, sections, exData); }; /** * Returns whether the executable is for 32-bit architecture */ NtExecutable.prototype.is32bit = function () { return this._nh.is32bit(); }; NtExecutable.prototype.getTotalHeaderSize = function () { return this._headers.byteLength; }; Object.defineProperty(NtExecutable.prototype, "dosHeader", { get: function () { return this._dh; }, enumerable: false, configurable: true }); Object.defineProperty(NtExecutable.prototype, "newHeader", { get: function () { return this._nh; }, enumerable: false, configurable: true }); NtExecutable.prototype.getRawHeader = function () { return this._headers; }; NtExecutable.prototype.getImageBase = function () { return this._nh.optionalHeader.imageBase; }; NtExecutable.prototype.getFileAlignment = function () { return this._nh.optionalHeader.fileAlignment; }; NtExecutable.prototype.getSectionAlignment = function () { return this._nh.optionalHeader.sectionAlignment; }; /** * Return all sections. The returned array is sorted by raw address. */ NtExecutable.prototype.getAllSections = function () { return this._sections; }; /** * Return the section data from ImageDirectoryEntry enum value. */ NtExecutable.prototype.getSectionByEntry = function (entry) { var dd = this._dda.get(entry); var r = this._sections .filter(function (sec) { var vaEnd = sec.info.virtualAddress + sec.info.virtualSize; return (dd.virtualAddress >= sec.info.virtualAddress && dd.virtualAddress < vaEnd); }) .shift(); return r !== undefined ? r : null; }; /** * Set the section data from ImageDirectoryEntry enum value. * If entry is found, then replaces the secion data. If not found, then adds the section data. * * NOTE: 'virtualAddress' and 'pointerToRawData' of section object is ignored * and calculated automatically. 'virtualSize' and 'sizeOfRawData' are used, but * if the 'section.data.byteLength' is larger than 'sizeOfRawData', then * these members are replaced. * * @param entry ImageDirectoryEntry enum value for the section * @param section the section data, or null to remove the section */ NtExecutable.prototype.setSectionByEntry = function (entry, section) { var sec = section ? { data: section.data, info: section.info } : null; var dd = this._dda.get(entry); var hasEntry = dd.size > 0; if (!sec) { if (!hasEntry) { // no need to replace } else { // clear entry this._dda.set(entry, { size: 0, virtualAddress: 0 }); var len = this._sections.length; for (var i = 0; i < len; ++i) { var sec_1 = this._sections[i]; var vaStart = sec_1.info.virtualAddress; var vaLast = vaStart + sec_1.info.virtualSize; if (dd.virtualAddress >= vaStart && dd.virtualAddress < vaLast) { this._sections.splice(i, 1); // section count changed this._nh.fileHeader.numberOfSections = this._sections.length; break; } } } } else { var rawSize = !sec.data ? 0 : sec.data.byteLength; var fileAlign = this._nh.optionalHeader.fileAlignment; var secAlign = this._nh.optionalHeader.sectionAlignment; var alignedFileSize = !sec.data ? 0 : roundUp(rawSize, fileAlign); var alignedSecSize = !sec.data ? 0 : roundUp(sec.info.virtualSize, secAlign); if (sec.info.sizeOfRawData < alignedFileSize) { sec.info.sizeOfRawData = alignedFileSize; } else { alignedFileSize = sec.info.sizeOfRawData; } if (!hasEntry) { var virtAddr_1 = 0; var rawAddr_1 = roundUp(this._headers.byteLength, fileAlign); // get largest addresses this._sections.forEach(function (secExist) { if (secExist.info.pointerToRawData) { if (rawAddr_1 <= secExist.info.pointerToRawData) { rawAddr_1 = secExist.info.pointerToRawData + secExist.info.sizeOfRawData; } } if (virtAddr_1 <= secExist.info.virtualAddress) { virtAddr_1 = secExist.info.virtualAddress + secExist.info.virtualSize; } }); if (!alignedFileSize) { rawAddr_1 = 0; } if (!virtAddr_1) { virtAddr_1 = this.newHeader.optionalHeader.baseOfCode; } virtAddr_1 = roundUp(virtAddr_1, secAlign); sec.info.pointerToRawData = rawAddr_1; sec.info.virtualAddress = virtAddr_1; // add entry this._dda.set(entry, { size: rawSize, virtualAddress: virtAddr_1, }); this._sections.push(sec); // section count changed this._nh.fileHeader.numberOfSections = this._sections.length; // change image size this._nh.optionalHeader.sizeOfImage = roundUp(virtAddr_1 + alignedSecSize, this._nh.optionalHeader.sectionAlignment); } else { // replace entry this.replaceSectionImpl(dd.virtualAddress, sec.info, sec.data); } } }; /** * Returns the extra data in the executable, or `null` if nothing. * You can rewrite the returned buffer without using `setExtraData` if * the size of the new data is equal to the old data. */ NtExecutable.prototype.getExtraData = function () { return this._ex; }; /** * Specifies the new extra data in the executable. * The specified buffer will be cloned and you can release it after calling this method. * @param bin buffer containing the new data * @note * The extra data will not be aligned by `NtExecutable`. */ NtExecutable.prototype.setExtraData = function (bin) { if (bin === null) { this._ex = null; } else { this._ex = cloneToArrayBuffer(bin); } }; /** * Generates the executable binary data. */ NtExecutable.prototype.generate = function (paddingSize) { // calculate binary size var dh = this._dh; var nh = this._nh; var secOff = dh.newHeaderAddress + nh.getSectionHeaderOffset(); var size = secOff; size += this._sections.length * ImageSectionHeaderArray.itemSize; var align = nh.optionalHeader.fileAlignment; size = roundUp(size, align); this._sections.forEach(function (sec) { if (!sec.info.pointerToRawData) { return; } var lastOff = sec.info.pointerToRawData + sec.info.sizeOfRawData; if (size < lastOff) { size = lastOff; size = roundUp(size, align); } }); var lastPosition = size; if (this._ex !== null) { size += this._ex.byteLength; } if (typeof paddingSize === 'number') { size += paddingSize; } // make buffer var bin = new ArrayBuffer(size); var u8bin = new Uint8Array(bin); u8bin.set(new Uint8Array(this._headers, 0, secOff)); // reset Security section offset (eliminate it) ImageDataDirectoryArray.from(bin, dh.newHeaderAddress + nh.getDataDirectoryOffset()).set(ImageDirectoryEntry.Certificate, { size: 0, virtualAddress: 0, }); var secArray = ImageSectionHeaderArray.from(bin, this._sections.length, secOff); this._sections.forEach(function (sec, i) { if (!sec.data) { sec.info.pointerToRawData = 0; sec.info.sizeOfRawData = 0; } secArray.set(i, sec.info); if (!sec.data || !sec.info.pointerToRawData) { return; } u8bin.set(new Uint8Array(sec.data), sec.info.pointerToRawData); }); if (this._ex !== null) { u8bin.set(new Uint8Array(this._ex), lastPosition); } // re-calc checksum if (nh.optionalHeader.checkSum !== 0) { calculateCheckSumForPE(bin, true); } return bin; }; NtExecutable.prototype.rearrangeSections = function (rawAddressStart, rawDiff, virtualAddressStart, virtualDiff) { if (!rawDiff && !virtualDiff) { return; } var nh = this._nh; var secAlign = nh.optionalHeader.sectionAlignment; var dirs = this._dda; var len = this._sections.length; var lastVirtAddress = 0; for (var i = 0; i < len; ++i) { var sec = this._sections[i]; var virtAddr = sec.info.virtualAddress; if (virtualDiff && virtAddr >= virtualAddressStart) { var iDir = dirs.findIndexByVirtualAddress(virtAddr); virtAddr += virtualDiff; if (iDir !== null) { dirs.set(iDir, { virtualAddress: virtAddr, size: sec.info.virtualSize, }); } sec.info.virtualAddress = virtAddr; } var fileAddr = sec.info.pointerToRawData; if (rawDiff && fileAddr >= rawAddressStart) { sec.info.pointerToRawData = fileAddr + rawDiff; } lastVirtAddress = roundUp(sec.info.virtualAddress + sec.info.virtualSize, secAlign); } // fix image size from last virtual address nh.optionalHeader.sizeOfImage = lastVirtAddress; }; // NOTE: info.virtualSize must be valid NtExecutable.prototype.replaceSectionImpl = function (virtualAddress, info, data) { var len = this._sections.length; for (var i = 0; i < len; ++i) { var s = this._sections[i]; // console.log(`replaceSectionImpl: ${virtualAddress} <--> ${s.info.virtualAddress}`); if (s.info.virtualAddress === virtualAddress) { // console.log(` found`); var secAlign = this._nh.optionalHeader.sectionAlignment; var fileAddr = s.info.pointerToRawData; var oldFileAddr = fileAddr + s.info.sizeOfRawData; var oldVirtAddr = virtualAddress + roundUp(s.info.virtualSize, secAlign); s.info = cloneObject(info); s.info.virtualAddress = virtualAddress; s.info.pointerToRawData = fileAddr; s.data = data; // shift addresses var newFileAddr = fileAddr + info.sizeOfRawData; var newVirtAddr = virtualAddress + roundUp(info.virtualSize, secAlign); this.rearrangeSections(oldFileAddr, newFileAddr - oldFileAddr, oldVirtAddr, newVirtAddr - oldVirtAddr); // BLOCK: rewrite DataDirectory entry for specified virtualAddress { var dirs = this._dda; var iDir = dirs.findIndexByVirtualAddress(virtualAddress); if (iDir !== null) { dirs.set(iDir, { virtualAddress: virtualAddress, size: info.virtualSize, }); } } break; } } }; return NtExecutable; }()); export default NtExecutable;