From 7ce6c39b7b47e62c173b694800657e70549afd1a Mon Sep 17 00:00:00 2001 From: relative Date: Tue, 17 Oct 2023 10:28:06 -0400 Subject: [PATCH 1/4] fix(literalmap): fix prototype pollution vuln --- src/transformers/literalmap.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/transformers/literalmap.ts b/src/transformers/literalmap.ts index 3d27377..9b07663 100644 --- a/src/transformers/literalmap.ts +++ b/src/transformers/literalmap.ts @@ -24,7 +24,7 @@ export default class LiteralMap extends Transformer { demap(context: Context) { walk(context.ast, { BlockStatement(node) { - const map: { [x: string]: { [x: string]: any } } = {} + const map = new Map>() walk(node, { VariableDeclaration(vd) { @@ -48,16 +48,16 @@ export default class LiteralMap extends Transformer { continue const name = decl.id.name - map[name] = map[name] || {} - + const localMap = map.get(name) || new Map() for (const _prop of decl.init.properties) { const prop = _prop as Property let key = prop.key.type === 'Identifier' ? prop.key.name : ((prop.key as Literal).value as string) - map[name][key] = (prop.value as Literal).value as string + localMap.set(key, (prop.value as Literal).value as string) } + if (!map.has(name)) map.set(name, localMap) if (context.removeGarbage) { rm.push(`${decl.start}!${decl.end}`) @@ -77,13 +77,13 @@ export default class LiteralMap extends Transformer { !Guard.isIdentifier(exp.property)) ) return - let mapObj = map[exp.object.name] + let mapObj = map.get(exp.object.name) if (!mapObj) return let key = Guard.isIdentifier(exp.property) ? exp.property.name : ((exp.property as Literal).value as string) - let val = mapObj[key] + let val = mapObj.get(key) if (typeof val === 'undefined') return // ! check causes !0 == true. sp(exp, { type: 'Literal', From 785d7007a64483390dd4a729f1a09d86f93ef1ee Mon Sep 17 00:00:00 2001 From: relative Date: Tue, 17 Oct 2023 10:45:56 -0400 Subject: [PATCH 2/4] feat: remove prettier dependency too complex and out of scope for this package --- package.json | 2 -- pnpm-lock.yaml | 20 ++++---------------- src/deobfuscator.ts | 38 ++------------------------------------ 3 files changed, 6 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 2bafca5..d0c6579 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@types/estree": "0.0.51", "@types/mersenne-twister": "1.1.2", "@types/node": "17.0.17", - "@types/prettier": "2.4.4", "@types/yargs": "17.0.8", "esbuild": "0.14.21", "escodegen": "2.0.0", @@ -47,7 +46,6 @@ "acorn-walk": "8.2.0", "eslint-scope": "7.1.1", "mersenne-twister": "1.1.0", - "prettier": "2.5.1", "yargs": "17.3.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49bb3d7..1ede8f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + dependencies: '@javascript-obfuscator/escodegen': specifier: 2.3.0 @@ -19,9 +23,6 @@ dependencies: mersenne-twister: specifier: 1.1.0 version: 1.1.0 - prettier: - specifier: 2.5.1 - version: 2.5.1 yargs: specifier: 17.3.1 version: 17.3.1 @@ -42,9 +43,6 @@ devDependencies: '@types/node': specifier: 17.0.17 version: 17.0.17 - '@types/prettier': - specifier: 2.4.4 - version: 2.4.4 '@types/yargs': specifier: 17.0.8 version: 17.0.8 @@ -122,10 +120,6 @@ packages: resolution: {integrity: sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw==} dev: true - /@types/prettier@2.4.4: - resolution: {integrity: sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==} - dev: true - /@types/yargs-parser@20.2.1: resolution: {integrity: sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==} dev: true @@ -952,12 +946,6 @@ packages: resolution: {integrity: sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=} engines: {node: '>= 0.8.0'} - /prettier@2.5.1: - resolution: {integrity: sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: false - /process@0.11.10: resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=} engines: {node: '>= 0.6.0'} diff --git a/src/deobfuscator.ts b/src/deobfuscator.ts index 4c64308..e40be43 100644 --- a/src/deobfuscator.ts +++ b/src/deobfuscator.ts @@ -4,7 +4,6 @@ import * as acornLoose from 'acorn-loose' import { Transformer, TransformerOptions } from './transformers/transformer' import { Node, Program, sp } from './util/types' import Context from './context' -import prettier from 'prettier' import { walk } from './util/walk' const FILE_REGEX = /(? { - let ast = this.parse(text, acornOptions, options) - if (options.transformChainExpressions) { - walk(ast as Node, { - ChainExpression(cx) { - if (cx.expression.type === 'CallExpression') { - sp(cx, { - ...cx.expression, - type: 'OptionalCallExpression', - expression: undefined, - }) - } else if (cx.expression.type === 'MemberExpression') { - sp(cx, { - ...cx.expression, - type: 'OptionalMemberExpression', - expression: undefined, - }) - } - }, - }) - } - return ast - }, - }) - } catch (err) { - // I don't think we should log here, but throwing the error is not very - // important since it is non fatal - console.log(err) - } return source } From dd55998c4ebd2d5a2f93dabc7406a2f011f2c845 Mon Sep 17 00:00:00 2001 From: relative Date: Tue, 17 Oct 2023 10:50:28 -0400 Subject: [PATCH 3/4] fix(literalmap): remove debugger statement --- src/transformers/literalmap.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/transformers/literalmap.ts b/src/transformers/literalmap.ts index 9b07663..57133a6 100644 --- a/src/transformers/literalmap.ts +++ b/src/transformers/literalmap.ts @@ -104,7 +104,6 @@ export default class LiteralMap extends Transformer { if (!scope) return for (const v of scope.variables) { - if (/*func.start === 3547 && */ v.name === 'q') debugger if (v.name === 'arguments') continue if (v.identifiers.length !== 1) continue // ? if (v.defs.length !== 1) continue // ? From 9e499f0d662de06cd60e63292cb031e27d09e564 Mon Sep 17 00:00:00 2001 From: relative Date: Tue, 17 Oct 2023 11:08:59 -0400 Subject: [PATCH 4/4] fix: stop using user-input for object properties --- src/context.ts | 8 ++- src/transformers/controlflow.ts | 46 +++++++++-------- src/transformers/jsconfuser/controlflow.ts | 57 ++++++++++++---------- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/context.ts b/src/context.ts index 22a3407..f174790 100644 --- a/src/context.ts +++ b/src/context.ts @@ -69,7 +69,7 @@ interface ControlFlowLiteral { identifier: string value: string | number } -interface ControlFlowStorage { +export interface ControlFlowStorage { identifier: string aliases: string[] functions: ControlFlowFunction[] @@ -96,9 +96,7 @@ export default class Context { stringDecoders: DecoderFunction[] = [] stringDecoderReferences: DecoderReference[] = [] - controlFlowStorageNodes: { - [x: BlockId]: ControlFlowStorage - } = {} + controlFlowStorageNodes = new Map() removeGarbage: boolean = true transformers: InstanceType[] @@ -112,7 +110,7 @@ export default class Context { ast: Program, transformers: [string, Partial][], isModule: boolean, - source?: string, + source?: string ) { this.ast = ast this.transformers = this.buildTransformerList(transformers) diff --git a/src/transformers/controlflow.ts b/src/transformers/controlflow.ts index a3763a2..357aa53 100644 --- a/src/transformers/controlflow.ts +++ b/src/transformers/controlflow.ts @@ -10,11 +10,12 @@ import { Identifier, ObjectExpression, Statement, + BlockStatement, } from '../util/types' import { Transformer, TransformerOptions } from './transformer' import { walk } from '../util/walk' import * as Guard from '../util/guard' -import Context from '../context' +import Context, { ControlFlowStorage } from '../context' import { immutate, literalOrIdentifierToString, @@ -38,24 +39,33 @@ export default class ControlFlow extends Transformer { if (!fx.body.body[0].argument) throw new TypeError('Function in CFSN was invalid (void return)') - let params = fx.params as Identifier[], - paramMap: { [ident: string]: Node } = {} + const params = fx.params as Identifier[], + paramMap = new Map() let i = 0 for (const p of params) { - paramMap[p.name] = cx.arguments[i] + paramMap.set(p.name, cx.arguments[i]) ++i } let immRtn = immutate(fx.body.body[0].argument) walk(immRtn, { Identifier(id) { - if (!paramMap[id.name]) return - sp(id, paramMap[id.name]) + const node = paramMap.get(id.name) + if (!node) return + sp(id, node) }, }) return immRtn as Node } + private getStorageNode( + context: Context, + node: BlockStatement + ): ControlFlowStorage | undefined { + const bid = getBlockId(node) + return context.controlFlowStorageNodes.get(bid) + } + // fixes empty object inits where there are setters in the same block populateEmptyObjects(context: Context) { walk(context.ast, { @@ -125,7 +135,8 @@ export default class ControlFlow extends Transformer { // /shrug let bid = getBlockId(node) - if (context.controlFlowStorageNodes[bid]) return + let cfsn = context.controlFlowStorageNodes.get(bid) + if (cfsn) return if (node.body.length === 0) return walk(node, { @@ -145,13 +156,14 @@ export default class ControlFlow extends Transformer { ) ) continue - context.controlFlowStorageNodes[bid] = { + + cfsn = { identifier: decl.id.name, aliases: [decl.id.name], functions: [], literals: [], } - const cfsn = context.controlFlowStorageNodes[bid] + context.controlFlowStorageNodes.set(bid, cfsn) for (const prop of decl.init.properties as PropertyLiteral[]) { let kn: Identifier | Literal = prop.key let key = ( @@ -228,12 +240,10 @@ export default class ControlFlow extends Transformer { findStorageNodeAliases = (context: Context, ast: Node) => { walk(ast, { - BlockStatement(node) { - let bid = getBlockId(node) - - if (!context.controlFlowStorageNodes[bid]) return + BlockStatement: (node) => { if (node.body.length === 0) return - const cfsn = context.controlFlowStorageNodes[bid] + const cfsn = this.getStorageNode(context, node) + if (!cfsn) return walk(node, { VariableDeclaration(vd) { @@ -268,11 +278,9 @@ export default class ControlFlow extends Transformer { replacer = (context: Context, ast: Node) => { const { translateCallExp } = this walk(ast, { - BlockStatement(node) { - const bid = getBlockId(node) - if (!context.controlFlowStorageNodes[bid]) return - const cfsn = context.controlFlowStorageNodes[bid] - + BlockStatement: (node) => { + const cfsn = this.getStorageNode(context, node) + if (!cfsn) return walk(node, { MemberExpression(mx) { if (!Guard.isIdentifier(mx.object)) return diff --git a/src/transformers/jsconfuser/controlflow.ts b/src/transformers/jsconfuser/controlflow.ts index c1d1bcb..82fb1e5 100644 --- a/src/transformers/jsconfuser/controlflow.ts +++ b/src/transformers/jsconfuser/controlflow.ts @@ -48,9 +48,7 @@ function inverseOperator(operator: BinaryOperator) { throw new Error("Invalid operator to inverse '" + operator + "'") } } -interface VarStack { - [x: string]: number -} +type VarStack = Map function generateCode(ast: Node): string { return escodegen.generate(ast as any, { sourceMapWithCode: true, @@ -69,31 +67,37 @@ function evaluateAssignmentExpr( operator: AssignmentOperator, value: number ) { + if (operator === '=') return stack.set(vk, value) + + const stackVal = stack.get(vk) + if (typeof stackVal !== 'number') + throw new Error( + 'Unexpected non-numeric value in jsconfuser controlflow stack' + ) + switch (operator) { - case '=': - return (stack[vk] = value) case '+=': - return (stack[vk] += value) + return stack.set(vk, stackVal + value) case '-=': - return (stack[vk] -= value) + return stack.set(vk, stackVal - value) case '*=': - return (stack[vk] *= value) + return stack.set(vk, stackVal * value) case '/=': - return (stack[vk] /= value) + return stack.set(vk, stackVal / value) case '%=': - return (stack[vk] %= value) + return stack.set(vk, stackVal % value) case '<<=': - return (stack[vk] <<= value) + return stack.set(vk, stackVal << value) case '>>=': - return (stack[vk] >>= value) + return stack.set(vk, stackVal >> value) case '>>>=': - return (stack[vk] >>>= value) + return stack.set(vk, stackVal >>> value) case '&=': - return (stack[vk] &= value) + return stack.set(vk, stackVal & value) case '^=': - return (stack[vk] ^= value) + return stack.set(vk, stackVal ^ value) case '|=': - return (stack[vk] |= value) + return stack.set(vk, stackVal | value) default: throw new Error( 'Invalid assignment expression operator "' + operator + '"' @@ -101,9 +105,8 @@ function evaluateAssignmentExpr( } } function updateIdentifiers(stack: VarStack, obj: any) { - for (const vk in stack) { - let value = stack[vk], - node = createLiteral(value) + for (const [vk, value] of stack) { + const node = createLiteral(value) walk(obj, { Identifier(id) { @@ -149,7 +152,7 @@ function evaluateSequenceAssignments( continue } if (!Guard.isIdentifier(expr.left)) continue - if (!(expr.left.name in stack)) continue + if (!stack.has(expr.left.name)) continue const vk = expr.left.name, operator = expr.operator @@ -179,7 +182,7 @@ function evaluateSequenceAssignments( let effect = literalOrUnaryExpressionToNumber(ie) evaluateAssignmentExpr(stack, vk, operator, effect) - log(`stack[${vk}] = ${stack[vk]}`) + log(`stack[${vk}] = ${stack.get(vk)}`) log('='.repeat(32)) ;(expr as any).type = 'EmptyStatement' } @@ -208,24 +211,24 @@ export default class JSCControlFlow extends Transformer { ) continue - const stack: VarStack = {} + const stack: VarStack = new Map() let bx = w.test, additive = false while (Guard.isBinaryExpression(bx)) { additive = bx.operator === '+' if (Guard.isIdentifier(bx.left)) { - stack[bx.left.name] = bx.left.start + stack.set(bx.left.name, bx.left.start) } if (Guard.isIdentifier(bx.right)) { - stack[bx.right.name] = bx.right.start + stack.set(bx.right.name, bx.right.start) } bx = bx.left as BinaryExpression } if (!additive) continue - for (const vk in stack) { + for (const [vk, value] of stack) { let vref = scope.references.find( - (i) => i.identifier.range![0] === stack[vk] + (i) => i.identifier.range![0] === value ) if (!vref) continue if ( @@ -246,7 +249,7 @@ export default class JSCControlFlow extends Transformer { i.range![0] !== def.node.range![0] && i.range![1] !== def.node.range![1] ) - stack[vk] = literalOrUnaryExpressionToNumber(def.node.init) + stack.set(vk, literalOrUnaryExpressionToNumber(def.node.init)) } const endState = literalOrUnaryExpressionToNumber(w.test.right) context.log(stack, endState)