securityos/node_modules/pe-library/dist/NtExecutable.js

417 lines
18 KiB
JavaScript
Raw Permalink Normal View History

2024-09-06 15:32:35 +00:00
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;