Desofuscar Javascript y reconstruir cadenas. Simplifique la lógica engorrosa siempre que sea posible respetando las limitaciones del alcance.
Pruébelo en línea @ restringer.tech.
Para comentarios y sugerencias, no dude en abrir un número o buscarme en Twitter: @ctrl__esc.
npm install -g restringer
Requiere el Nodo 16 o posterior.
git clone [email protected]:PerimeterX/restringer.git
cd restringer
npm install
Restringer.js utiliza métodos genéricos de desofuscación que reconstruyen y restauran cadenas ofuscadas y simplifican la lógica redundante destinada únicamente a obstaculizar. REstringer emplea el detector de ofuscación para identificar tipos específicos de ofuscación para los cuales es necesario aplicar métodos de desofuscación específicos para eludir mecanismos anti-depuración u otras trampas de código que impiden que el script se desofusque.
Usage: restringer input_filename [-h] [-c] [-q | -v] [-m M] [-o [output_filename]]
positional arguments:
input_filename The obfuscated JS file
optional arguments:
-h, --help Show this help message and exit.
-c, --clean Remove dead nodes from script after deobfuscation is complete (unsafe).
-q, --quiet Suppress output to stdout. Output result only to stdout if the -o option is not set.
Does not go with the -v option.
-m, --max-iterations M Run at most M iterations
-v, --verbose Show more debug messages while deobfuscating. Does not go with the -q option.
-o, --output [output_filename] Write deobfuscated script to output_filename.
<input_filename>-deob.js is used if no filename is provided.
Ejemplos:
restringer [target-file.js]
restringer [target-file.js] -o output.js
restringer [target-file.js] -v
restringer [target-file.js] -q
import { REstringer } from 'restringer' ;
const restringer = new REstringer ( '"RE" + "stringer"' ) ;
if ( restringer . deobfuscate ( ) ) {
console . log ( restringer . script ) ;
} else {
console . log ( 'Nothing was deobfuscated :/' ) ;
}
// Output: 'REstringer';
REstringer está altamente modularizado. Expone módulos que permiten crear desofuscadores personalizados que pueden resolver problemas específicos.
La estructura básica de dicho desofuscador sería una serie de módulos de desofuscación (seguros o no seguros), ejecutados a través de la función de utilidad applyIteratively de flAST.
Los módulos inseguros ejecutan código a través de eval
(usando Insulated-vm para estar seguros) mientras que los módulos seguros no lo hacen.
import { applyIteratively } from 'flast' ;
import { safe , unsafe } from 'restringer' ;
const { normalizeComputed } = safe ;
const { resolveDefiniteBinaryExpressions , resolveLocalCalls } = unsafe ;
let script = 'obfuscated JS here' ;
const deobModules = [
resolveDefiniteBinaryExpressions ,
resolveLocalCalls ,
normalizeComputed ,
] ;
script = applyIteratively ( script , deobModules ) ;
console . log ( script ) ; // Deobfuscated script
Con el argumento adicional de la función candidateFilter
, es posible limitar los nodos objetivo:
import { unsafe } from 'restringer' ;
const { resolveLocalCalls } = unsafe ;
import { applyIteratively } from 'flast' ;
let script = 'obfuscated JS here' ;
// It's better to define a function with a meaningful name that can show up in the log
function resolveLocalCallsInGlobalScope ( arb ) {
return resolveLocalCalls ( arb , n => n . parentNode ?. type === 'Program' ) ;
}
script = applyIteratively ( script , [ resolveLocalCallsInGlobalScope ] ) ;
console . log ( script ) ; // Deobfuscated script
También puedes personalizar cualquier método de desofuscación mientras sigues usando REstringer sin ejecutar el bucle tú mismo:
import fs from 'node:fs' ;
import { REstringer } from 'restringer' ;
const inputFilename = process . argv [ 2 ] ;
const code = fs . readFileSync ( inputFilename , 'utf-8' ) ;
const res = new REstringer ( code ) ;
// res.logger.setLogLevelDebug();
res . detectObfuscationType = false ; // Skip obfuscation type detection, including any pre and post processors
const targetFunc = res . unsafeMethods . find ( m => m . name === 'resolveLocalCalls' ) ;
let changes = 0 ; // Resolve only the first 5 calls
res . safeMethods [ res . unsafeMethods . indexOf ( targetFunc ) ] = function customResolveLocalCalls ( n ) { return targetFunc ( n , ( ) => changes ++ < 5 ) }
res . deobfuscate ( ) ;
if ( res . script !== code ) {
console . log ( '[+] Deob successful' ) ;
fs . writeFileSync ( ` ${ inputFilename } -deob.js` , res . script , 'utf-8' ) ;
} else console . log ( '[-] Nothing deobfuscated :/' ) ;
import { applyIteratively , logger } from 'flast' ;
// Optional loading from file
// import fs from 'node:fs';
// const inputFilename = process.argv[2] || 'target.js';
// const code = fs.readFileSync(inputFilename, 'utf-8');
const code = `(function() {
function createMessage() {return 'Hello' + ' ' + 'there!';}
function print(msg) {console.log(msg);}
print(createMessage());
})();` ;
logger . setLogLevelDebug ( ) ;
/**
* Replace specific strings with other strings
* @param {Arborist} arb
* @return {Arborist}
*/
function replaceSpecificLiterals ( arb ) {
const replacements = {
'Hello' : 'General' ,
'there!' : 'Kenobi!' ,
} ;
// Iterate over only the relevant nodes by targeting specific types using the typeMap property on the root node
const relevantNodes = [
... ( arb . ast [ 0 ] . typeMap . Literal || [ ] ) ,
// ...(arb.ast.typeMap.TemplateLiteral || []), // unnecessary for this example, but this is how to add more types
] ;
for ( const n of relevantNodes ) {
if ( replacements [ n . value ] ) {
// dynamically define a replacement node by creating an object with a type and value properties
// markNode(n) would delete the node, while markNode(n, {...}) would replace the node with the supplied node.
arb . markNode ( n , { type : 'Literal' , value : replacements [ n . value ] } ) ;
}
}
return arb ;
}
let script = code ;
script = applyIteratively ( script , [
replaceSpecificLiterals ,
] ) ;
if ( code !== script ) {
console . log ( script ) ;
// fs.writeFileSync(inputFilename + '-deob.js', script, 'utf-8');
} else console . log ( `No changes` ) ;