npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

shift-interpreter

v1.0.0

Published

Shift-interpreter is an experimental JavaScript meta-interpreter useful for reverse engineering and analysis. One notable difference from other projects is that shift-interpreter retains state over an entire script but can be fed expressions and statement

Downloads

72

Readme

Shift Interpreter

Shift-interpreter is an experimental JavaScript meta-interpreter useful for reverse engineering and analysis. One notable difference from other projects is that shift-interpreter retains state over an entire script but can be fed expressions and statements piecemeal, regardless of their original location. This allows a user to execute only the portions of JavaScript necessary for analysis.

Who is this for

JavaScript analyzers.

This was created to aid "static" analysis, reverse engineering, and deobfuscation of JavaScript code.

Status

Experimental.

This library is frequently used in ad-hoc analysis but has never been used in any high performance or mission critical applications.

Warning!

This should not be considered a secure JavaScript sandbox. This is not a secure JavaScript sandbox.

Installation

$ npm install shift-interpreter

Usage

Basic usage

const { interpret } = require('shift-intepreter');

const source = `
const a = 40;
a + a + 20;
`;

const result = interpret(source);
console.log(result); // 100

The above is equivalent to the following:

const { parseScript } = require('shift-parser');
const { Interpreter } = require('shift-intepreter');

const source = `
const a = 40;
a + a + 20;
`;

const tree = parseScript(source);

const interpreter = new Interpreter();
interpreter.load(tree);

const result = interpreter.run();
console.log(result); // 100

Passing contexts

By default, a script has access to nothing, its global context is empty. Pass a JavaScript object as the second parameter to .load() to use as the global context.

const { parseScript } = require('shift-parser');
const { Interpreter } = require('shift-intepreter');

const source = `
console.log("hello world!");
`;

const tree = parseScript(source);

const interpreter = new Interpreter();
interpreter.load(tree);
const result = interpreter.run(); // ReferenceError: console is not defined

interpreter.load(tree, { console });
const result = interpreter.run(); // "hello world!"

Selective Execution

The following is an example of selective execution. This program decodes an array of strings while only actually executing one statement in the target source.

const { parseScript } = require('shift-parser');
const { Interpreter } = require('shift-intepreter');

const source = `
const strings = [ "ZG9jdW1lbnQ=", "YWRkRXZlbnRMaXN0ZW5lcg==", "bG9hZA==" ];
function decode(str) {
  return atob(str)
}
window[decode(strings[0])][decode(strings[1])](decode(strings[2]), () => {
  somework();
})
`;

const tree = parseScript(source);

const interpreter = new Interpreter();

// load the tree with a context that has an "atob" function
interpreter.load(tree, {
  atob: str => Buffer.from(str, 'base64').toString('ascii'),
});

// run the second statement in the script (the "decode" function declaration)
interpreter.run(tree.statements[1]);

// retrieve the array expression node from the parsed source.
const stringArrayExpression = tree.statements[0].declaration.declarators[0].init;

// get the runtime value of the function declaration above. This is the interpreter's
// value for the passed identifier, which in this case is an executable function.
const decodeFunction = interpreter.getRuntimeValue(tree.statements[1].name);

// map over the elements of the array expression, decoding each value with the function from the interpreter.
const decodedStrings = stringArrayExpression.elements.map(node => decodeFunction(node.value));

console.log(decodedStrings); // [ 'document', 'addEventListener', 'load' ]

API

interpret/intrepretSource(src, context)

interpretTree(ast, context)

These methods run the source (or AST), with the optional context, and return the result of execution. These are convenience methods exposed for rapid testing and are not the main use of the library. If you find your use case covered with these, you probably want another tool (or eval)

Interpreter(options)

Constructor for the interpreter, takes an options object.

options

options.skipUnsupported: boolean

Skip unsupported nodes, false by default. There are few nodes unsupported outright but there are numerous combinations of nodes that are not supported (e.g. array assignments in for..of). These throw an error by default but you can skip them if their execution isn't important to you by passing {skipUnsupported: true}

Note: Submit issues for unsupported nodes along with use cases and minimal reproducible source. Most cases are not supported only due to time and lack of a pressing need, not due to complexity.

options.handler: NodeHandler

Pass your own handler to override or augment behavior for specific nodes.

.load(ast, context = {})

Load an ast as the script this intepreter will analyze for state and execution. Optionally pass a context object to use as the global context

.run(node?)

Run the script or the passed node. Returns the result of execution.

.runToFirstError(node?)

Like .run(), but swallows the error so you don't need to litter try/catches around implementing code. Useful when you find unsupported cases but enough of the program runs to cover your needs.

.getRuntimeValue(identifierNode)

Get the interpreter's runtime value for the passed identifier node.

Known limitations

Too many to list, but here are a few.

  • This is not a sandbox. Modifications of native APIs will persist in the host environment.
  • Edge cases around Symbols not explored.
  • Does not support Class constructors with super(). These could be supported but I haven't had a reason to work on it yet.

The following support is deferred until necessary. The syntax is not often found in production code due to common practices or transpilation.

  • Does not support with statements.
  • Does not support label statements.
  • Does not support yield expressions.
  • Does not support tagged template strings.
  • Does not support await expressions.

Contributing

This is a "get stuff done" library. It has grown as limitations have been found in real world usage. Contributions need to reflect real world use cases. Improvements to interpreter accuracy that primarily address edge cases will only be considered if they make the codebase more maintainable.

Are you an ECMAScript specification or interpreter guru and can point out the million ways this fails? Feel free to submit issues or PRs with (skipped) tests for minimal reproducible cases. They will be included to help future implementers.