From bfae5e10d32af84fabe4c83408c8f67d8843af63 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Wed, 19 Feb 2025 21:38:52 +0800 Subject: [PATCH] feat: enhance responsive layout and improve i18n support (#39) --- web/bun.lockb | Bin 186268 -> 187804 bytes web/package.json | 15 +- web/src/components/code-viewer.tsx | 30 ++- web/src/components/copyable-field.tsx | 52 +++-- web/src/components/main-config-card.tsx | 61 ++++-- web/src/components/package-config-card.tsx | 6 +- web/src/components/shell-result.tsx | 216 +++++++++++--------- web/src/i18n/en.json | 222 +++++++++++---------- web/src/i18n/zh-CN.json | 211 +++++++++++--------- web/src/index.css | 36 ++++ web/src/routes/__root.tsx | 6 +- web/tailwind.config.js | 4 +- 12 files changed, 505 insertions(+), 354 deletions(-) diff --git a/web/bun.lockb b/web/bun.lockb index e83158806f294c87b309563d1b94256ebd9d2ad5..1c0d17df4af624eb6b7481d56102aae78c6553b4 100755 GIT binary patch delta 34853 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 delta 34206 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 diff --git a/web/package.json b/web/package.json index f94a75ba..a72c06d1 100644 --- a/web/package.json +++ b/web/package.json @@ -13,16 +13,16 @@ }, "devDependencies": { "@biomejs/biome": "1.9.4", - "@tanstack/router-plugin": "^1.105.0", + "@tanstack/router-plugin": "^1.106.0", "@types/node": "^22.13.4", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-syntax-highlighter": "^15.5.13", - "tailwindcss": "^4.0.6", + "tailwindcss": "^4.0.7", "typescript": "^5.7.3", - "vite": "^6.1.0" + "vite": "^6.1.1" }, "dependencies": { "@hookform/resolvers": "^4.1.0", @@ -37,10 +37,10 @@ "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", - "@tailwindcss/vite": "^4.0.6", + "@tailwindcss/vite": "^4.0.7", "@tanstack/react-query": "^5.66.7", - "@tanstack/react-router": "^1.105.0", - "@tanstack/router-devtools": "^1.105.0", + "@tanstack/react-router": "^1.106.0", + "@tanstack/router-devtools": "^1.106.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "i18next": "^24.2.2", @@ -51,8 +51,9 @@ "react-hook-form": "^7.54.2", "react-i18next": "^15.4.1", "react-syntax-highlighter": "^15.6.1", - "sonner": "^1.7.4", + "sonner": "^2.0.0", "tailwind-merge": "^3.0.1", + "tailwind-scrollbar": "^4.0.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.24.2" }, diff --git a/web/src/components/code-viewer.tsx b/web/src/components/code-viewer.tsx index 77021aa3..2d4a94c7 100644 --- a/web/src/components/code-viewer.tsx +++ b/web/src/components/code-viewer.tsx @@ -1,7 +1,8 @@ import { Button, type ButtonProps } from "@/components/ui/button.tsx"; import { Check, Copy } from "lucide-react"; -import { type HTMLProps, type ReactNode, useEffect, useState } from "react"; +import { type HTMLProps, type ReactNode, useCallback, useEffect, useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; +import { useTranslation } from "react-i18next"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import { toast } from "sonner"; @@ -13,24 +14,33 @@ interface CopyButtonProps extends ButtonProps { export function CopyButton({ value }: CopyButtonProps) { const [hasCopied, setHasCopied] = useState(false); + const { t } = useTranslation(); useEffect(() => { if (hasCopied) { - setTimeout(() => { + 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 ( - { - setHasCopied(true); - toast.success("复制成功", { duration: 1000 }); - }} - > - diff --git a/web/src/components/copyable-field.tsx b/web/src/components/copyable-field.tsx index 6b8ca075..e9afc8fa 100644 --- a/web/src/components/copyable-field.tsx +++ b/web/src/components/copyable-field.tsx @@ -1,8 +1,9 @@ import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Check, Copy } from "lucide-react"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; +import { useTranslation } from "react-i18next"; import { toast } from "sonner"; interface CopyableFieldProps { @@ -13,30 +14,39 @@ interface CopyableFieldProps { export function CopyableField({ label, value, text }: CopyableFieldProps) { const [hasCopied, setHasCopied] = useState(false); + const { t } = useTranslation(); - const copyToClipboard = useCallback(() => { - setHasCopied(true); - toast.success(`复制${label}成功`, { - duration: 1000, - }); - setTimeout(() => { - setHasCopied(false); - }, 1000); - }, [label]); + useEffect(() => { + if (hasCopied) { + const timer = setTimeout(() => { + setHasCopied(false); + }, 1000); + return () => clearTimeout(timer); + } + }, [hasCopied]); + + const handleCopy = useCallback(() => { + if (!hasCopied) { + setHasCopied(true); + toast.success(t("copyLabelSuccess", { label }), { + duration: 1000, + }); + } + }, [hasCopied, label, t]); return ( -

-
- -

{text}

+
+
+ + {value && ( + + + + )}
- {value && ( - - - - )} +

{text}

); } diff --git a/web/src/components/main-config-card.tsx b/web/src/components/main-config-card.tsx index f0dc881c..d126de34 100644 --- a/web/src/components/main-config-card.tsx +++ b/web/src/components/main-config-card.tsx @@ -10,9 +10,17 @@ import { MainConfig } from "@/types/shell.ts"; import { JreTip } from "@/components/tips/jre-tip.tsx"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx"; -import { cn } from "@/lib/utils.ts"; -import { ArrowUpRightIcon, ServerIcon } from "lucide-react"; -import { useState } from "react"; +import { + ArrowUpRightIcon, + AxeIcon, + CommandIcon, + NetworkIcon, + ServerIcon, + ShieldOffIcon, + SwordIcon, + WaypointsIcon, +} from "lucide-react"; +import { JSX, useState } from "react"; import { FormProvider, UseFormReturn } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -25,6 +33,17 @@ const JDKVersion = [ { name: "Java21", value: "65" }, ]; +type ShellToolType = "Behinder" | "Godzilla" | "Command" | "AntSword" | "Suo5" | "Neo-reGeorg"; + +const shellToolIcons: Record = { + Behinder: , + Godzilla: , + Command: , + AntSword: , + Suo5: , + "Neo-reGeorg": , +}; + export function MainConfigCard({ mainConfig, form, @@ -101,6 +120,12 @@ export function MainConfigCard({ form.resetField("headerValue"); }; + const resetAntSword = () => { + form.resetField("antSwordPass"); + form.resetField("headerName"); + form.resetField("headerValue"); + }; + if (shellToolMap) { setShellTypes(shellToolMap[value]); form.resetField("urlPattern"); @@ -115,6 +140,8 @@ export function MainConfigCard({ resetCommand(); } else if (value === "Suo5") { resetSuo5(); + } else if (value === "AntSword") { + resetAntSword(); } } form.setValue("shellTool", value); @@ -130,7 +157,7 @@ export function MainConfigCard({ -
+
- 3 ? "grid-flow-col" : `grid-cols-${shellTools.length}`)} - > - {shellTools.map((shellTool) => ( - - {shellTool} - - ))} - +
+ + {shellTools.map((shellTool) => ( + + + {shellToolIcons[shellTool as ShellToolType]} + {shellTool} + + + ))} + +
+ diff --git a/web/src/components/package-config-card.tsx b/web/src/components/package-config-card.tsx index 6dff057d..447d1263 100644 --- a/web/src/components/package-config-card.tsx +++ b/web/src/components/package-config-card.tsx @@ -69,7 +69,11 @@ export function PackageConfigCard({ {t("packageConfig.title")} - + {options.map(({ name, value }) => ( diff --git a/web/src/components/shell-result.tsx b/web/src/components/shell-result.tsx index 41de3703..b0232629 100644 --- a/web/src/components/shell-result.tsx +++ b/web/src/components/shell-result.tsx @@ -28,21 +28,25 @@ import { Suo5ShellToolConfig, } from "@/types/shell.ts"; import { TFunction } from "i18next"; -import { CircleHelpIcon, TicketCheckIcon, TriangleAlertIcon } from "lucide-react"; +import { CircleHelpIcon, FileTextIcon, ScrollTextIcon, TriangleAlertIcon } from "lucide-react"; import { Fragment, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; function AgentResult({ packResult, generateResult }: { packResult: string; generateResult?: GenerateResult }) { + const { t } = useTranslation(); return ( - 使用方法 + + + {t("generateResult.usage")} +
  1. - 下载 MemShellAgent.jar + {t("download")} MemShellAgent.jar
  2. - 下载 Jattach 工具(后期考虑直接封装在 Jar 中) + {t("tips.download-jattach")}
  3. -
  4. 将 MemShellAgent.jar 和 jattach 移动到容器中(如果测试环境使用容器部署)
  5. -
  6. 获取目标 jvm 的进程 pid (使用 jps 或 ps)
  7. -
  8. 执行命令进行注入:/path/to/jattach pid load instrument false /path/to/agent.jar
  9. -
  10. 尝试利用内存马
  11. +
  12. {t("tips.move-to-container")}
  13. +
  14. {t("tips.get-pid")}
  15. +
  16. {t("tips.execute-command")}
  17. +
  18. {t("tips.try-to-use-shell")}
@@ -145,105 +149,119 @@ function BasicInfo({ generateResult }: { generateResult?: GenerateResult }) {
- + {t("generateResult.basicInfo")}
-
+
+ +
- - - {generateResult?.shellConfig.shellTool === "Behinder" && ( - - - - - - + + + {(generateResult?.shellConfig.shellTool === "Behinder" || + generateResult?.shellConfig.shellTool === "Godzilla" || + generateResult?.shellConfig.shellTool === "Command" || + generateResult?.shellConfig.shellTool === "AntSword" || + generateResult?.shellConfig.shellTool === "Suo5") && ( +
+ {/* Tool-specific fields */} + {generateResult?.shellConfig.shellTool === "Behinder" && ( + <> + + + + + + )} + {generateResult?.shellConfig.shellTool === "Godzilla" && ( + + + + + + + + )} + {generateResult?.shellConfig.shellTool === "Command" && ( + + + + )} + {generateResult?.shellConfig.shellTool === "Suo5" && ( + + + + )} + {generateResult?.shellConfig.shellTool === "AntSword" && ( + + + + + )} +
)} - {generateResult?.shellConfig.shellTool === "Godzilla" && ( - - - - - - - - )} - {generateResult?.shellConfig.shellTool === "Command" && ( - - - - )} - {generateResult?.shellConfig.shellTool === "Suo5" && ( - - - - )} - {generateResult?.shellConfig.shellTool === "AntSword" && ( - - - - - )} - - + +
+ + +
); diff --git a/web/src/i18n/en.json b/web/src/i18n/en.json index 35c62092..7e1f3020 100644 --- a/web/src/i18n/en.json +++ b/web/src/i18n/en.json @@ -1,115 +1,127 @@ { - "download": "Downlaod", - "feedback": "Feedback", - "cancel": "Cancel", - "placeholders": { - "select": "Please select", - "input": "Please input" - }, - "configs": { - "main-config": "Main Config", - "package-config": "Package Config" - }, - "optional": "(Optional)", - "mainConfig": { - "server": "Target Server", - "jre": "Target JRE Version", - "debug": "Debug Mode", - "bypassJavaModule": "Bypass Java Module", - "shellMountType": "Shell Mount Type", - "shellTool": "Shell Tool", - "urlPattern": "URL Pattern", - "shellClassName": "Shell ClassName", - "injectorClassName": "Injector ClassName" - }, - "shellToolConfig": { - "godzilla": "Godzilla", - "behinder": "Behinder", - "command": "Command", - "customHeader": "Custom Header", - "headerName": "Header Name", - "headerValue": "Header Value", - "pass": "Pass", - "key": "Key", - "godzillaHeader": "Request Config -> Request Header", - "godzillaPayload": "Payload", - "godzillaEncryptor": "Encryptor", - "behinderPass": "Pass", - "behinderScriptType": "Script Type", - "behinderEncryptType": "Encrypt Type", - "behinderDefaultEncryptType": "Default", - "paramName": "Param Name", - "suo5Header": "AdvanceConfiguration -> Request Header", - "antSwordPass": "Shell pwd", - "httpHeader": "HTTP -> HTTP HEADERS" - }, - "packageConfig": { - "title": "Package Method", - "packer": { - "Base64": "Base64", - "GzipBase64": "GzipBase64", - "BCEL": "BCEL", - "JSP": "JSP", - "Jar": "Jar", - "EL": "EL", - "SpEL": "SpEL", - "OGNL": "OGNL", - "MVEL": "MVEL", - "Freemarker": "Freemarker", - "Velocity": "Velocity", - "Groovy": "Groovy", - "AgentJar": "AgentJar", - "JavaDeserialize": "JavaDeserialize", - "ScriptEngine": "ScriptEngine", - "XxlJob": "XXL-JOB Executor", - "JinJava": "JinJava", - "Aviator": "Aviator", - "JXPath": "JXPath", - "JEXL": "JEXL", - "Rhino": "Rhino", - "BeanShell": "BeanShell" - } - }, - "tips": { - "shellToolNotSelected": "Please select a shell tool type first", - "targetServerNotFound": "Target server not found?", - "waitingForGeneration": "// Waiting for generation...", - "targetServerRequest": "Request", - "servletUrlPattern": "Servlet type requires a specific URL Pattern, e.g., /hello_servlet", - "controllerUrlPattern": "ControllerHandler type requires a specific URL Pattern, e.g., /hello_controller", - "handlerUrlPattern": "HandlerMethod/HandlerFunction type requires a specific URL Pattern, e.g., /hello_handler", - "jreTip": "Target JRE version, generally speaking, Java 6 is the default version for maximum compatibility, and Java high versions can load low version bytecode.", - "jreTip2": "In specific cases, such as JDK8 being able to use lambda expressions, and JDK9 and above having module restrictions, a specific version is required.", - "decompileTip": "Decompilation is still under development, so the current only sees the base64 encoding format", - "shellBytesEmpty": "Shell bytes is empty, please generate shell first" - }, - "quickUsage": { - "title": "Quick Usage", - "step1": "Select Target Server", - "step2": "Select Shell Tool, Godzilla, Behinder, etc.", - "step3": "Select Shell Mount Type, Filter, Listener, etc.", - "step4": "Select Packing Method", - "step5": "Click Generate Shell" - }, - "generateResult": { - "basicInfo": "Basic Info", - "usage": "Usage", - "title1": "Generate Result", - "title2": "Shell Class", - "title3": "Injector Class" - }, - "shellNotWork": { - "title": "Shell Not Work ?", - "step1": "1. Try to enable the debug mode, regenerate the memory shell and inject it, check the console or log", - "step2": "2. If an exception stack trace is displayed, or no exception is seen, please take a screenshot of the current generation interface and the exception log, and describe the target environment as much as possible for feedback" + "basicInfo": { + "classInfo": "ShellClass", + "serverInfo": "TargetServer", + "toolInfo": "ShellTool" }, "buttons": { "generate": "Generate Shell" }, + "cancel": "Cancel", + "configs": { + "main-config": "Main Config", + "package-config": "Package Config" + }, + "copyLabelSuccess": "Copy {{label}} successfully", + "copySuccess": "Copy successfully", + "download": "Downlaod", + "errors": { + "generationFailed": "Generation failed, {{error}}" + }, + "feedback": "Feedback", + "generateResult": { + "basicInfo": "Basic Info", + "title1": "Generate Result", + "title2": "Shell Class", + "title3": "Injector Class", + "usage": "Usage" + }, + "mainConfig": { + "bypassJavaModule": "Bypass Java Module", + "debug": "Debug Mode", + "injectorClassName": "Injector ClassName", + "jre": "Target JRE Version", + "server": "Target Server", + "shellClassName": "Shell ClassName", + "shellMountType": "Shell Mount Type", + "shellTool": "Shell Tool", + "urlPattern": "URL Pattern" + }, + "optional": "(Optional)", + "packageConfig": { + "packer": { + "AgentJar": "AgentJar", + "Aviator": "Aviator", + "BCEL": "BCEL", + "Base64": "Base64", + "BeanShell": "BeanShell", + "EL": "EL", + "Freemarker": "Freemarker", + "Groovy": "Groovy", + "GzipBase64": "GzipBase64", + "JEXL": "JEXL", + "JSP": "JSP", + "JXPath": "JXPath", + "Jar": "Jar", + "JavaDeserialize": "JavaDeserialize", + "JinJava": "JinJava", + "MVEL": "MVEL", + "OGNL": "OGNL", + "Rhino": "Rhino", + "ScriptEngine": "ScriptEngine", + "SpEL": "SpEL", + "Velocity": "Velocity", + "XxlJob": "XXL-JOB Executor" + }, + "title": "Package Method" + }, + "placeholders": { + "input": "Please input", + "select": "Please select" + }, + "quickUsage": { + "step1": "Select Target Server", + "step2": "Select Shell Tool, Godzilla, Behinder, etc.", + "step3": "Select Shell Mount Type, Filter, Listener, etc.", + "step4": "Select Packing Method", + "step5": "Click Generate Shell", + "title": "Quick Usage" + }, + "shellNotWork": { + "step1": "1. Try to enable the debug mode, regenerate the memory shell and inject it, check the console or log", + "step2": "2. If an exception stack trace is displayed, or no exception is seen, please take a screenshot of the current generation interface and the exception log, and describe the target environment as much as possible for feedback", + "title": "Shell Not Work ?" + }, + "shellToolConfig": { + "antSwordPass": "Shell pwd", + "behinder": "Behinder", + "behinderDefaultEncryptType": "Default", + "behinderEncryptType": "Encrypt Type", + "behinderPass": "Pass", + "behinderScriptType": "Script Type", + "command": "Command", + "customHeader": "Custom Header", + "godzilla": "Godzilla", + "godzillaEncryptor": "Encryptor", + "godzillaHeader": "Request Config -> Request Header", + "godzillaPayload": "Payload", + "headerName": "Header Name", + "headerValue": "Header Value", + "httpHeader": "HTTP -> HTTP HEADERS", + "key": "Key", + "paramName": "Param Name", + "pass": "Pass", + "suo5Header": "AdvanceConfiguration -> Request Header" + }, "success": { "generated": "Generation successful" }, - "errors": { - "generationFailed": "Generation failed, {{error}}" + "tips": { + "controllerUrlPattern": "ControllerHandler type requires a specific URL Pattern, e.g., /hello_controller", + "decompileTip": "Decompilation is still under development, so the current only sees the base64 encoding format", + "handlerUrlPattern": "HandlerMethod/HandlerFunction type requires a specific URL Pattern, e.g., /hello_handler", + "jreTip": "Target JRE version, generally speaking, Java 6 is the default version for maximum compatibility, and Java high versions can load low version bytecode.", + "jreTip2": "In specific cases, such as JDK8 being able to use lambda expressions, and JDK9 and above having module restrictions, a specific version is required.", + "servletUrlPattern": "Servlet type requires a specific URL Pattern, e.g., /hello_servlet", + "shellBytesEmpty": "Shell bytes is empty, please generate shell first", + "shellToolNotSelected": "Please select a shell tool type first", + "targetServerNotFound": "Target server not found?", + "targetServerRequest": "Request", + "waitingForGeneration": "// Waiting for generation...", + "download-jattach": "Download the Jattach tool (consider it directly encapsulated in Jar later)", + "move-to-container": "Move MemShellAgent.jar and jattach to the container (if the test environment uses a container deployment)", + "get-pid": "Get the process pid of the target jvm (use jps or ps)", + "execute-command": "Execute the command to inject: /path/to/jattach pid load instrument false /path/to/agent.jar", + "try-to-use-shell": "Try to use the memory shell" } } diff --git a/web/src/i18n/zh-CN.json b/web/src/i18n/zh-CN.json index 328df7e2..1e38c1cd 100644 --- a/web/src/i18n/zh-CN.json +++ b/web/src/i18n/zh-CN.json @@ -1,104 +1,127 @@ { - "download": "下载", - "feedback": "反馈", - "cancel": "取消", - "placeholders": { - "select": "请选择", - "input": "请输入" - }, - "configs": { - "main-config": "核心配置", - "package-config": "打包配置" - }, - "optional": "(可选)", - "mainConfig": { - "server": "目标服务", - "jre": "目标 JRE 版本", - "debug": "调试模式", - "bypassJavaModule": "绕过 Java 模块限制", - "shellMountType": "内存马挂载类型", - "shellTool": "内存马功能", - "urlPattern": "请求路径", - "shellClassName": "内存马类名", - "injectorClassName": "注入器类名" - }, - "shellToolConfig": { - "godzilla": "哥斯拉", - "behinder": "冰蝎", - "command": "命令回显", - "customHeader": "自定义请求头", - "headerName": "请求头键", - "headerValue": "请求头值", - "pass": "密码", - "key": "密钥", - "godzillaPayload": "有效载荷", - "godzillaEncryptor": "加密器", - "godzillaHeader": "请求配置 -> 请求头", - "behinderPass": "连接密码", - "behinderScriptType": "脚本类型", - "behinderEncryptType": "加密类型", - "behinderDefaultEncryptType": "默认", - "paramName": "请求参数", - "suo5Header": "高级配置 -> 请求头", - "antSwordPass": "连接密码", - "httpHeader": "请求信息 -> HTTP HEADERS" - }, - "packageConfig": { - "title": "打包方式", - "packer": { - "EL": "EL 表达式", - "SpEL": "SpEL 表达式", - "OGNL": "OGNL 表达式", - "MVEL": "MVEL 表达式", - "JEXL": "JEXL 表达式", - "BeanShell": "BeanShell 表达式", - "Aviator": "Aviator 表达式", - "JXPath": "JXPath 表达式", - "JavaDeserialize": "Java反序列化", - "ScriptEngine": "内置脚本引擎", - "Rhino": "Rhino 脚本引擎" - } - }, - "tips": { - "shellToolNotSelected": "请先选择内存马工具类型", - "targetServerNotFound": "找不到目标服务 ?", - "targetServerRequest": "请求适配", - "waitingForGeneration": "// 等待填写参数生成中...", - "servletUrlPattern": "Servlet 类型的需要填写具体的 URL Pattern,例如 /hello_servlet", - "controllerUrlPattern": "ControllerHandler 类型的需要填写具体的 URL Pattern,例如 /hello_controller", - "handlerUrlPattern": "HandlerMethod/HandlerFunction 类型的需要填写具体的 URL Pattern,例如 /hello_handler", - "jreTip": "目标 JRE 版本,一般而言为了最大的兼容性,默认 Java 6 即可,Java 高版本能加载低版本的字节码。", - "jreTip2": "特定情况下,例如 JDK8 才能使用 lambda 表达式,JDK9 以上存在模块限制时才需要选择特定的版本。", - "decompileTip": "反编译还在开发中,因此当前仅能看到 base64 编码格式", - "shellBytesEmpty": "内存马字节码为空,无法下载, 请先生成内存马" - }, - "quickUsage": { - "title": "快速使用", - "step1": "选择目标服务", - "step2": "选择内存马功能,Godzilla、Behinder 或者其他", - "step3": "选择内存马挂载类型,Filter、Listener 或者其他", - "step4": "选择打包方式", - "step5": "点击生成内存马" - }, - "generateResult": { - "basicInfo": "基本信息", - "usage": "使用方法", - "title1": "生成结果", - "title2": "内存马类", - "title3": "注入器类" - }, - "shellNotWork": { - "title": "内存马利用失败 ?", - "step1": "1. 尝试开启调试模式,重新生成内存马并注入,查看控制台或日志", - "step2": "2. 如果出现异常堆栈信息,或未见异常,请截图当前生成界面以及异常日志,并尽可能描述目标环境进行反馈" + "basicInfo": { + "classInfo": "内存马类信息", + "serverInfo": "目标服务信息", + "toolInfo": "内存马工具信息" }, "buttons": { "generate": "生成内存马" }, + "cancel": "取消", + "configs": { + "main-config": "核心配置", + "package-config": "打包配置" + }, + "copyLabelSuccess": "复制 {{label}} 成功", + "copySuccess": "复制成功", + "download": "下载", + "errors": { + "generationFailed": "生成失败,{{error}}" + }, + "feedback": "反馈", + "generateResult": { + "basicInfo": "基本信息", + "title1": "生成结果", + "title2": "内存马类", + "title3": "注入器类", + "usage": "使用方法" + }, + "mainConfig": { + "bypassJavaModule": "绕过 Java 模块限制", + "debug": "调试模式", + "injectorClassName": "注入器类名", + "jre": "目标 JRE 版本", + "server": "目标服务", + "shellClassName": "内存马类名", + "shellMountType": "内存马挂载类型", + "shellTool": "内存马功能", + "urlPattern": "请求路径" + }, + "optional": "(可选)", + "packageConfig": { + "packer": { + "AgentJar": "AgentJar", + "Aviator": "Aviator 表达式", + "BCEL": "BCEL", + "Base64": "Base64", + "BeanShell": "BeanShell 表达式", + "EL": "EL 表达式", + "Freemarker": "Freemarker", + "Groovy": "Groovy", + "GzipBase64": "GzipBase64", + "JEXL": "JEXL 表达式", + "JSP": "JSP", + "JXPath": "JXPath 表达式", + "Jar": "Jar", + "JavaDeserialize": "Java反序列化", + "JinJava": "JinJava", + "MVEL": "MVEL 表达式", + "OGNL": "OGNL 表达式", + "Rhino": "Rhino 脚本引擎", + "ScriptEngine": "内置脚本引擎", + "SpEL": "SpEL 表达式", + "Velocity": "Velocity", + "XxlJob": "XXL-JOB Executor" + }, + "title": "打包方式" + }, + "placeholders": { + "input": "请输入", + "select": "请选择" + }, + "quickUsage": { + "step1": "选择目标服务", + "step2": "选择内存马功能,Godzilla、Behinder 或者其他", + "step3": "选择内存马挂载类型,Filter、Listener 或者其他", + "step4": "选择打包方式", + "step5": "点击生成内存马", + "title": "快速使用" + }, + "shellNotWork": { + "step1": "1. 尝试开启调试模式,重新生成内存马并注入,查看控制台或日志", + "step2": "2. 如果出现异常堆栈信息,或未见异常,请截图当前生成界面以及异常日志,并尽可能描述目标环境进行反馈", + "title": "内存马利用失败 ?" + }, + "shellToolConfig": { + "antSwordPass": "连接密码", + "behinder": "冰蝎", + "behinderDefaultEncryptType": "默认", + "behinderEncryptType": "加密类型", + "behinderPass": "连接密码", + "behinderScriptType": "脚本类型", + "command": "命令回显", + "customHeader": "自定义请求头", + "godzilla": "哥斯拉", + "godzillaEncryptor": "加密器", + "godzillaHeader": "请求配置 -> 请求头", + "godzillaPayload": "有效载荷", + "headerName": "请求头键", + "headerValue": "请求头值", + "httpHeader": "请求信息 -> HTTP HEADERS", + "key": "密钥", + "paramName": "请求参数", + "pass": "密码", + "suo5Header": "高级配置 -> 请求头" + }, "success": { "generated": "生成成功" }, - "errors": { - "generationFailed": "生成失败,{{error}}" + "tips": { + "controllerUrlPattern": "ControllerHandler 类型的需要填写具体的 URL Pattern,例如 /hello_controller", + "decompileTip": "反编译还在开发中,因此当前仅能看到 base64 编码格式", + "handlerUrlPattern": "HandlerMethod/HandlerFunction 类型的需要填写具体的 URL Pattern,例如 /hello_handler", + "jreTip": "目标 JRE 版本,一般而言为了最大的兼容性,默认 Java 6 即可,Java 高版本能加载低版本的字节码。", + "jreTip2": "特定情况下,例如 JDK8 才能使用 lambda 表达式,JDK9 以上存在模块限制时才需要选择特定的版本。", + "servletUrlPattern": "Servlet 类型的需要填写具体的 URL Pattern,例如 /hello_servlet", + "shellBytesEmpty": "内存马字节码为空,无法下载, 请先生成内存马", + "shellToolNotSelected": "请先选择内存马工具类型", + "targetServerNotFound": "找不到目标服务 ?", + "targetServerRequest": "请求适配", + "waitingForGeneration": "// 等待填写参数生成中...", + "download-jattach": "下载 Jattach 工具(后期考虑直接封装在 Jar 中)", + "move-to-container": "将 MemShellAgent.jar 和 jattach 移动到容器中(如果测试环境使用容器部署)", + "get-pid": "获取目标 jvm 的进程 pid (使用 jps 或 ps)", + "execute-command": "执行命令进行注入:/path/to/jattach pid load instrument false /path/to/agent.jar", + "try-to-use-shell": "尝试利用内存马" } } diff --git a/web/src/index.css b/web/src/index.css index a4b116f9..476b2f90 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -103,3 +103,39 @@ @apply bg-background text-foreground; } } + +.tabs-list-container { + @apply relative; + + /* Add fade effect for overflow indication */ + &::after { + content: ""; + @apply absolute right-0 top-0 h-full w-8 + bg-gradient-to-l from-background to-transparent + pointer-events-none + md:hidden; + } +} + +/* Custom scrollbar for tabs list */ +.tabs-list { + @apply pb-0.5 overflow-x-auto; + scrollbar-width: thin; +} + +.tabs-list::-webkit-scrollbar { + @apply h-1.5 w-1.5; +} + +.tabs-list::-webkit-scrollbar-track { + @apply bg-transparent; +} + +.tabs-list::-webkit-scrollbar-thumb { + @apply bg-muted-foreground/20 hover:bg-muted-foreground/30 rounded-full; +} + +/* Ensure tab triggers have consistent sizing */ +.tab-trigger { + @apply whitespace-nowrap text-sm; +} diff --git a/web/src/routes/__root.tsx b/web/src/routes/__root.tsx index 4dfcc1ba..63a5ca70 100644 --- a/web/src/routes/__root.tsx +++ b/web/src/routes/__root.tsx @@ -15,12 +15,12 @@ function RootComponent() {
-
+
-

MemShellParty - JavaWeb

+

MemShellParty - JavaWeb

-
+