You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NtExecutable.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. import ImageDataDirectoryArray from './format/ImageDataDirectoryArray.js';
  2. import ImageDirectoryEntry from './format/ImageDirectoryEntry.js';
  3. import ImageDosHeader from './format/ImageDosHeader.js';
  4. import ImageNtHeaders from './format/ImageNtHeaders.js';
  5. import ImageSectionHeaderArray from './format/ImageSectionHeaderArray.js';
  6. import { allocatePartialBinary, calculateCheckSumForPE, cloneObject, cloneToArrayBuffer, roundUp, } from './util/functions.js';
  7. import { makeEmptyNtExecutableBinary } from './util/generate.js';
  8. var NtExecutable = /** @class */ (function () {
  9. function NtExecutable(_headers, _sections, _ex) {
  10. this._headers = _headers;
  11. this._sections = _sections;
  12. this._ex = _ex;
  13. var dh = ImageDosHeader.from(_headers);
  14. var nh = ImageNtHeaders.from(_headers, dh.newHeaderAddress);
  15. this._dh = dh;
  16. this._nh = nh;
  17. this._dda = nh.optionalHeaderDataDirectory;
  18. _sections.sort(function (a, b) {
  19. var ra = a.info.pointerToRawData;
  20. var rb = a.info.pointerToRawData;
  21. if (ra !== rb) {
  22. return ra - rb;
  23. }
  24. var va = a.info.virtualAddress;
  25. var vb = b.info.virtualAddress;
  26. if (va === vb) {
  27. return a.info.virtualSize - b.info.virtualSize;
  28. }
  29. return va - vb;
  30. });
  31. }
  32. /**
  33. * Creates an NtExecutable instance with an 'empty' executable binary.
  34. * @param is32Bit set true if the binary is for 32-bit (default: false)
  35. * @param isDLL set true if the binary is DLL (default: true)
  36. * @return NtExecutable instance
  37. */
  38. NtExecutable.createEmpty = function (is32Bit, isDLL) {
  39. if (is32Bit === void 0) { is32Bit = false; }
  40. if (isDLL === void 0) { isDLL = true; }
  41. return this.from(makeEmptyNtExecutableBinary(is32Bit, isDLL));
  42. };
  43. /**
  44. * Parse the binary and create NtExecutable instance.
  45. * An error will be thrown if the binary data is invalid
  46. * @param bin binary data
  47. * @param options additional option for parsing
  48. * @return NtExecutable instance
  49. */
  50. NtExecutable.from = function (bin, options) {
  51. var dh = ImageDosHeader.from(bin);
  52. var nh = ImageNtHeaders.from(bin, dh.newHeaderAddress);
  53. if (!dh.isValid() || !nh.isValid()) {
  54. throw new TypeError('Invalid binary format');
  55. }
  56. if (nh.fileHeader.numberOfSymbols > 0) {
  57. throw new Error('Binary with symbols is not supported now');
  58. }
  59. var fileAlignment = nh.optionalHeader.fileAlignment;
  60. var securityEntry = nh.optionalHeaderDataDirectory.get(ImageDirectoryEntry.Certificate);
  61. if (securityEntry.size > 0) {
  62. // Signed executables should be parsed only when `ignoreCert` is true
  63. if (!(options === null || options === void 0 ? void 0 : options.ignoreCert)) {
  64. throw new Error('Parsing signed executable binary is not allowed by default.');
  65. }
  66. }
  67. var secOff = dh.newHeaderAddress + nh.getSectionHeaderOffset();
  68. var secCount = nh.fileHeader.numberOfSections;
  69. var sections = [];
  70. var tempSectionHeaderBinary = allocatePartialBinary(bin, secOff, secCount * ImageSectionHeaderArray.itemSize);
  71. var secArray = ImageSectionHeaderArray.from(tempSectionHeaderBinary, secCount, 0);
  72. var lastOffset = roundUp(secOff + secCount * ImageSectionHeaderArray.itemSize, fileAlignment);
  73. // console.log(`from data size 0x${bin.byteLength.toString(16)}:`);
  74. secArray.forEach(function (info) {
  75. if (!info.pointerToRawData || !info.sizeOfRawData) {
  76. info.pointerToRawData = 0;
  77. info.sizeOfRawData = 0;
  78. sections.push({
  79. info: info,
  80. data: null,
  81. });
  82. }
  83. else {
  84. // console.log(` section ${info.name}: 0x${info.pointerToRawData.toString(16)}, size = 0x${info.sizeOfRawData.toString(16)}`);
  85. var secBin = allocatePartialBinary(bin, info.pointerToRawData, info.sizeOfRawData);
  86. sections.push({
  87. info: info,
  88. data: secBin,
  89. });
  90. var secEndOffset = roundUp(info.pointerToRawData + info.sizeOfRawData, fileAlignment);
  91. if (secEndOffset > lastOffset) {
  92. lastOffset = secEndOffset;
  93. }
  94. }
  95. });
  96. // the size of DOS and NT headers is equal to section offset
  97. var headers = allocatePartialBinary(bin, 0, secOff);
  98. // extra data
  99. var exData = null;
  100. var lastExDataOffset = bin.byteLength;
  101. // It may contain that both extra data and certificate data are available.
  102. // In this case the extra data is followed by the certificate data.
  103. if (securityEntry.size > 0) {
  104. lastExDataOffset = securityEntry.virtualAddress;
  105. }
  106. if (lastOffset < lastExDataOffset) {
  107. exData = allocatePartialBinary(bin, lastOffset, lastExDataOffset - lastOffset);
  108. }
  109. return new NtExecutable(headers, sections, exData);
  110. };
  111. /**
  112. * Returns whether the executable is for 32-bit architecture
  113. */
  114. NtExecutable.prototype.is32bit = function () {
  115. return this._nh.is32bit();
  116. };
  117. NtExecutable.prototype.getTotalHeaderSize = function () {
  118. return this._headers.byteLength;
  119. };
  120. Object.defineProperty(NtExecutable.prototype, "dosHeader", {
  121. get: function () {
  122. return this._dh;
  123. },
  124. enumerable: false,
  125. configurable: true
  126. });
  127. Object.defineProperty(NtExecutable.prototype, "newHeader", {
  128. get: function () {
  129. return this._nh;
  130. },
  131. enumerable: false,
  132. configurable: true
  133. });
  134. NtExecutable.prototype.getRawHeader = function () {
  135. return this._headers;
  136. };
  137. NtExecutable.prototype.getImageBase = function () {
  138. return this._nh.optionalHeader.imageBase;
  139. };
  140. NtExecutable.prototype.getFileAlignment = function () {
  141. return this._nh.optionalHeader.fileAlignment;
  142. };
  143. NtExecutable.prototype.getSectionAlignment = function () {
  144. return this._nh.optionalHeader.sectionAlignment;
  145. };
  146. /**
  147. * Return all sections. The returned array is sorted by raw address.
  148. */
  149. NtExecutable.prototype.getAllSections = function () {
  150. return this._sections;
  151. };
  152. /**
  153. * Return the section data from ImageDirectoryEntry enum value.
  154. * @note
  155. * The returned instance is equal to the value in {@link getAllSections}'s return value.
  156. */
  157. NtExecutable.prototype.getSectionByEntry = function (entry) {
  158. var dd = this._dda.get(entry);
  159. var r = this._sections
  160. .filter(function (sec) {
  161. var vaEnd = sec.info.virtualAddress + sec.info.virtualSize;
  162. return (dd.virtualAddress >= sec.info.virtualAddress &&
  163. dd.virtualAddress < vaEnd);
  164. })
  165. .shift();
  166. return r !== undefined ? r : null;
  167. };
  168. /**
  169. * Set the section data from ImageDirectoryEntry enum value.
  170. * If entry is found, then replaces the secion data. If not found, then adds the section data.
  171. *
  172. * NOTE: 'virtualAddress' and 'pointerToRawData' of section object is ignored
  173. * and calculated automatically. 'virtualSize' and 'sizeOfRawData' are used, but
  174. * if the 'section.data.byteLength' is larger than 'sizeOfRawData', then
  175. * these members are replaced.
  176. *
  177. * @param entry ImageDirectoryEntry enum value for the section
  178. * @param section the section data, or null to remove the section
  179. */
  180. NtExecutable.prototype.setSectionByEntry = function (entry, section) {
  181. var sec = section
  182. ? { data: section.data, info: section.info }
  183. : null;
  184. var dd = this._dda.get(entry);
  185. var hasEntry = dd.size > 0;
  186. if (!sec) {
  187. if (!hasEntry) {
  188. // no need to replace
  189. }
  190. else {
  191. // clear entry
  192. this._dda.set(entry, { size: 0, virtualAddress: 0 });
  193. var len = this._sections.length;
  194. for (var i = 0; i < len; ++i) {
  195. var sec_1 = this._sections[i];
  196. var vaStart = sec_1.info.virtualAddress;
  197. var vaLast = vaStart + sec_1.info.virtualSize;
  198. if (dd.virtualAddress >= vaStart &&
  199. dd.virtualAddress < vaLast) {
  200. this._sections.splice(i, 1);
  201. // section count changed
  202. this._nh.fileHeader.numberOfSections =
  203. this._sections.length;
  204. break;
  205. }
  206. }
  207. }
  208. }
  209. else {
  210. var rawSize = !sec.data ? 0 : sec.data.byteLength;
  211. var fileAlign = this._nh.optionalHeader.fileAlignment;
  212. var secAlign = this._nh.optionalHeader.sectionAlignment;
  213. var alignedFileSize = !sec.data ? 0 : roundUp(rawSize, fileAlign);
  214. var alignedSecSize = !sec.data
  215. ? 0
  216. : roundUp(sec.info.virtualSize, secAlign);
  217. if (sec.info.sizeOfRawData < alignedFileSize) {
  218. sec.info.sizeOfRawData = alignedFileSize;
  219. }
  220. else {
  221. alignedFileSize = sec.info.sizeOfRawData;
  222. }
  223. if (!hasEntry) {
  224. var virtAddr_1 = 0;
  225. var rawAddr_1 = roundUp(this._headers.byteLength, fileAlign);
  226. // get largest addresses
  227. this._sections.forEach(function (secExist) {
  228. if (secExist.info.pointerToRawData) {
  229. if (rawAddr_1 <= secExist.info.pointerToRawData) {
  230. rawAddr_1 =
  231. secExist.info.pointerToRawData +
  232. secExist.info.sizeOfRawData;
  233. }
  234. }
  235. if (virtAddr_1 <= secExist.info.virtualAddress) {
  236. virtAddr_1 =
  237. secExist.info.virtualAddress +
  238. secExist.info.virtualSize;
  239. }
  240. });
  241. if (!alignedFileSize) {
  242. rawAddr_1 = 0;
  243. }
  244. if (!virtAddr_1) {
  245. virtAddr_1 = this.newHeader.optionalHeader.baseOfCode;
  246. }
  247. virtAddr_1 = roundUp(virtAddr_1, secAlign);
  248. sec.info.pointerToRawData = rawAddr_1;
  249. sec.info.virtualAddress = virtAddr_1;
  250. // add entry
  251. this._dda.set(entry, {
  252. size: rawSize,
  253. virtualAddress: virtAddr_1,
  254. });
  255. this._sections.push(sec);
  256. // section count changed
  257. this._nh.fileHeader.numberOfSections = this._sections.length;
  258. // change image size
  259. this._nh.optionalHeader.sizeOfImage = roundUp(virtAddr_1 + alignedSecSize, this._nh.optionalHeader.sectionAlignment);
  260. }
  261. else {
  262. // replace entry
  263. this.replaceSectionImpl(dd.virtualAddress, sec.info, sec.data);
  264. }
  265. }
  266. };
  267. /**
  268. * Returns the extra data in the executable, or `null` if nothing.
  269. * You can rewrite the returned buffer without using `setExtraData` if
  270. * the size of the new data is equal to the old data.
  271. */
  272. NtExecutable.prototype.getExtraData = function () {
  273. return this._ex;
  274. };
  275. /**
  276. * Specifies the new extra data in the executable.
  277. * The specified buffer will be cloned and you can release it after calling this method.
  278. * @param bin buffer containing the new data
  279. * @note
  280. * The extra data will not be aligned by `NtExecutable`.
  281. */
  282. NtExecutable.prototype.setExtraData = function (bin) {
  283. if (bin === null) {
  284. this._ex = null;
  285. }
  286. else {
  287. this._ex = cloneToArrayBuffer(bin);
  288. }
  289. };
  290. /**
  291. * Generates the executable binary data.
  292. */
  293. NtExecutable.prototype.generate = function (paddingSize) {
  294. // calculate binary size
  295. var dh = this._dh;
  296. var nh = this._nh;
  297. var secOff = dh.newHeaderAddress + nh.getSectionHeaderOffset();
  298. var size = secOff;
  299. size += this._sections.length * ImageSectionHeaderArray.itemSize;
  300. var align = nh.optionalHeader.fileAlignment;
  301. size = roundUp(size, align);
  302. this._sections.forEach(function (sec) {
  303. if (!sec.info.pointerToRawData) {
  304. return;
  305. }
  306. var lastOff = sec.info.pointerToRawData + sec.info.sizeOfRawData;
  307. if (size < lastOff) {
  308. size = lastOff;
  309. size = roundUp(size, align);
  310. }
  311. });
  312. var lastPosition = size;
  313. if (this._ex !== null) {
  314. size += this._ex.byteLength;
  315. }
  316. if (typeof paddingSize === 'number') {
  317. size += paddingSize;
  318. }
  319. // make buffer
  320. var bin = new ArrayBuffer(size);
  321. var u8bin = new Uint8Array(bin);
  322. u8bin.set(new Uint8Array(this._headers, 0, secOff));
  323. // reset Security section offset (eliminate it)
  324. ImageDataDirectoryArray.from(bin, dh.newHeaderAddress + nh.getDataDirectoryOffset()).set(ImageDirectoryEntry.Certificate, {
  325. size: 0,
  326. virtualAddress: 0,
  327. });
  328. var secArray = ImageSectionHeaderArray.from(bin, this._sections.length, secOff);
  329. this._sections.forEach(function (sec, i) {
  330. if (!sec.data) {
  331. sec.info.pointerToRawData = 0;
  332. sec.info.sizeOfRawData = 0;
  333. }
  334. secArray.set(i, sec.info);
  335. if (!sec.data || !sec.info.pointerToRawData) {
  336. return;
  337. }
  338. u8bin.set(new Uint8Array(sec.data), sec.info.pointerToRawData);
  339. });
  340. if (this._ex !== null) {
  341. u8bin.set(new Uint8Array(this._ex), lastPosition);
  342. }
  343. // re-calc checksum
  344. if (nh.optionalHeader.checkSum !== 0) {
  345. calculateCheckSumForPE(bin, true);
  346. }
  347. return bin;
  348. };
  349. NtExecutable.prototype.rearrangeSections = function (rawAddressStart, rawDiff, virtualAddressStart, virtualDiff) {
  350. if (!rawDiff && !virtualDiff) {
  351. return;
  352. }
  353. var nh = this._nh;
  354. var secAlign = nh.optionalHeader.sectionAlignment;
  355. var dirs = this._dda;
  356. var len = this._sections.length;
  357. var lastVirtAddress = 0;
  358. for (var i = 0; i < len; ++i) {
  359. var sec = this._sections[i];
  360. var virtAddr = sec.info.virtualAddress;
  361. if (virtualDiff && virtAddr >= virtualAddressStart) {
  362. var iDir = dirs.findIndexByVirtualAddress(virtAddr);
  363. virtAddr += virtualDiff;
  364. if (iDir !== null) {
  365. dirs.set(iDir, {
  366. virtualAddress: virtAddr,
  367. size: sec.info.virtualSize,
  368. });
  369. }
  370. sec.info.virtualAddress = virtAddr;
  371. }
  372. var fileAddr = sec.info.pointerToRawData;
  373. if (rawDiff && fileAddr >= rawAddressStart) {
  374. sec.info.pointerToRawData = fileAddr + rawDiff;
  375. }
  376. lastVirtAddress = roundUp(sec.info.virtualAddress + sec.info.virtualSize, secAlign);
  377. }
  378. // fix image size from last virtual address
  379. nh.optionalHeader.sizeOfImage = lastVirtAddress;
  380. };
  381. // NOTE: info.virtualSize must be valid
  382. NtExecutable.prototype.replaceSectionImpl = function (virtualAddress, info, data) {
  383. var len = this._sections.length;
  384. for (var i = 0; i < len; ++i) {
  385. var s = this._sections[i];
  386. // console.log(`replaceSectionImpl: ${virtualAddress} <--> ${s.info.virtualAddress}`);
  387. if (s.info.virtualAddress === virtualAddress) {
  388. // console.log(` found`);
  389. var secAlign = this._nh.optionalHeader.sectionAlignment;
  390. var fileAddr = s.info.pointerToRawData;
  391. var oldFileAddr = fileAddr + s.info.sizeOfRawData;
  392. var oldVirtAddr = virtualAddress + roundUp(s.info.virtualSize, secAlign);
  393. s.info = cloneObject(info);
  394. s.info.virtualAddress = virtualAddress;
  395. s.info.pointerToRawData = fileAddr;
  396. s.data = data;
  397. // shift addresses
  398. var newFileAddr = fileAddr + info.sizeOfRawData;
  399. var newVirtAddr = virtualAddress + roundUp(info.virtualSize, secAlign);
  400. this.rearrangeSections(oldFileAddr, newFileAddr - oldFileAddr, oldVirtAddr, newVirtAddr - oldVirtAddr);
  401. // BLOCK: rewrite DataDirectory entry for specified virtualAddress
  402. {
  403. var dirs = this._dda;
  404. var iDir = dirs.findIndexByVirtualAddress(virtualAddress);
  405. if (iDir !== null) {
  406. dirs.set(iDir, {
  407. virtualAddress: virtualAddress,
  408. size: info.virtualSize,
  409. });
  410. }
  411. }
  412. break;
  413. }
  414. }
  415. };
  416. return NtExecutable;
  417. }());
  418. export default NtExecutable;