Darken
What is darken?
Darken is a library providing useful components to be used when doing simple obfuscation of Brightscript scripts. It is not intended to be a tool or complete solution on its own.
When combined with a parser that can return AST (such as @roku-road/bright), the library can be used to do basic literal obfuscation, reference renaming, comment stripping and type removal. The project includes a simple manifest parser to determine entry points and related files to ensure consistent renaming.
Usage
Parse the XML files using manifestParser. manifestParser
takes parsed XML data and yields an object comprised of the field names, function names, and Brightscript scripts defined in the XML.
const data = XML.parse(String(fs.readFileSync(manifest)));
const {
fields: publicFields,
fns: publicFns,
scripts
} = manifestParser(data);
fields
and fns
are the public fields and functions and will be used later when traversing the AST with the ObfuscationVisitor.
scripts
is an array of objects which each have a uri
property. They will be used to generate the AST which will be run through the obfuscator.
Once you have the script URIs, the basics steps are as follows:
- Map each script file to its contents as a String
- Map each file's contents to a Brightscript AST (see @roku-road/bright for one such parser)
- Obfuscate each AST using
traverse
, ObfuscationVisitor
, fields
and fns
from above. - Generate new syntax using OutputVisitor
Map script URI to file contents
For each script, you'll have to read the file based on the uri
property. These path names have "no concept of a current working directory or relative paths" (Roku File System), so you will need to replace the "device field" portion of the uri
.
const { uri } = script;
const sourcePath = path.resolve(uri.replace(DEVICE_FIELD, RELATIVE_PARENT_FOLDER));
const content = String(fs.readFileSync(sourcePath));
Map script contents to Brightscript AST
Provide the script content to a Brightscript AST parser (@roku-road/bright).
const { ast: toBrightAST } = require('@roku-road/bright');
const ast = toBrightAST(content);
Obfuscate AST using traverse + ObfuscationVisitor
Provide the AST and a new ObfuscationVisitor
to traverse
to yield an obfuscated AST. You will also need to provide an object with a "renamed" property along with a new ObfuscationVisitor. The "renamed" property should contain an array of objects with the following shape: {src: String, dst: String}
. The ObfuscationVisitor
uses this array to help determine if an identifier can be safely renamed during obfuscation. If dst
does not exist or if it does and does not equal the name of the current identifier being visited, it will be renamed. publicFields
and publicFns
pulled out of the manifestParser
should be included in the renamed
array so they can be publicly accessed after obfuscation.
const renamed = [...publicFields, publicFns].map( ({id}) => ({src: id, dst: id}));
const { ast: obfuscatedAST } = traverse(ast)(new ObfuscationVisitor, {renamed});
Generate obfuscated syntax using traverse + OutputVisitor
Provide the AST and a new OutputVisitor
to traverse
to generate Brightscript syntax from the AST.
const { syntax } = traverse(obfuscatedAST)(new OutputVisitor);
syntax
will be a String representation of the obfuscated Brighscript code and can be written out the file system at this point.
Full Example
const { ast: toBrightAST } = require('@roku-road/bright');
const toContents = script => {
const { uri } = script;
const sourcePath = path.resolve(uri.replace(DEVICE_FIELD, RELATIVE_PARENT_FOLDER));
const contents = String(fs.readFileSync(sourcePath));
return contents;
};
const toAST = content => {
const ast = toBrightAST(content);
return ast;
};
const toObfuscation = (publicFields, publicFns) => ast => {
const renamed = [...publicFields, publicFns].map( ({id}) => ({src: id, dst: id}));
const { ast: obfuscatedAST } = traverse(ast)(new ObfuscationnVisitor, {renamed});
return obfuscatedAST;
};
const toSyntax = ast => {
const { syntax } = traverse(ast)(new OutputVisitor);
return syntax;
};
const data = XML.parse(String(fs.readFileSync()));
const {
fields: publicFields,
fns: publicFns,
scripts
} = manifestParser(data);
const obfuscatedScripts = scripts
.map(toContents)
.map(toAST)
.map(toObfuscation(publicFields, publicFns))
.map(toSyntax);