123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662 |
- import ImageDirectoryEntry from './format/ImageDirectoryEntry.js';
- import { binaryToString, cloneObject, copyBuffer, roundUp, stringToBinary, } from './util/functions.js';
- function removeDuplicates(a) {
- return a.reduce(function (p, c) {
- return p.indexOf(c) >= 0 ? p : p.concat(c);
- }, []);
- }
- function readString(view, offset) {
- var length = view.getUint16(offset, true);
- var r = '';
- offset += 2;
- for (var i = 0; i < length; ++i) {
- r += String.fromCharCode(view.getUint16(offset, true));
- offset += 2;
- }
- return r;
- }
- function readLanguageTable(view, typeEntry, name, languageTable, cb) {
- var off = languageTable;
- var nameEntry = {
- name: name,
- languageTable: languageTable,
- characteristics: view.getUint32(off, true),
- dateTime: view.getUint32(off + 4, true),
- majorVersion: view.getUint16(off + 8, true),
- minorVersion: view.getUint16(off + 10, true),
- };
- var nameCount = view.getUint16(off + 12, true);
- var idCount = view.getUint16(off + 14, true);
- off += 16;
- for (var i = 0; i < nameCount; ++i) {
- var nameOffset = view.getUint32(off, true) & 0x7fffffff;
- var dataOffset = view.getUint32(off + 4, true);
- // ignore if the offset refers to the next table
- if ((dataOffset & 0x80000000) !== 0) {
- off += 8;
- continue;
- }
- var name_1 = readString(view, nameOffset);
- cb(typeEntry, nameEntry, { lang: name_1, dataOffset: dataOffset });
- off += 8;
- }
- for (var i = 0; i < idCount; ++i) {
- var id = view.getUint32(off, true) & 0x7fffffff;
- var dataOffset = view.getUint32(off + 4, true);
- // ignore if the offset refers to the next table
- if ((dataOffset & 0x80000000) !== 0) {
- off += 8;
- continue;
- }
- cb(typeEntry, nameEntry, { lang: id, dataOffset: dataOffset });
- off += 8;
- }
- }
- function readNameTable(view, type, nameTable, cb) {
- var off = nameTable;
- var typeEntry = {
- type: type,
- nameTable: nameTable,
- characteristics: view.getUint32(off, true),
- dateTime: view.getUint32(off + 4, true),
- majorVersion: view.getUint16(off + 8, true),
- minorVersion: view.getUint16(off + 10, true),
- };
- var nameCount = view.getUint16(off + 12, true);
- var idCount = view.getUint16(off + 14, true);
- off += 16;
- for (var i = 0; i < nameCount; ++i) {
- var nameOffset = view.getUint32(off, true) & 0x7fffffff;
- var nextTable = view.getUint32(off + 4, true);
- // ignore if no next table is available
- if (!(nextTable & 0x80000000)) {
- off += 8;
- continue;
- }
- nextTable &= 0x7fffffff;
- var name_2 = readString(view, nameOffset);
- readLanguageTable(view, typeEntry, name_2, nextTable, cb);
- off += 8;
- }
- for (var i = 0; i < idCount; ++i) {
- var id = view.getUint32(off, true) & 0x7fffffff;
- var nextTable = view.getUint32(off + 4, true);
- // ignore if no next table is available
- if (!(nextTable & 0x80000000)) {
- off += 8;
- continue;
- }
- nextTable &= 0x7fffffff;
- readLanguageTable(view, typeEntry, id, nextTable, cb);
- off += 8;
- }
- }
- function divideEntriesImplByID(r, names, entries) {
- var entriesByString = {};
- var entriesByNumber = {};
- entries.forEach(function (e) {
- if (typeof e.lang === 'string') {
- entriesByString[e.lang] = e;
- names.push(e.lang);
- }
- else {
- entriesByNumber[e.lang] = e;
- }
- });
- var strKeys = Object.keys(entriesByString);
- strKeys.sort().forEach(function (type) {
- r.s.push(entriesByString[type]);
- });
- var numKeys = Object.keys(entriesByNumber);
- numKeys
- .map(function (k) { return Number(k); })
- .sort(function (a, b) { return a - b; })
- .forEach(function (type) {
- r.n.push(entriesByNumber[type]);
- });
- return 16 + 8 * (strKeys.length + numKeys.length);
- }
- function divideEntriesImplByName(r, names, entries) {
- var entriesByString = {};
- var entriesByNumber = {};
- entries.forEach(function (e) {
- var _a, _b;
- if (typeof e.id === 'string') {
- var a = (_a = entriesByString[e.id]) !== null && _a !== void 0 ? _a : (entriesByString[e.id] = []);
- names.push(e.id);
- a.push(e);
- }
- else {
- var a = (_b = entriesByNumber[e.id]) !== null && _b !== void 0 ? _b : (entriesByNumber[e.id] = []);
- a.push(e);
- }
- });
- var sSum = Object.keys(entriesByString)
- .sort()
- .map(function (id) {
- var o = {
- id: id,
- s: [],
- n: [],
- };
- r.s.push(o);
- return divideEntriesImplByID(o, names, entriesByString[id]);
- })
- .reduce(function (p, c) { return p + 8 + c; }, 0);
- var nSum = Object.keys(entriesByNumber)
- .map(function (k) { return Number(k); })
- .sort(function (a, b) { return a - b; })
- .map(function (id) {
- var o = {
- id: id,
- s: [],
- n: [],
- };
- r.n.push(o);
- return divideEntriesImplByID(o, names, entriesByNumber[id]);
- })
- .reduce(function (p, c) { return p + 8 + c; }, 0);
- return 16 + sSum + nSum;
- }
- function divideEntriesImplByType(r, names, entries) {
- var entriesByString = {};
- var entriesByNumber = {};
- entries.forEach(function (e) {
- var _a, _b;
- if (typeof e.type === 'string') {
- var a = (_a = entriesByString[e.type]) !== null && _a !== void 0 ? _a : (entriesByString[e.type] = []);
- names.push(e.type);
- a.push(e);
- }
- else {
- var a = (_b = entriesByNumber[e.type]) !== null && _b !== void 0 ? _b : (entriesByNumber[e.type] = []);
- a.push(e);
- }
- });
- var sSum = Object.keys(entriesByString)
- .sort()
- .map(function (type) {
- var o = { type: type, s: [], n: [] };
- r.s.push(o);
- return divideEntriesImplByName(o, names, entriesByString[type]);
- })
- .reduce(function (p, c) { return p + 8 + c; }, 0);
- var nSum = Object.keys(entriesByNumber)
- .map(function (k) { return Number(k); })
- .sort(function (a, b) { return a - b; })
- .map(function (type) {
- var o = { type: type, s: [], n: [] };
- r.n.push(o);
- return divideEntriesImplByName(o, names, entriesByNumber[type]);
- })
- .reduce(function (p, c) { return p + 8 + c; }, 0);
- return 16 + sSum + nSum;
- }
- function calculateStringLengthForWrite(text) {
- var length = text.length;
- // limit to 65535 because the 'length' field is uint16
- return length > 65535 ? 65535 : length;
- }
- function getStringOffset(target, strings) {
- var l = strings.length;
- for (var i = 0; i < l; ++i) {
- var s = strings[i];
- if (s.text === target) {
- return s.offset;
- }
- }
- throw new Error('Unexpected');
- }
- /** (returns offset just after the written text) */
- function writeString(view, offset, text) {
- var length = calculateStringLengthForWrite(text);
- view.setUint16(offset, length, true);
- offset += 2;
- for (var i = 0; i < length; ++i) {
- view.setUint16(offset, text.charCodeAt(i), true);
- offset += 2;
- }
- return offset;
- }
- function writeLanguageTable(view, offset, strings, data) {
- // characteristics
- view.setUint32(offset, 0, true);
- // timestamp
- view.setUint32(offset + 4, 0, true);
- // major version / minor version
- view.setUint32(offset + 8, 0, true);
- // name entries
- view.setUint16(offset + 12, data.s.length, true);
- // id entries
- view.setUint16(offset + 14, data.n.length, true);
- offset += 16;
- // name entries (not in specification)
- data.s.forEach(function (e) {
- var strOff = getStringOffset(e.lang, strings);
- view.setUint32(offset, strOff, true);
- view.setUint32(offset + 4, e.offset, true);
- offset += 8;
- });
- // id entries
- data.n.forEach(function (e) {
- view.setUint32(offset, e.lang, true);
- view.setUint32(offset + 4, e.offset, true);
- offset += 8;
- });
- return offset;
- }
- function writeNameTable(view, offset, leafOffset, strings, data) {
- // characteristics
- view.setUint32(offset, 0, true);
- // timestamp
- view.setUint32(offset + 4, 0, true);
- // major version / minor version
- view.setUint32(offset + 8, 0, true);
- // name entries
- view.setUint16(offset + 12, data.s.length, true);
- // id entries
- view.setUint16(offset + 14, data.n.length, true);
- offset += 16;
- data.s.forEach(function (e) {
- e.offset = leafOffset;
- leafOffset = writeLanguageTable(view, leafOffset, strings, e);
- });
- data.n.forEach(function (e) {
- e.offset = leafOffset;
- leafOffset = writeLanguageTable(view, leafOffset, strings, e);
- });
- data.s.forEach(function (e) {
- var strOff = getStringOffset(e.id, strings);
- view.setUint32(offset, strOff + 0x80000000, true);
- view.setUint32(offset + 4, e.offset + 0x80000000, true);
- offset += 8;
- });
- data.n.forEach(function (e) {
- view.setUint32(offset, e.id, true);
- view.setUint32(offset + 4, e.offset + 0x80000000, true);
- offset += 8;
- });
- return leafOffset;
- }
- function writeTypeTable(view, offset, strings, data) {
- // characteristics
- view.setUint32(offset, 0, true);
- // timestamp
- view.setUint32(offset + 4, 0, true);
- // major version / minor version
- view.setUint32(offset + 8, 0, true);
- // name entries
- view.setUint16(offset + 12, data.s.length, true);
- // id entries
- view.setUint16(offset + 14, data.n.length, true);
- offset += 16;
- var nextTableOffset = offset + 8 * (data.s.length + data.n.length);
- data.s.forEach(function (e) {
- e.offset = nextTableOffset;
- nextTableOffset += 16 + 8 * (e.s.length + e.n.length);
- });
- data.n.forEach(function (e) {
- e.offset = nextTableOffset;
- nextTableOffset += 16 + 8 * (e.s.length + e.n.length);
- });
- data.s.forEach(function (e) {
- var strOff = getStringOffset(e.type, strings);
- view.setUint32(offset, strOff + 0x80000000, true);
- view.setUint32(offset + 4, e.offset + 0x80000000, true);
- offset += 8;
- nextTableOffset = writeNameTable(view, e.offset, nextTableOffset, strings, e);
- });
- data.n.forEach(function (e) {
- view.setUint32(offset, e.type, true);
- view.setUint32(offset + 4, e.offset + 0x80000000, true);
- offset += 8;
- nextTableOffset = writeNameTable(view, e.offset, nextTableOffset, strings, e);
- });
- return nextTableOffset;
- }
- ////////////////////////////////////////////////////////////////////////////////
- /** Manages resource data for NtExecutable */
- var NtExecutableResource = /** @class */ (function () {
- function NtExecutableResource() {
- /** The timestamp for resource */
- this.dateTime = 0;
- /** The major version data for resource */
- this.majorVersion = 0;
- /** The minor version data for resource */
- this.minorVersion = 0;
- /** Resource entries */
- this.entries = [];
- /**
- * The section data header of resource data (used by outputResource method).
- * This instance will be null if the base executable does not contain resource data.
- * You can override this field before calling outputResource method.
- * (Note that the addresses and sizes are ignored for output)
- */
- this.sectionDataHeader = null;
- this.originalSize = 0;
- }
- NtExecutableResource.prototype.parse = function (section, ignoreUnparsableData) {
- if (!section.data) {
- return;
- }
- var view = new DataView(section.data);
- // --- First: Resource Directory Table ---
- // (off: 0 -- Characteristics (uint32))
- this.dateTime = view.getUint32(4, true);
- this.majorVersion = view.getUint16(8, true);
- this.minorVersion = view.getUint16(10, true);
- var nameCount = view.getUint16(12, true);
- var idCount = view.getUint16(14, true);
- var off = 16;
- var res = [];
- var cb = function (t, n, l) {
- var off = view.getUint32(l.dataOffset, true) -
- section.info.virtualAddress;
- var size = view.getUint32(l.dataOffset + 4, true);
- var cp = view.getUint32(l.dataOffset + 8, true);
- if (off >= 0) {
- var bin = new Uint8Array(size);
- bin.set(new Uint8Array(section.data, off, size));
- res.push({
- type: t.type,
- id: n.name,
- lang: l.lang,
- codepage: cp,
- bin: bin.buffer,
- });
- }
- else {
- if (!ignoreUnparsableData) {
- throw new Error('Cannot parse resource directory entry; RVA seems to be invalid.');
- }
- res.push({
- type: t.type,
- id: n.name,
- lang: l.lang,
- codepage: cp,
- bin: new ArrayBuffer(0),
- rva: l.dataOffset,
- });
- }
- };
- for (var i = 0; i < nameCount; ++i) {
- var nameOffset = view.getUint32(off, true) & 0x7fffffff;
- var nextTable = view.getUint32(off + 4, true);
- // ignore if no next table is available
- if (!(nextTable & 0x80000000)) {
- off += 8;
- continue;
- }
- nextTable &= 0x7fffffff;
- var name_3 = readString(view, nameOffset);
- readNameTable(view, name_3, nextTable, cb);
- off += 8;
- }
- for (var i = 0; i < idCount; ++i) {
- var typeId = view.getUint32(off, true) & 0x7fffffff;
- var nextTable = view.getUint32(off + 4, true);
- // ignore if no next table is available
- if (!(nextTable & 0x80000000)) {
- off += 8;
- continue;
- }
- nextTable &= 0x7fffffff;
- readNameTable(view, typeId, nextTable, cb);
- off += 8;
- }
- this.entries = res;
- this.originalSize = section.data.byteLength;
- };
- /**
- * Parses resource data for `NtExecutable`.
- * This function returns valid instance even if
- * the executable does not have resource data.
- * @param exe `NtExecutable` instance
- * @param ignoreUnparsableData (default: false) specify true if skipping 'unparsable' (e.g. unusual format) data.
- * When true, the resource data may break on write operation.
- */
- NtExecutableResource.from = function (exe, ignoreUnparsableData) {
- if (ignoreUnparsableData === void 0) { ignoreUnparsableData = false; }
- var secs = []
- .concat(exe.getAllSections())
- .sort(function (a, b) { return a.info.virtualAddress - b.info.virtualAddress; });
- var entry = exe.getSectionByEntry(ImageDirectoryEntry.Resource);
- // check if the section order is supported
- // (not supported if any other sections except 'relocation' is available,
- // because the recalculation of virtual address is not simple)
- if (entry) {
- var reloc = exe.getSectionByEntry(ImageDirectoryEntry.BaseRelocation);
- for (var i = 0; i < secs.length; ++i) {
- var s = secs[i];
- if (s === entry) {
- for (var j = i + 1; j < secs.length; ++j) {
- if (!reloc || secs[j] !== reloc) {
- throw new Error('After Resource section, sections except for relocation are not supported');
- }
- }
- break;
- }
- }
- }
- var r = new NtExecutableResource();
- r.sectionDataHeader = entry ? cloneObject(entry.info) : null;
- if (entry) {
- r.parse(entry, ignoreUnparsableData);
- }
- return r;
- };
- /**
- * Add or replace the resource entry.
- * This method replaces the entry only if there is an entry with `type`, `id` and `lang` equal.
- */
- NtExecutableResource.prototype.replaceResourceEntry = function (entry) {
- for (var len = this.entries.length, i = 0; i < len; ++i) {
- var e = this.entries[i];
- if (e.type === entry.type &&
- e.id === entry.id &&
- e.lang === entry.lang) {
- this.entries[i] = entry;
- return;
- }
- }
- this.entries.push(entry);
- };
- /**
- * Returns all resource entries, which has specified type and id, as UTF-8 string data.
- * @param type Resource type
- * @param id Resource id
- * @returns an array of lang and value pair (tuple)
- */
- NtExecutableResource.prototype.getResourceEntriesAsString = function (type, id) {
- return this.entries
- .filter(function (entry) { return entry.type === type && entry.id === id; })
- .map(function (entry) { return [entry.lang, binaryToString(entry.bin)]; });
- };
- /**
- * Add or replace the resource entry with UTF-8 string data.
- * This method is a wrapper of {@link NtExecutableResource.replaceResourceEntry}.
- */
- NtExecutableResource.prototype.replaceResourceEntryFromString = function (type, id, lang, value) {
- var entry = {
- type: type,
- id: id,
- lang: lang,
- codepage: 1200,
- bin: stringToBinary(value),
- };
- this.replaceResourceEntry(entry);
- };
- /**
- * Removes resource entries which has specified type and id.
- */
- NtExecutableResource.prototype.removeResourceEntry = function (type, id, lang) {
- this.entries = this.entries.filter(function (entry) {
- return !(entry.type === type &&
- entry.id === id &&
- (typeof lang === 'undefined' || entry.lang === lang));
- });
- };
- /**
- * Generates resource data binary for NtExecutable (not for .res file)
- * @param virtualAddress The virtual address for the section
- * @param alignment File alignment value of executable
- * @param noGrow Set true to disallow growing resource section (throw errors if data exceeds)
- * @param allowShrink Set true to allow shrinking resource section (if the data size is less than original)
- */
- NtExecutableResource.prototype.generateResourceData = function (virtualAddress, alignment, noGrow, allowShrink) {
- if (noGrow === void 0) { noGrow = false; }
- if (allowShrink === void 0) { allowShrink = false; }
- // estimate data size and divide to output table
- var r = {
- s: [],
- n: [],
- };
- var strings = [];
- var size = divideEntriesImplByType(r, strings, this.entries);
- strings = removeDuplicates(strings);
- var stringsOffset = size;
- size += strings.reduce(function (prev, cur) {
- return prev + 2 + calculateStringLengthForWrite(cur) * 2;
- }, 0);
- size = roundUp(size, 8);
- var descOffset = size;
- size = this.entries.reduce(function (p, e) {
- e.offset = p;
- return p + 16;
- }, descOffset);
- var dataOffset = size;
- size = this.entries.reduce(function (p, e) {
- return roundUp(p, 8) + e.bin.byteLength;
- }, dataOffset);
- var alignedSize = roundUp(size, alignment);
- var originalAlignedSize = roundUp(this.originalSize, alignment);
- if (noGrow) {
- if (alignedSize > originalAlignedSize) {
- throw new Error('New resource data is larger than original');
- }
- }
- if (!allowShrink) {
- if (alignedSize < originalAlignedSize) {
- alignedSize = originalAlignedSize;
- }
- }
- // generate binary
- var bin = new ArrayBuffer(alignedSize);
- var view = new DataView(bin);
- var o = descOffset;
- var va = virtualAddress + dataOffset;
- this.entries.forEach(function (e) {
- var len = e.bin.byteLength;
- if (typeof e.rva !== 'undefined') {
- // RVA
- view.setUint32(o, e.rva, true);
- }
- else {
- va = roundUp(va, 8);
- // RVA
- view.setUint32(o, va, true);
- va += len;
- }
- // size
- view.setUint32(o + 4, len, true);
- // codepage
- view.setUint32(o + 8, e.codepage, true);
- // (zero)
- view.setUint32(o + 12, 0, true);
- o += 16;
- });
- o = dataOffset;
- this.entries.forEach(function (e) {
- var len = e.bin.byteLength;
- copyBuffer(bin, o, e.bin, 0, len);
- o += roundUp(len, 8);
- });
- var stringsData = [];
- o = stringsOffset;
- strings.forEach(function (s) {
- stringsData.push({
- offset: o,
- text: s,
- });
- o = writeString(view, o, s);
- });
- writeTypeTable(view, 0, stringsData, r);
- // fill with 'PADDINGX'
- if (alignedSize > size) {
- var pad = 'PADDINGX';
- for (var i = size, j = 0; i < alignedSize; ++i, ++j) {
- if (j === 8) {
- j = 0;
- }
- view.setUint8(i, pad.charCodeAt(j));
- }
- }
- return {
- bin: bin,
- rawSize: size,
- dataOffset: dataOffset,
- descEntryOffset: descOffset,
- descEntryCount: this.entries.length,
- };
- };
- /**
- * Writes holding resource data to specified NtExecutable instance.
- * @param exeDest An NtExecutable instance to write resource section to
- * @param noGrow Set true to disallow growing resource section (throw errors if data exceeds)
- * @param allowShrink Set true to allow shrinking resource section (if the data size is less than original)
- */
- NtExecutableResource.prototype.outputResource = function (exeDest, noGrow, allowShrink) {
- if (noGrow === void 0) { noGrow = false; }
- if (allowShrink === void 0) { allowShrink = false; }
- // make section data
- var fileAlign = exeDest.getFileAlignment();
- var sectionData;
- if (this.sectionDataHeader) {
- sectionData = {
- data: null,
- info: cloneObject(this.sectionDataHeader),
- };
- }
- else {
- sectionData = {
- data: null,
- info: {
- name: '.rsrc',
- virtualSize: 0,
- virtualAddress: 0,
- sizeOfRawData: 0,
- pointerToRawData: 0,
- pointerToRelocations: 0,
- pointerToLineNumbers: 0,
- numberOfRelocations: 0,
- numberOfLineNumbers: 0,
- characteristics: 0x40000040, // read access and initialized data
- },
- };
- }
- // first, set virtualAddress to 0 because
- // the virtual address is not determined now
- var data = this.generateResourceData(0, fileAlign, noGrow, allowShrink);
- sectionData.data = data.bin;
- sectionData.info.sizeOfRawData = data.bin.byteLength;
- sectionData.info.virtualSize = data.rawSize;
- // write as section
- exeDest.setSectionByEntry(ImageDirectoryEntry.Resource, sectionData);
- // rewrite section raw-data
- var generatedSection = exeDest.getSectionByEntry(ImageDirectoryEntry.Resource);
- var view = new DataView(generatedSection.data);
- // set RVA
- var o = data.descEntryOffset;
- var va = generatedSection.info.virtualAddress + data.dataOffset;
- for (var i = 0; i < data.descEntryCount; ++i) {
- var len = view.getUint32(o + 4, true);
- va = roundUp(va, 8);
- // RVA
- view.setUint32(o, va, true);
- va += len;
- o += 16;
- }
- };
- return NtExecutableResource;
- }());
- export default NtExecutableResource;
|