authenticode.js 139 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490
  1. /**
  2. * @description Authenticode parsing
  3. * @author Ylian Saint-Hilaire & Bryan Roe
  4. * @copyright Intel Corporation 2018-2022
  5. * @license Apache-2.0
  6. * @version v0.0.1
  7. */
  8. /*jslint node: true */
  9. /*jshint node: true */
  10. /*jshint strict:false */
  11. /*jshint -W097 */
  12. /*jshint esversion: 6 */
  13. "use strict";
  14. const fs = require('fs');
  15. const crypto = require('crypto');
  16. const forge = require('node-forge');
  17. const pki = forge.pki;
  18. const p7 = require('./pkcs7-modified');
  19. // Generate a test self-signed certificate with code signing extension
  20. function createSelfSignedCert(args) {
  21. var keys = pki.rsa.generateKeyPair(2048);
  22. var cert = pki.createCertificate();
  23. cert.publicKey = keys.publicKey;
  24. cert.serialNumber = (typeof args.serial == 'string')?args.serial:'012345'; // Serial number must always have a single leading '0', otherwise toPEM/fromPEM will not work right.
  25. cert.validity.notBefore = new Date();
  26. cert.validity.notAfter = new Date();
  27. cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
  28. var attrs = [];
  29. if (typeof args.cn == 'string') { attrs.push({ name: 'commonName', value: args.cn }); }
  30. if (typeof args.country == 'string') { attrs.push({ name: 'countryName', value: args.country }); }
  31. if (typeof args.state == 'string') { attrs.push({ name: 'ST', value: args.state }); }
  32. if (typeof args.locality == 'string') { attrs.push({ name: 'localityName', value: args.locality }); }
  33. if (typeof args.org == 'string') { attrs.push({ name: 'organizationName', value: args.org }); }
  34. if (typeof args.orgunit == 'string') { attrs.push({ name: 'OU', value: args.orgunit }); }
  35. cert.setSubject(attrs);
  36. cert.setIssuer(attrs);
  37. cert.setExtensions([{ name: 'basicConstraints', cA: false }, { name: 'keyUsage', keyCertSign: false, digitalSignature: true, nonRepudiation: false, keyEncipherment: false, dataEncipherment: false }, { name: 'extKeyUsage', codeSigning: true }, { name: "subjectKeyIdentifier" }]);
  38. cert.sign(keys.privateKey, forge.md.sha384.create());
  39. return { cert: cert, key: keys.privateKey, extraCerts: [] };
  40. }
  41. // Create the output filename if not already specified
  42. function createOutFile(args, filename) {
  43. if (typeof args.out == 'string') return;
  44. var outputFileName = filename.split('.');
  45. outputFileName[outputFileName.length - 2] += '-out';
  46. args.out = outputFileName.join('.');
  47. }
  48. // Hash an object
  49. function hashObject(obj) {
  50. if (obj == null) { return null; }
  51. const hash = crypto.createHash('sha384');
  52. if (Buffer.isBuffer(obj)) { hash.update(obj); } else { hash.update(JSON.stringify(obj)); }
  53. return hash.digest().toString('hex');
  54. }
  55. // Load a .bmp file.
  56. function loadBitmap(bitmapFile) {
  57. var bitmapData = null;
  58. try { bitmapData = fs.readFileSync(bitmapFile); } catch (ex) { }
  59. if ((bitmapData == null) || (bitmapData.length < 14) || (bitmapData[0] != 0x42) || (bitmapData[1] != 0x4D)) return null;
  60. return bitmapData.slice(14);
  61. }
  62. // Load a .ico file. This will load all icons in the file into a icon group object
  63. function loadIcon(iconFile) {
  64. var iconData = null;
  65. try { iconData = fs.readFileSync(iconFile); } catch (ex) { }
  66. if ((iconData == null) || (iconData.length < 6) || (iconData[0] != 0) || (iconData[1] != 0)) return null;
  67. const r = { resType: iconData.readUInt16LE(2), resCount: iconData.readUInt16LE(4), icons: {} };
  68. if (r.resType != 1) return null;
  69. var ptr = 6;
  70. for (var i = 1; i <= r.resCount; i++) {
  71. var icon = {};
  72. icon.width = iconData[ptr + 0];
  73. icon.height = iconData[ptr + 1];
  74. icon.colorCount = iconData[ptr + 2];
  75. icon.planes = iconData.readUInt16LE(ptr + 4);
  76. icon.bitCount = iconData.readUInt16LE(ptr + 6);
  77. icon.bytesInRes = iconData.readUInt32LE(ptr + 8);
  78. icon.iconCursorId = i;
  79. const offset = iconData.readUInt32LE(ptr + 12);
  80. icon.icon = iconData.slice(offset, offset + icon.bytesInRes);
  81. r.icons[i] = icon;
  82. ptr += 16;
  83. }
  84. return r;
  85. }
  86. // Load certificates and private key from PEM files
  87. function loadCertificates(pemFileNames) {
  88. var certs = [], keys = [];
  89. if (pemFileNames == null) return;
  90. if (typeof pemFileNames == 'string') { pemFileNames = [pemFileNames]; }
  91. for (var i in pemFileNames) {
  92. try {
  93. // Read certificate
  94. var pem = fs.readFileSync(pemFileNames[i]).toString();
  95. var pemCerts = pem.split('-----BEGIN CERTIFICATE-----');
  96. for (var j in pemCerts) {
  97. var k = pemCerts[j].indexOf('-----END CERTIFICATE-----');
  98. if (k >= 0) { certs.push(pki.certificateFromPem('-----BEGIN CERTIFICATE-----' + pemCerts[j].substring(0, k) + '-----END CERTIFICATE-----')); }
  99. }
  100. var PemKeys = pem.split('-----BEGIN RSA PRIVATE KEY-----');
  101. for (var j in PemKeys) {
  102. var k = PemKeys[j].indexOf('-----END RSA PRIVATE KEY-----');
  103. if (k >= 0) { keys.push(pki.privateKeyFromPem('-----BEGIN RSA PRIVATE KEY-----' + PemKeys[j].substring(0, k) + '-----END RSA PRIVATE KEY-----')); }
  104. }
  105. PemKeys = pem.split('-----BEGIN PRIVATE KEY-----');
  106. for (var j in PemKeys) {
  107. var k = PemKeys[j].indexOf('-----END PRIVATE KEY-----');
  108. if (k >= 0) { keys.push(pki.privateKeyFromPem('-----BEGIN PRIVATE KEY-----' + PemKeys[j].substring(0, k) + '-----END PRIVATE KEY-----')); }
  109. }
  110. } catch (ex) { }
  111. }
  112. if ((certs.length == 0) || (keys.length != 1)) return; // No certificates or private keys
  113. var r = { cert: certs[0], key: keys[0], extraCerts: [] }
  114. if (certs.length > 1) { for (var i = 1; i < certs.length; i++) { r.extraCerts.push(certs[i]); } }
  115. return r;
  116. }
  117. function createAuthenticodeHandler(path) {
  118. const obj = {};
  119. obj.header = { path: path }
  120. // Read a file slice
  121. function readFileSlice(start, length) {
  122. var buffer = Buffer.alloc(length);
  123. var len = fs.readSync(obj.fd, buffer, 0, buffer.length, start);
  124. if (len < buffer.length) { buffer = buffer.slice(0, len); }
  125. return buffer;
  126. }
  127. // Close the file
  128. obj.close = function () {
  129. if (obj.fd == null) return;
  130. fs.closeSync(obj.fd);
  131. delete obj.fd;
  132. }
  133. // Private OIDS
  134. obj.Oids = {
  135. SPC_INDIRECT_DATA_OBJID: '1.3.6.1.4.1.311.2.1.4',
  136. SPC_STATEMENT_TYPE_OBJID: '1.3.6.1.4.1.311.2.1.11',
  137. SPC_SP_OPUS_INFO_OBJID: '1.3.6.1.4.1.311.2.1.12',
  138. SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID: '1.3.6.1.4.1.311.2.1.21',
  139. SPC_COMMERCIAL_SP_KEY_PURPOSE_OBJID: '1.3.6.1.4.1.311.2.1.22',
  140. SPC_MS_JAVA_SOMETHING: '1.3.6.1.4.1.311.15.1',
  141. SPC_PE_IMAGE_DATA_OBJID: '1.3.6.1.4.1.311.2.1.15',
  142. SPC_CAB_DATA_OBJID: '1.3.6.1.4.1.311.2.1.25',
  143. SPC_TIME_STAMP_REQUEST_OBJID: '1.3.6.1.4.1.311.3.2.1',
  144. SPC_SIPINFO_OBJID: '1.3.6.1.4.1.311.2.1.30',
  145. SPC_PE_IMAGE_PAGE_HASHES_V1: '1.3.6.1.4.1.311.2.3.1',
  146. SPC_PE_IMAGE_PAGE_HASHES_V2: '1.3.6.1.4.1.311.2.3.2',
  147. SPC_NESTED_SIGNATURE_OBJID: '1.3.6.1.4.1.311.2.4.1',
  148. SPC_RFC3161_OBJID: '1.3.6.1.4.1.311.3.3.1'
  149. }
  150. // Open the file and read header information
  151. function openFile() {
  152. if (obj.fd != null) return true;
  153. // Open the file descriptor
  154. obj.path = path;
  155. try { obj.fd = fs.openSync(path, 'r'); } catch (ex) { return false; } // Unable to open file
  156. obj.stats = fs.fstatSync(obj.fd);
  157. obj.filesize = obj.stats.size;
  158. if (obj.filesize < 64) { obj.close(); return false; } // File too short.
  159. // Read the DOS header (64 bytes)
  160. var buf = readFileSlice(60, 4);
  161. obj.header.peHeaderLocation = buf.readUInt32LE(0); // The DOS header is 64 bytes long, the last 4 bytes are a pointer to the PE header.
  162. obj.header.peOptionalHeaderLocation = obj.header.peHeaderLocation + 24; // The PE optional header is located just after the PE header which is 24 bytes long.
  163. // Check file size and signature
  164. if (obj.filesize < (160 + obj.header.peHeaderLocation)) { obj.close(); return false; } // Invalid SizeOfHeaders.
  165. if (readFileSlice(obj.header.peHeaderLocation, 4).toString('hex') != '50450000') { obj.close(); return false; } // Invalid PE header, must start with "PE" (HEX: 50 45 00 00).
  166. // Read the COFF header
  167. // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image
  168. var coffHeader = readFileSlice(obj.header.peHeaderLocation + 4, 20)
  169. obj.header.coff = {};
  170. obj.header.coff.machine = coffHeader.readUInt16LE(0);
  171. obj.header.coff.numberOfSections = coffHeader.readUInt16LE(2);
  172. obj.header.coff.timeDateStamp = coffHeader.readUInt32LE(4);
  173. obj.header.coff.pointerToSymbolTable = coffHeader.readUInt32LE(8);
  174. obj.header.coff.numberOfSymbols = coffHeader.readUInt32LE(12);
  175. obj.header.coff.sizeOfOptionalHeader = coffHeader.readUInt16LE(16);
  176. obj.header.coff.characteristics = coffHeader.readUInt16LE(18);
  177. // Read the entire PE optional header
  178. var optinalHeader = readFileSlice(obj.header.peOptionalHeaderLocation, obj.header.coff.sizeOfOptionalHeader);
  179. // Decode the PE optional header standard fields
  180. // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-standard-fields-image-only
  181. obj.header.peStandard = {};
  182. obj.header.peStandard.magic = optinalHeader.readUInt16LE(0);
  183. switch (obj.header.peStandard.magic) { // Check magic value
  184. case 0x020B: obj.header.pe32plus = 1; break;
  185. case 0x010B: obj.header.pe32plus = 0; break;
  186. default: { obj.close(); return false; } // Invalid Magic in PE
  187. }
  188. obj.header.peStandard.majorLinkerVersion = optinalHeader[2];
  189. obj.header.peStandard.minorLinkerVersion = optinalHeader[3];
  190. obj.header.peStandard.sizeOfCode = optinalHeader.readUInt32LE(4);
  191. obj.header.peStandard.sizeOfInitializedData = optinalHeader.readUInt32LE(8);
  192. obj.header.peStandard.sizeOfUninitializedData = optinalHeader.readUInt32LE(12);
  193. obj.header.peStandard.addressOfEntryPoint = optinalHeader.readUInt32LE(16);
  194. obj.header.peStandard.baseOfCode = optinalHeader.readUInt32LE(20);
  195. if (obj.header.pe32plus == 0) { obj.header.peStandard.baseOfData = optinalHeader.readUInt32LE(24); }
  196. // Decode the PE optional header windows fields
  197. // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-windows-specific-fields-image-only
  198. obj.header.peWindows = {}
  199. if (obj.header.pe32plus == 0) {
  200. // 32bit header
  201. //obj.header.peWindows.imageBase = optinalHeader.readUInt32LE(28);
  202. obj.header.peWindows.sectionAlignment = optinalHeader.readUInt32LE(32);
  203. obj.header.peWindows.fileAlignment = optinalHeader.readUInt32LE(36);
  204. obj.header.peWindows.majorOperatingSystemVersion = optinalHeader.readUInt16LE(40);
  205. obj.header.peWindows.minorOperatingSystemVersion = optinalHeader.readUInt16LE(42);
  206. obj.header.peWindows.majorImageVersion = optinalHeader.readUInt16LE(44);
  207. obj.header.peWindows.minorImageVersion = optinalHeader.readUInt16LE(46);
  208. obj.header.peWindows.majorSubsystemVersion = optinalHeader.readUInt16LE(48);
  209. obj.header.peWindows.minorSubsystemVersion = optinalHeader.readUInt16LE(50);
  210. obj.header.peWindows.win32VersionValue = optinalHeader.readUInt32LE(52);
  211. obj.header.peWindows.sizeOfImage = optinalHeader.readUInt32LE(56);
  212. obj.header.peWindows.sizeOfHeaders = optinalHeader.readUInt32LE(60);
  213. obj.header.peWindows.checkSum = optinalHeader.readUInt32LE(64);
  214. obj.header.peWindows.subsystem = optinalHeader.readUInt16LE(68);
  215. obj.header.peWindows.dllCharacteristics = optinalHeader.readUInt16LE(70);
  216. //obj.header.peWindows.sizeOfStackReserve = optinalHeader.readUInt32LE(72);
  217. //obj.header.peWindows.sizeOfStackCommit = optinalHeader.readUInt32LE(76);
  218. //obj.header.peWindows.sizeOfHeapReserve = optinalHeader.readUInt32LE(80);
  219. //obj.header.peWindows.sizeOfHeapCommit = optinalHeader.readUInt32LE(84);
  220. obj.header.peWindows.loaderFlags = optinalHeader.readUInt32LE(88);
  221. obj.header.peWindows.numberOfRvaAndSizes = optinalHeader.readUInt32LE(92);
  222. } else {
  223. // 64bit header
  224. //obj.header.peWindows.imageBase = optinalHeader.readBigUInt64LE(24); // TODO: readBigUInt64LE is not supported in older NodeJS versions
  225. obj.header.peWindows.sectionAlignment = optinalHeader.readUInt32LE(32);
  226. obj.header.peWindows.fileAlignment = optinalHeader.readUInt32LE(36);
  227. obj.header.peWindows.majorOperatingSystemVersion = optinalHeader.readUInt16LE(40);
  228. obj.header.peWindows.minorOperatingSystemVersion = optinalHeader.readUInt16LE(42);
  229. obj.header.peWindows.majorImageVersion = optinalHeader.readUInt16LE(44);
  230. obj.header.peWindows.minorImageVersion = optinalHeader.readUInt16LE(46);
  231. obj.header.peWindows.majorSubsystemVersion = optinalHeader.readUInt16LE(48);
  232. obj.header.peWindows.minorSubsystemVersion = optinalHeader.readUInt16LE(50);
  233. obj.header.peWindows.win32VersionValue = optinalHeader.readUInt32LE(52);
  234. obj.header.peWindows.sizeOfImage = optinalHeader.readUInt32LE(56);
  235. obj.header.peWindows.sizeOfHeaders = optinalHeader.readUInt32LE(60);
  236. obj.header.peWindows.checkSum = optinalHeader.readUInt32LE(64);
  237. obj.header.peWindows.subsystem = optinalHeader.readUInt16LE(68);
  238. obj.header.peWindows.dllCharacteristics = optinalHeader.readUInt16LE(70);
  239. //obj.header.peWindows.sizeOfStackReserve = optinalHeader.readBigUInt64LE(72);
  240. //obj.header.peWindows.sizeOfStackCommit = optinalHeader.readBigUInt64LE(80);
  241. //obj.header.peWindows.sizeOfHeapReserve = optinalHeader.readBigUInt64LE(88);
  242. //obj.header.peWindows.sizeOfHeapCommit = optinalHeader.readBigUInt64LE(96);
  243. obj.header.peWindows.loaderFlags = optinalHeader.readUInt32LE(104);
  244. obj.header.peWindows.numberOfRvaAndSizes = optinalHeader.readUInt32LE(108);
  245. }
  246. // Decode the PE optional header data directories
  247. // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-data-directories-image-only
  248. obj.header.dataDirectories = {}
  249. const pePlusOffset = (obj.header.pe32plus == 0) ? 0 : 16; // This header is the same for 32 and 64 bit, but 64bit is offset by 16 bytes.
  250. obj.header.dataDirectories.exportTable = { addr: optinalHeader.readUInt32LE(96 + pePlusOffset), size: optinalHeader.readUInt32LE(100 + pePlusOffset) };
  251. obj.header.dataDirectories.importTable = { addr: optinalHeader.readUInt32LE(104 + pePlusOffset), size: optinalHeader.readUInt32LE(108 + pePlusOffset) };
  252. obj.header.dataDirectories.resourceTable = { addr: optinalHeader.readUInt32LE(112 + pePlusOffset), size: optinalHeader.readUInt32LE(116 + pePlusOffset) }; // Same as .rsrc virtual address & size
  253. obj.header.dataDirectories.exceptionTableAddr = { addr: optinalHeader.readUInt32LE(120 + pePlusOffset), size: optinalHeader.readUInt32LE(124 + pePlusOffset) }; // Same as .pdata virtual address & size
  254. obj.header.dataDirectories.certificateTable = { addr: optinalHeader.readUInt32LE(128 + pePlusOffset), size: optinalHeader.readUInt32LE(132 + pePlusOffset) };
  255. obj.header.dataDirectories.baseRelocationTable = { addr: optinalHeader.readUInt32LE(136 + pePlusOffset), size: optinalHeader.readUInt32LE(140 + pePlusOffset) }; // Same as .reloc virtual address & size
  256. obj.header.dataDirectories.debug = { addr: optinalHeader.readUInt32LE(144 + pePlusOffset), size: optinalHeader.readUInt32LE(148 + pePlusOffset) };
  257. // obj.header.dataDirectories.architecture = optinalHeader.readBigUInt64LE(152 + pePlusOffset); // Must be zero
  258. obj.header.dataDirectories.globalPtr = { addr: optinalHeader.readUInt32LE(160 + pePlusOffset), size: optinalHeader.readUInt32LE(164 + pePlusOffset) };
  259. obj.header.dataDirectories.tLSTable = { addr: optinalHeader.readUInt32LE(168 + pePlusOffset), size: optinalHeader.readUInt32LE(172 + pePlusOffset) };
  260. obj.header.dataDirectories.loadConfigTable = { addr: optinalHeader.readUInt32LE(176 + pePlusOffset), size: optinalHeader.readUInt32LE(180 + pePlusOffset) };
  261. obj.header.dataDirectories.boundImport = { addr: optinalHeader.readUInt32LE(184 + pePlusOffset), size: optinalHeader.readUInt32LE(188 + pePlusOffset) };
  262. obj.header.dataDirectories.iAT = { addr: optinalHeader.readUInt32LE(192 + pePlusOffset), size: optinalHeader.readUInt32LE(196 + pePlusOffset) };
  263. obj.header.dataDirectories.delayImportDescriptor = { addr: optinalHeader.readUInt32LE(200 + pePlusOffset), size: optinalHeader.readUInt32LE(204 + pePlusOffset) };
  264. obj.header.dataDirectories.clrRuntimeHeader = { addr: optinalHeader.readUInt32LE(208 + pePlusOffset), size: optinalHeader.readUInt32LE(212 + pePlusOffset) };
  265. // obj.header.dataDirectories.reserved = optinalHeader.readBigUInt64LE(216 + pePlusOffset); // Must be zero
  266. // Get the certificate table location and size
  267. obj.header.sigpos = obj.header.dataDirectories.certificateTable.addr;
  268. obj.header.siglen = obj.header.dataDirectories.certificateTable.size
  269. obj.header.signed = ((obj.header.sigpos != 0) && (obj.header.siglen != 0));
  270. // The section headers are located after the optional PE header
  271. obj.header.SectionHeadersPtr = obj.header.peOptionalHeaderLocation + obj.header.coff.sizeOfOptionalHeader;
  272. // Read the sections
  273. obj.header.sections = {};
  274. for (var i = 0; i < obj.header.coff.numberOfSections; i++) {
  275. var section = {};
  276. buf = readFileSlice(obj.header.SectionHeadersPtr + (i * 40), 40);
  277. if ((buf[0] != 46) && (buf[0] != 95)) { obj.close(); return false; }; // Name of the section must start with a dot or underscore. If not, something is wrong.
  278. var sectionName = buf.slice(0, 8).toString().trim('\0');
  279. var j = sectionName.indexOf('\0');
  280. if (j >= 0) { sectionName = sectionName.substring(0, j); } // Trim any trailing zeroes
  281. section.ptr = obj.header.SectionHeadersPtr + (i * 40);
  282. section.virtualSize = buf.readUInt32LE(8);
  283. section.virtualAddr = buf.readUInt32LE(12);
  284. section.rawSize = buf.readUInt32LE(16);
  285. section.rawAddr = buf.readUInt32LE(20);
  286. section.relocAddr = buf.readUInt32LE(24);
  287. section.lineNumbers = buf.readUInt32LE(28);
  288. section.relocNumber = buf.readUInt16LE(32);
  289. section.lineNumbersNumber = buf.readUInt16LE(34);
  290. section.characteristics = buf.readUInt32LE(36);
  291. obj.header.sections[sectionName] = section;
  292. }
  293. // Compute the checkSum value for this file
  294. obj.header.peWindows.checkSumActual = runChecksum();
  295. // If there is a .rsrc section, read the resource information and locations
  296. if (obj.header.sections['.rsrc'] != null) {
  297. obj.resources = readResourceTable(obj.header.sections['.rsrc'].rawAddr, 0); // Read all resources recursively
  298. }
  299. if (obj.header.signed) {
  300. // Read signature block
  301. // Check if the file size allows for the signature block
  302. if (obj.filesize < (obj.header.sigpos + obj.header.siglen)) { obj.close(); return false; } // Executable file too short to contain the signature block.
  303. // Remove the padding if needed
  304. var i, pkcs7raw = readFileSlice(obj.header.sigpos + 8, obj.header.siglen - 8);
  305. var derlen = forge.asn1.getBerValueLength(forge.util.createBuffer(pkcs7raw.slice(1, 5))) + 4;
  306. if (derlen != pkcs7raw.length) { pkcs7raw = pkcs7raw.slice(0, derlen); }
  307. // Decode the signature block and check that it's valid
  308. var pkcs7der = null, valid = false;
  309. try { pkcs7der = forge.asn1.fromDer(forge.util.createBuffer(pkcs7raw)); } catch (ex) { }
  310. try { valid = ((pkcs7der != null) && (forge.asn1.derToOid(pkcs7der.value[1].value[0].value[2].value[0].value) == "1.3.6.1.4.1.311.2.1.4")); } catch (ex) { }
  311. if (pkcs7der == null) {
  312. // Can't decode the signature
  313. obj.header.sigpos = 0;
  314. obj.header.siglen = 0;
  315. obj.header.signed = false;
  316. } else {
  317. // To work around ForgeJS PKCS#7 limitation, this may break PKCS7 verify if ForgeJS adds support for it in the future
  318. // Switch content type from "1.3.6.1.4.1.311.2.1.4" to "1.2.840.113549.1.7.1"
  319. pkcs7der.value[1].value[0].value[2].value[0].value = forge.asn1.oidToDer(forge.pki.oids.data).data;
  320. // Decode the PKCS7 message
  321. var pkcs7 = null, pkcs7content = null;
  322. try {
  323. pkcs7 = p7.messageFromAsn1(pkcs7der);
  324. pkcs7content = pkcs7.rawCapture.content.value[0];
  325. } catch (ex) { }
  326. if ((pkcs7 == null) || (pkcs7content == null)) {
  327. // Can't decode the signature
  328. obj.header.sigpos = 0;
  329. obj.header.siglen = 0;
  330. obj.header.signed = false;
  331. } else {
  332. // Verify a PKCS#7 signature
  333. // Verify is not currently supported in node-forge, but if implemented in the future, this code could work.
  334. //var caStore = forge.pki.createCaStore();
  335. //for (var i in obj.certificates) { caStore.addCertificate(obj.certificates[i]); }
  336. // Return is true if all signatures are valid and chain up to a provided CA
  337. //if (!pkcs7.verify(caStore)) { throw ('Executable file has an invalid signature.'); }
  338. // Get the signing attributes
  339. obj.signingAttribs = [];
  340. try {
  341. for (var i in pkcs7.rawCapture.authenticatedAttributes) {
  342. if (
  343. (pkcs7.rawCapture.authenticatedAttributes[i].value != null) &&
  344. (pkcs7.rawCapture.authenticatedAttributes[i].value[0] != null) &&
  345. (pkcs7.rawCapture.authenticatedAttributes[i].value[0].value != null) &&
  346. (pkcs7.rawCapture.authenticatedAttributes[i].value[1] != null) &&
  347. (pkcs7.rawCapture.authenticatedAttributes[i].value[1].value != null) &&
  348. (pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0] != null) &&
  349. (pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value != null) &&
  350. (forge.asn1.derToOid(pkcs7.rawCapture.authenticatedAttributes[i].value[0].value) == obj.Oids.SPC_SP_OPUS_INFO_OBJID)) {
  351. for (var j in pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value) {
  352. if (
  353. (pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value[j] != null) &&
  354. (pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value[j].value != null) &&
  355. (pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value[j].value[0] != null) &&
  356. (pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value[j].value[0].value != null)
  357. ) {
  358. var v = pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value[j].value[0].value;
  359. if (v.startsWith('http://') || v.startsWith('https://') || ((v.length % 2) == 1)) { obj.signingAttribs.push(v); } else {
  360. var r = ''; // This string value is in UCS2 format, convert it to a normal string.
  361. for (var k = 0; k < v.length; k += 2) { r += String.fromCharCode((v.charCodeAt(k + 8) << 8) + v.charCodeAt(k + 1)); }
  362. obj.signingAttribs.push(r);
  363. }
  364. }
  365. }
  366. }
  367. }
  368. } catch (ex) { }
  369. // Set the certificate chain
  370. obj.certificates = pkcs7.certificates;
  371. // Set the signature
  372. obj.signature = Buffer.from(pkcs7.rawCapture.signature, 'binary');
  373. // Get the file hashing algorithm
  374. var hashAlgoOid = forge.asn1.derToOid(pkcs7content.value[1].value[0].value[0].value);
  375. switch (hashAlgoOid) {
  376. case forge.pki.oids.sha256: { obj.fileHashAlgo = 'sha256'; break; }
  377. case forge.pki.oids.sha384: { obj.fileHashAlgo = 'sha384'; break; }
  378. case forge.pki.oids.sha512: { obj.fileHashAlgo = 'sha512'; break; }
  379. case forge.pki.oids.sha224: { obj.fileHashAlgo = 'sha224'; break; }
  380. case forge.pki.oids.md5: { obj.fileHashAlgo = 'md5'; break; }
  381. }
  382. // Get the signed file hash
  383. obj.fileHashSigned = Buffer.from(pkcs7content.value[1].value[1].value, 'binary')
  384. // Compute the actual file hash
  385. if (obj.fileHashAlgo != null) { obj.fileHashActual = obj.getHash(obj.fileHashAlgo); }
  386. }
  387. }
  388. }
  389. return true;
  390. }
  391. // Make a timestamp signature request
  392. obj.timeStampRequest = function (args, func) {
  393. // Create the timestamp request in DER format
  394. const asn1 = forge.asn1;
  395. const pkcs7dataOid = asn1.oidToDer('1.2.840.113549.1.7.1').data;
  396. const microsoftCodeSigningOid = asn1.oidToDer('1.3.6.1.4.1.311.3.2.1').data;
  397. const asn1obj =
  398. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  399. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, microsoftCodeSigningOid),
  400. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  401. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, pkcs7dataOid),
  402. asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
  403. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, obj.signature.toString('binary')) // Signature here
  404. ])
  405. ])
  406. ]);
  407. // Serialize an ASN.1 object to DER format in Base64
  408. const requestBody = Buffer.from(asn1.toDer(asn1obj).data, 'binary').toString('base64');
  409. // Make an HTTP request
  410. const options = { url: args.time, proxy: args.proxy };
  411. // Make a request to the time server
  412. httpRequest(options, requestBody, function (err, data) {
  413. if (err != null) { func(err); return; }
  414. // Decode the timestamp signature block
  415. var timepkcs7der = null;
  416. try { timepkcs7der = forge.asn1.fromDer(forge.util.createBuffer(Buffer.from(data, 'base64').toString('binary'))); } catch (ex) { func("Unable to parse time-stamp response: " + ex); return; }
  417. // Decode the executable signature block
  418. var pkcs7der = null;
  419. try {
  420. var pkcs7der = forge.asn1.fromDer(forge.util.createBuffer(Buffer.from(obj.getRawSignatureBlock(), 'base64').toString('binary')));
  421. // Get the ASN1 certificates used to sign the timestamp and add them to the certs in the PKCS7 of the executable
  422. // TODO: We could look to see if the certificate is already present in the executable
  423. const timeasn1Certs = timepkcs7der.value[1].value[0].value[3].value;
  424. for (var i in timeasn1Certs) { pkcs7der.value[1].value[0].value[3].value.push(timeasn1Certs[i]); }
  425. // Remove any existing time stamp signatures
  426. var newValues = [];
  427. for (var i in pkcs7der.value[1].value[0].value[4].value[0].value) {
  428. const j = pkcs7der.value[1].value[0].value[4].value[0].value[i];
  429. if ((j.tagClass != 128) || (j.type != 1)) { newValues.push(j); } // If this is not a time stamp, add it to out new list.
  430. }
  431. pkcs7der.value[1].value[0].value[4].value[0].value = newValues; // Set the new list
  432. // Get the time signature and add it to the executables PKCS7
  433. const timeasn1Signature = timepkcs7der.value[1].value[0].value[4];
  434. const countersignatureOid = asn1.oidToDer('1.2.840.113549.1.9.6').data;
  435. const asn1obj2 =
  436. asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
  437. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  438. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, countersignatureOid),
  439. timeasn1Signature
  440. ])
  441. ]);
  442. pkcs7der.value[1].value[0].value[4].value[0].value.push(asn1obj2);
  443. // Re-encode the executable signature block
  444. const p7signature = Buffer.from(forge.asn1.toDer(pkcs7der).data, 'binary');
  445. // Open the output file
  446. var output = null;
  447. try { output = fs.openSync(args.out, 'w+'); } catch (ex) { }
  448. if (output == null) return false;
  449. var tmp, written = 0;
  450. var executableSize = obj.header.sigpos ? obj.header.sigpos : this.filesize;
  451. // Compute pre-header length and copy that to the new file
  452. var preHeaderLen = (obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
  453. var tmp = readFileSlice(written, preHeaderLen);
  454. fs.writeSync(output, tmp);
  455. written += tmp.length;
  456. // Quad Align the results, adding padding if necessary
  457. var len = executableSize + p7signature.length;
  458. var padding = (8 - ((len) % 8)) % 8;
  459. // Write the signature header
  460. var addresstable = Buffer.alloc(8);
  461. addresstable.writeUInt32LE(executableSize);
  462. addresstable.writeUInt32LE(8 + p7signature.length + padding, 4);
  463. fs.writeSync(output, addresstable);
  464. written += addresstable.length;
  465. // Copy the rest of the file until the start of the signature block
  466. while ((executableSize - written) > 0) {
  467. tmp = readFileSlice(written, Math.min(executableSize - written, 65536));
  468. fs.writeSync(output, tmp);
  469. written += tmp.length;
  470. }
  471. // Write the signature block header and signature
  472. var win = Buffer.alloc(8); // WIN CERTIFICATE Structure
  473. win.writeUInt32LE(p7signature.length + padding + 8); // DWORD length
  474. win.writeUInt16LE(512, 4); // WORD revision
  475. win.writeUInt16LE(2, 6); // WORD type
  476. fs.writeSync(output, win);
  477. fs.writeSync(output, p7signature);
  478. if (padding > 0) { fs.writeSync(output, Buffer.alloc(padding, 0)); }
  479. written += (p7signature.length + padding + 8);
  480. // Compute the checksum and write it in the PE header checksum location
  481. var tmp = Buffer.alloc(4);
  482. tmp.writeUInt32LE(runChecksumOnFile(output, written, ((obj.header.peOptionalHeaderLocation + 64) / 4)));
  483. fs.writeSync(output, tmp, 0, 4, obj.header.peOptionalHeaderLocation + 64);
  484. // Close the file
  485. fs.closeSync(output);
  486. // Indicate we are done
  487. func(null);
  488. } catch (ex) { func('' + ex); return; }
  489. });
  490. }
  491. // Read a resource table.
  492. // ptr: The pointer to the start of the resource section
  493. // offset: The offset start of the resource table to read
  494. function readResourceTable(ptr, offset) {
  495. var buf = readFileSlice(ptr + offset, 16);
  496. var r = {};
  497. r.characteristics = buf.readUInt32LE(0);
  498. r.timeDateStamp = buf.readUInt32LE(4);
  499. r.majorVersion = buf.readUInt16LE(8);
  500. r.minorVersion = buf.readUInt16LE(10);
  501. var numberOfNamedEntries = buf.readUInt16LE(12);
  502. var numberOfIdEntries = buf.readUInt16LE(14);
  503. r.entries = [];
  504. var totalResources = numberOfNamedEntries + numberOfIdEntries;
  505. //console.log('readResourceTable', offset, 16 + (totalResources) * 8, offset + (16 + (totalResources) * 8));
  506. for (var i = 0; i < totalResources; i++) {
  507. buf = readFileSlice(ptr + offset + 16 + (i * 8), 8);
  508. var resource = {};
  509. resource.name = buf.readUInt32LE(0);
  510. var offsetToData = buf.readUInt32LE(4);
  511. if ((resource.name & 0x80000000) != 0) {
  512. var oname = resource.name;
  513. resource.name = readLenPrefixUnicodeString(ptr + (resource.name - 0x80000000));
  514. //console.log('readResourceName', offset + (oname - 0x80000000), 2 + (resource.name.length * 2), offset + (oname - 0x80000000) + (2 + resource.name.length * 2), resource.name);
  515. }
  516. if ((offsetToData & 0x80000000) != 0) { resource.table = readResourceTable(ptr, offsetToData - 0x80000000); } else { resource.item = readResourceItem(ptr, offsetToData); }
  517. r.entries.push(resource);
  518. }
  519. return r;
  520. }
  521. // Read a resource item
  522. // ptr: The pointer to the start of the resource section
  523. // offset: The offset start of the resource item to read
  524. function readResourceItem(ptr, offset) {
  525. //console.log('readResourceItem', offset, 16, offset + 16);
  526. var buf = readFileSlice(ptr + offset, 16), r = {};
  527. r.offsetToData = buf.readUInt32LE(0);
  528. r.size = buf.readUInt32LE(4);
  529. //console.log('readResourceData', r.offsetToData - obj.header.sections['.rsrc'].virtualAddr, r.size, r.offsetToData + r.size - obj.header.sections['.rsrc'].virtualAddr);
  530. r.codePage = buf.readUInt32LE(8);
  531. //r.reserved = buf.readUInt32LE(12);
  532. return r;
  533. }
  534. // Read a unicode stting that starts with the string length as the first byte.
  535. function readLenPrefixUnicodeString(ptr) {
  536. var nameLen = readFileSlice(ptr, 2).readUInt16LE(0);
  537. var buf = readFileSlice(ptr + 2, nameLen * 2), name = '';
  538. for (var i = 0; i < nameLen; i++) { name += String.fromCharCode(buf.readUInt16LE(i * 2)); }
  539. return name;
  540. }
  541. // Generate a complete resource section and pad the section
  542. function generateResourceSection(resources) {
  543. // Call a resursive method the compute the size needed for each element
  544. const resSizes = { tables: 0, items: 0, names: 0, data: 0 };
  545. getResourceSectionSize(resources, resSizes);
  546. // Pad the resource section & allocate the buffer
  547. const fileAlign = obj.header.peWindows.fileAlignment
  548. var resSizeTotal = resSizes.tables + resSizes.items + resSizes.names + resSizes.data;
  549. var resNoPadding = resSizeTotal + 4; // TODO: Not sure why this is off by 4
  550. if ((resSizeTotal % fileAlign) != 0) { resSizeTotal += (fileAlign - (resSizeTotal % fileAlign)); }
  551. const resSectionBuffer = Buffer.alloc(resSizeTotal);
  552. // Write the resource section, calling a recursive method
  553. const resPointers = { tables: 0, items: resSizes.tables, names: resSizes.tables + resSizes.items, data: resSizes.tables + resSizes.items + resSizes.names };
  554. createResourceSection(resources, resSectionBuffer, resPointers);
  555. //console.log('generateResourceSection', resPointers);
  556. // Done, return the result
  557. return { size: resNoPadding, data: resSectionBuffer };
  558. }
  559. // Return the total size of a resource header, this is a recursive method
  560. function getResourceSectionSize(resources, sizes) {
  561. sizes.tables += (16 + (resources.entries.length * 8));
  562. for (var i in resources.entries) {
  563. if (typeof resources.entries[i].name == 'string') {
  564. var dataSize = (2 + (resources.entries[i].name.length * 2));
  565. if ((dataSize % 8) != 0) { dataSize += (8 - (dataSize % 8)); }
  566. sizes.names += dataSize;
  567. }
  568. if (resources.entries[i].table) { getResourceSectionSize(resources.entries[i].table, sizes); }
  569. else if (resources.entries[i].item) {
  570. sizes.items += 16;
  571. if (resources.entries[i].item.buffer) {
  572. sizes.data += resources.entries[i].item.buffer.length;
  573. } else {
  574. var dataSize = resources.entries[i].item.size;
  575. if ((dataSize % 8) != 0) { dataSize += (8 - (dataSize % 8)); }
  576. sizes.data += dataSize;
  577. }
  578. }
  579. }
  580. }
  581. // Write the resource section in the buffer, this is a recursive method
  582. function createResourceSection(resources, buf, resPointers) {
  583. var numberOfNamedEntries = 0, numberOfIdEntries = 0, ptr = resPointers.tables;
  584. //console.log('createResourceSection', resPointers, ptr);
  585. // Figure out how many items we have to save
  586. for (var i in resources.entries) {
  587. if (typeof resources.entries[i].name == 'string') { numberOfNamedEntries++; } else { numberOfIdEntries++; }
  588. }
  589. // Move the table pointer forward
  590. resPointers.tables += (16 + (8 * numberOfNamedEntries) + (8 * numberOfIdEntries));
  591. // Write the table header
  592. buf.writeUInt32LE(resources.characteristics, ptr);
  593. buf.writeUInt32LE(resources.timeDateStamp, ptr + 4);
  594. buf.writeUInt16LE(resources.majorVersion, ptr + 8);
  595. buf.writeUInt16LE(resources.minorVersion, ptr + 10);
  596. buf.writeUInt16LE(numberOfNamedEntries, ptr + 12);
  597. buf.writeUInt16LE(numberOfIdEntries, ptr + 14);
  598. // For each table entry, write the entry for it
  599. for (var i in resources.entries) {
  600. // Write the name
  601. var name = resources.entries[i].name;
  602. if (typeof resources.entries[i].name == 'string') {
  603. // Set the pointer to the name
  604. name = resPointers.names + 0x80000000;
  605. // Write the name length, followed by the name string in unicode
  606. buf.writeUInt16LE(resources.entries[i].name.length, resPointers.names);
  607. for (var j = 0; j < resources.entries[i].name.length; j++) {
  608. buf.writeUInt16LE(resources.entries[i].name.charCodeAt(j), 2 + resPointers.names + (j * 2));
  609. }
  610. // Move the names pointer forward, 8 byte align
  611. var dataSize = (2 + (resources.entries[i].name.length * 2));
  612. if ((dataSize % 8) != 0) { dataSize += (8 - (dataSize % 8)); }
  613. resPointers.names += dataSize;
  614. }
  615. buf.writeUInt32LE(name, ptr + 16 + (i * 8));
  616. // Write the data
  617. var data;
  618. if (resources.entries[i].table) {
  619. // This is a pointer to a table entry
  620. data = resPointers.tables + 0x80000000;
  621. createResourceSection(resources.entries[i].table, buf, resPointers);
  622. } else if (resources.entries[i].item) {
  623. // This is a pointer to a data entry
  624. data = resPointers.items;
  625. // Write the data
  626. var entrySize = 0;
  627. if (resources.entries[i].item.buffer) {
  628. // Write the data from given buffer
  629. resources.entries[i].item.buffer.copy(buf, resPointers.data, 0, resources.entries[i].item.buffer.length);
  630. entrySize = resources.entries[i].item.buffer.length;
  631. } else {
  632. // Write the data from original file
  633. const actualPtr = (resources.entries[i].item.offsetToData - obj.header.sections['.rsrc'].virtualAddr) + obj.header.sections['.rsrc'].rawAddr;
  634. const tmp = readFileSlice(actualPtr, resources.entries[i].item.size);
  635. tmp.copy(buf, resPointers.data, 0, tmp.length);
  636. entrySize = resources.entries[i].item.size;;
  637. }
  638. // Write the item entry
  639. buf.writeUInt32LE(resPointers.data + obj.header.sections['.rsrc'].virtualAddr, resPointers.items); // Write the pointer relative to the virtual address
  640. buf.writeUInt32LE(entrySize, resPointers.items + 4);
  641. buf.writeUInt32LE(resources.entries[i].item.codePage, resPointers.items + 8);
  642. buf.writeUInt32LE(resources.entries[i].item.reserved, resPointers.items + 12);
  643. // Move items pointers forward
  644. resPointers.items += 16;
  645. var dataSize = entrySize;
  646. if ((dataSize % 8) != 0) { dataSize += (8 - (dataSize % 8)); }
  647. resPointers.data += dataSize;
  648. }
  649. buf.writeUInt32LE(data, ptr + 20 + (i * 8));
  650. }
  651. }
  652. // Convert a unicode buffer to a string
  653. function unicodeToString(buf) {
  654. var r = '', c;
  655. for (var i = 0; i < (buf.length / 2) ; i++) {
  656. c = buf.readUInt16LE(i * 2);
  657. if (c != 0) { r += String.fromCharCode(c); } else { return r; }
  658. }
  659. return r;
  660. }
  661. // Convert a string to a unicode buffer
  662. // Input is a string, a buffer to write to and the offset in the buffer (0 is default).
  663. function stringToUnicode(str, buf, offset) {
  664. if (offset == null) { offset = 0; }
  665. for (var i = 0; i < str.length; i++) { buf.writeInt16LE(str.charCodeAt(i), offset + (i * 2)); }
  666. }
  667. var resourceDefaultNames = {
  668. 'bitmaps': 2,
  669. 'icon': 3,
  670. 'dialogs': 5,
  671. 'iconGroups': 14,
  672. 'versionInfo': 16,
  673. 'configurationFiles': 24
  674. }
  675. // Return the raw signature block buffer with padding removed
  676. obj.getRawSignatureBlock = function () {
  677. if ((obj.header.sigpos == 0) || (obj.header.siglen == 0)) return null;
  678. var pkcs7raw = readFileSlice(obj.header.sigpos + 8, obj.header.siglen - 8);
  679. var derlen = forge.asn1.getBerValueLength(forge.util.createBuffer(pkcs7raw.slice(1, 5))) + 4;
  680. if (derlen != pkcs7raw.length) { pkcs7raw = pkcs7raw.slice(0, derlen); }
  681. return pkcs7raw;
  682. }
  683. // Get bitmaps information from resource
  684. obj.getBitmapInfo = function () {
  685. const r = {}, ptr = obj.header.sections['.rsrc'].rawAddr;
  686. // Find and parse each icon
  687. const bitmaps = {}
  688. for (var i = 0; i < obj.resources.entries.length; i++) {
  689. if (obj.resources.entries[i].name == resourceDefaultNames.bitmaps) {
  690. for (var j = 0; j < obj.resources.entries[i].table.entries.length; j++) {
  691. const bitmapName = obj.resources.entries[i].table.entries[j].name;
  692. const offsetToData = obj.resources.entries[i].table.entries[j].table.entries[0].item.offsetToData;
  693. const size = obj.resources.entries[i].table.entries[j].table.entries[0].item.size;
  694. const actualPtr = (offsetToData - obj.header.sections['.rsrc'].virtualAddr) + ptr;
  695. bitmaps[bitmapName] = readFileSlice(actualPtr, size);
  696. }
  697. }
  698. }
  699. return bitmaps;
  700. }
  701. // Get icon information from resource
  702. obj.getIconInfo = function () {
  703. const r = {}, ptr = obj.header.sections['.rsrc'].rawAddr;
  704. // Find and parse each icon
  705. const icons = {}
  706. for (var i = 0; i < obj.resources.entries.length; i++) {
  707. if (obj.resources.entries[i].name == resourceDefaultNames.icon) {
  708. for (var j = 0; j < obj.resources.entries[i].table.entries.length; j++) {
  709. const iconName = obj.resources.entries[i].table.entries[j].name;
  710. const offsetToData = obj.resources.entries[i].table.entries[j].table.entries[0].item.offsetToData;
  711. const size = obj.resources.entries[i].table.entries[j].table.entries[0].item.size;
  712. const actualPtr = (offsetToData - obj.header.sections['.rsrc'].virtualAddr) + ptr;
  713. icons[iconName] = readFileSlice(actualPtr, size);
  714. }
  715. }
  716. }
  717. // Find and parse each icon group
  718. for (var i = 0; i < obj.resources.entries.length; i++) {
  719. if (obj.resources.entries[i].name == resourceDefaultNames.iconGroups) {
  720. for (var j = 0; j < obj.resources.entries[i].table.entries.length; j++) {
  721. const groupName = obj.resources.entries[i].table.entries[j].name;
  722. const offsetToData = obj.resources.entries[i].table.entries[j].table.entries[0].item.offsetToData;
  723. const size = obj.resources.entries[i].table.entries[j].table.entries[0].item.size;
  724. const actualPtr = (offsetToData - obj.header.sections['.rsrc'].virtualAddr) + ptr;
  725. const group = {};
  726. const groupData = readFileSlice(actualPtr, size);
  727. // Parse NEWHEADER structure: https://docs.microsoft.com/en-us/windows/win32/menurc/newheader
  728. group.resType = groupData.readUInt16LE(2);
  729. group.resCount = groupData.readUInt16LE(4);
  730. // Parse many RESDIR structure: https://docs.microsoft.com/en-us/windows/win32/menurc/resdir
  731. group.icons = {};
  732. for (var p = 6; p < size; p += 14) {
  733. var icon = {}
  734. icon.width = groupData[p];
  735. icon.height = groupData[p + 1];
  736. icon.colorCount = groupData[p + 2];
  737. icon.planes = groupData.readUInt16LE(p + 4);
  738. icon.bitCount = groupData.readUInt16LE(p + 6);
  739. icon.bytesInRes = groupData.readUInt32LE(p + 8);
  740. icon.iconCursorId = groupData.readUInt16LE(p + 12);
  741. icon.icon = icons[icon.iconCursorId];
  742. group.icons[icon.iconCursorId] = icon;
  743. }
  744. // Add an icon group
  745. r[groupName] = group;
  746. }
  747. }
  748. }
  749. return r;
  750. }
  751. // Set bitmap information
  752. obj.setBitmapInfo = function (bitmapInfo) {
  753. // Delete all bitmaps resources
  754. var resourcesEntries = [];
  755. for (var i = 0; i < obj.resources.entries.length; i++) {
  756. if (obj.resources.entries[i].name != resourceDefaultNames.bitmaps) {
  757. resourcesEntries.push(obj.resources.entries[i]);
  758. }
  759. }
  760. obj.resources.entries = resourcesEntries;
  761. // Add all bitmap entries
  762. const bitmapEntry = { name: resourceDefaultNames.bitmaps, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [] } };
  763. for (var i in bitmapInfo) {
  764. var name = i;
  765. if (parseInt(i) == name) { name = parseInt(i); }
  766. const bitmapItemEntry = { name: name, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [{ name: 1033, item: { buffer: bitmapInfo[i], codePage: 0 } }] } }
  767. bitmapEntry.table.entries.push(bitmapItemEntry);
  768. }
  769. obj.resources.entries.push(bitmapEntry);
  770. // Sort the resources by name. This is required.
  771. function resSort(a, b) {
  772. if ((typeof a == 'string') && (typeof b == 'string')) { if (a < b) return -1; if (a > b) return 1; return 0; }
  773. if ((typeof a == 'number') && (typeof b == 'number')) { return a - b; }
  774. if ((typeof a == 'string') && (typeof b == 'number')) { return -1; }
  775. return 1;
  776. }
  777. const names = [];
  778. for (var i = 0; i < obj.resources.entries.length; i++) { names.push(obj.resources.entries[i].name); }
  779. names.sort(resSort);
  780. var newEntryOrder = [];
  781. for (var i in names) {
  782. for (var j = 0; j < obj.resources.entries.length; j++) {
  783. if (obj.resources.entries[j].name == names[i]) { newEntryOrder.push(obj.resources.entries[j]); }
  784. }
  785. }
  786. obj.resources.entries = newEntryOrder;
  787. }
  788. // Set icon information
  789. obj.setIconInfo = function (iconInfo) {
  790. // Delete all icon and icon groups resources
  791. var resourcesEntries = [];
  792. for (var i = 0; i < obj.resources.entries.length; i++) {
  793. if ((obj.resources.entries[i].name != resourceDefaultNames.icon) && (obj.resources.entries[i].name != resourceDefaultNames.iconGroups)) {
  794. resourcesEntries.push(obj.resources.entries[i]);
  795. }
  796. }
  797. obj.resources.entries = resourcesEntries;
  798. // Count the icon groups and re-number all icons
  799. var iconGroupCount = 0, nextIconNumber = 1;
  800. for (var i in iconInfo) {
  801. iconGroupCount++;
  802. var xicons = {};
  803. for (var j in iconInfo[i].icons) { xicons[nextIconNumber++] = iconInfo[i].icons[j]; }
  804. iconInfo[i].icons = xicons;
  805. }
  806. if (iconGroupCount == 0) return; // If there are no icon groups, we are done
  807. // Add the new icons entry
  808. const iconsEntry = { name: resourceDefaultNames.icon, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [] } };
  809. for (var i in iconInfo) {
  810. for (var j in iconInfo[i].icons) {
  811. var name = j;
  812. if (parseInt(j) == name) { name = parseInt(j); }
  813. const iconItemEntry = { name: name, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [{ name: 1033, item: { buffer: iconInfo[i].icons[j].icon, codePage: 0 } }] } }
  814. iconsEntry.table.entries.push(iconItemEntry);
  815. }
  816. }
  817. obj.resources.entries.push(iconsEntry);
  818. // Add the new icon group entry
  819. const groupEntry = { name: resourceDefaultNames.iconGroups, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [] } };
  820. for (var i in iconInfo) {
  821. // Build icon group struct
  822. var iconCount = 0, p = 6;
  823. for (var j in iconInfo[i].icons) { iconCount++; }
  824. const buf = Buffer.alloc(6 + (iconCount * 14));
  825. buf.writeUInt16LE(iconInfo[i].resType, 2);
  826. buf.writeUInt16LE(iconCount, 4);
  827. for (var j in iconInfo[i].icons) {
  828. buf[p] = iconInfo[i].icons[j].width;
  829. buf[p + 1] = iconInfo[i].icons[j].height;
  830. buf[p + 2] = iconInfo[i].icons[j].colorCount;
  831. buf.writeUInt16LE(iconInfo[i].icons[j].planes, p + 4);
  832. buf.writeUInt16LE(iconInfo[i].icons[j].bitCount, p + 6);
  833. buf.writeUInt32LE(iconInfo[i].icons[j].bytesInRes, p + 8);
  834. buf.writeUInt16LE(j, p + 12);
  835. p += 14;
  836. }
  837. var name = i;
  838. if (parseInt(i) == name) { name = parseInt(i); }
  839. const groupItemEntry = { name: name, table: { characteristics: 0, timeDateStamp: 0, majorVersion: 0, minorVersion: 0, entries: [{ name: 1033, item: { buffer: buf, codePage: 0 } }] } }
  840. groupEntry.table.entries.push(groupItemEntry);
  841. }
  842. obj.resources.entries.push(groupEntry);
  843. // Sort the resources by name. This is required.
  844. function resSort(a, b) {
  845. if ((typeof a == 'string') && (typeof b == 'string')) { if (a < b) return -1; if (a > b) return 1; return 0; }
  846. if ((typeof a == 'number') && (typeof b == 'number')) { return a - b; }
  847. if ((typeof a == 'string') && (typeof b == 'number')) { return -1; }
  848. return 1;
  849. }
  850. const names = [];
  851. for (var i = 0; i < obj.resources.entries.length; i++) { names.push(obj.resources.entries[i].name); }
  852. names.sort(resSort);
  853. var newEntryOrder = [];
  854. for (var i in names) {
  855. for (var j = 0; j < obj.resources.entries.length; j++) {
  856. if (obj.resources.entries[j].name == names[i]) { newEntryOrder.push(obj.resources.entries[j]); }
  857. }
  858. }
  859. obj.resources.entries = newEntryOrder;
  860. }
  861. // Decode the version information from the resource
  862. obj.getVersionInfo = function () {
  863. var r = {}, info = readVersionInfo(getVersionInfoData(), 0);
  864. if ((info == null) || (info.stringFiles == null)) return null;
  865. var StringFileInfo = null;
  866. for (var i in info.stringFiles) { if (info.stringFiles[i].szKey == 'StringFileInfo') { StringFileInfo = info.stringFiles[i]; } }
  867. if ((StringFileInfo == null) || (StringFileInfo.stringTable == null) || (StringFileInfo.stringTable.strings == null)) return null;
  868. const strings = StringFileInfo.stringTable.strings;
  869. for (var i in strings) { r[strings[i].key] = strings[i].value; }
  870. r['~FileVersion'] = (info.fixedFileInfo.dwFileVersionMS >> 16) + '.' + (info.fixedFileInfo.dwFileVersionMS & 0xFFFF) + '.' + (info.fixedFileInfo.dwFileVersionLS >> 16) + '.' + (info.fixedFileInfo.dwFileVersionLS & 0xFFFF);
  871. r['~ProductVersion'] = (info.fixedFileInfo.dwProductVersionMS >> 16) + '.' + (info.fixedFileInfo.dwProductVersionMS & 0xFFFF) + '.' + (info.fixedFileInfo.dwProductVersionLS >> 16) + '.' + (info.fixedFileInfo.dwProductVersionLS & 0xFFFF);
  872. return r;
  873. }
  874. // Encode the version information to the resource
  875. obj.setVersionInfo = function (versions) {
  876. // Convert the version information into a string array
  877. const stringArray = [];
  878. for (var i in versions) { if (!i.startsWith('~')) { stringArray.push({ key: i, value: versions[i] }); } }
  879. // Get the existing version data and switch the strings to the new strings
  880. var r = {}, info = readVersionInfo(getVersionInfoData(), 0);
  881. if ((info == null) || (info.stringFiles == null)) return;
  882. var StringFileInfo = null;
  883. for (var i in info.stringFiles) { if (info.stringFiles[i].szKey == 'StringFileInfo') { StringFileInfo = info.stringFiles[i]; } }
  884. if ((StringFileInfo == null) || (StringFileInfo.stringTable == null) || (StringFileInfo.stringTable.strings == null)) return;
  885. StringFileInfo.stringTable.strings = stringArray;
  886. // Set the file version
  887. if (versions['~FileVersion'] != null) {
  888. const FileVersionSplit = versions['~FileVersion'].split('.');
  889. info.fixedFileInfo.dwFileVersionMS = (parseInt(FileVersionSplit[0]) << 16) + parseInt(FileVersionSplit[1]);
  890. info.fixedFileInfo.dwFileVersionLS = (parseInt(FileVersionSplit[2]) << 16) + parseInt(FileVersionSplit[3]);
  891. }
  892. // Set the product version
  893. if (versions['~ProductVersion'] != null) {
  894. const ProductVersionSplit = versions['~ProductVersion'].split('.');
  895. info.fixedFileInfo.dwProductVersionMS = (parseInt(ProductVersionSplit[0]) << 16) + parseInt(ProductVersionSplit[1]);
  896. info.fixedFileInfo.dwProductVersionLS = (parseInt(ProductVersionSplit[2]) << 16) + parseInt(ProductVersionSplit[3]);
  897. }
  898. // Re-encode the version information into a buffer
  899. var verInfoResBufArray = [];
  900. writeVersionInfo(verInfoResBufArray, info);
  901. var verInfoRes = Buffer.concat(verInfoResBufArray);
  902. // Display all buffers
  903. //console.log('--WRITE BUF ARRAY START--');
  904. //for (var i in verInfoResBufArray) { console.log(verInfoResBufArray[i].toString('hex')); }
  905. //console.log('--WRITE BUF ARRAY END--');
  906. //console.log('OUT', Buffer.concat(verInfoResBufArray).toString('hex'));
  907. // Set the new buffer as part of the resources
  908. for (var i = 0; i < obj.resources.entries.length; i++) {
  909. if (obj.resources.entries[i].name == resourceDefaultNames.versionInfo) {
  910. const verInfo = obj.resources.entries[i].table.entries[0].table.entries[0].item;
  911. delete verInfo.size;
  912. delete verInfo.offsetToData;
  913. verInfo.buffer = verInfoRes;
  914. obj.resources.entries[i].table.entries[0].table.entries[0].item = verInfo;
  915. }
  916. }
  917. }
  918. // Return the version info data block
  919. function getVersionInfoData() {
  920. if (obj.resources == null) return null;
  921. const ptr = obj.header.sections['.rsrc'].rawAddr;
  922. for (var i = 0; i < obj.resources.entries.length; i++) {
  923. if (obj.resources.entries[i].name == resourceDefaultNames.versionInfo) {
  924. const verInfo = obj.resources.entries[i].table.entries[0].table.entries[0].item;
  925. if (verInfo.buffer != null) {
  926. return verInfo.buffer;
  927. } else {
  928. const actualPtr = (verInfo.offsetToData - obj.header.sections['.rsrc'].virtualAddr) + ptr;
  929. return readFileSlice(actualPtr, verInfo.size);
  930. }
  931. }
  932. }
  933. return null;
  934. }
  935. // Create a VS_VERSIONINFO structure as a array of buffer that is ready to be placed in the resource section
  936. // VS_VERSIONINFO structure: https://docs.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
  937. function writeVersionInfo(bufArray, info) {
  938. const buf = Buffer.alloc(40);
  939. buf.writeUInt16LE(0, 4); // wType
  940. stringToUnicode('VS_VERSION_INFO', buf, 6);
  941. bufArray.push(buf);
  942. var wLength = 40;
  943. var wValueLength = 0;
  944. if (info.fixedFileInfo != null) {
  945. const buf2 = Buffer.alloc(52);
  946. wLength += 52;
  947. wValueLength += 52;
  948. buf2.writeUInt32LE(info.fixedFileInfo.dwSignature, 0); // dwSignature
  949. buf2.writeUInt32LE(info.fixedFileInfo.dwStrucVersion, 4); // dwStrucVersion
  950. buf2.writeUInt32LE(info.fixedFileInfo.dwFileVersionMS, 8); // dwFileVersionMS
  951. buf2.writeUInt32LE(info.fixedFileInfo.dwFileVersionLS, 12); // dwFileVersionLS
  952. buf2.writeUInt32LE(info.fixedFileInfo.dwProductVersionMS, 16); // dwProductVersionMS
  953. buf2.writeUInt32LE(info.fixedFileInfo.dwProductVersionLS, 20); // dwProductVersionLS
  954. buf2.writeUInt32LE(info.fixedFileInfo.dwFileFlagsMask, 24); // dwFileFlagsMask
  955. buf2.writeUInt32LE(info.fixedFileInfo.dwFileFlags, 28); // dwFileFlags
  956. buf2.writeUInt32LE(info.fixedFileInfo.dwFileOS, 32); // dwFileOS
  957. buf2.writeUInt32LE(info.fixedFileInfo.dwFileType, 36); // dwFileType
  958. buf2.writeUInt32LE(info.fixedFileInfo.dwFileSubtype, 40); // dwFileSubtype
  959. buf2.writeUInt32LE(info.fixedFileInfo.dwFileDateMS, 44); // dwFileDateMS
  960. buf2.writeUInt32LE(info.fixedFileInfo.dwFileDateLS, 48); // dwFileDateLS
  961. bufArray.push(buf2);
  962. }
  963. if (info.stringFiles != null) { wLength += writeStringFileInfo(bufArray, info.stringFiles); }
  964. buf.writeUInt16LE(Buffer.concat(bufArray).length, 0); // wLength
  965. buf.writeUInt16LE(wValueLength, 2); // wValueLength
  966. return wLength;
  967. }
  968. // StringFileInfo structure: https://docs.microsoft.com/en-us/windows/win32/menurc/stringfileinfo
  969. function writeStringFileInfo(bufArray, stringFiles) {
  970. var totalLen = 0;
  971. for (var i in stringFiles) {
  972. var l = 6 + (stringFiles[i].szKey.length * 2);
  973. if (stringFiles[i].szKey == 'VarFileInfo') { l += 4; } // TODO: This is a hack, not sure what the correct code should be
  974. const buf2 = Buffer.alloc(padPointer(l));
  975. buf2.writeUInt16LE(1, 4); // wType
  976. stringToUnicode(stringFiles[i].szKey, buf2, 6);
  977. bufArray.push(buf2);
  978. var wLength = 0, wValueLength = 0;
  979. if (stringFiles[i].szKey == 'StringFileInfo') { wLength += writeStringTableStruct(bufArray, stringFiles[i].stringTable); }
  980. if (stringFiles[i].szKey == 'VarFileInfo') { wLength += writeVarFileInfoStruct(bufArray, stringFiles[i].varFileInfo); }
  981. buf2.writeUInt16LE(l + wLength, 0); // wLength
  982. buf2.writeUInt16LE(wValueLength, 2); // wValueLength
  983. totalLen += buf2.length + wLength;
  984. }
  985. return totalLen;
  986. }
  987. // VarFileInfo structure: https://docs.microsoft.com/en-us/windows/win32/menurc/var-str
  988. function writeVarFileInfoStruct(bufArray, varFileInfo) {
  989. var l = 8 + (varFileInfo.szKey.length * 2);
  990. const buf = Buffer.alloc(padPointer(l));
  991. buf.writeUInt16LE(0, 4); // wType
  992. stringToUnicode(varFileInfo.szKey, buf, 6);
  993. bufArray.push(buf);
  994. var wLength = 0;
  995. var wValueLength = 0;
  996. if (varFileInfo.value) {
  997. bufArray.push(varFileInfo.value);
  998. wLength += varFileInfo.value.length;
  999. wValueLength += varFileInfo.value.length;
  1000. }
  1001. buf.writeUInt16LE(buf.length + wLength, 0); // wLength
  1002. buf.writeUInt16LE(wValueLength, 2); // wValueLength
  1003. return buf.length + wLength;
  1004. }
  1005. // StringTable structure: https://docs.microsoft.com/en-us/windows/win32/menurc/stringtable
  1006. function writeStringTableStruct(bufArray, stringTable) {
  1007. //console.log('writeStringTableStruct', stringTable);
  1008. var l = 6 + (stringTable.szKey.length * 2);
  1009. const buf = Buffer.alloc(padPointer(l));
  1010. buf.writeUInt16LE(1, 4); // wType
  1011. stringToUnicode(stringTable.szKey, buf, 6);
  1012. bufArray.push(buf);
  1013. var wLength = 0;
  1014. var wValueLength = 0;
  1015. if (stringTable.strings) { wLength += writeStringStructs(bufArray, stringTable.strings); }
  1016. buf.writeUInt16LE(l + wLength, 0); // wLength
  1017. buf.writeUInt16LE(wValueLength, 2); // wValueLength
  1018. return buf.length + wLength;
  1019. }
  1020. // String structure: https://docs.microsoft.com/en-us/windows/win32/menurc/string-str
  1021. function writeStringStructs(bufArray, stringTable) {
  1022. //console.log('writeStringStructs', stringTable);
  1023. var totalLen = 0, bufadd = 0;
  1024. for (var i in stringTable) {
  1025. //console.log('writeStringStructs', stringTable[i]);
  1026. const buf = Buffer.alloc(padPointer(6 + ((stringTable[i].key.length + 1) * 2)));
  1027. var buf2, wLength = buf.length;
  1028. var wValueLength = 0;
  1029. stringToUnicode(stringTable[i].key, buf, 6);
  1030. bufArray.push(buf);
  1031. bufadd += buf.length;
  1032. if (typeof stringTable[i].value == 'string') {
  1033. // wType (string)
  1034. buf.writeUInt16LE(1, 4);
  1035. var l = (stringTable[i].value.length + 1) * 2;
  1036. buf2 = Buffer.alloc(padPointer(l));
  1037. stringToUnicode(stringTable[i].value, buf2, 0);
  1038. bufArray.push(buf2);
  1039. bufadd += buf2.length;
  1040. wValueLength = stringTable[i].value.length + 1;
  1041. wLength += l;
  1042. }
  1043. if (typeof stringTable[i].value == 'object') {
  1044. // wType (binary)
  1045. buf.writeUInt16LE(2, 4); // TODO: PADDING
  1046. bufArray.push(stringTable[i].value);
  1047. bufadd += stringTable[i].value.length;
  1048. wValueLength = stringTable[i].value.length;
  1049. wLength += wValueLength;
  1050. }
  1051. buf.writeUInt16LE(wLength, 0); // wLength
  1052. buf.writeUInt16LE(wValueLength, 2); // wValueLength
  1053. //console.log('WStringStruct', buf.toString('hex'), buf2.toString('hex'));
  1054. totalLen += wLength;
  1055. }
  1056. //return totalLen;
  1057. return bufadd;
  1058. }
  1059. // VS_VERSIONINFO structure: https://docs.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
  1060. function readVersionInfo(buf, ptr) {
  1061. const r = {};
  1062. if (buf.length < 2) return null;
  1063. const wLength = buf.readUInt16LE(ptr);
  1064. if (buf.length < wLength) return null;
  1065. const wValueLength = buf.readUInt16LE(ptr + 2);
  1066. const wType = buf.readUInt16LE(ptr + 4);
  1067. r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + 36));
  1068. if (r.szKey != 'VS_VERSION_INFO') return null;
  1069. //console.log('getVersionInfo', wLength, wValueLength, wType, r.szKey.toString());
  1070. if (wValueLength == 52) { r.fixedFileInfo = readFixedFileInfoStruct(buf, ptr + 40); }
  1071. r.stringFiles = readStringFilesStruct(buf, ptr + 40 + wValueLength, wLength - 40 - wValueLength);
  1072. return r;
  1073. }
  1074. // VS_FIXEDFILEINFO structure: https://docs.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
  1075. function readFixedFileInfoStruct(buf, ptr) {
  1076. if (buf.length - ptr < 50) return null;
  1077. var r = {};
  1078. r.dwSignature = buf.readUInt32LE(ptr);
  1079. if (r.dwSignature != 0xFEEF04BD) return null;
  1080. r.dwStrucVersion = buf.readUInt32LE(ptr + 4);
  1081. r.dwFileVersionMS = buf.readUInt32LE(ptr + 8);
  1082. r.dwFileVersionLS = buf.readUInt32LE(ptr + 12);
  1083. r.dwProductVersionMS = buf.readUInt32LE(ptr + 16);
  1084. r.dwProductVersionLS = buf.readUInt32LE(ptr + 20);
  1085. r.dwFileFlagsMask = buf.readUInt32LE(ptr + 24);
  1086. r.dwFileFlags = buf.readUInt32LE(ptr + 28);
  1087. r.dwFileOS = buf.readUInt32LE(ptr + 32);
  1088. r.dwFileType = buf.readUInt32LE(ptr + 36);
  1089. r.dwFileSubtype = buf.readUInt32LE(ptr + 40);
  1090. r.dwFileDateMS = buf.readUInt32LE(ptr + 44);
  1091. r.dwFileDateLS = buf.readUInt32LE(ptr + 48);
  1092. return r;
  1093. }
  1094. // StringFileInfo structure: https://docs.microsoft.com/en-us/windows/win32/menurc/stringfileinfo
  1095. function readStringFilesStruct(buf, ptr, len) {
  1096. var t = [], startPtr = ptr;
  1097. while (ptr < (startPtr + len)) {
  1098. const r = {};
  1099. const wLength = buf.readUInt16LE(ptr);
  1100. if (wLength == 0) return t;
  1101. const wValueLength = buf.readUInt16LE(ptr + 2);
  1102. const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary
  1103. r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + 6 + (wLength - 6))); // String value
  1104. //console.log('readStringFileStruct', wLength, wValueLength, wType, r.szKey);
  1105. if (r.szKey == 'StringFileInfo') { r.stringTable = readStringTableStruct(buf, ptr + 36); }
  1106. if (r.szKey == 'VarFileInfo') { r.varFileInfo = readVarFileInfoStruct(buf, ptr + 32); }
  1107. t.push(r);
  1108. ptr += wLength;
  1109. ptr = padPointer(ptr);
  1110. }
  1111. return t;
  1112. }
  1113. // VarFileInfo structure: https://docs.microsoft.com/en-us/windows/win32/menurc/var-str
  1114. function readVarFileInfoStruct(buf, ptr) {
  1115. const r = {};
  1116. const wLength = buf.readUInt16LE(ptr);
  1117. const wValueLength = buf.readUInt16LE(ptr + 2);
  1118. const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary
  1119. r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + wLength)); // "VarFileInfo"
  1120. r.value = buf.slice(ptr + wLength - wValueLength, ptr + wLength)
  1121. //console.log('readVarFileInfoStruct', wLength, wValueLength, wType, r.szKey, r.value.toString('hex'));
  1122. return r;
  1123. }
  1124. // StringTable structure: https://docs.microsoft.com/en-us/windows/win32/menurc/stringtable
  1125. function readStringTableStruct(buf, ptr) {
  1126. const r = {};
  1127. const wLength = buf.readUInt16LE(ptr);
  1128. const wValueLength = buf.readUInt16LE(ptr + 2);
  1129. const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary
  1130. //console.log('RStringTableStruct', buf.slice(ptr, ptr + wLength).toString('hex'));
  1131. r.szKey = unicodeToString(buf.slice(ptr + 6, ptr + 6 + 16)); // An 8-digit hexadecimal number stored as a Unicode string.
  1132. //console.log('readStringTableStruct', wLength, wValueLength, wType, r.szKey);
  1133. r.strings = readStringStructs(buf, ptr + 24 + wValueLength, wLength - 22);
  1134. return r;
  1135. }
  1136. // String structure: https://docs.microsoft.com/en-us/windows/win32/menurc/string-str
  1137. function readStringStructs(buf, ptr, len) {
  1138. var t = [], startPtr = ptr;
  1139. while ((ptr + 6) < (startPtr + len)) {
  1140. const r = {};
  1141. const wLength = buf.readUInt16LE(ptr);
  1142. if (wLength == 0) return t;
  1143. //console.log('RStringStruct', buf.slice(ptr, ptr + wLength).toString('hex'));
  1144. const wValueLength = buf.readUInt16LE(ptr + 2);
  1145. const wType = buf.readUInt16LE(ptr + 4); // 1 = Text, 2 = Binary
  1146. //console.log('R', buf.slice(ptr, ptr + wLength).toString('hex'));
  1147. r.key = unicodeToString(buf.slice(ptr + 6, ptr + (wLength - (wValueLength * 2)) - 2)); // Key
  1148. if (wType == 1) { r.value = unicodeToString(buf.slice(ptr + wLength - (wValueLength * 2), ptr + wLength - 2)); } // String value
  1149. if (wType == 2) { r.value = buf.slice(ptr + wLength - (wValueLength * 2), ptr + wLength); } // Binary value
  1150. //console.log('readStringStruct', wLength, wValueLength, wType, r.key, r.value);
  1151. t.push(r);
  1152. ptr += wLength;
  1153. ptr = padPointer(ptr);
  1154. }
  1155. return t;
  1156. }
  1157. // Return the next 4 byte aligned number
  1158. function padPointer(ptr) { return ptr + (((ptr % 4) == 0) ? 0 : (4 - (ptr % 4))); }
  1159. //function padPointer(ptr) { return ptr + (ptr % 4); }
  1160. // Hash the file using the selected hashing system
  1161. // This hash skips the executables CRC and code signing data and signing block
  1162. obj.getHash = function(algo) {
  1163. const hash = crypto.createHash(algo);
  1164. runHash(hash, 0, obj.header.peHeaderLocation + 88);
  1165. runHash(hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
  1166. runHash(hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, obj.header.sigpos > 0 ? obj.header.sigpos : obj.filesize);
  1167. return hash.digest();
  1168. }
  1169. // Hash of an open file using the selected hashing system
  1170. // This hash skips the executables CRC and code signing data and signing block
  1171. obj.getHashOfFile = function(fd, algo, filesize) {
  1172. const hash = crypto.createHash(algo);
  1173. runHashOnFile(fd, hash, 0, obj.header.peHeaderLocation + 88);
  1174. runHashOnFile(fd, hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
  1175. runHashOnFile(fd, hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, obj.header.sigpos > 0 ? obj.header.sigpos : filesize);
  1176. return hash.digest();
  1177. }
  1178. // Hash the file using the selected hashing system skipping resource section
  1179. // This hash skips the executables CRC, sections table, resource section, code signing data and signing block
  1180. obj.getHashNoResources = function (algo) {
  1181. if (obj.header.sections['.rsrc'] == null) { return obj.getHash(algo); } // No resources in this executable, return a normal hash
  1182. // Get the sections table start and size
  1183. const sectionHeaderPtr = obj.header.SectionHeadersPtr;
  1184. const sectionHeaderSize = obj.header.coff.numberOfSections * 40;
  1185. // Get the resource section start and size
  1186. const resPtr = obj.header.sections['.rsrc'].rawAddr;
  1187. const resSize = obj.header.sections['.rsrc'].rawSize;
  1188. // Get the end-of-file location
  1189. const eof = obj.header.sigpos > 0 ? obj.header.sigpos : obj.filesize;
  1190. // Hash the remaining data
  1191. const hash = crypto.createHash(algo);
  1192. runHash(hash, 0, obj.header.peHeaderLocation + 88);
  1193. runHash(hash, obj.header.peHeaderLocation + 88 + 4, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
  1194. runHash(hash, obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16) + 8, sectionHeaderPtr);
  1195. runHash(hash, sectionHeaderPtr + sectionHeaderSize, resPtr);
  1196. runHash(hash, resPtr + resSize, eof);
  1197. return hash.digest();
  1198. }
  1199. // Hash the file using the selected hashing system skipping resource section
  1200. // This hash skips the executables CRC, sections table, resource section, code signing data and signing block
  1201. obj.getHashOfSection = function (algo, sectionName) {
  1202. if (obj.header.sections[sectionName] == null) return null;
  1203. // Get the section start and size
  1204. const sectionPtr = obj.header.sections[sectionName].rawAddr;
  1205. const sectionSize = obj.header.sections[sectionName].rawSize;
  1206. // Hash the remaining data
  1207. const hash = crypto.createHash(algo);
  1208. runHash(hash, sectionPtr, sectionPtr + sectionSize);
  1209. return hash.digest();
  1210. }
  1211. // Hash the file from start to end loading 64k chunks
  1212. function runHash(hash, start, end) {
  1213. var ptr = start;
  1214. while (ptr < end) { const buf = readFileSlice(ptr, Math.min(65536, end - ptr)); hash.update(buf); ptr += buf.length; }
  1215. }
  1216. // Hash the open file loading 64k chunks
  1217. // TODO: Do chunks on this!!!
  1218. function runHashOnFile(fd, hash, start, end) {
  1219. const buf = Buffer.alloc(end - start);
  1220. const len = fs.readSync(fd, buf, 0, buf.length, start);
  1221. if (len != buf.length) { console.log('BAD runHashOnFile'); }
  1222. hash.update(buf);
  1223. }
  1224. // Checksum the file loading 64k chunks
  1225. function runChecksum() {
  1226. var ptr = 0, c = createChecksum(((obj.header.peOptionalHeaderLocation + 64) / 4));
  1227. while (ptr < obj.filesize) { const buf = readFileSlice(ptr, Math.min(65536, obj.filesize - ptr)); c.update(buf); ptr += buf.length; }
  1228. return c.digest();
  1229. }
  1230. // Checksum the open file loading 64k chunks
  1231. function runChecksumOnFile(fd, filesize, checksumLocation) {
  1232. var ptr = 0, c = createChecksum(checksumLocation), buf = Buffer.alloc(65536);
  1233. while (ptr < filesize) { var len = fs.readSync(fd, buf, 0, Math.min(65536, filesize - ptr), ptr); c.update(buf, len); ptr += len; }
  1234. return c.digest();
  1235. }
  1236. // Steaming checksum methods
  1237. // TODO: Works only with files padded to 4 byte.
  1238. function createChecksum(checksumLocation) {
  1239. const obj = { checksum: 0, length: 0 };
  1240. obj.update = function (data, len) {
  1241. if (!len) { len = data.length; }
  1242. for (var i = 0; i < (len / 4) ; i++) {
  1243. if (((obj.length / 4) + i) == checksumLocation) continue; // Skip PE checksum location
  1244. const dword = data.readUInt32LE(i * 4);
  1245. var checksumlo = (obj.checksum > 4294967296) ? (obj.checksum - 4294967296) : obj.checksum;
  1246. var checksumhi = (obj.checksum > 4294967296) ? 1 : 0;
  1247. obj.checksum = checksumlo + dword + checksumhi;
  1248. if (obj.checksum > 4294967296) {
  1249. checksumlo = (obj.checksum > 4294967296) ? (obj.checksum - 4294967296) : obj.checksum;
  1250. checksumhi = (obj.checksum > 4294967296) ? 1 : 0;
  1251. obj.checksum = checksumlo + checksumhi;
  1252. }
  1253. }
  1254. obj.length += len;
  1255. }
  1256. obj.digest = function () {
  1257. obj.checksum = (obj.checksum & 0xffff) + (obj.checksum >>> 16);
  1258. obj.checksum = (obj.checksum) + (obj.checksum >>> 16);
  1259. obj.checksum = obj.checksum & 0xffff;
  1260. obj.checksum += obj.length;
  1261. return obj.checksum;
  1262. }
  1263. return obj;
  1264. }
  1265. // Compute the PE checksum of an entire file
  1266. function getChecksum(data, checksumLocation) {
  1267. var checksum = 0;
  1268. for (var i = 0; i < (data.length / 4) ; i++) {
  1269. if (i == (checksumLocation / 4)) continue; // Skip PE checksum location
  1270. var dword = data.readUInt32LE(i * 4);
  1271. var checksumlo = (checksum > 4294967296) ? (checksum - 4294967296) : checksum;
  1272. var checksumhi = (checksum > 4294967296) ? 1 : 0;
  1273. checksum = checksumlo + dword + checksumhi;
  1274. if (checksum > 4294967296) {
  1275. checksumlo = (checksum > 4294967296) ? (checksum - 4294967296) : checksum;
  1276. checksumhi = (checksum > 4294967296) ? 1 : 0;
  1277. checksum = checksumlo + checksumhi;
  1278. }
  1279. }
  1280. checksum = (checksum & 0xffff) + (checksum >>> 16);
  1281. checksum = (checksum) + (checksum >>> 16);
  1282. checksum = checksum & 0xffff;
  1283. checksum += data.length;
  1284. return checksum;
  1285. }
  1286. // Sign the file using the certificate and key. If none is specified, generate a dummy one
  1287. obj.sign = function (cert, args, func) {
  1288. if (cert == null) { cert = createSelfSignedCert({ cn: 'Test' }); }
  1289. // Set the hash algorithm hash OID
  1290. var hashOid = null, fileHash = null;
  1291. if (args.hash == null) { args.hash = 'sha384'; }
  1292. if (args.hash == 'sha256') { hashOid = forge.pki.oids.sha256; fileHash = obj.getHash('sha256'); }
  1293. if (args.hash == 'sha384') { hashOid = forge.pki.oids.sha384; fileHash = obj.getHash('sha384'); }
  1294. if (args.hash == 'sha512') { hashOid = forge.pki.oids.sha512; fileHash = obj.getHash('sha512'); }
  1295. if (args.hash == 'sha224') { hashOid = forge.pki.oids.sha224; fileHash = obj.getHash('sha224'); }
  1296. if (args.hash == 'md5') { hashOid = forge.pki.oids.md5; fileHash = obj.getHash('md5'); }
  1297. if (hashOid == null) { func('Invalid signing hash: ' + args.hash); return; };
  1298. // Create the signature block
  1299. var xp7 = forge.pkcs7.createSignedData();
  1300. var content = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 6, 'constructed': false, 'composed': false, 'value': forge.asn1.oidToDer('1.3.6.1.4.1.311.2.1.15').data }, { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 3, 'constructed': false, 'composed': false, 'value': '\u0000', 'bitStringContents': '\u0000', 'original': { 'tagClass': 0, 'type': 3, 'constructed': false, 'composed': false, 'value': '\u0000' } }, { 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 2, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': '' }] }] }] }] }, { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 6, 'constructed': false, 'composed': false, 'value': forge.asn1.oidToDer(hashOid).data }, { 'tagClass': 0, 'type': 5, 'constructed': false, 'composed': false, 'value': '' }] }, { 'tagClass': 0, 'type': 4, 'constructed': false, 'composed': false, 'value': fileHash.toString('binary') }] }] };
  1301. xp7.contentInfo = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, forge.asn1.oidToDer('1.3.6.1.4.1.311.2.1.4').getBytes())]);
  1302. xp7.contentInfo.value.push(forge.asn1.create(forge.asn1.Class.CONTEXT_SPECIFIC, 0, true, [content]));
  1303. xp7.content = {}; // We set .contentInfo and have .content empty to bypass node-forge limitation on the type of content it can sign.
  1304. xp7.addCertificate(cert.cert);
  1305. if (cert.extraCerts) { for (var i = 0; i < cert.extraCerts.length; i++) { xp7.addCertificate(cert.extraCerts[0]); } } // Add any extra certificates that form the cert chain
  1306. // Build authenticated attributes
  1307. var authenticatedAttributes = [
  1308. { type: forge.pki.oids.contentType, value: forge.pki.oids.data },
  1309. { type: forge.pki.oids.messageDigest } // This value will populated at signing time by node-forge
  1310. ]
  1311. if ((typeof args.desc == 'string') || (typeof args.url == 'string')) {
  1312. var codeSigningAttributes = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [] };
  1313. if (args.desc != null) { // Encode description as big-endian unicode.
  1314. var desc = "", ucs = Buffer.from(args.desc, 'ucs2').toString()
  1315. for (var k = 0; k < ucs.length; k += 2) { desc += String.fromCharCode(ucs.charCodeAt(k + 1), ucs.charCodeAt(k)); }
  1316. codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': desc }] });
  1317. }
  1318. if (args.url != null) { codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 1, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': args.url }] }); }
  1319. authenticatedAttributes.push({ type: obj.Oids.SPC_SP_OPUS_INFO_OBJID, value: codeSigningAttributes });
  1320. }
  1321. // Add the signer and sign
  1322. xp7.addSigner({
  1323. key: cert.key,
  1324. certificate: cert.cert,
  1325. digestAlgorithm: forge.pki.oids.sha384,
  1326. authenticatedAttributes: authenticatedAttributes
  1327. });
  1328. xp7.sign();
  1329. var p7signature = Buffer.from(forge.pkcs7.messageToPem(xp7).split('-----BEGIN PKCS7-----')[1].split('-----END PKCS7-----')[0], 'base64');
  1330. if (args.time == null) {
  1331. // Sign the executable without timestamp
  1332. signEx(args, p7signature, obj.filesize, func);
  1333. } else {
  1334. // Decode the signature block
  1335. var pkcs7der = null;
  1336. try { pkcs7der = forge.asn1.fromDer(forge.util.createBuffer(p7signature)); } catch (ex) { func('' + ex); return; }
  1337. // To work around ForgeJS PKCS#7 limitation, this may break PKCS7 verify if ForgeJS adds support for it in the future
  1338. // Switch content type from "1.3.6.1.4.1.311.2.1.4" to "1.2.840.113549.1.7.1"
  1339. pkcs7der.value[1].value[0].value[2].value[0].value = forge.asn1.oidToDer(forge.pki.oids.data).data;
  1340. // Decode the PKCS7 message
  1341. var pkcs7 = p7.messageFromAsn1(pkcs7der);
  1342. // Create the timestamp request in DER format
  1343. const asn1 = forge.asn1;
  1344. const pkcs7dataOid = asn1.oidToDer('1.2.840.113549.1.7.1').data;
  1345. const microsoftCodeSigningOid = asn1.oidToDer('1.3.6.1.4.1.311.3.2.1').data;
  1346. const asn1obj =
  1347. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  1348. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, microsoftCodeSigningOid),
  1349. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  1350. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, pkcs7dataOid),
  1351. asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
  1352. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, pkcs7.rawCapture.signature.toString('binary')) // Signature here
  1353. ])
  1354. ])
  1355. ]);
  1356. // Re-decode the PKCS7 from the executable, this time, no workaround needed
  1357. try { pkcs7der = forge.asn1.fromDer(forge.util.createBuffer(p7signature)); } catch (ex) { func('' + ex); return; }
  1358. // Serialize an ASN.1 object to DER format in Base64
  1359. const requestBody = Buffer.from(asn1.toDer(asn1obj).data, 'binary').toString('base64');
  1360. // Make an HTTP request
  1361. const options = { url: args.time, proxy: args.proxy };
  1362. // Make a request to the time server
  1363. httpRequest(options, requestBody, function (err, data) {
  1364. if (err != null) { func(err); return; }
  1365. // Decode the timestamp signature block
  1366. var timepkcs7der = null;
  1367. try { timepkcs7der = forge.asn1.fromDer(forge.util.createBuffer(Buffer.from(data, 'base64').toString('binary'))); } catch (ex) { func("Unable to parse time-stamp response: " + ex); return; }
  1368. try {
  1369. // Get the ASN1 certificates used to sign the timestamp and add them to the certs in the PKCS7 of the executable
  1370. // TODO: We could look to see if the certificate is already present in the executable
  1371. const timeasn1Certs = timepkcs7der.value[1].value[0].value[3].value;
  1372. for (var i in timeasn1Certs) { pkcs7der.value[1].value[0].value[3].value.push(timeasn1Certs[i]); }
  1373. // Get the time signature and add it to the executables PKCS7
  1374. const timeasn1Signature = timepkcs7der.value[1].value[0].value[4];
  1375. const countersignatureOid = asn1.oidToDer('1.2.840.113549.1.9.6').data;
  1376. const asn1obj2 =
  1377. asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
  1378. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  1379. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, countersignatureOid),
  1380. timeasn1Signature
  1381. ])
  1382. ]);
  1383. pkcs7der.value[1].value[0].value[4].value[0].value.push(asn1obj2);
  1384. // Re-encode the executable signature block
  1385. const p7signature = Buffer.from(forge.asn1.toDer(pkcs7der).data, 'binary');
  1386. // Write the file with the signature block
  1387. signEx(args, p7signature, obj.filesize, func);
  1388. } catch (ex) { func('' + ex); }
  1389. });
  1390. }
  1391. }
  1392. // Make a HTTP request, use a proxy if needed
  1393. function httpRequest(options, requestBody, func) {
  1394. // Decode the URL
  1395. const timeServerUrl = new URL(options.url);
  1396. options.protocol = timeServerUrl.protocol;
  1397. options.hostname = timeServerUrl.hostname;
  1398. options.path = timeServerUrl.pathname;
  1399. let http = require("http")
  1400. if (options.protocol === "https:"){
  1401. http = require("https")
  1402. }
  1403. options.port = ((timeServerUrl.port == '') ? (options.protocol === "https:" ? 443 : 80) : parseInt(timeServerUrl.port));
  1404. if (options.proxy == null) {
  1405. // No proxy needed
  1406. // Setup the options
  1407. delete options.url;
  1408. options.method = 'POST';
  1409. options.headers = {
  1410. 'accept': 'application/octet-stream',
  1411. 'cache-control': 'no-cache',
  1412. 'user-agent': 'Transport',
  1413. 'content-type': 'application/octet-stream',
  1414. 'content-length': Buffer.byteLength(requestBody)
  1415. };
  1416. // Set up the request
  1417. var responseAccumulator = '';
  1418. var req = http.request(options, function (res) {
  1419. res.setEncoding('utf8');
  1420. res.on('data', function (chunk) { responseAccumulator += chunk; });
  1421. res.on('end', function () { func(null, responseAccumulator); });
  1422. });
  1423. // Post the data
  1424. req.on('error', function (err) { func('' + err); });
  1425. req.write(requestBody);
  1426. req.end();
  1427. } else {
  1428. // We are using a proxy
  1429. // This is a fairly basic proxy implementation, should work most of the time.
  1430. // Setup the options and decode the proxy URL
  1431. var proxyOptions = { method: 'CONNECT' };
  1432. if (options.proxy) {
  1433. const proxyUrl = new URL(options.proxy);
  1434. proxyOptions.protocol = proxyUrl.protocol;
  1435. proxyOptions.hostname = proxyUrl.hostname;
  1436. proxyOptions.path = options.hostname + ':' + options.port;
  1437. proxyOptions.port = ((proxyUrl.port == '') ? (options.protocol === "https:" ? 443 : 80) : parseInt(proxyUrl.port));
  1438. }
  1439. // Set up the proxy request
  1440. var responseAccumulator = '';
  1441. var req = http.request(proxyOptions);
  1442. req.on('error', function (err) { func('' + err); });
  1443. req.on('connect', function (res, socket, head) {
  1444. // Make a request over the HTTP tunnel
  1445. socket.write('POST ' + options.path + ' HTTP/1.1\r\n' +
  1446. 'host: ' + options.hostname + ':' + options.port + '\r\n' +
  1447. 'accept: application/octet-stream\r\n' +
  1448. 'cache-control: no-cache\r\n' +
  1449. 'user-agent: Transport\r\n' +
  1450. 'content-type: application/octet-stream\r\n' +
  1451. 'content-length: ' + Buffer.byteLength(requestBody) + '\r\n' +
  1452. '\r\n' + requestBody);
  1453. socket.on('data', function (chunk) {
  1454. responseAccumulator += chunk.toString();
  1455. var responseData = parseHttpResponse(responseAccumulator);
  1456. if (responseData != null) { try { socket.end(); } catch (ex) { console.log('ex', ex); } socket.xdone = true; func(null, responseData); }
  1457. });
  1458. socket.on('end', function () {
  1459. if (socket.xdone == true) return;
  1460. var responseData = parseHttpResponse(responseAccumulator);
  1461. if (responseData != null) { func(null, responseData); } else { func("Unable to parse response."); }
  1462. });
  1463. });
  1464. req.end();
  1465. }
  1466. }
  1467. // Parse the HTTP response and return data if available
  1468. function parseHttpResponse(data) {
  1469. var dataSplit = data.split('\r\n\r\n');
  1470. if (dataSplit.length < 2) return null;
  1471. // Parse the HTTP header
  1472. var headerSplit = dataSplit[0].split('\r\n'), headers = {};
  1473. for (var i in headerSplit) {
  1474. if (i != 0) {
  1475. var x = headerSplit[i].indexOf(':');
  1476. headers[headerSplit[i].substring(0, x).toLowerCase()] = headerSplit[i].substring(x + 2);
  1477. }
  1478. }
  1479. // If there is a content-length in the header, keep accumulating data until we have the right length
  1480. if (headers['content-length'] != null) {
  1481. const contentLength = parseInt(headers['content-length']);
  1482. if (dataSplit[1].length < contentLength) return null; // Wait for more data
  1483. return dataSplit[1];
  1484. }
  1485. return dataSplit[1];
  1486. }
  1487. // Complete the signature of an executable
  1488. function signEx(args, p7signature, filesize, func) {
  1489. // Open the output file
  1490. var output = null;
  1491. try { output = fs.openSync(args.out, 'w+'); } catch (ex) { }
  1492. if (output == null) { func('Unable to open output file: ' + args.out); return; }
  1493. var tmp, written = 0, executableSize = obj.header.sigpos ? obj.header.sigpos : filesize;
  1494. // Compute pre-header length and copy that to the new file
  1495. var preHeaderLen = (obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
  1496. var tmp = readFileSlice(written, preHeaderLen);
  1497. fs.writeSync(output, tmp);
  1498. written += tmp.length;
  1499. // Quad Align the results, adding padding if necessary
  1500. var len = executableSize + p7signature.length;
  1501. var padding = (8 - ((len) % 8)) % 8;
  1502. // Write the signature header
  1503. var addresstable = Buffer.alloc(8);
  1504. addresstable.writeUInt32LE(executableSize);
  1505. addresstable.writeUInt32LE(8 + p7signature.length + padding, 4);
  1506. fs.writeSync(output, addresstable);
  1507. written += addresstable.length;
  1508. // Copy the rest of the file until the start of the signature block
  1509. while ((executableSize - written) > 0) {
  1510. tmp = readFileSlice(written, Math.min(executableSize - written, 65536));
  1511. fs.writeSync(output, tmp);
  1512. written += tmp.length;
  1513. }
  1514. // Write the signature block header and signature
  1515. var win = Buffer.alloc(8); // WIN CERTIFICATE Structure
  1516. win.writeUInt32LE(p7signature.length + padding + 8); // DWORD length
  1517. win.writeUInt16LE(512, 4); // WORD revision
  1518. win.writeUInt16LE(2, 6); // WORD type
  1519. fs.writeSync(output, win);
  1520. fs.writeSync(output, p7signature);
  1521. if (padding > 0) { fs.writeSync(output, Buffer.alloc(padding, 0)); }
  1522. written += (p7signature.length + padding + 8);
  1523. // Compute the checksum and write it in the PE header checksum location
  1524. var tmp = Buffer.alloc(4);
  1525. tmp.writeUInt32LE(runChecksumOnFile(output, written, ((obj.header.peOptionalHeaderLocation + 64) / 4)));
  1526. fs.writeSync(output, tmp, 0, 4, obj.header.peOptionalHeaderLocation + 64);
  1527. // Close the file
  1528. fs.closeSync(output);
  1529. func(null);
  1530. }
  1531. // Save an executable without the signature
  1532. obj.unsign = function (args) {
  1533. // Open the file
  1534. var output = fs.openSync(args.out, 'w+');
  1535. var written = 0, totalWrite = obj.header.sigpos;
  1536. // Compute pre-header length and copy that to the new file
  1537. var preHeaderLen = (obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
  1538. var tmp = readFileSlice(written, preHeaderLen);
  1539. fs.writeSync(output, tmp);
  1540. written += tmp.length;
  1541. // Write the new signature header
  1542. fs.writeSync(output, Buffer.alloc(8));
  1543. written += 8;
  1544. // Copy the rest of the file until the start of the signature block
  1545. while ((totalWrite - written) > 0) {
  1546. tmp = readFileSlice(written, Math.min(totalWrite - written, 65536));
  1547. fs.writeSync(output, tmp);
  1548. written += tmp.length;
  1549. }
  1550. // Compute the checksum and write it in the PE checksum header at position
  1551. var tmp = Buffer.alloc(4);
  1552. tmp.writeUInt32LE(runChecksumOnFile(output, written));
  1553. fs.writeSync(output, tmp, 0, 4, obj.header.peOptionalHeaderLocation + 64);
  1554. fs.closeSync(output);
  1555. }
  1556. // Find where a directory value is in the old sections and map it to the new sections
  1557. function correctDirectoryValue(oldSections, newSections, value) {
  1558. for (var i in oldSections) {
  1559. if ((value >= oldSections[i].virtualAddr) && (value < (oldSections[i].virtualAddr + oldSections[i].virtualSize))) {
  1560. return newSections[i].virtualAddr + (value - oldSections[i].virtualAddr);
  1561. }
  1562. }
  1563. return 0;
  1564. }
  1565. // Save the executable
  1566. obj.writeExecutable = function (args, cert, func) {
  1567. // Open the file
  1568. var output = fs.openSync(args.out, 'w+');
  1569. var tmp, written = 0;
  1570. // Compute the size of the complete executable header up to after the sections header
  1571. var fullHeaderLen = obj.header.SectionHeadersPtr + (obj.header.coff.numberOfSections * 40);
  1572. var fullHeader = readFileSlice(written, fullHeaderLen);
  1573. // Create the resource section
  1574. const rsrcSectionX = generateResourceSection(obj.resources); // This section is created with padding already included
  1575. var rsrcSection = rsrcSectionX.data;
  1576. var rsrcSectionVirtualSize = rsrcSectionX.size;
  1577. var rsrcSectionRawSize = rsrcSection.length;
  1578. // Calculate the location and original and new size of the resource segment
  1579. var fileAlign = obj.header.peWindows.fileAlignment
  1580. var resPtr = obj.header.sections['.rsrc'].rawAddr;
  1581. var oldResSize = obj.header.sections['.rsrc'].rawSize;
  1582. var newResSize = rsrcSection.length;
  1583. var resDeltaSize = newResSize - oldResSize;
  1584. // Compute the sizeOfInitializedData
  1585. var sizeOfInitializedData = 0;
  1586. for (var i in obj.header.sections) {
  1587. if (i != '.text') {
  1588. if (i == '.rsrc') {
  1589. sizeOfInitializedData += rsrcSectionRawSize;
  1590. } else {
  1591. sizeOfInitializedData += obj.header.sections[i].rawSize;
  1592. }
  1593. }
  1594. }
  1595. // Change PE optional header sizeOfInitializedData standard field
  1596. fullHeader.writeUInt32LE(sizeOfInitializedData, obj.header.peOptionalHeaderLocation + 8);
  1597. // Update the checksum to zero
  1598. fullHeader.writeUInt32LE(0, obj.header.peOptionalHeaderLocation + 64);
  1599. // We are going to setup the old a new sections here, we need this to correct directory values
  1600. var oldSections = obj.header.sections;
  1601. var newSections = {};
  1602. // Make changes to the segments table
  1603. var virtualAddress = 4096;
  1604. for (var i in obj.header.sections) {
  1605. const section = obj.header.sections[i];
  1606. newSections[i] = { virtualSize: section.virtualSize };
  1607. if (i == '.rsrc') {
  1608. // Change the size of the resource section
  1609. fullHeader.writeUInt32LE(rsrcSectionVirtualSize, section.ptr + 8); // virtualSize
  1610. fullHeader.writeUInt32LE(rsrcSectionRawSize, section.ptr + 16); // rawSize
  1611. // Set the virtual address of the section
  1612. fullHeader.writeUInt32LE(virtualAddress, section.ptr + 12); // Virtual address
  1613. newSections[i].virtualAddr = virtualAddress;
  1614. var virtualAddressPadding = (rsrcSectionVirtualSize % 4096);
  1615. virtualAddress += rsrcSectionVirtualSize;
  1616. if (virtualAddressPadding != 0) { virtualAddress += (4096 - virtualAddressPadding); }
  1617. } else {
  1618. // Change the location of any other section if located after the resource section
  1619. if (section.rawAddr > resPtr) { fullHeader.writeUInt32LE(section.rawAddr + resDeltaSize, section.ptr + 20); }
  1620. // Set the virtual address of the section
  1621. fullHeader.writeUInt32LE(virtualAddress, section.ptr + 12); // Virtual address
  1622. newSections[i].virtualAddr = virtualAddress;
  1623. var virtualAddressPadding = (section.virtualSize % 4096);
  1624. virtualAddress += section.virtualSize;
  1625. if (virtualAddressPadding != 0) { virtualAddress += (4096 - virtualAddressPadding); }
  1626. }
  1627. }
  1628. // Make change to the data directories header to fix resource segment size and add/remove signature
  1629. const pePlusOffset = (obj.header.pe32plus == 0) ? 0 : 16; // This header is the same for 32 and 64 bit, but 64bit is offset by 16 bytes.
  1630. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.exportTable.addr), obj.header.peOptionalHeaderLocation + 96 + pePlusOffset);
  1631. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.importTable.addr), obj.header.peOptionalHeaderLocation + 104 + pePlusOffset);
  1632. fullHeader.writeUInt32LE(rsrcSectionVirtualSize, obj.header.peOptionalHeaderLocation + 116 + pePlusOffset); // Change the resource segment size
  1633. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.exceptionTableAddr.addr), obj.header.peOptionalHeaderLocation + 120 + pePlusOffset);
  1634. fullHeader.writeUInt32LE(0, obj.header.peOptionalHeaderLocation + 128 + pePlusOffset); // certificate table addr (TODO)
  1635. fullHeader.writeUInt32LE(0, obj.header.peOptionalHeaderLocation + 132 + pePlusOffset); // certificate table size (TODO)
  1636. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.baseRelocationTable.addr), obj.header.peOptionalHeaderLocation + 136 + pePlusOffset);
  1637. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.debug.addr), obj.header.peOptionalHeaderLocation + 144 + pePlusOffset);
  1638. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.globalPtr.addr), obj.header.peOptionalHeaderLocation + 160 + pePlusOffset);
  1639. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.tLSTable.addr), obj.header.peOptionalHeaderLocation + 168 + pePlusOffset);
  1640. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.loadConfigTable.addr), obj.header.peOptionalHeaderLocation + 176 + pePlusOffset);
  1641. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.boundImport.addr), obj.header.peOptionalHeaderLocation + 184 + pePlusOffset);
  1642. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.iAT.addr), obj.header.peOptionalHeaderLocation + 192 + pePlusOffset);
  1643. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.delayImportDescriptor.addr), obj.header.peOptionalHeaderLocation + 200 + pePlusOffset);
  1644. fullHeader.writeUInt32LE(correctDirectoryValue(oldSections, newSections, obj.header.dataDirectories.clrRuntimeHeader.addr), obj.header.peOptionalHeaderLocation + 208 + pePlusOffset);
  1645. // Write size of image. We put the next virtual address.
  1646. fullHeader.writeUInt32LE(virtualAddress, obj.header.peOptionalHeaderLocation + 56); // sizeOfImage
  1647. // Write the entire header to the destination file
  1648. //console.log('Write header', fullHeader.length, written);
  1649. fs.writeSync(output, fullHeader);
  1650. written += fullHeader.length;
  1651. // Write the entire executable until the start to the resource segment
  1652. var totalWrite = resPtr;
  1653. //console.log('Write until res', totalWrite, written);
  1654. while ((totalWrite - written) > 0) {
  1655. tmp = readFileSlice(written, Math.min(totalWrite - written, 65536));
  1656. fs.writeSync(output, tmp);
  1657. written += tmp.length;
  1658. }
  1659. // Write the new resource section
  1660. fs.writeSync(output, rsrcSection);
  1661. written += rsrcSection.length;
  1662. //console.log('Write res', rsrcSection.length, written);
  1663. // Write until the signature block
  1664. if (obj.header.sigpos > 0) {
  1665. // Since the original file was signed, write from the end of the resources to the start of the signature block.
  1666. totalWrite = obj.header.sigpos + resDeltaSize;
  1667. } else {
  1668. // The original file was not signed, write from the end of the resources to the end of the file.
  1669. totalWrite = obj.filesize + resDeltaSize;
  1670. }
  1671. //console.log('Write until signature', totalWrite, written);
  1672. while ((totalWrite - written) > 0) {
  1673. tmp = readFileSlice(written - resDeltaSize, Math.min(totalWrite - written, 65536));
  1674. fs.writeSync(output, tmp);
  1675. written += tmp.length;
  1676. }
  1677. //console.log('Write to signature', written);
  1678. // Write the signature if needed
  1679. if (cert != null) {
  1680. //if (cert == null) { cert = createSelfSignedCert({ cn: 'Test' }); }
  1681. // Set the hash algorithm hash OID
  1682. var hashOid = null, fileHash = null;
  1683. if (args.hash == null) { args.hash = 'sha384'; }
  1684. if (args.hash == 'sha256') { hashOid = forge.pki.oids.sha256; fileHash = obj.getHashOfFile(output, 'sha256', written); }
  1685. if (args.hash == 'sha384') { hashOid = forge.pki.oids.sha384; fileHash = obj.getHashOfFile(output, 'sha384', written); }
  1686. if (args.hash == 'sha512') { hashOid = forge.pki.oids.sha512; fileHash = obj.getHashOfFile(output, 'sha512', written); }
  1687. if (args.hash == 'sha224') { hashOid = forge.pki.oids.sha224; fileHash = obj.getHashOfFile(output, 'sha224', written); }
  1688. if (args.hash == 'md5') { hashOid = forge.pki.oids.md5; fileHash = obj.getHashOfFile(output, 'md5', written); }
  1689. if (hashOid == null) { func('Bad hash method OID'); return; }
  1690. // Create the signature block
  1691. var xp7 = forge.pkcs7.createSignedData();
  1692. var content = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 6, 'constructed': false, 'composed': false, 'value': forge.asn1.oidToDer('1.3.6.1.4.1.311.2.1.15').data }, { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 3, 'constructed': false, 'composed': false, 'value': '\u0000', 'bitStringContents': '\u0000', 'original': { 'tagClass': 0, 'type': 3, 'constructed': false, 'composed': false, 'value': '\u0000' } }, { 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 2, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': '' }] }] }] }] }, { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 6, 'constructed': false, 'composed': false, 'value': forge.asn1.oidToDer(hashOid).data }, { 'tagClass': 0, 'type': 5, 'constructed': false, 'composed': false, 'value': '' }] }, { 'tagClass': 0, 'type': 4, 'constructed': false, 'composed': false, 'value': fileHash.toString('binary') }] }] };
  1693. xp7.contentInfo = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, forge.asn1.oidToDer('1.3.6.1.4.1.311.2.1.4').getBytes())]);
  1694. xp7.contentInfo.value.push(forge.asn1.create(forge.asn1.Class.CONTEXT_SPECIFIC, 0, true, [content]));
  1695. xp7.content = {}; // We set .contentInfo and have .content empty to bypass node-forge limitation on the type of content it can sign.
  1696. xp7.addCertificate(cert.cert);
  1697. if (cert.extraCerts) { for (var i = 0; i < cert.extraCerts.length; i++) { xp7.addCertificate(cert.extraCerts[0]); } } // Add any extra certificates that form the cert chain
  1698. // Build authenticated attributes
  1699. var authenticatedAttributes = [
  1700. { type: forge.pki.oids.contentType, value: forge.pki.oids.data },
  1701. { type: forge.pki.oids.messageDigest } // This value will populated at signing time by node-forge
  1702. ]
  1703. if ((typeof args.desc == 'string') || (typeof args.url == 'string')) {
  1704. var codeSigningAttributes = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [] };
  1705. if (args.desc != null) { // Encode description as big-endian unicode.
  1706. var desc = "", ucs = Buffer.from(args.desc, 'ucs2').toString()
  1707. for (var k = 0; k < ucs.length; k += 2) { desc += String.fromCharCode(ucs.charCodeAt(k + 1), ucs.charCodeAt(k)); }
  1708. codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': desc }] });
  1709. }
  1710. if (args.url != null) { codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 1, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': args.url }] }); }
  1711. authenticatedAttributes.push({ type: obj.Oids.SPC_SP_OPUS_INFO_OBJID, value: codeSigningAttributes });
  1712. }
  1713. // Add the signer and sign
  1714. xp7.addSigner({
  1715. key: cert.key,
  1716. certificate: cert.cert,
  1717. digestAlgorithm: forge.pki.oids.sha384,
  1718. authenticatedAttributes: authenticatedAttributes
  1719. });
  1720. xp7.sign();
  1721. var p7signature = Buffer.from(forge.pkcs7.messageToPem(xp7).split('-----BEGIN PKCS7-----')[1].split('-----END PKCS7-----')[0], 'base64');
  1722. //console.log('Signature', Buffer.from(p7signature, 'binary').toString('base64'));
  1723. if (args.time == null) {
  1724. // Write the signature block to the output executable without time stamp
  1725. writeExecutableEx(output, p7signature, written, func);
  1726. } else {
  1727. // Decode the signature block
  1728. var pkcs7der = null;
  1729. try { pkcs7der = forge.asn1.fromDer(forge.util.createBuffer(p7signature)); } catch (ex) { func('' + ex); return; }
  1730. // To work around ForgeJS PKCS#7 limitation, this may break PKCS7 verify if ForgeJS adds support for it in the future
  1731. // Switch content type from "1.3.6.1.4.1.311.2.1.4" to "1.2.840.113549.1.7.1"
  1732. pkcs7der.value[1].value[0].value[2].value[0].value = forge.asn1.oidToDer(forge.pki.oids.data).data;
  1733. // Decode the PKCS7 message
  1734. var pkcs7 = p7.messageFromAsn1(pkcs7der);
  1735. // Create the timestamp request in DER format
  1736. const asn1 = forge.asn1;
  1737. const pkcs7dataOid = asn1.oidToDer('1.2.840.113549.1.7.1').data;
  1738. const microsoftCodeSigningOid = asn1.oidToDer('1.3.6.1.4.1.311.3.2.1').data;
  1739. const asn1obj =
  1740. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  1741. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, microsoftCodeSigningOid),
  1742. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  1743. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, pkcs7dataOid),
  1744. asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
  1745. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, pkcs7.rawCapture.signature.toString('binary')) // Signature here
  1746. ])
  1747. ])
  1748. ]);
  1749. // Re-decode the PKCS7 from the executable, this time, no workaround needed
  1750. try { pkcs7der = forge.asn1.fromDer(forge.util.createBuffer(p7signature)); } catch (ex) { func('' + ex); return; }
  1751. // Serialize an ASN.1 object to DER format in Base64
  1752. const requestBody = Buffer.from(asn1.toDer(asn1obj).data, 'binary').toString('base64');
  1753. // Make an HTTP request
  1754. const options = { url: args.time, proxy: args.proxy };
  1755. // Make a request to the time server
  1756. httpRequest(options, requestBody, function (err, data) {
  1757. if (err != null) { func(err); return; }
  1758. // Decode the timestamp signature block
  1759. var timepkcs7der = null;
  1760. try { timepkcs7der = forge.asn1.fromDer(forge.util.createBuffer(Buffer.from(data, 'base64').toString('binary'))); } catch (ex) { func("Unable to parse time-stamp response: " + ex); return; }
  1761. // Get the ASN1 certificates used to sign the timestamp and add them to the certs in the PKCS7 of the executable
  1762. // TODO: We could look to see if the certificate is already present in the executable
  1763. try {
  1764. var timeasn1Certs = timepkcs7der.value[1].value[0].value[3].value;
  1765. for (var i in timeasn1Certs) { pkcs7der.value[1].value[0].value[3].value.push(timeasn1Certs[i]); }
  1766. // Get the time signature and add it to the executables PKCS7
  1767. const timeasn1Signature = timepkcs7der.value[1].value[0].value[4];
  1768. const countersignatureOid = asn1.oidToDer('1.2.840.113549.1.9.6').data;
  1769. const asn1obj2 =
  1770. asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
  1771. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
  1772. asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, countersignatureOid),
  1773. timeasn1Signature
  1774. ])
  1775. ]);
  1776. pkcs7der.value[1].value[0].value[4].value[0].value.push(asn1obj2);
  1777. // Re-encode the executable signature block
  1778. const p7signature = Buffer.from(forge.asn1.toDer(pkcs7der).data, 'binary');
  1779. // Write the file with the signature block
  1780. writeExecutableEx(output, p7signature, written, func);
  1781. } catch (ex) { func('' + ex); return; } // Something failed
  1782. });
  1783. }
  1784. return;
  1785. }
  1786. // Close the file
  1787. fs.closeSync(output);
  1788. // Indicate success
  1789. func(null);
  1790. }
  1791. function writeExecutableEx(output, p7signature, written, func) {
  1792. // Quad Align the results, adding padding if necessary
  1793. var len = written + p7signature.length;
  1794. var padding = (8 - ((len) % 8)) % 8;
  1795. // Write the signature block header and signature
  1796. var win = Buffer.alloc(8); // WIN CERTIFICATE Structure
  1797. win.writeUInt32LE(p7signature.length + padding + 8); // DWORD length
  1798. win.writeUInt16LE(512, 4); // WORD revision
  1799. win.writeUInt16LE(2, 6); // WORD type
  1800. fs.writeSync(output, win);
  1801. fs.writeSync(output, p7signature);
  1802. if (padding > 0) { fs.writeSync(output, Buffer.alloc(padding, 0)); }
  1803. // Write the signature header
  1804. var addresstable = Buffer.alloc(8);
  1805. addresstable.writeUInt32LE(written);
  1806. addresstable.writeUInt32LE(8 + p7signature.length + padding, 4);
  1807. var signatureHeaderLocation = (obj.header.peHeaderLocation + 152 + (obj.header.pe32plus * 16));
  1808. fs.writeSync(output, addresstable, 0, 8, signatureHeaderLocation);
  1809. written += (p7signature.length + padding + 8); // Add the signature block to written counter
  1810. // Compute the checksum and write it in the PE header checksum location
  1811. var tmp = Buffer.alloc(4);
  1812. tmp.writeUInt32LE(runChecksumOnFile(output, written, ((obj.header.peOptionalHeaderLocation + 64) / 4)));
  1813. fs.writeSync(output, tmp, 0, 4, obj.header.peOptionalHeaderLocation + 64);
  1814. // Close the file
  1815. fs.closeSync(output);
  1816. // Indicate success
  1817. func(null);
  1818. }
  1819. // Return null if we could not open the file
  1820. return (openFile() ? obj : null);
  1821. }
  1822. function start() {
  1823. // Parse the arguments
  1824. const args = require('minimist')(process.argv.slice(2));
  1825. // Show tool help
  1826. if (process.argv.length < 3) {
  1827. console.log("MeshCentral Authenticode Tool.");
  1828. console.log("Usage:");
  1829. console.log(" node authenticode.js [command] [options]");
  1830. console.log("Commands:");
  1831. console.log(" info: Show information about an executable.");
  1832. console.log(" --exe [file] Required executable to view information.");
  1833. console.log(" --json Show information in JSON format.");
  1834. console.log(" sign: Sign an executable.");
  1835. console.log(" --exe [file] Required executable to sign.");
  1836. console.log(" --out [file] Resulting signed executable.");
  1837. console.log(" --pem [pemfile] Certificate & private key to sign the executable with.");
  1838. console.log(" --desc [description] Description string to embbed into signature.");
  1839. console.log(" --url [url] URL to embbed into signature.");
  1840. console.log(" --hash [method] Default is SHA384, possible value: MD5, SHA224, SHA256, SHA384 or SHA512.");
  1841. console.log(" --time [url] The time signing server URL.");
  1842. console.log(" --proxy [url] The HTTP proxy to use to contact the time signing server, must start with http://");
  1843. console.log(" unsign: Remove the signature from the executable.");
  1844. console.log(" --exe [file] Required executable to un-sign.");
  1845. console.log(" --out [file] Resulting executable with signature removed.");
  1846. console.log(" createcert: Create a code signging self-signed certificate and key.");
  1847. console.log(" --out [pemfile] Required certificate file to create.");
  1848. console.log(" --cn [value] Required certificate common name.");
  1849. console.log(" --country [value] Certificate country name.");
  1850. console.log(" --state [value] Certificate state name.");
  1851. console.log(" --locality [value] Certificate locality name.");
  1852. console.log(" --org [value] Certificate organization name.");
  1853. console.log(" --ou [value] Certificate organization unit name.");
  1854. console.log(" --serial [value] Certificate serial number.");
  1855. console.log(" timestamp: Add a signed timestamp to an already signed executable.");
  1856. console.log(" --exe [file] Required executable to timestamp.");
  1857. console.log(" --out [file] Resulting signed executable.");
  1858. console.log(" --time [url] The time signing server URL.");
  1859. console.log(" --proxy [url] The HTTP proxy to use to contact the time signing server, must start with http://");
  1860. console.log(" bitmaps: Show bitmap resources in the executable.");
  1861. console.log(" --exe [file] Input executable.");
  1862. console.log(" savebitmap: Save a single bitmap to a .bmp file.");
  1863. console.log(" --exe [file] Input executable.");
  1864. console.log(" --out [file] Resulting .ico file.");
  1865. console.log(" --bitmap [number] Bitmap number to save to file.");
  1866. console.log(" icons: Show the icon resources in the executable.");
  1867. console.log(" --exe [file] Input executable.");
  1868. console.log(" saveicon: Save a single icon bitmap to a .ico file.");
  1869. console.log(" --exe [file] Input executable.");
  1870. console.log(" --out [file] Resulting .ico file.");
  1871. console.log(" --icon [number] Icon number to save to file.");
  1872. console.log(" saveicons: Save an icon group to a .ico file.");
  1873. console.log(" --exe [file] Input executable.");
  1874. console.log(" --out [file] Resulting .ico file.");
  1875. console.log(" --icongroup [groupNumber] Icon groupnumber to save to file.");
  1876. console.log("");
  1877. console.log("Note that certificate PEM files must first have the signing certificate,");
  1878. console.log("followed by all certificates that form the trust chain.");
  1879. console.log("");
  1880. console.log("When doing sign/unsign, you can also change resource properties of the generated file.");
  1881. console.log("");
  1882. console.log(" --fileversionnumber n.n.n.n");
  1883. console.log(" --productversionnumber n.n.n.n");
  1884. console.log(" --filedescription [value]");
  1885. console.log(" --fileversion [value]");
  1886. console.log(" --internalname [value]");
  1887. console.log(" --legalcopyright [value]");
  1888. console.log(" --originalfilename [value]");
  1889. console.log(" --productname [value]");
  1890. console.log(" --productversion [value]");
  1891. console.log(" --removeicongroup [number]");
  1892. console.log(" --removebitmap [number]");
  1893. console.log(" --icon [groupNumber],[filename.ico]");
  1894. console.log(" --bitmap [number],[filename.bmp]");
  1895. return;
  1896. }
  1897. // Check that a valid command is passed in
  1898. if (['info', 'sign', 'unsign', 'createcert', 'icons', 'bitmaps', 'saveicon', 'saveicons', 'savebitmap', 'header', 'sections', 'timestamp', 'signblock'].indexOf(process.argv[2].toLowerCase()) == -1) {
  1899. console.log("Invalid command: " + process.argv[2]);
  1900. console.log("Valid commands are: info, sign, unsign, createcert, timestamp");
  1901. return;
  1902. }
  1903. var exe = null;
  1904. if (args.exe) {
  1905. // Check the file exists and open the file
  1906. var stats = null;
  1907. try { stats = require('fs').statSync(args.exe); } catch (ex) { }
  1908. if (stats == null) { console.log("Unable to executable open file: " + args.exe); return; }
  1909. exe = createAuthenticodeHandler(args.exe);
  1910. if (exe == null) { console.log("Unable to parse executable file: " + args.exe); return; }
  1911. }
  1912. // Parse the string resources and make any required changes
  1913. var resChanges = false, versionStrings = null;
  1914. if (exe != null) {
  1915. versionStrings = exe.getVersionInfo();
  1916. var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion'];
  1917. for (var i in versionProperties) {
  1918. const prop = versionProperties[i], propl = prop.toLowerCase();
  1919. if (args[propl] && (args[propl] != versionStrings[prop])) { versionStrings[prop] = args[propl]; resChanges = true; }
  1920. }
  1921. if (args['fileversionnumber'] != null) {
  1922. const fileVerSplit = args['fileversionnumber'].split('.');
  1923. if (fileVerSplit.length != 4) { console.log("--fileversionnumber must be of format n.n.n.n, for example: 1.2.3.4"); return; }
  1924. for (var i in fileVerSplit) { var n = parseInt(fileVerSplit[i]); if ((n < 0) || (n > 65535)) { console.log("--fileversionnumber numbers must be between 0 and 65535."); return; } }
  1925. if (args['fileversionnumber'] != versionStrings['~FileVersion']) { versionStrings['~FileVersion'] = args['fileversionnumber']; resChanges = true; }
  1926. }
  1927. if (args['productversionnumber'] != null) {
  1928. const productVerSplit = args['productversionnumber'].split('.');
  1929. if (productVerSplit.length != 4) { console.log("--productversionnumber must be of format n.n.n.n, for example: 1.2.3.4"); return; }
  1930. for (var i in productVerSplit) { var n = parseInt(productVerSplit[i]); if ((n < 0) || (n > 65535)) { console.log("--productversionnumber numbers must be between 0 and 65535."); return; } }
  1931. if (args['productversionnumber'] != versionStrings['~ProductVersion']) { versionStrings['~ProductVersion'] = args['productversionnumber']; resChanges = true; }
  1932. }
  1933. if (resChanges == true) { exe.setVersionInfo(versionStrings); }
  1934. }
  1935. // Parse the icon changes
  1936. resChanges = false;
  1937. var icons = null, bitmaps = null;
  1938. if (exe != null) {
  1939. icons = exe.getIconInfo();
  1940. bitmaps = exe.getBitmapInfo();
  1941. if (typeof args['removeicongroup'] == 'string') { // If --removeicongroup is used, it's to remove an existing icon group
  1942. const groupsToRemove = args['removeicongroup'].split(',');
  1943. for (var i in groupsToRemove) { if (icons[groupsToRemove[i]] != null) { delete icons[groupsToRemove[i]]; resChanges = true; } }
  1944. } else if (typeof args['removeicongroup'] == 'number') {
  1945. if (icons[args['removeicongroup']] != null) { delete icons[args['removeicongroup']]; resChanges = true; }
  1946. }
  1947. if (typeof args['removebitmap'] == 'string') { // If --removebitmap is used
  1948. const bitmapsToRemove = args['removebitmap'].split(',');
  1949. for (var i in bitmapsToRemove) { if (bitmaps[bitmapsToRemove[i]] != null) { delete bitmaps[bitmapsToRemove[i]]; resChanges = true; } }
  1950. } else if (typeof args['removebitmap'] == 'number') {
  1951. if (bitmaps[args['removebitmap']] != null) { delete bitmaps[args['removebitmap']]; resChanges = true; }
  1952. }
  1953. if (typeof args['icon'] == 'string') { // If --icon is used, it's to add or replace an existing icon group
  1954. const iconToAddSplit = args['icon'].split(',');
  1955. if (iconToAddSplit.length != 2) { console.log("The --icon format is: --icon [number],[file]."); return; }
  1956. const iconName = parseInt(iconToAddSplit[0]);
  1957. const iconFile = iconToAddSplit[1];
  1958. const icon = loadIcon(iconFile);
  1959. if (icon == null) { console.log("Unable to load icon: " + iconFile); return; }
  1960. if (icons[iconName] != null) {
  1961. const iconHash = hashObject(icon); // Compute the new icon group hash
  1962. const iconHash2 = hashObject(icons[iconName]); // Computer the old icon group hash
  1963. if (iconHash != iconHash2) { icons[iconName] = icon; resChanges = true; } // If different, replace the icon group
  1964. } else {
  1965. icons[iconName] = icon; // We are adding an icon group
  1966. resChanges = true;
  1967. }
  1968. }
  1969. if (typeof args['bitmap'] == 'string') { // If --bitmap is used, it's to add or replace an existing bitmap
  1970. const bitmapToAddSplit = args['bitmap'].split(',');
  1971. if (bitmapToAddSplit.length != 2) { console.log("The --bitmap format is: --bitmap [number],[file]."); return; }
  1972. const bitmapName = parseInt(bitmapToAddSplit[0]);
  1973. const bitmapFile = bitmapToAddSplit[1];
  1974. const bitmap = loadBitmap(bitmapFile);
  1975. if (bitmap == null) { console.log("Unable to load bitmap: " + bitmapFile); return; }
  1976. if (bitmaps[bitmapName] != null) {
  1977. const bitmapHash = hashObject(bitmap); // Compute the new bitmap hash
  1978. const bitmapHash2 = hashObject(bitmaps[bitmapName]); // Computer the old bitmap hash
  1979. if (bitmapHash != bitmapHash2) { bitmaps[bitmapName] = bitmap; resChanges = true; } // If different, replace the new bitmap
  1980. } else {
  1981. bitmaps[bitmapName] = bitmap; // We are adding an new bitmap
  1982. resChanges = true;
  1983. }
  1984. }
  1985. if (resChanges == true) {
  1986. exe.setIconInfo(icons);
  1987. exe.setBitmapInfo(bitmaps);
  1988. }
  1989. }
  1990. // Execute the command
  1991. var command = process.argv[2].toLowerCase();
  1992. if (command == 'info') { // Get signature information about an executable
  1993. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  1994. if (args.json) {
  1995. var r = {}, stringInfo = exe.getVersionInfo();
  1996. if (stringInfo != null) {
  1997. r.versionInfo = {};
  1998. r.stringInfo = {};
  1999. for (var i in stringInfo) { if (i.startsWith('~')) { r.versionInfo[i.substring(1)] = stringInfo[i]; } else { r.stringInfo[i] = stringInfo[i]; } }
  2000. }
  2001. if (exe.fileHashAlgo != null) {
  2002. r.signture = {};
  2003. if (exe.fileHashAlgo != null) { r.signture.hashMethod = exe.fileHashAlgo; }
  2004. if (exe.fileHashSigned != null) { r.signture.hashSigned = exe.fileHashSigned.toString('hex'); }
  2005. if (exe.fileHashActual != null) { r.signture.hashActual = exe.fileHashActual.toString('hex'); }
  2006. if (exe.signingAttribs && exe.signingAttribs.length > 0) { r.signture.attributes = exe.signingAttribs; }
  2007. }
  2008. console.log(JSON.stringify(r, null, 2));
  2009. } else {
  2010. var versionInfo = exe.getVersionInfo();
  2011. if (versionInfo != null) {
  2012. console.log("Version Information:");
  2013. for (var i in versionInfo) { if (i.startsWith('~') == true) { console.log(' ' + i.substring(1) + ': ' + versionInfo[i] + ''); } }
  2014. console.log("String Information:");
  2015. for (var i in versionInfo) { if (i.startsWith('~') == false) { if (versionInfo[i] == null) { console.log(' ' + i + ': (Empty)'); } else { console.log(' ' + i + ': \"' + versionInfo[i] + '\"'); } } }
  2016. }
  2017. console.log("Checksum Information:");
  2018. console.log(" Header CheckSum: 0x" + exe.header.peWindows.checkSum.toString(16));
  2019. console.log(" Actual CheckSum: 0x" + exe.header.peWindows.checkSumActual.toString(16));
  2020. console.log("Signature Information:");
  2021. if (exe.fileHashAlgo != null) {
  2022. console.log(" Hash Method:", exe.fileHashAlgo);
  2023. if (exe.fileHashSigned != null) { console.log(" Signed Hash:", exe.fileHashSigned.toString('hex')); }
  2024. if (exe.fileHashActual != null) { console.log(" Actual Hash:", exe.fileHashActual.toString('hex')); }
  2025. } else {
  2026. console.log(" This file is not signed.");
  2027. }
  2028. if (exe.signingAttribs && exe.signingAttribs.length > 0) { console.log("Signature Attributes:"); for (var i in exe.signingAttribs) { console.log(' ' + exe.signingAttribs[i]); } }
  2029. }
  2030. }
  2031. if (command == 'header') { // Display the full executable header in JSON format
  2032. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2033. console.log(exe.header);
  2034. // Check that the header is valid
  2035. var ptr = 1024, sizeOfCode = 0, sizeOfInitializedData = 0;
  2036. for (var i in exe.header.sections) {
  2037. if (i == '.text') { sizeOfCode += exe.header.sections[i].rawSize; } else { sizeOfInitializedData += exe.header.sections[i].rawSize; }
  2038. if (exe.header.sections[i].rawAddr != ptr) { console.log('WARNING: ' + i + ' section should have a rawAddr or ' + ptr + ', but has ' + exe.header.sections[i].rawAddr + ' instead.'); }
  2039. ptr += exe.header.sections[i].rawSize;
  2040. }
  2041. if (exe.header.peStandard.sizeOfCode != sizeOfCode) { console.log('WARNING: Size of code is ' + exe.header.peStandard.sizeOfCode + ', should be ' + sizeOfCode + '.'); }
  2042. if (exe.header.peStandard.sizeOfInitializedData != sizeOfInitializedData) { console.log('WARNING: Size of initialized data is ' + exe.header.peStandard.sizeOfInitializedData + ', should be ' + sizeOfInitializedData + '.'); }
  2043. }
  2044. if (command == 'sections') { // Display sections in CSV format
  2045. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2046. var csvHeader = 'section';
  2047. for (var i in exe.header.sections['.text']) { csvHeader += ',' + i; }
  2048. console.log(csvHeader);
  2049. for (var i in exe.header.sections) {
  2050. var csvData = i;
  2051. for (var j in exe.header.sections[i]) { csvData += ',' + exe.header.sections[i][j]; }
  2052. console.log(csvData);
  2053. }
  2054. }
  2055. if (command == 'sign') { // Sign an executable
  2056. if (typeof args.exe != 'string') { console.log("Missing --exe [filename]"); return; }
  2057. if (typeof args.hash == 'string') { args.hash = args.hash.toLowerCase(); if (['md5', 'sha224', 'sha256', 'sha384', 'sha512'].indexOf(args.hash) == -1) { console.log("Invalid hash method, must be SHA256 or SHA384"); return; } }
  2058. if (args.hash == null) { args.hash = 'sha384'; }
  2059. createOutFile(args, args.exe);
  2060. var cert = loadCertificates(args.pem);
  2061. if (cert == null) { console.log("Unable to load certificate and/or private key, generating test certificate."); cert = createSelfSignedCert({ cn: 'Test' }); }
  2062. if (resChanges == false) {
  2063. console.log("Signing to " + args.out);
  2064. exe.sign(cert, args, function (err) { // Simple signing, copy most of the original file.
  2065. if (err == null) { console.log("Done."); } else { console.log(err); }
  2066. if (exe != null) { exe.close(); }
  2067. });
  2068. return;
  2069. } else {
  2070. console.log("Changing resources and signing to " + args.out);
  2071. exe.writeExecutable(args, cert, function (err) { // Signing with resources decoded and re-encoded.
  2072. if (err == null) { console.log("Done."); } else { console.log(err); }
  2073. if (exe != null) { exe.close(); }
  2074. });
  2075. return;
  2076. }
  2077. }
  2078. if (command == 'unsign') { // Unsign an executable
  2079. if (typeof args.exe != 'string') { console.log("Missing --exe [filename]"); return; }
  2080. createOutFile(args, args.exe);
  2081. if (resChanges == false) {
  2082. if (exe.header.signed) {
  2083. console.log("Unsigning to " + args.out);
  2084. exe.unsign(args); // Simple unsign, copy most of the original file.
  2085. console.log("Done.");
  2086. } else {
  2087. console.log("Executable is not signed.");
  2088. }
  2089. } else {
  2090. console.log("Changing resources and unsigning to " + args.out);
  2091. exe.writeExecutable(args, null, function (err) { // Unsigning with resources decoded and re-encoded.
  2092. if (err == null) { console.log("Done."); } else { console.log(err); }
  2093. if (exe != null) { exe.close(); }
  2094. });
  2095. }
  2096. }
  2097. if (command == 'createcert') { // Create a code signing certificate and private key
  2098. if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
  2099. if (typeof args.cn != 'string') { console.log("Missing --cn [name]"); return; }
  2100. if (typeof args.serial == 'string') { if (args.serial != parseInt(args.serial)) { console.log("Invalid serial number."); return; } else { args.serial = parseInt(args.serial); } }
  2101. if (typeof args.serial == 'number') { args.serial = '0' + args.serial; } // Serial number must be a integer string with a single leading '0'
  2102. const cert = createSelfSignedCert(args);
  2103. console.log("Writing to " + args.out);
  2104. fs.writeFileSync(args.out, pki.certificateToPem(cert.cert) + '\r\n' + pki.privateKeyToPem(cert.key));
  2105. console.log("Done.");
  2106. }
  2107. if (command == 'bitmaps') { // Show bitmaps in the executable
  2108. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2109. if (args.json) {
  2110. var bitmapInfo = exe.getBitmapInfo();
  2111. console.log(JSON.stringify(bitmapInfo, null, 2));
  2112. } else {
  2113. var bitmapInfo = exe.getBitmapInfo();
  2114. if (bitmapInfo != null) {
  2115. console.log("Bitmap Information:");
  2116. for (var i in bitmapInfo) { console.log(' ' + i + ': ' + bitmapInfo[i].length + ' byte' + ((bitmapInfo[i].length > 1) ? 's' : '') + '.'); }
  2117. }
  2118. }
  2119. }
  2120. if (command == 'savebitmap') { // Save an bitmap to file
  2121. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2122. if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
  2123. if (typeof args.bitmap != 'number') { console.log("Missing or incorrect --bitmap [number]"); return; }
  2124. const bitmapInfo = exe.getBitmapInfo();
  2125. if (bitmapInfo[args.bitmap] == null) { console.log("Unknown bitmap: " + args.bitmap); return; }
  2126. console.log("Writing to " + args.out);
  2127. var bitmapHeader = Buffer.from('424D000000000000000036000000', 'hex');
  2128. bitmapHeader.writeUInt32LE(14 + bitmapInfo[args.bitmap].length, 2); // Write the full size of the bitmap file
  2129. fs.writeFileSync(args.out, Buffer.concat([bitmapHeader, bitmapInfo[args.bitmap]]));
  2130. console.log("Done.");
  2131. }
  2132. if (command == 'icons') { // Show icons in the executable
  2133. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2134. if (args.json) {
  2135. var r = {}, iconInfo = exe.getIconInfo();
  2136. if (iconInfo != null) { r.iconInfo = iconInfo; }
  2137. console.log(JSON.stringify(r, null, 2));
  2138. } else {
  2139. var iconInfo = exe.getIconInfo();
  2140. if (iconInfo != null) {
  2141. console.log("Icon Information:");
  2142. for (var i in iconInfo) { console.log(' Group ' + i + ':'); for (var j in iconInfo[i].icons) { console.log(' Icon ' + j + ': ' + ((iconInfo[i].icons[j].width == 0) ? 256 : iconInfo[i].icons[j].width) + 'x' + ((iconInfo[i].icons[j].height == 0) ? 256 : iconInfo[i].icons[j].height) + ', size: ' + iconInfo[i].icons[j].icon.length); } }
  2143. }
  2144. }
  2145. }
  2146. if (command == 'saveicon') { // Save an icon to file
  2147. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2148. if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
  2149. if (typeof args.icon != 'number') { console.log("Missing or incorrect --icon [number]"); return; }
  2150. const iconInfo = exe.getIconInfo();
  2151. var icon = null;
  2152. for (var i in iconInfo) { if (iconInfo[i].icons[args.icon]) { icon = iconInfo[i].icons[args.icon]; } }
  2153. if (icon == null) { console.log("Unknown icon: " + args.icon); return; }
  2154. // .ico header: https://en.wikipedia.org/wiki/ICO_(file_format)
  2155. var buf = Buffer.alloc(22);
  2156. buf.writeUInt16LE(1, 2); // 1 = Icon, 2 = Cursor
  2157. buf.writeUInt16LE(1, 4); // Icon Count, always 1 in our case
  2158. buf[6] = icon.width; // Width (0 = 256)
  2159. buf[7] = icon.height; // Height (0 = 256)
  2160. buf[8] = icon.colorCount; // Colors
  2161. buf.writeUInt16LE(icon.planes, 10); // Color planes
  2162. buf.writeUInt16LE(icon.bitCount, 12); // Bits per pixel
  2163. buf.writeUInt32LE(icon.icon.length, 14); // Size
  2164. buf.writeUInt32LE(22, 18); // Offset, always 22 in our case
  2165. console.log("Writing to " + args.out);
  2166. fs.writeFileSync(args.out, Buffer.concat([buf, icon.icon]));
  2167. console.log("Done.");
  2168. }
  2169. if (command == 'saveicons') { // Save an icon group to file
  2170. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2171. if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
  2172. if (typeof args.icongroup != 'number') { console.log("Missing or incorrect --icongroup [number]"); return; }
  2173. const iconInfo = exe.getIconInfo();
  2174. const iconGroup = iconInfo[args.icongroup];
  2175. if (iconGroup == null) { console.log("Invalid or incorrect --icongroup [number]"); return; }
  2176. // Count the number of icons in the group
  2177. var iconCount = 0;
  2178. for (var i in iconGroup.icons) { iconCount++; }
  2179. // .ico header: https://en.wikipedia.org/wiki/ICO_(file_format)
  2180. const iconFileData = [];
  2181. const header = Buffer.alloc(6);
  2182. header.writeUInt16LE(1, 2); // 1 = Icon, 2 = Cursor
  2183. header.writeUInt16LE(iconCount, 4); // Icon Count, always 1 in our case
  2184. iconFileData.push(header);
  2185. // Store each icon header
  2186. var offsetPtr = 6 + (16 * iconCount);
  2187. for (var i in iconGroup.icons) {
  2188. const buf = Buffer.alloc(16);
  2189. buf[0] = iconGroup.icons[i].width; // Width (0 = 256)
  2190. buf[1] = iconGroup.icons[i].height; // Height (0 = 256)
  2191. buf[2] = iconGroup.icons[i].colorCount; // Colors
  2192. buf.writeUInt16LE(iconGroup.icons[i].planes, 4); // Color planes
  2193. buf.writeUInt16LE(iconGroup.icons[i].bitCount, 6); // Bits per pixel
  2194. buf.writeUInt32LE(iconGroup.icons[i].icon.length, 8); // Size
  2195. buf.writeUInt32LE(offsetPtr, 12); // Offset
  2196. offsetPtr += iconGroup.icons[i].icon.length;
  2197. iconFileData.push(buf);
  2198. }
  2199. // Store each icon
  2200. for (var i in iconGroup.icons) { iconFileData.push(iconGroup.icons[i].icon); }
  2201. // Write the .ico file
  2202. console.log("Writing to " + args.out);
  2203. fs.writeFileSync(args.out, Buffer.concat(iconFileData));
  2204. console.log("Done.");
  2205. }
  2206. if (command == 'signblock') { // Display the raw signature block of the executable in hex
  2207. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2208. var buf = exe.getRawSignatureBlock();
  2209. if (buf == null) { console.log("Executable is not signed."); return } else { console.log(buf.toString('hex')); return }
  2210. }
  2211. if (command == 'timestamp') {
  2212. if (exe == null) { console.log("Missing --exe [filename]"); return; }
  2213. if (exe.signature == null) { console.log("Executable is not signed."); return; }
  2214. if (typeof args.time != 'string') { console.log("Missing --time [url]"); return; }
  2215. createOutFile(args, args.exe);
  2216. console.log("Requesting time signature...");
  2217. exe.timeStampRequest(args, function (err) {
  2218. if (err == null) { console.log("Done."); } else { console.log(err); }
  2219. if (exe != null) { exe.close(); }
  2220. })
  2221. return;
  2222. }
  2223. // Close the file
  2224. if (exe != null) { exe.close(); }
  2225. }
  2226. // If this is the main module, run the command line version
  2227. if (require.main === module) { start(); }
  2228. // Exports
  2229. module.exports.createAuthenticodeHandler = createAuthenticodeHandler;
  2230. module.exports.loadCertificates = loadCertificates;
  2231. module.exports.loadIcon = loadIcon;
  2232. module.exports.loadBitmap = loadBitmap;
  2233. module.exports.hashObject = hashObject;