An open API service indexing awesome lists of open source software.

https://github.com/jet2jet/resedit-js

JS library editing Windows Resource data
https://github.com/jet2jet/resedit-js

authenticode nodejs pe-executable resource windows winres

Last synced: 2 months ago
JSON representation

JS library editing Windows Resource data

Awesome Lists containing this project

README

          

[![NPM Version](https://img.shields.io/npm/v/resedit)](https://www.npmjs.com/package/resedit)
[![NPM Downloads](https://img.shields.io/npm/dw/resedit)](https://www.npmjs.com/package/resedit)
[![Build Status](https://github.com/jet2jet/resedit-js/actions/workflows/main-ci.yml/badge.svg)](https://github.com/jet2jet/resedit-js)

# resedit-js

resedit-js is a library that manipulates resouces contained by Windows Executable files. All implementations are written in JavaScript (TypeScript), without using any native binaries. resedit-js works in both Node.js environment and Web environment.

This library is not tested well for modifying and/or signing executables yet. Please be careful with the emitted binaries.

To use from command line, [resedit-js-cli](https://www.npmjs.com/package/resedit-cli) is suitable.

The demo page: [resedit demo](https://www.pg-fl.jp/program/resedit/index.en.htm)

- [Install](#install)
- [Usage](#usage)
- [Supported formats](#supported-formats)
- [Parsing signed executables](#parsing-signed-executables)
- [Signing executables with resedit-js](#signing-executables-with-resedit-js)
- [Notes](#notes)
- [Examples](#examples)
- [License](#license)

## Install

```
npm install resedit
```

## Usage

- For detailed code using `ResEdit` namespace, see [Examples](#examples).
- For more APIs, please see `dist` directory of the package. And, [some test codes](./src/test) may help you for usages.

For ESM (ES Modules):

```js
//import * as PELibrary from 'pe-library';
import * as ResEdit from 'resedit';
```

For CJS (CommonJS modules including classic Node.js scripts):

```js
//const PELibrary = require('pe-library');
const ResEdit = require('resedit');
```

> If your Node.js version is prior to v20.19.5, `require` cannot be used for `'resedit'` package and you must use `'resedit/cjs'` package as described in [Migrate from v1.x to v2.x](#migrate-from-v1x-to-v2x) (collapsed in `Migration from prior resedit versions` area).

Migration from prior resedit versions

### Migrate from v2.x to v3.x

This major version up includes 'Change requirements of Node.js version (_v20.19.5 or later_ is required)' and 'Remove TypeScript enum usage' only. If your code base (project) meets following conditions, you can safely upgrade to v3.x (without no more actions):

- You uses resedit v2.x
- If you use v1.x, please follow [Migrate from v1.x to v2.x](#migrate-from-v1x-to-v2x).
- The code base already uses Node.js v20.19.5 or later (including v22, v24, or higher)
- Node.js v18 or earlier is already end-of-life. Upgrade to v22 or v24 is recommended.
- For using Node.js v20, upgrading to v20.19.5 or later would not be difficult.
- Following enum's members are not used or used only as values: `VersionFileFlags`, `VersionFileOS`, `VersionFileDriverSubtype`, `VersionFileFontSubtype`, and `VersionFileType`
- If you use the members as types (e.g. `let x: VersionFileFlags.Debug`), rewrite with using `typeof` (e.g, `let x: typeof VersionFileFlags.Debug`).

### Migrate from v1.x to v2.x

- If you use from ES module (.mjs) and load by using `import`, no need for migration.
- If you use from ES module (.mjs) and load by using `require` (Node.js: via `createRequire`), replace with `import` statement: `import * as ResEdit from 'resedit'`.
- If you use from CommonJS module (.cjs) and load by using `require`, choose followings:
- If you are using under Node.js environment, _only_ update Node.js to v20.19.0 or later
- Starting from Node.js v20.19.0, you can `require` synchronous ES module. resedit does not use top-level `await`, so you can write `require('resedit')` from CommonJS module.
- Convert CommonJS module to ES module and replace `require` call with `import` statement
- Use `resedit/cjs` module and call `load` function (see below)
- If you use TypeScript,
- For `"module": "ES2015"` or higher, no need for migration.
- For `"module": "CommonJS"`, import `resedit/cjs` and call `load` function

The sample of using `resedit/cjs` in CommonJS module is:

```js
const { load } = require('resedit/cjs');
load().then((ResEdit) => {
// ResEdit will be the namespace object of resedit library
// (for example ResEdit.Data.IconFile is available)
});
```

Similarly, the sample of using `resedit/cjs` in TypeScript CommonJS module is:

```ts
// You can use `ResEdit` for type references (cannot be used for value references)
import { type ResEdit, load } from 'resedit/cjs';
load().then((RE: typeof ResEdit) => {
// RE will be the namespace object of resedit library
// (for example RE.Data.IconFile is available)
});
```

## Supported formats

- Windows Executables (PE Format, such as `.exe` and `.dll`), both 32-bit and 64-bit, are supported.
- Executables for 16-bit Windows is not supported.
- `.res` file is not supported now.
- PNG-based icon data is supported on `require('resedit').Resource.IconGroupEntry` class.

## Parsing signed executables

- Parsing signed executables (by using Authenticode or etc.) is not allowed by default and an exception will be thrown if `NtExecutable.from` receives a signed binary.
- To parse signed, `{ ignoreCert: true }` object must be passed to the second argument of `NtExecutable.from`.
- Although the base executable data is signed, `NtExecutable.generate` will generate unsigned executable binary. If you want to re-sign it, you must use generate-function with signing (see below) or any other signing tool such as Microsoft `signtool`.

## Signing executables with resedit-js

resedit-js provides basic signing process `generateExecutableWithSign` function, which is based on [Authenticode specification](https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx) and related RFCs.

To keep resedit-js generic library, the followings are required to use signing process.

- Encryption / calculating hash (digest) process (e.g. Node.js built-in `crypto` module)
- A private key data is implicitly required to encrypt data.
- DER-format certificate binary (such as `*.cer` file data or `*.p7b` file data with DER-format), which is paired with the private key used by encryption process.
- (optional) Generating timestamp data, especially communicating with TSA server (e.g. HTTP/HTTPS API)

These requirements are represented as [`SignerObject`](./src/main/sign/SignerObject.ts). The caller of `generateExecutableWithSign` function must implement this object to sign executables.

An example code is here: [signTest.mjs](./examples/sign/signTest.mjs)

Note that resedit-js only provides basic signing process, and provides as beta version. For example adding more attributes/informations to certificates are not supported now.

> Some digest algorithms, such as SHA3 algorithms, might not be supported by current Windows.

## Notes

- **It is not strongly recommended that the destination executable file is equal to the source executable file (which is not an intermediate data).**

## Examples

```js
import * as PELibrary from 'pe-library';
import * as ResEdit from 'resedit';
import * as fs from 'fs';

// load and parse data
const data = fs.readFileSync('MyApp.exe');
// (the Node.js Buffer instance can be specified directly to NtExecutable.from)
const exe = PELibrary.NtExecutable.from(data);
const res = PELibrary.NtExecutableResource.from(exe);

// rewrite resources
// - You can use helper classes as followings:
// - ResEdit.Resource.IconGroupEntry: access icon resource data
// - ResEdit.Resource.StringTable: access string resource data
// - ResEdit.Resource.VersionInfo: access version info data

// -- replace icons

// load icon data from file
// (you can use ResEdit.Data.IconFile to parse icon data)
const iconFile = ResEdit.Data.IconFile.from(fs.readFileSync('MyIcon.ico'));

ResEdit.Resource.IconGroupEntry.replaceIconsForResource(
// destEntries
res.entries,
// iconGroupID
// - This ID is originally defined in base executable file
// (the ID list can be retrieved by `ResEdit.Resource.IconGroupEntry.fromEntries(res.entries).map((entry) => entry.id)`)
101,
// lang ('lang: 1033' means 'en-US')
1033,
// icons (map IconFileItem to IconItem/RawIconItem)
iconFile.icons.map((item) => item.data)
);

// -- replace version

const viList = ResEdit.Resource.VersionInfo.fromEntries(res.entries);
const vi = viList[0];
// setFileVersion will set `vi.fixedInfo.fileVersionMS`/`fileVersionLS` and 'FileVersion' string value
// ('1033' means 'en-US')
vi.setFileVersion(1, 0, 0, 0, 1033);
// ('lang: 1033' means 'en-US', 'codepage: 1200' is the default codepage)
vi.setStringValues(
{ lang: 1033, codepage: 1200 },
{
FileDescription: 'My application',
ProductName: 'My product',
}
);
vi.outputToResourceEntries(res.entries);

// write to another binary
res.outputResource(exe);
const newBinary = exe.generate();
fs.writeFileSync('MyApp_modified.exe', Buffer.from(newBinary));
```

## License

[MIT License](./LICENSE)