diff --git a/arkguard/src/ArkObfuscator.ts b/arkguard/src/ArkObfuscator.ts index dfb6d55532adc41fe914839b39bc006739304685..7ae2b9e0019ac8e721da692759db1b41948c5208 100644 --- a/arkguard/src/ArkObfuscator.ts +++ b/arkguard/src/ArkObfuscator.ts @@ -100,6 +100,7 @@ export class ArkObfuscator { */ public init(config?: IOptions): boolean { if (!this.mConfigPath && !config) { + console.error('obfuscation config file is not found and no given config.'); return false; } @@ -139,10 +140,10 @@ export class ArkObfuscator { if (!path.isAbsolute(this.mCustomProfiles.mOutputDir)) { this.mCustomProfiles.mOutputDir = path.join(path.dirname(this.mConfigPath), this.mCustomProfiles.mOutputDir); } - if (this.mCustomProfiles.mOutputDir && !fs.existsSync(this.mCustomProfiles.mOutputDir)) { fs.mkdirSync(this.mCustomProfiles.mOutputDir); } + readProjectProperties(this.mSourceFiles, this.mCustomProfiles); this.readPropertyCache(this.mCustomProfiles.mOutputDir); @@ -197,7 +198,7 @@ export class ArkObfuscator { } private readNameCache(sourceFile: string, outputDir: string): void { - if (!this.mCustomProfiles.mNameObfuscation.mEnable || !this.mCustomProfiles.mEnableNameCache) { + if (!this.mCustomProfiles.mNameObfuscation?.mEnable || !this.mCustomProfiles.mEnableNameCache) { return; } @@ -208,7 +209,7 @@ export class ArkObfuscator { } private readPropertyCache(outputDir: string): void { - if (!this.mCustomProfiles.mNameObfuscation.mRenameProperties || !this.mCustomProfiles.mEnableNameCache) { + if (!this.mCustomProfiles.mNameObfuscation?.mRenameProperties || !this.mCustomProfiles.mEnableNameCache) { return; } @@ -227,7 +228,9 @@ export class ArkObfuscator { } private producePropertyCache(outputDir: string): void { - if (this.mCustomProfiles.mNameObfuscation.mRenameProperties && this.mCustomProfiles.mEnableNameCache) { + if (this.mCustomProfiles.mNameObfuscation && + this.mCustomProfiles.mNameObfuscation.mRenameProperties && + this.mCustomProfiles.mEnableNameCache) { const propertyCachePath: string = path.join(outputDir, PROPERTY_CACHE_FILE); writeCache(renamePropertyModule.globalMangledTable, propertyCachePath); } @@ -418,7 +421,10 @@ export class ArkObfuscator { if (renameIdentifierModule.nameCache) { renameIdentifierModule.nameCache.clear(); } + + renameIdentifierModule.historyNameCache = undefined; return result; } } + export {ApiExtractor}; diff --git a/arkguard/src/transformers/rename/RenameIdentifierTransformer.ts b/arkguard/src/transformers/rename/RenameIdentifierTransformer.ts index dcb507dd9e65cfde79fbae508a0860c4bc480441..5b77b256d3737a94e3913438c7fd2a83239a99e8 100644 --- a/arkguard/src/transformers/rename/RenameIdentifierTransformer.ts +++ b/arkguard/src/transformers/rename/RenameIdentifierTransformer.ts @@ -46,7 +46,6 @@ import { isEnumScope, isInterfaceScope, isObjectLiteralScope, - mangledIdentifierNames } from '../../utils/ScopeAnalyzer'; import type { @@ -85,6 +84,12 @@ namespace secharmony { return null; } + let options: NameGeneratorOptions = {}; + if (profile.mNameGeneratorType === NameGeneratorType.HEX) { + options.hexWithPrefixSuffix = true; + } + let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options); + const openTopLevel: boolean = option?.mTopLevel; const exportObfuscation: boolean = option?.mExportObfuscation; return renameIdentifierFactory; @@ -94,13 +99,6 @@ namespace secharmony { let mangledSymbolNames: Map = new Map(); let mangledLabelNames: Map = new Map(); - let options: NameGeneratorOptions = {}; - if (profile.mNameGeneratorType === NameGeneratorType.HEX) { - options.hexWithPrefixSuffix = true; - } - - let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options); - let historyMangledNames: Set = undefined; if (historyNameCache && historyNameCache.size > 0) { historyMangledNames = new Set(Array.from(historyNameCache.values())); @@ -135,10 +133,6 @@ namespace secharmony { reservedNames.push(name); }); } - // collect all identifiers of shadow sourceFile - const identifiersAndStructs = collectIdentifiersAndStructs(shadowSourceAst, context); - shadowIdentifiers = identifiersAndStructs.shadowIdentifiers; - shadowStructs = identifiersAndStructs.shadowStructs; if (nameCache === undefined) { nameCache = new Map(); @@ -146,6 +140,12 @@ namespace secharmony { let root: Scope = manager.getRootScope(); renameInScope(root); + root = undefined; + // collect all identifiers of shadow sourceFile + const identifiersAndStructs = collectIdentifiersAndStructs(shadowSourceAst, context); + shadowIdentifiers = identifiersAndStructs.shadowIdentifiers; + shadowStructs = identifiersAndStructs.shadowStructs; + let ret: Node = visit(node); ret = tryRemoveVirtualConstructor(ret); return setParentRecursive(ret, true); @@ -162,26 +162,28 @@ namespace secharmony { // process symbols in scope, exclude property name. renameNamesInScope(scope); - for (const subScope of scope.children) { + let subScope = undefined; + while (scope.children.length > 0) { + subScope = scope.children.pop(); renameInScope(subScope); + subScope = undefined; } } function renameNamesInScope(scope: Scope): void { - if (scope.parent) { - scope.parent.importNames.forEach((value) => { - scope.importNames.add(value); - }); - } - if (isExcludeScope(scope)) { return; } if (!exportObfuscation) { scope.defs.forEach((def) => { - if (scope.importNames.has(def.name)) { - scope.defs.delete(def); + let parentScope = scope; + while (parentScope) { + if (parentScope.importNames && parentScope.importNames.has(def.name)) { + scope.defs.delete(def); + scope.mangledNames.add(def.name); + } + parentScope = parentScope.parent; } }); } @@ -190,15 +192,14 @@ namespace secharmony { } function renames(scope: Scope, defs: Set, generator: INameGenerator): void { - const localCache: Map = new Map(); - findNoSymbolIdentifiers(scope); - defs.forEach((def) => { const original: string = def.name; let mangled: string = original; // No allow to rename reserved names. - if (reservedNames.includes(original) || (!exportObfuscation && scope.exportNames.has(def.name)) || isSkippedGlobal(openTopLevel, scope)) { - mangledIdentifierNames.add(mangled); + if (reservedNames.includes(original) || + (!exportObfuscation && scope.exportNames.has(def.name)) || + isSkippedGlobal(openTopLevel, scope)) { + scope.mangledNames.add(mangled); mangledSymbolNames.set(def, mangled); return; } @@ -215,16 +216,14 @@ namespace secharmony { if (specifyName) { mangled = specifyName; } else { - const sameMangled: string = localCache.get(original); - mangled = sameMangled ? sameMangled : getMangled(scope, generator); + mangled = getMangled(scope, generator); } // add new names to name cache nameCache.set(path, mangled); globalNameCache.set(original, mangled); - mangledIdentifierNames.add(mangled); + scope.mangledNames.add(mangled); mangledSymbolNames.set(def, mangled); - localCache.set(original, mangled); }); } @@ -244,6 +243,21 @@ namespace secharmony { return isObjectLiteralScope(scope); } + function searchMangledInParent(scope: Scope, name: string): boolean { + let found: boolean = false; + let parentScope = scope; + while (parentScope) { + if (parentScope.mangledNames.has(name)) { + found = true; + break; + } + + parentScope = parentScope.parent; + } + + return found; + } + function getMangled(scope: Scope, localGenerator: INameGenerator): string { let mangled: string = ''; do { @@ -254,11 +268,6 @@ namespace secharmony { continue; } - if (scope.importNames && scope.importNames.has(mangled)) { - mangled = ''; - continue; - } - if (scope.exportNames && scope.exportNames.has(mangled)) { mangled = ''; continue; @@ -269,8 +278,7 @@ namespace secharmony { continue; } - // the anme has already been generated in the current scope - if (mangledIdentifierNames.has(mangled)) { + if (searchMangledInParent(scope, mangled) || manager.getRootScope().constructorReservedParams.has(mangled)) { mangled = ''; } } while (mangled === ''); @@ -362,27 +370,6 @@ namespace secharmony { return visitEachChild(node, tryRemoveVirtualConstructor, context); } - function findNoSymbolIdentifiers(scope: Scope): void { - const noSymbolVisit = (targetNode: Node): void => { - if (!isIdentifier(targetNode)) { - forEachChild(targetNode, noSymbolVisit); - return; - } - - // skip property in property access expression - if (NodeUtils.isPropertyAccessNode(targetNode)) { - return; - } - - const sym: Symbol | undefined = checker.getSymbolAtLocation(targetNode); - if (!sym) { - mangledIdentifierNames.add((targetNode as Identifier).escapedText.toString()); - } - }; - - noSymbolVisit(scope.block); - } - function updateNameNode(node: Identifier, shadowNode: Identifier): Node { // skip property in property access expression if (NodeUtils.isPropertyAccessNode(node)) { diff --git a/arkguard/src/utils/ScopeAnalyzer.ts b/arkguard/src/utils/ScopeAnalyzer.ts index c187da60ae58bfc6f0eb2249b1319c21f6ea7c17..671d8c4615f37f6431ed2f701d9a1973320dc08c 100644 --- a/arkguard/src/utils/ScopeAnalyzer.ts +++ b/arkguard/src/utils/ScopeAnalyzer.ts @@ -16,6 +16,7 @@ import { forEachChild, getModifiers, + isCatchClause, isClassDeclaration, isConstructorDeclaration, isExportSpecifier, @@ -42,7 +43,6 @@ import type { ImportSpecifier, InterfaceDeclaration, LabeledStatement, - Modifier, ModuleDeclaration, Node, ObjectBindingPattern, @@ -107,7 +107,6 @@ namespace secharmony { return scope.kind === ScopeKind.OBJECT_LITERAL; } - export const mangledIdentifierNames: Set = new Set(); /** * Structure of a scope */ @@ -156,6 +155,10 @@ namespace secharmony { exportNames?: Set; + mangledNames?: Set; + + constructorReservedParams?: Set; + /** * add a sub scope to current scope * @@ -216,6 +219,8 @@ namespace secharmony { let mangledNames: Set = new Set(); + let constructorReservedParams: Set = new Set(); + // location path let loc: string = parent?.loc ? parent.loc + '#' + scopeName : scopeName; @@ -231,6 +236,8 @@ namespace secharmony { 'loc': loc, 'importNames': importNames, 'exportNames': exportNames, + 'mangledNames': mangledNames, + 'constructorReservedParams': constructorReservedParams, addChild, addDefinition, addLabel, @@ -306,6 +313,7 @@ namespace secharmony { * get reserved names like ViewPU component class name */ getReservedNames(): Set; + /** * do scope analysis * @@ -493,6 +501,11 @@ namespace secharmony { } } + /** example + * const { x1, y: customY, z = 0 }: { x: number; y?: number; z?: number } = { x: 1, y: 2 }; + * bindingElement.name is x1 for the first element. + * bindingElement.name is customY for the second element. + */ function analyzeObjectBindingPatternRequire(node: ObjectBindingPattern): void { if (!NodeUtils.isObjectBindingPatternAssignment(node)) { forEachChild(node, analyzeScope); @@ -508,6 +521,8 @@ namespace secharmony { return; } + findNoSymbolIdentifiers(bindingElement); + if (!bindingElement.name || !isIdentifier(bindingElement.name)) { return; } @@ -588,7 +603,7 @@ namespace secharmony { addSymbolInScope(node.block); } - forEachChild(node.block, analyzeScope); + forEachChild(node, analyzeScope); current = current.parent || current; } @@ -622,9 +637,9 @@ namespace secharmony { * @param node */ function analyzeModule(node: ModuleDeclaration): void { - /** - * if it is an anonymous scope, generate the scope name with a number, - * which is based on the order of its child scopes in the upper scope + /** + * if it is an anonymous scope, generate the scope name with a number, + * which is based on the order of its child scopes in the upper scope */ let scopeName: string = node.name.text ?? '$' + current.children.length; current = createScope(scopeName, node, ScopeKind.MODULE, true, current); @@ -646,17 +661,24 @@ namespace secharmony { const visitParam = (param: ParameterDeclaration): void => { const modifiers = getModifiers(param); - if (modifiers && modifiers.length > 0) { - const hasParameterPropertyModifier: boolean = modifiers.find(modifier => isParameterPropertyModifier(modifier)) !== undefined; - if (isIdentifier(param.name) && hasParameterPropertyModifier) { - current.defs.forEach((def) => { - if (def.name === param.name.getText()) { - current.defs.delete(def); - mangledIdentifierNames.add(def.name); - } - }); - } + if (!modifiers || modifiers.length <= 0) { + return; } + + const findRet = modifiers.find(modifier => isParameterPropertyModifier(modifier)); + if (!isIdentifier(param.name) || findRet === undefined) { + return; + } + + current.defs.forEach((def) => { + if (def.name !== param.name.getText()) { + return; + } + + current.defs.delete(def); + current.mangledNames.add(def.name); + root.constructorReservedParams.add(def.name); + }); }; node.parameters.forEach((param) => { @@ -671,6 +693,11 @@ namespace secharmony { * @param node */ function analyzeFunctionLike(node: FunctionLikeDeclaration): void { + // For example, the constructor of the StructDeclaration, inserted by arkui, will add a virtual attribute. + // @ts-ignore + if (node.virtual) { + return; + } let scopeName: string = (node?.name as Identifier)?.text ?? '$' + current.children.length; let loc: string = current?.loc ? current.loc + '#' + scopeName : scopeName; let overloading: boolean = false; @@ -696,6 +723,7 @@ namespace secharmony { // function declaration requires skipping function names node.forEachChild((sub: Node) => { if (isIdentifier(sub)) { + tryAddNoSymbolIdentifiers(sub); return; } @@ -742,12 +770,7 @@ namespace secharmony { } }); - node.members?.forEach((sub: Node) => { - // @ts-ignore - if (!sub.virtual) { - analyzeScope(sub); - } - }); + forEachChild(node, analyzeScope); } catch (e) { console.error(e); } @@ -766,7 +789,7 @@ namespace secharmony { function analyzeBlock(node: Node): void { // when block is body of a function - if (isFunctionScope(current) && isFunctionLike(node.parent)) { + if ((isFunctionScope(current) && isFunctionLike(node.parent)) || isCatchClause(node.parent)) { // skip direct block scope in function scope forEachChild(node, analyzeScope); return; @@ -810,19 +833,11 @@ namespace secharmony { } } - for (const subNode of node.members) { - forEachChild(subNode, analyzeScope); - } - + forEachChild(node, analyzeScope); current = current.parent || current; } function analyzeSymbol(node: Identifier): void { - // ignore all identifiers that treat as property in property declaration - if (NodeUtils.isPropertyDeclarationNode(node)) { - return; - } - // ignore all identifiers that treat as property in property access if (NodeUtils.isPropertyAccessNode(node)) { return; @@ -838,6 +853,12 @@ namespace secharmony { } if (!symbol) { + current.mangledNames.add(node.escapedText.toString()); + return; + } + + // ignore all identifiers that treat as property in property declaration + if (NodeUtils.isPropertyDeclarationNode(node)) { return; } @@ -894,6 +915,34 @@ namespace secharmony { return undefined; } + + function tryAddNoSymbolIdentifiers(node: Identifier): void { + if (!isIdentifier(node)) { + return; + } + + // skip property in property access expression + if (NodeUtils.isPropertyAccessNode(node)) { + return; + } + + const sym: Symbol | undefined = checker.getSymbolAtLocation(node); + if (!sym) { + current.mangledNames.add((node as Identifier).escapedText.toString()); + } + } + + function findNoSymbolIdentifiers(node: Node): void { + const noSymbolVisit = (targetNode: Node): void => { + if (!isIdentifier(targetNode)) { + forEachChild(targetNode, noSymbolVisit); + return; + } + tryAddNoSymbolIdentifiers(targetNode); + }; + + noSymbolVisit(node); + } } } diff --git a/arkguard/test/grammar/compact/decoratorAndModifier_expected.txt b/arkguard/test/grammar/compact/decoratorAndModifier_expected.txt index c1e17c3db58b2179d6aca01a606f1070f118be09..7821adaaf4257cfcc7b20cad3302572923948852 100644 --- a/arkguard/test/grammar/compact/decoratorAndModifier_expected.txt +++ b/arkguard/test/grammar/compact/decoratorAndModifier_expected.txt @@ -1 +1 @@ - function a(f: Function) { console.log(`Class name: ${f.name}`); } function b(g: any, h: string) { console.log(`Property name: ${h}`); } function c(g: any, h: string) { console.log(`Property name: ${h}`); } @a class e { @b @c private g: string; @c @b public h: number; @b async i(): Promise { } } function d() { let i = 1; let j = 2; let k = 3; let l = 4; let m = 5; } \ No newline at end of file + function a(m: Function) { console.log(`Class name: ${m.name}`); } function b(k: any, l: string) { console.log(`Property name: ${l}`); } function c(k: any, l: string) { console.log(`Property name: ${l}`); } @a class e { @b @c private g: string; @c @b public h: number; @b async i(): Promise { } } function d() { let f = 1; let g = 2; let h = 3; let i = 4; let j = 5; } \ No newline at end of file diff --git a/arkguard/test/grammar/export_obfuscation/export_obfuscation_1_expected.txt b/arkguard/test/grammar/export_obfuscation/export_obfuscation_1_expected.txt index 4ea80a7e5f9938b02a4651464463ac2281603e4a..93e92441e428f3af4e6f73277e551d1bd7f7d0f9 100644 --- a/arkguard/test/grammar/export_obfuscation/export_obfuscation_1_expected.txt +++ b/arkguard/test/grammar/export_obfuscation/export_obfuscation_1_expected.txt @@ -16,13 +16,13 @@ export class c { name: string; g: number; } -export function a(d: number, e: number) { +export function a(h: number, i: number) { const f = 1; const g = 2; - return d + e + f + g; + return h + i + f + g; } -function b(h: string, i: number) { +function b(d: string, e: number) { const f = 4; const g = 5; - return h + i.toString(); + return d + e.toString(); } diff --git a/arkguard/test/grammar/export_obfuscation/export_obfuscation_3_expected.txt b/arkguard/test/grammar/export_obfuscation/export_obfuscation_3_expected.txt index 200530a7a41942c149b4d2a91e6d12a6a3d613b0..4d22a8c5752e6added68925c2898b695107d2411 100644 --- a/arkguard/test/grammar/export_obfuscation/export_obfuscation_3_expected.txt +++ b/arkguard/test/grammar/export_obfuscation/export_obfuscation_3_expected.txt @@ -16,17 +16,17 @@ class o { constructor() { this.head = false; } - compare(p, q) { + compare(r, s) { if (!this.head) { throw new Error('List is empty'); } - return p > q; + return r > s; } - o(p, q) { - const r = q; - q = p; + o(r, s) { + const t = s; + s = r; } } -function n(s, t) { - return s + t; +function n(p, q) { + return p + q; } diff --git a/arkguard/test/grammar/export_obfuscation/export_obfuscation_declaration_1_expected.txt b/arkguard/test/grammar/export_obfuscation/export_obfuscation_declaration_1_expected.txt index 0b4b1c76f7464a6a1d37c8e057e294e650225041..2d19d0ab68a1f719771e57342749e6136535b316 100644 --- a/arkguard/test/grammar/export_obfuscation/export_obfuscation_declaration_1_expected.txt +++ b/arkguard/test/grammar/export_obfuscation/export_obfuscation_declaration_1_expected.txt @@ -16,9 +16,9 @@ export declare class c { p: string | undefined; q: number; t: string; - constructor(v?: u); + constructor(x?: u); } -export declare function a(w: number, x: number): number; +export declare function a(v: number, w: number): number; declare class u { u: string; v: number;