diff --git a/Dockerfile b/Dockerfile index 3d41ea28..1c5b61e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ ENV VITE_APP_API_URL=${CONTEXT_PATH} \ VITE_APP_BASE_PATH=${ROUTE_ROOT_PATH}/ui COPY --from=source /usr/src/web/package.json /usr/src/web/bun.lock /usr/src/web/source.config.ts /usr/src/web/ +COPY --from=source /usr/src/web/vendor/cfr /usr/src/web/vendor/cfr RUN bun install --frozen-lockfile diff --git a/web/app/components/code-viewer.tsx b/web/app/components/code-viewer.tsx index dffb4a94..37ad6445 100644 --- a/web/app/components/code-viewer.tsx +++ b/web/app/components/code-viewer.tsx @@ -10,6 +10,12 @@ import materialDark from "react-syntax-highlighter/dist/esm/styles/prism/materia import { toast } from "sonner"; import { Button, type buttonVariants } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { cn } from "@/lib/utils"; SyntaxHighlighter.registerLanguage("java", java); @@ -18,8 +24,16 @@ interface CopyButtonProps extends React.ComponentProps<"button"> { src?: string; } +export interface CopyOption { + label: string; + value: string; + disabled?: boolean; +} + export function CopyButton({ value, + className, + ...buttonProps }: Readonly>) { const [hasCopied, setHasCopied] = useState(false); const { t } = useTranslation(["common"]); @@ -43,11 +57,12 @@ export function CopyButton({ return ( @@ -55,6 +70,62 @@ export function CopyButton({ ); } +export function CopyMenuButton({ + options, + className, + ...buttonProps +}: Readonly< + { options: CopyOption[] } & React.ComponentProps<"button"> & VariantProps +>) { + const [hasCopied, setHasCopied] = useState(false); + const { t } = useTranslation(["common"]); + + useEffect(() => { + if (hasCopied) { + const timer = setTimeout(() => { + setHasCopied(false); + }, 1000); + return () => clearTimeout(timer); + } + }, [hasCopied]); + + const handleCopy = useCallback(() => { + if (!hasCopied) { + setHasCopied(true); + toast.success(t("copySuccess"), { duration: 1000 }); + } + }, [hasCopied, t]); + + return ( + + + } + > + {hasCopied ? : } + + + {options.map((option) => ( + + {option.label} + + ))} + + + ); +} + export default function CodeViewer({ code, header, @@ -63,6 +134,9 @@ export default function CodeViewer({ height, showLineNumbers = true, wrapLongLines = true, + copyLabel, + copyDisabled = false, + copyOptions, }: Readonly) { const lineProps: lineTagPropsFunction | HTMLProps | undefined = wrapLongLines ? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } } @@ -75,7 +149,24 @@ export default function CodeViewer({ {header}
{button} - + {copyOptions ? ( + + ) : ( + + )}
@@ -110,4 +201,7 @@ interface CodeViewerProps { showLineNumbers?: boolean; wrapLongLines?: boolean; lineProps?: (lineNumber: number) => React.HTMLProps; + copyLabel?: string; + copyDisabled?: boolean; + copyOptions?: CopyOption[]; } diff --git a/web/app/components/memshell/decompiled-code-viewer.tsx b/web/app/components/memshell/decompiled-code-viewer.tsx new file mode 100644 index 00000000..79f85246 --- /dev/null +++ b/web/app/components/memshell/decompiled-code-viewer.tsx @@ -0,0 +1,123 @@ +import type { TokenLine } from "@/lib/decompile/decompile.worker"; + +import { memo, type ReactNode } from "react"; + +import { CopyMenuButton, type CopyOption } from "@/components/code-viewer"; + +/** material-dark palette (react-syntax-highlighter prism theme) */ +const TOKEN_COLORS: Record = { + atrule: "#c792ea", + boolean: "#c792ea", + builtin: "#ffcb6b", + char: "#80cbc4", + "class-name": "#f2ff00", + comment: "#616161", + constant: "#c792ea", + function: "#c792ea", + keyword: "#c792ea", + number: "#fd9170", + operator: "#89ddff", + property: "#80cbc4", + punctuation: "#89ddff", + regex: "#f2ff00", + string: "#a5e844", + variable: "#ff6666", +}; + +const BACKGROUND = "#2f2f2f"; +const PLAIN = "#eee"; +const GUTTER_COLOR = "#616161"; +const FONT_FAMILY = "Roboto Mono, monospace"; + +interface DecompiledCodeViewerProps { + lines: TokenLine[] | null; + placeholder: string; + header?: ReactNode; + button?: ReactNode; + height: number; + copyLabel: string; + copyOptions: CopyOption[]; +} + +export default memo(function DecompiledCodeViewer({ + lines, + placeholder, + header, + button, + height, + copyLabel, + copyOptions, +}: Readonly) { + return ( +
+
+ {header} +
+ {button} + +
+
+
+ {lines ? ( +
+
+ {lines.map((_, index) => ( +
{index + 1}
+ ))} +
+
+              {lines.map((line, index) => (
+                
+ {line.map(([type, text], segmentIndex) => + type === "plain" ? ( + text + ) : ( + + {text} + + ), + )} + {line.length === 0 ? " " : null} +
+ ))} +
+
+ ) : ( +
+            {placeholder}
+          
+ )} +
+
+ ); +}); diff --git a/web/app/components/memshell/shell-result.tsx b/web/app/components/memshell/shell-result.tsx index 9a2d6b3d..d1581bf7 100644 --- a/web/app/components/memshell/shell-result.tsx +++ b/web/app/components/memshell/shell-result.tsx @@ -4,12 +4,13 @@ import { DownloadIcon } from "lucide-react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; +import DecompiledCodeViewer from "@/components/memshell/decompiled-code-viewer"; import { QuickUsage } from "@/components/memshell/quick-usage"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useDecompiledSources } from "@/lib/decompile/use-decompiled-sources"; import { downloadBytes } from "@/lib/utils"; -import CodeViewer from "../code-viewer"; import { BasicInfo } from "./results/basic-info"; import { ResultComponent } from "./results/result-component"; @@ -23,10 +24,24 @@ export default function ShellResult({ generateResult?: MemShellResult; }>) { const { t } = useTranslation(["common", "memshell"]); + const { sources, isDecompiling, error } = useDecompiledSources(generateResult); + if (!generateResult) { return ; } + + const shellClassName = generateResult.shellClassName; + const shellBytesBase64 = generateResult.shellBytesBase64Str; + const injectorClassName = generateResult.injectorClassName; + const injectorBytesBase64 = generateResult.injectorBytesBase64Str; + const height = 800; + const sourcePlaceholder = isDecompiling + ? `// ${t("common:decompiling")}` + : error + ? `// ${t("common:decompileFailed", { error })}` + : ""; + return ( @@ -42,61 +57,84 @@ export default function ShellResult({ generateResult={generateResult} /> - - {generateResult?.shellClassName}
} + + {shellClassName}} button={ } - wrapLongLines={true} height={height} - code={generateResult?.shellBytesBase64Str ?? ""} - language="text" + lines={sources.shell?.lines ?? null} + placeholder={sourcePlaceholder} /> - - {generateResult?.injectorClassName}} + + {injectorClassName}} button={ } height={height} - code={generateResult?.injectorBytesBase64Str ?? ""} - language="text" + lines={sources.injector?.lines ?? null} + placeholder={sourcePlaceholder} /> diff --git a/web/app/components/ui/dropdown-menu.tsx b/web/app/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..733a2e56 --- /dev/null +++ b/web/app/components/ui/dropdown-menu.tsx @@ -0,0 +1,53 @@ +import { Menu as MenuPrimitive } from "@base-ui/react/menu"; + +import { cn } from "@/lib/utils"; + +const DropdownMenu = MenuPrimitive.Root; + +const DropdownMenuTrigger = MenuPrimitive.Trigger; + +function DropdownMenuContent({ + className, + side = "bottom", + sideOffset = 4, + align = "center", + alignOffset = 0, + ...props +}: MenuPrimitive.Popup.Props & + Pick) { + return ( + + + + + + ); +} + +function DropdownMenuItem({ className, ...props }: MenuPrimitive.Item.Props) { + return ( + + ); +} + +export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger }; diff --git a/web/app/i18n/common/en.json b/web/app/i18n/common/en.json index c4ead374..97723d70 100644 --- a/web/app/i18n/common/en.json +++ b/web/app/i18n/common/en.json @@ -4,6 +4,7 @@ "byPassJavaModule": "BypassModule", "byPassJavaModule.description": "JDK 9+ module system strictly restricts reflective defineClass calls. When enabled, automatically inserts Unsafe-based module bypass code into injector", "cancel": "Cancel", + "copy": "Copy", "copyLabelSuccess": "Copy {{label}} successfully", "copySuccess": "Copy successfully", "debug": "Debug Mode", @@ -56,5 +57,8 @@ "commandTemplate": "Command Template", "commandTemplate.placeholder": "e.g., sh -c \"{command}\" 2>&1", "commandTemplate.description": "Use {command} as placeholder", - "targetJdkVersion": "JRE Version" + "targetJdkVersion": "JRE Version", + "decompile": "Decompile", + "decompiling": "Decompiling...", + "decompileFailed": "Failed to decompile source: {{error}}" } diff --git a/web/app/i18n/common/zh-CN.json b/web/app/i18n/common/zh-CN.json index 0a91905d..5e2fae1f 100644 --- a/web/app/i18n/common/zh-CN.json +++ b/web/app/i18n/common/zh-CN.json @@ -4,6 +4,7 @@ "byPassJavaModule": "绕过模块限制", "byPassJavaModule.description": "JDK 9+ 模块化系统严格限制反射调用 defineClass。开启后会在注入器中自动插入使用 Unsafe 绕过模块限制的代码", "cancel": "取消", + "copy": "复制", "copyLabelSuccess": "复制 {{label}} 成功", "copySuccess": "复制成功", "debug": "调试模式", @@ -56,5 +57,8 @@ "commandTemplate": "命令模板", "commandTemplate.placeholder": "例如:sh -c \"{command}\" 2>&1", "commandTemplate.description": "使用 {command} 作为占位符", - "targetJdkVersion": "JRE 版本" + "targetJdkVersion": "JRE 版本", + "decompile": "反编译", + "decompiling": "正在反编译...", + "decompileFailed": "反编译源码失败:{{error}}" } diff --git a/web/app/i18n/memshell/en.json b/web/app/i18n/memshell/en.json index 8458bd88..cba34fb3 100644 --- a/web/app/i18n/memshell/en.json +++ b/web/app/i18n/memshell/en.json @@ -57,5 +57,7 @@ "tips.try-to-use-shell": "Try to use the memory shell", "tips.download-jar": "Download the jar file and upload it to the public network server, so that it can be accessed through the http link to download", "tips.load-jar-with-scriptenginemanager": "Load the jar file with javax.script.ScriptEngineManager to implement injection", - "tips.trigger-injector-class-loading": "Trigger the injector class loading with RCE vulnerability" + "tips.trigger-injector-class-loading": "Trigger the injector class loading with RCE vulnerability", + "copySource": "Copy Source", + "copyBase64": "Copy Base64" } diff --git a/web/app/i18n/memshell/zh-CN.json b/web/app/i18n/memshell/zh-CN.json index 2894fdbd..ffd54a01 100644 --- a/web/app/i18n/memshell/zh-CN.json +++ b/web/app/i18n/memshell/zh-CN.json @@ -57,5 +57,7 @@ "tips.try-to-use-shell": "尝试利用内存马", "tips.download-jar": "下载 jar 包并上传至公网服务器,使其能通过 http 链接访问下载", "tips.load-jar-with-scriptenginemanager": "通过 RCE 漏洞使用 javax.script.ScriptEngineManager 加载 jar 包实现注入", - "tips.trigger-injector-class-loading": "通过 RCE 漏洞触发注入器类加载" + "tips.trigger-injector-class-loading": "通过 RCE 漏洞触发注入器类加载", + "copySource": "复制源码", + "copyBase64": "复制 Base64" } diff --git a/web/app/lib/decompile/decompile.worker.ts b/web/app/lib/decompile/decompile.worker.ts new file mode 100644 index 00000000..8042bba8 --- /dev/null +++ b/web/app/lib/decompile/decompile.worker.ts @@ -0,0 +1,97 @@ +import "./prism-worker-guard"; +import { decompile } from "@run-slicer/cfr"; +import Prism from "prismjs"; +import "prismjs/components/prism-clike"; +import "prismjs/components/prism-java"; + +/** [tokenType, text] segments of one rendered line */ +export type TokenLine = Array<[string, string]>; + +export interface DecompiledClass { + source: string; + lines: TokenLine[]; +} + +export interface DecompileRequest { + id: number; + shellClassName: string; + shellBytesBase64: string; + injectorClassName: string; + injectorBytesBase64: string; +} + +export interface DecompileResponse { + id: number; + shell?: DecompiledClass; + injector?: DecompiledClass; + error?: string; +} + +function base64ToBytes(base64String: string) { + const byteCharacters = atob(base64String); + const bytes = new Uint8Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + bytes[i] = byteCharacters.charCodeAt(i); + } + return bytes; +} + +function toJvmClassName(className: string) { + return className.replaceAll(".", "/"); +} + +function tokenizeToLines(code: string): TokenLine[] { + const lines: TokenLine[] = [[]]; + const push = (type: string, text: string) => { + const parts = text.split("\n"); + for (let i = 0; i < parts.length; i++) { + if (i > 0) lines.push([]); + if (parts[i]) lines[lines.length - 1].push([type, parts[i]]); + } + }; + const walk = (token: string | Prism.Token, inheritedType: string) => { + if (typeof token === "string") { + push(inheritedType, token); + return; + } + const { type, content } = token; + if (typeof content === "string") { + push(type, content); + return; + } + for (const child of Array.isArray(content) ? content : [content]) { + walk(child, type); + } + }; + for (const token of Prism.tokenize(code, Prism.languages.java)) { + walk(token, "plain"); + } + return lines; +} + +async function decompileClass( + jvmClassName: string, + classes: Map, +): Promise { + const source = await decompile(jvmClassName, { source: (name) => classes.get(name) ?? null }); + return { source, lines: tokenizeToLines(source) }; +} + +self.onmessage = async (event: MessageEvent) => { + const { id, shellClassName, shellBytesBase64, injectorClassName, injectorBytesBase64 } = + event.data; + try { + const classes = new Map([ + [toJvmClassName(shellClassName), base64ToBytes(shellBytesBase64)], + [toJvmClassName(injectorClassName), base64ToBytes(injectorBytesBase64)], + ]); + const shell = await decompileClass(toJvmClassName(shellClassName), classes); + const injector = await decompileClass(toJvmClassName(injectorClassName), classes); + self.postMessage({ id, shell, injector } satisfies DecompileResponse); + } catch (error) { + self.postMessage({ + id, + error: error instanceof Error ? error.message : String(error), + } satisfies DecompileResponse); + } +}; diff --git a/web/app/lib/decompile/prism-worker-guard.ts b/web/app/lib/decompile/prism-worker-guard.ts new file mode 100644 index 00000000..63ca8938 --- /dev/null +++ b/web/app/lib/decompile/prism-worker-guard.ts @@ -0,0 +1,15 @@ +/** + * prismjs auto-registers a `message` listener when loaded inside a Web Worker + * (it expects JSON strings and calls JSON.parse on evt.data). Our worker + * exchanges structured-clone objects, which crashes that handler with an + * uncaught SyntaxError. Setting `self.Prism.disableWorkerMessageHandler` + * before prism-core evaluates suppresses the registration. + * + * Imported for side effects before any prismjs import — ES module evaluation + * order guarantees this runs first. + */ +(globalThis as unknown as { Prism: { disableWorkerMessageHandler: boolean } }).Prism = { + disableWorkerMessageHandler: true, +}; + +export {}; diff --git a/web/app/lib/decompile/use-decompiled-sources.ts b/web/app/lib/decompile/use-decompiled-sources.ts new file mode 100644 index 00000000..20e58d10 --- /dev/null +++ b/web/app/lib/decompile/use-decompiled-sources.ts @@ -0,0 +1,143 @@ +import type { DecompiledClass, DecompileRequest, DecompileResponse } from "./decompile.worker"; +import type { MemShellResult } from "@/types/memshell"; + +import { useEffect, useState } from "react"; + +export interface DecompiledSources { + shell: DecompiledClass | null; + injector: DecompiledClass | null; +} + +interface DecompileState { + sources: DecompiledSources; + isDecompiling: boolean; + error: string | null; +} + +const IDLE: DecompileState = { + sources: { shell: null, injector: null }, + isDecompiling: false, + error: null, +}; + +const CACHE_LIMIT = 8; +const cache = new Map(); + +function cacheGet(key: string) { + const value = cache.get(key); + if (value) { + cache.delete(key); + cache.set(key, value); + } + return value; +} + +function cacheSet(key: string, value: DecompiledSources) { + cache.set(key, value); + if (cache.size > CACHE_LIMIT) { + const oldest = cache.keys().next().value; + if (oldest !== undefined) cache.delete(oldest); + } +} + +/** FNV-1a over both base64 payloads; cheap identity for one generation. */ +function cacheKey(shellBytes: string, injectorBytes: string) { + let hash = 0x811c9dc5; + for (const str of [shellBytes, injectorBytes]) { + for (let i = 0; i < str.length; i++) { + hash ^= str.charCodeAt(i); + hash = Math.imul(hash, 0x01000193); + } + } + return `${(hash >>> 0).toString(36)}:${shellBytes.length}:${injectorBytes.length}`; +} + +let worker: Worker | null = null; +let nextId = 0; +const pending = new Map< + number, + { resolve: (value: DecompiledSources) => void; reject: (reason: Error) => void } +>(); + +function getWorker() { + if (!worker) { + worker = new Worker(new URL("./decompile.worker.ts", import.meta.url), { type: "module" }); + worker.onmessage = (event: MessageEvent) => { + const { id, shell, injector, error } = event.data; + const entry = pending.get(id); + if (!entry) return; + pending.delete(id); + if (error || !shell || !injector) { + entry.reject(new Error(error ?? "decompile failed")); + } else { + entry.resolve({ shell, injector }); + } + }; + worker.onerror = (event) => { + const reason = new Error(event.message || "decompile worker failed"); + for (const entry of pending.values()) entry.reject(reason); + pending.clear(); + }; + } + return worker; +} + +function decompileInWorker(request: Omit) { + return new Promise((resolve, reject) => { + const id = nextId++; + pending.set(id, { resolve, reject }); + getWorker().postMessage({ ...request, id } satisfies DecompileRequest); + }); +} + +export function useDecompiledSources(generateResult: MemShellResult | undefined) { + const shellClassName = generateResult?.shellClassName; + const shellBytesBase64 = generateResult?.shellBytesBase64Str; + const injectorClassName = generateResult?.injectorClassName; + const injectorBytesBase64 = generateResult?.injectorBytesBase64Str; + + const [state, setState] = useState(IDLE); + + useEffect(() => { + if (!shellClassName || !shellBytesBase64 || !injectorClassName || !injectorBytesBase64) { + setState(IDLE); + return; + } + + const key = cacheKey(shellBytesBase64, injectorBytesBase64); + const cached = cacheGet(key); + if (cached) { + setState({ sources: cached, isDecompiling: false, error: null }); + return; + } + + let cancelled = false; + setState({ sources: { shell: null, injector: null }, isDecompiling: true, error: null }); + + decompileInWorker({ + shellClassName, + shellBytesBase64, + injectorClassName, + injectorBytesBase64, + }) + .then((sources) => { + cacheSet(key, sources); + if (!cancelled) setState({ sources, isDecompiling: false, error: null }); + }) + .catch((error: unknown) => { + if (!cancelled) { + setState({ + sources: { shell: null, injector: null }, + isDecompiling: false, + error: error instanceof Error ? error.message : String(error), + }); + } + }); + + return () => { + cancelled = true; + }; + }, [injectorBytesBase64, injectorClassName, shellBytesBase64, shellClassName]); + + return state; +} diff --git a/web/bun.lock b/web/bun.lock index 02a3d971..da077431 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -11,6 +11,7 @@ "@orama/stopwords": "^3.1.18", "@orama/tokenizers": "^3.1.18", "@react-router/node": "^7.15.1", + "@run-slicer/cfr": "file:./vendor/cfr", "@tanstack/react-query": "^5.101.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -452,6 +453,8 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA=="], + "@run-slicer/cfr": ["@run-slicer/cfr@file:./vendor/cfr", {}], + "@shikijs/core": ["@shikijs/core@4.3.0", "", { "dependencies": { "@shikijs/primitive": "4.3.0", "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-EooU3i9F6IAE8kEu+AnGf9DFZWkQBZ+hJn3tLVbsH+61mtQiva5biai66fAA6nvFPXkLgvrh7BrR7YcJU83xQQ=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.3.0", "", { "dependencies": { "@shikijs/types": "4.3.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-hTv/KiFf2tpiqlACPiztGGurEARWIutB8YUhcrA1pUC7VzzwKO+g5crUocrLztrZ5ro5Z4hbXg7bYclETn3gSQ=="], diff --git a/web/package.json b/web/package.json index 20404758..fb129b3f 100644 --- a/web/package.json +++ b/web/package.json @@ -20,6 +20,7 @@ "@orama/stopwords": "^3.1.18", "@orama/tokenizers": "^3.1.18", "@react-router/node": "^7.15.1", + "@run-slicer/cfr": "file:./vendor/cfr", "@tanstack/react-query": "^5.101.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/web/vendor/cfr/cfr.d.ts b/web/vendor/cfr/cfr.d.ts new file mode 100644 index 00000000..b5221da0 --- /dev/null +++ b/web/vendor/cfr/cfr.d.ts @@ -0,0 +1,10 @@ +declare module "@run-slicer/cfr" { + export type Options = Record; + + export interface Config { + source?: (name: string) => Uint8Array | null; + options?: Options; + } + + export function decompile(name: string, config?: Config): Promise; +} diff --git a/web/vendor/cfr/cfr.js b/web/vendor/cfr/cfr.js new file mode 100644 index 00000000..ef86da88 --- /dev/null +++ b/web/vendor/cfr/cfr.js @@ -0,0 +1,19 @@ +const isNode = + typeof process !== "undefined" && + process?.versions?.node != null; +const wasmPath = async () => { + const url = new URL("./cfr.wasm", import.meta.url).href; + return isNode ? await import("node:url").then((mod) => mod.fileURLToPath(url)) : url; +}; + +let decompileFunc = null; +export const decompile = async (name, options) => { + if (!decompileFunc) { + const { load } = await import("./cfr.wasm-runtime.js"); + const { exports } = await load(await wasmPath(), { noAutoImports: true }); + + decompileFunc = exports.decompile; + } + + return decompileFunc(name, options); +}; diff --git a/web/vendor/cfr/cfr.wasm b/web/vendor/cfr/cfr.wasm new file mode 100644 index 00000000..a2debba8 Binary files /dev/null and b/web/vendor/cfr/cfr.wasm differ diff --git a/web/vendor/cfr/cfr.wasm-runtime.js b/web/vendor/cfr/cfr.wasm-runtime.js new file mode 100644 index 00000000..4b62e766 --- /dev/null +++ b/web/vendor/cfr/cfr.wasm-runtime.js @@ -0,0 +1,30 @@ +var n=new Map;var s=/.+:wasm-function\[[0-9]+]:0x([0-9a-f]+).*/;function D(e){let t=n.get(e);if(typeof t==="undefined"){t=new Function("return "+e+";");n.set(e,t)}return t()}function O(e,t){new Function("value",e+" = value;")(t)}function x(e,t,n){const r={exports:null,userExports:t,stackDeobfuscator:null};if(!n){o(e)}a(e);l(e);i(e,r);u(e);f(e,r,n);e.teavmMath=Math;return{supplyExports(e){r.exports=e},supplyStackDeobfuscator(e){r.stackDeobfuscator=e}}}var _=Symbol("javaException");var E=class extends Error{#context;[_];constructor(e,t){super();this.#context=e;this[_]=t;e.exports["teavm.setJsException"](t,this)}get message(){const e=this.#context.exports["teavm.exceptionMessage"];const t=this.#context.exports["teavm.stringToJs"];if(typeof e==="function"&&typeof t==="function"){const n=e(this[_]);if(n!=null){return t(n)}}return"(could not fetch message)"}};function o(e){e["wasm:js-string"]={fromCharCode:e=>String.fromCharCode(e),fromCharCodeArray:()=>{throw new Error("Not supported")},intoCharCodeArray:()=>{throw new Error("Not supported")},concat:(e,t)=>e+t,charCodeAt:(e,t)=>e.charCodeAt(t),length:e=>e.length,substring:(e,t,n)=>e.substring(t,n)}}function a(e){e.teavmDate={currentTimeMillis:()=>(new Date).getTime(),dateToString:e=>new Date(e).toString(),getYear:e=>new Date(e).getFullYear(),setYear(e,t){const n=new Date(e);n.setFullYear(t);return n.getTime()},getMonth:e=>new Date(e).getMonth(),setMonth(e,t){const n=new Date(e);n.setMonth(t);return n.getTime()},getDate:e=>new Date(e).getDate(),setDate(e,t){const n=new Date(e);n.setDate(t);return n.getTime()},create:(e,t,n,r,o,a)=>new Date(e,t,n,r,o,a).getTime(),createFromUTC:(e,t,n,r,o,a)=>Date.UTC(e,t,n,r,o,a)}}function l(e){let t="";let n="";e.teavmConsole={putcharStderr(e){if(e===10){console.error(t);t=""}else{t+=String.fromCharCode(e)}},putcharStdout(e){if(e===10){console.log(n);n=""}else{n+=String.fromCharCode(e)}}}}function k(e,t){return(e-1>>t)+1<0){s=k(s,v);h.memoryOffset=s;s+=g}if(x>0){l=k(l,A);h.tableOffset=l;l+=x}}let p=-1;let m=-1;let y=-1;if(n.length>0){s=k(s,4);p=s;m=s;s+=a;s=k(s,4);y=s}let c=r.maxSize;s=k(s,8);const i=s;if(!f){const j=o.min??0;s+=j;s=Math.max(s,r.minSize??0);const J=r.shared??o.shared??false;c??=(1<<31)-1|0;const S=Math.max(j,n.length===0?1:256);const W=J?S:(c-1>>16)+1;f=new WebAssembly.Memory({shared:J,initial:S,maximum:W})}const b=l>0?new WebAssembly.Table({initial:l,element:"anyfunc"}):null;u.env={memory:f};u.teavmMemory={linearMemory(){return f.buffer},notifyHeapResized:r.onResize??function(){},heapOffset:new WebAssembly.Global({value:"i32",mutable:false},i),maxSize:new WebAssembly.Global({value:"i32",mutable:false},c)};const w={malloc:null,free:null,realloc:null};await Promise.all(n.map(async({name:e,wasmModule:n,jsLoader:t,tableOffset:r,memoryOffset:o})=>{const a=await t({wasmMemory:f,instantiateWasm(e,t){e.env={memory:f,malloc:e=>w.malloc(e),free:e=>w.free(e),realloc:(e,t)=>w.realloc(e,t),__indirect_function_table:b,__memory_base:new WebAssembly.Global({value:"i32",mutable:false},o??0),__table_base:new WebAssembly.Global({value:"i32",mutable:false},r??0),__stack_pointer:new WebAssembly.Global({value:"i32",mutable:true},p)};e["GOT.mem"]={__stack_low:new WebAssembly.Global({value:"i32",mutable:true},m),__stack_high:new WebAssembly.Global({value:"i32",mutable:true},y)};WebAssembly.instantiate(n,e).then(t)}});const s={};for(const{name:l,kind:c}of WebAssembly.Module.exports(n)){if(c==="function"){const i=a["_"+l];if(typeof i==="function"){s[l]=i}}}u[e]=s}));return w}var c=class{ptr=0;array;constructor(e){this.array=e}readVarInt(){let e=0;let t=0;while(true){const n=this.array[this.ptr++];e|=(n&127)<{const t=a.exports["teavm.reportGarbageCollectedValue"];if(typeof t!=="undefined"){t(e.queue,e.ref)}});const n=new FinalizationRegistry(e=>{const t=a.exports?.["teavm.reportGarbageCollectedString"];if(typeof t==="function"){t(e)}});e.teavm={createWeakRef(e,t,n){if(n!==null){r.register(e,{ref:t,queue:n})}return new WeakRef(e)},deref:e=>{const t=e.deref();return t!==void 0?t:null},createStringWeakRef(e,t){n.register(e,t);return new WeakRef(e)},stringDeref:e=>e.deref(),takeStackTrace(r){const e=(new Error).stack??"";const t=[];for(const n of e.split("\n")){const o=s.exec(n);if(o!==null&&o.length>=2){t.push(parseInt(o[1],16))}}return{getStack(){let n;if(a.stackDeobfuscator){try{n=a.stackDeobfuscator(t)}catch(e){console.warn("Could not deobfuscate stack",e)}}if(!n){n=t.map(e=>({className:"java.lang.Throwable$FakeClass",method:"fakeMethod",file:"Throwable.java",line:e}))}else if(r!==null){if(n.length>0&&n[0].className==="java.lang.Throwable"&&n[0].method==="fillInStackTrace"){n.shift()}let t=-1;for(let e=0;e"){break}if(n[e].className===r){t=e+1;break}}if(t>=0){n.splice(0,t)}}return n}}},decorateException(e){new E(a,e)}}}function u(e){e.teavmAsync={offer(e,t,n){const r=Math.max(0,n-Date.now());return setTimeout(()=>{t(e)},r)},kill(e){clearTimeout(e)}}}function M(e){const t=WebAssembly.Module.customSections(e,"teavm.memoryRequirements");if(t.length!==1){return{}}return JSON.parse((new TextDecoder).decode(t[0]))}function f(t,s,e){const l=Symbol("javaObject");const c=Symbol("functions");const o=Symbol("functionOrigin");const i=Symbol("wrapperCallMarker");const a=new WeakMap;const r=new WeakMap;const u=new Map;const f=new FinalizationRegistry(e=>u.delete(e));const n=new WeakMap;let p=2463534242;const m=()=>{let e=p;e^=e<<13;e^=e>>>17;e^=e<<5;p=e;return e};function y(e){return e}function b(t){let n="";const e=t.charAt(0);n+=w(e)?e:"_";for(let e=1;e="A"&&e<="Z"||e>="a"&&e<="z"||e==="_"||e==="$"}function h(e){return w(e)||e>="0"&&e<="9"}function d(e,t,n){if(e===null){O(t,n)}else{e[t]=n}}function g(t){if(t instanceof WebAssembly.Exception){const n=s.exports["teavm.javaException"];const r=s.exports["teavm.getJsException"];if(t.is(n)){const o=t.getArg(n,0);const a=A(o);if(a!==null){return a}let e=r(o);if(typeof e==="undefined"){e=new E(s,o)}return e}}return t}function v(e){if(_ in e){return e[_]}else{return s.exports["teavm.js.wrapException"](e)}}function x(e){s.exports["teavm.js.throwException"](v(e));throw e}function A(e){return s.exports["teavm.js.extractException"](e)}function j(e){throw g(e)}function J(e,t){try{return e!==null?e[t]:D(t)}catch(e){x(e)}}function S(t,e){const n=[];const r=[];for(let e=0;ee,isUndefined:e=>typeof e==="undefined",emptyArray:()=>[],appendToArray:(e,t)=>e.push(t),unwrapBoolean:e=>e?1:0,wrapBoolean:e=>!!e,getProperty:J,setProperty:d,setPropertyPure:d,global(e){try{return D(e)}catch(e){x(e)}},createClass(e,t,r){e=b(e??"JavaObject");let o;const n=W(e,function e(t,n){if(t===i){o.call(this,n)}else if(r===null){throw new Error("This class can't be instantiated directly")}else{try{return r.apply(null,arguments)}catch(e){j(e)}}});if(t===null){o=function(e){this[l]=e;this[c]=null}}else{o=function(e){t.call(this,e)}}n.prototype=Object.create(t?t.prototype:Object.prototype);n.prototype.constructor=n;const a=W(e,function(e){return n.call(this,i,e)});a[i]=n;a.prototype=n.prototype;return a},exportClass(e){return e[i]},defineMethod(e,t,n,r){const o=[];const a=[];for(let e=1;etypeof e===t||e===null,instanceOf:(e,t)=>e instanceof t,instanceOfOrNull:(e,t)=>e===null||e instanceof t,sameRef:(e,t)=>e===t,hashCode:t=>{if(typeof t==="object"||typeof t==="function"||typeof t==="symbol"){let e=n.get(t);if(typeof e==="number"){return e}e=m();n.set(t,e);return e}else if(typeof t==="number"){return t|0}else if(typeof t==="bigint"){return Number(BigInt.asIntN(32,t))}else if(typeof t==="boolean"){return t?1:0}else{return 0}},apply:(e,t,n)=>{try{if(e===null){const r=D(t);return r(...n)}else{return e[t](...n)}}catch(e){x(e)}},concatArray:(e,t)=>[...e,...t],getJavaException:e=>e[_],getJSException:e=>{const t=s.exports["teavm.getJsException"];return t(e)},jsExports:()=>s.userExports};for(const M of["wrapByte","wrapShort","wrapChar","wrapInt","wrapLong","wrapFloat","wrapDouble","unwrapByte","unwrapShort","unwrapChar","unwrapInt","unwrapLong","unwrapFloat","unwrapDouble"]){t.teavmJso[M]=y}function k(e){try{return e()}catch(e){x(e)}}const C=[];for(let e=0;e<32;++e){const F=C.length===0?"":C.join(", ");const T=[...C,"body"].join(", ");t.teavmJso["createFunction"+e]=new Function("wrapCallFromJavaToJs",...C,"body",`return new Function('wrapCallFromJavaToJs', ${T}).bind(this, wrapCallFromJavaToJs);`).bind(null,k);t.teavmJso["bindFunction"+e]=(e,...t)=>e.bind(null,...t);t.teavmJso["callFunction"+e]=new Function("rethrowJsAsJava","fn",...C,`try { + return fn(${F}); +} catch (e) { + rethrowJsAsJava(e); +}`).bind(null,x);t.teavmJso["callMethod"+e]=new Function("rethrowJsAsJava","getGlobalName","instance","method",...C,`try { + return instance !== null + ? instance[method](${F}) + : getGlobalName(method)(${F}); +} catch (e) { + rethrowJsAsJava(e); +}`).bind(null,x,D);t.teavmJso["construct"+e]=new Function("rethrowJsAsJava","constructor",...C,`try { + return new constructor(${F}); +} catch (e) { + rethrowJsAsJava(e); +}`).bind(null,x);t.teavmJso["arrayOf"+e]=new Function(...C,"return ["+F+"]");C.push("p"+(e+1))}}function e(e){return new Proxy(e,{get(e,t){const n=e[t];return new WebAssembly.Global({value:"externref",mutable:false},n)}})}async function j(e,o){const t=[];const n={};for(const{module:a,name:r}of p(e)){if(a in o){continue}let e=n[a];if(e===void 0){const s=[];e=s;n[a]=e;t.push((async()=>{const e=await import(a);const t={};for(const n of s){const r=n==="__self__"?e:e[n];t[n]=new WebAssembly.Global({value:"externref",mutable:false},r)}o[a]=t})())}e.push(r)}if(t.length===0){return}await Promise.all(t)}function p(e){const t=WebAssembly.Module.customSections(e,"teavm.imports");if(t.length!==1){return WebAssembly.Module.imports(e).filter(e=>e.kind==="global")}return JSON.parse((new TextDecoder).decode(t[0]))}async function t(e,t){if(!t){t={}}const n=t.nodejs||typeof process!=="undefined";const r=t.emscriptenModules??{};const o=t.stackDeobfuscator??{};const a=o.infoLocation??"auto";const s=J(e,n);const[l,c,i,u]=await Promise.all([o.enabled?W(e,o,n):Promise.resolve(null),s,T(e,a,o,n),I(r,n)]);const f={};const p={};const m=x(f,p,await S());const y=await A(f,t,c,u);if(typeof t.installImports!=="undefined"){t.installImports(f)}if(!t.noAutoImports){await j(c,f)}const b=await WebAssembly.instantiate(c,f);y.malloc=b.exports["teavm.malloc"];y.free=b.exports["teavm.free"];y.realloc=b.exports["teavm.realloc"];m.supplyExports(b.exports);if(l){const h=a==="auto"||a==="embedded"?c:null;const d=F(h,i,l);if(d!==null){m.supplyStackDeobfuscator(d)}}const w={exports:p,instance:b,module:c};for(const g in b.exports){const v=b.exports[g];if(v instanceof WebAssembly.Global){Object.defineProperty(p,g,{get:()=>v.value})}}return w}async function J(e,t){if(typeof e!=="string"){return await WebAssembly.compile(e,{builtins:["js-string"]})}const[n,r]=await m(e,t);const o=await WebAssembly.compileStreaming(n,{builtins:["js-string"]});r();return o}var r=null;function S(){if(r===null){r=(async()=>{const e=new Int8Array([0,97,115,109,1,0,0,0,1,7,1,96,1,127,1,100,111,2,31,1,14,119,97,115,109,58,106,115,45,115,116,114,105,110,103,12,102,114,111,109,67,104,97,114,67,111,100,101,0,0,3,1,0,5,4,1,1,0,0,7,10,1,6,109,101,109,111,114,121,2,0,10,-127,-128,-128,0,0]);try{const t=new Response(e,{headers:{"Content-Type":"application/wasm"}});const n=await WebAssembly.compileStreaming(t,{builtins:["js-string"]});await WebAssembly.instantiate(n,{});return true}catch(e){return false}})()}return r}async function W(e,t,n){if(typeof e!=="string"&&!t.path){return null}try{const r={};const o=await J(t.path??`${e}-deobfuscator.wasm`,n);const a=x(r,{},await S());await A(r,{},o,[]);const s=await WebAssembly.instantiate(o,r);a.supplyExports(s.exports);return s}catch(e){console.warn("Could not load deobfuscator",e);return null}}async function m(e,t){if(!t){const n=await fetch(e);return[n,()=>{}]}else{const r=await b();const o=await r.open(e,"r");const a=await o.readableWebStream();const n=new Response(a,{headers:{"Content-Type":"application/wasm"}});return[n,()=>o.close()]}}var y;async function b(){if(!y){y=import("node:fs/promises")}return await y}function F(e,t,n){let r=null;let o=false;function a(){if(!o){o=true;if(t!==null){try{r=n.exports["createFromExternalFile"].value(t)}catch(e){console.warn("Could not load create deobfuscator",e)}}if(r==null&&e!==null){try{r=n.exports["createForModule"].value(e)}catch(e){console.warn("Could not create deobfuscator from module data",e)}}}}return e=>{a();return r!==null?r.deobfuscate(e):[]}}async function T(e,t,n,r){if(!n.enabled){return null}if(typeof e!=="string"&&!n.externalInfoPath){return null}if(t!=="auto"&&t!=="external"){return null}if(typeof n.externalInfoPath==="object"&&n.externalInfoPath instanceof Int8Array){return n.externalInfoPath}const o=n.externalInfoPath??e+".teadbg";let a;if(!r){const s=await fetch(o);if(!s.ok){return null}a=await s.arrayBuffer()}else{const l=await b();a=(await l.readFile(o)).buffer}return new Int8Array(a)}async function I(e,c){const t=Object.entries(e);return Promise.all(t.map(async([e,{pathToJs:t,pathToWasm:n}])=>{const[r,o]=await m(n,c);const[a,s]=await Promise.all([import(t),WebAssembly.compileStreaming(r)]);const l={name:e,jsLoader:a.default,wasmModule:s};o();return l}))}export{x as defaults,t as load,e as wrapImport}; \ No newline at end of file diff --git a/web/vendor/cfr/package.json b/web/vendor/cfr/package.json new file mode 100644 index 00000000..0b93de6b --- /dev/null +++ b/web/vendor/cfr/package.json @@ -0,0 +1,24 @@ +{ + "name": "@run-slicer/cfr", + "version": "0.1.6-0.152", + "description": "A JavaScript port of the CFR decompiler (https://github.com/leibnitz27/cfr).", + "type": "module", + "main": "cfr.js", + "module": "cfr.js", + "exports": { + ".": { + "types": "./cfr.d.ts", + "import": "./cfr.js", + "default": "./cfr.js" + } + }, + "types": "cfr.d.ts", + "keywords": [ + "decompiler", + "java", + "decompilation", + "cfr" + ], + "author": "run-slicer", + "license": "MIT" +} \ No newline at end of file