Skip to content

Commit 0b619e5

Browse files
authored
feat: configurable smart rename (#100)
1 parent 2b311e0 commit 0b619e5

File tree

16 files changed

+218
-140
lines changed

16 files changed

+218
-140
lines changed

apps/docs/src/guide/api.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const result = await webcrack('const a = 1+1;');
1818
console.log(result.code); // 'const a = 2;'
1919
```
2020

21-
Save the deobufscated code and the unpacked bundle to the given directory:
21+
Save the deobfuscated code and the unpacked bundle to the given directory:
2222

2323
```js
2424
import fs from 'fs';
@@ -57,6 +57,14 @@ await webcrack(code, {
5757
});
5858
```
5959

60+
Only mangle variable names that match a filter:
61+
62+
```js
63+
await webcrack(code, {
64+
mangle: (id) => id.startsWith('_0x'),
65+
});
66+
```
67+
6068
## Customize Paths
6169

6270
Useful for reverse-engineering and tracking changes across multiple versions of a bundle.

apps/playground/src/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import { debounce } from './utils/debounce';
2222
import { downloadFile } from './utils/files';
2323
import type { DeobfuscateResult } from './webcrack.worker';
2424

25+
export type MangleMode = 'off' | 'all' | 'hex' | 'short';
26+
2527
export const [config, setConfig] = createStore({
2628
deobfuscate: true,
2729
unminify: true,
2830
unpack: true,
2931
jsx: true,
30-
mangle: false,
32+
mangleMode: 'off' as MangleMode,
3133
});
3234

3335
function App() {

apps/playground/src/components/Sidebar.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Show } from 'solid-js';
2-
import { config, setConfig } from '../App';
2+
import { config, setConfig, type MangleMode } from '../App';
33
import { useDeobfuscateContext } from '../context/DeobfuscateContext';
44
import FileTree from './FileTree';
55

@@ -205,15 +205,19 @@ export default function Sidebar(props: Props) {
205205
<path d="M10 8v6a2 2 0 1 0 4 0v-1a2 2 0 1 0 -4 0v1" />
206206
<path d="M20.732 12a2 2 0 0 0 -3.732 1v1a2 2 0 0 0 3.726 1.01" />
207207
</svg>
208-
<span class="label-text ml-4 mr-auto hidden sm:inline">
209-
Mangle Variables
210-
</span>
211-
<input
212-
type="checkbox"
213-
class="checkbox checkbox-sm hidden sm:inline"
214-
checked={config.mangle}
215-
onClick={(e) => setConfig('mangle', e.currentTarget.checked)}
216-
/>
208+
<span class="label-text ml-4 mr-auto hidden sm:inline">Mangle</span>
209+
<select
210+
class="select select-sm select-bordered ml-4 flex-1 w-full"
211+
value={config.mangleMode}
212+
onChange={(e) =>
213+
setConfig('mangleMode', e.currentTarget.value as MangleMode)
214+
}
215+
>
216+
<option value="off">Off</option>
217+
<option value="hex">Hex (_0x)</option>
218+
<option value="short">Short Names</option>
219+
<option value="all">All Names</option>
220+
</select>
217221
</label>
218222

219223
<FileTree

apps/playground/src/context/DeobfuscateContext.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ParentProps } from 'solid-js';
22
import { createContext, createSignal, useContext } from 'solid-js';
33
import type { Options } from 'webcrack';
4+
import type { MangleMode } from '../App';
45
import { evalCode } from '../sandbox';
56
import type {
67
DeobfuscateResult,
@@ -15,7 +16,7 @@ const postMessage = (message: WorkerRequest) => worker.postMessage(message);
1516

1617
interface Props {
1718
code: string | undefined;
18-
options: Options;
19+
options: Options & { mangleMode: MangleMode };
1920
onResult: (result: DeobfuscateResult) => void;
2021
}
2122

apps/playground/src/webcrack.worker.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import type { Options, Sandbox } from 'webcrack';
22
import { webcrack } from 'webcrack';
3+
import type { MangleMode } from './App';
34

45
export type WorkerRequest =
5-
| { type: 'deobfuscate'; code: string; options: Options }
6+
| {
7+
type: 'deobfuscate';
8+
code: string;
9+
options: Options & { mangleMode: MangleMode };
10+
}
611
| { type: 'sandbox'; result: unknown };
712

813
export type WorkerResponse =
@@ -45,6 +50,7 @@ self.onmessage = async ({ data }: MessageEvent<WorkerRequest>) => {
4550
sandbox,
4651
onProgress,
4752
...data.options,
53+
mangle: convertMangleMode(data.options.mangleMode),
4854
});
4955
const files = Array.from(result.bundle?.modules ?? [], ([, module]) => ({
5056
code: module.code,
@@ -56,3 +62,18 @@ self.onmessage = async ({ data }: MessageEvent<WorkerRequest>) => {
5662
postMessage({ type: 'error', error: error as Error });
5763
}
5864
};
65+
66+
function convertMangleMode(mode: MangleMode) {
67+
const HEX_IDENTIFIER = /_0x[a-f\d]+/i;
68+
69+
switch (mode) {
70+
case 'off':
71+
return false;
72+
case 'all':
73+
return true;
74+
case 'hex':
75+
return (id: string) => HEX_IDENTIFIER.test(id);
76+
case 'short':
77+
return (id: string) => id.length <= 2;
78+
}
79+
}

packages/webcrack/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
"@babel/traverse": "^7.24.7",
4848
"@babel/types": "^7.24.7",
4949
"@codemod/matchers": "^1.7.1",
50-
"babel-plugin-minify-mangle-names": "^0.5.1",
5150
"commander": "^12.1.0",
5251
"debug": "^4.3.5",
5352
"isolated-vm": "^5.0.0"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { Scope } from '@babel/traverse';
2+
import { toIdentifier } from '@babel/types';
3+
4+
/**
5+
* Like scope.generateUid from babel, but without the underscore prefix and name filters
6+
*/
7+
export function generateUid(scope: Scope, name: string = 'temp'): string {
8+
let uid = '';
9+
let i = 1;
10+
do {
11+
uid = toIdentifier(i > 1 ? `${name}${i}` : name);
12+
i++;
13+
} while (
14+
scope.hasLabel(uid) ||
15+
scope.hasBinding(uid) ||
16+
scope.hasGlobal(uid) ||
17+
scope.hasReference(uid)
18+
);
19+
20+
const program = scope.getProgramParent();
21+
program.references[uid] = true;
22+
program.uids[uid] = true;
23+
return uid;
24+
}

packages/webcrack/src/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface WebcrackResult {
4242
code: string;
4343
bundle: Bundle | undefined;
4444
/**
45-
* Save the deobufscated code and the extracted bundle to the given directory.
45+
* Save the deobfuscated code and the extracted bundle to the given directory.
4646
* @param path Output directory
4747
*/
4848
save(path: string): Promise<void>;
@@ -73,7 +73,7 @@ export interface Options {
7373
* Mangle variable names.
7474
* @default false
7575
*/
76-
mangle?: boolean;
76+
mangle?: boolean | ((id: string) => boolean);
7777
/**
7878
* Assigns paths to modules based on the given matchers.
7979
* This will also rewrite `require()` calls to use the new paths.
@@ -156,7 +156,13 @@ export async function webcrack(
156156
(() => {
157157
applyTransforms(ast, [transpile, unminify]);
158158
}),
159-
options.mangle && (() => applyTransform(ast, mangle)),
159+
options.mangle &&
160+
(() =>
161+
applyTransform(
162+
ast,
163+
mangle,
164+
typeof options.mangle === 'boolean' ? () => true : options.mangle,
165+
)),
160166
// TODO: Also merge unminify visitor (breaks selfDefending/debugProtection atm)
161167
(options.deobfuscate || options.jsx) &&
162168
(() => {

packages/webcrack/src/transforms/babel-plugin-minify-mangle-names.d.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/webcrack/src/transforms/jsx-new.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as t from '@babel/types';
22
import * as m from '@codemod/matchers';
33
import type { Transform } from '../ast-utils';
44
import { codePreview, constMemberExpression } from '../ast-utils';
5+
import { generateUid } from '../ast-utils/scope';
56

67
const DEFAULT_PRAGMA_CANDIDATES = [
78
'jsx',
@@ -55,7 +56,7 @@ export default {
5556
if (convertibleName.match(type.current!)) {
5657
name = convertType(type.current);
5758
} else {
58-
name = t.jsxIdentifier(path.scope.generateUid('Component'));
59+
name = t.jsxIdentifier(generateUid(path.scope, 'Component'));
5960
const componentVar = t.variableDeclaration('const', [
6061
t.variableDeclarator(t.identifier(name.name), type.current),
6162
]);

0 commit comments

Comments
 (0)