From f90f0f0713b2812882a0f782628648d523605d15 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Mon, 20 Jan 2025 00:34:10 +0800 Subject: [PATCH] feat: support i18n [skip ci] --- .../boot/controller/ConfigController.java | 13 +- .../reajason/javaweb/boot/entity/Config.java | 2 +- .../javaweb/memshell/packer/Packer.java | 27 +-- web/bun.lockb | Bin 163060 -> 164700 bytes web/package.json | 10 +- web/src/components/language-switcher.tsx | 23 ++ web/src/components/main-config-card.tsx | 139 ++++++++--- web/src/components/package-config-card.tsx | 30 ++- web/src/components/quick-usage.tsx | 14 +- web/src/components/shell-result.tsx | 217 ++++++++++-------- web/src/components/tips/jre-tip.tsx | 6 +- web/src/components/tips/url-pattern-tip.tsx | 9 +- web/src/i18n/i18n.ts | 32 +++ web/src/i18n/translations.ts | 217 ++++++++++++++++++ web/src/lib/utils.ts | 7 +- web/src/main.tsx | 10 +- web/src/routes/__root.tsx | 2 + web/src/routes/index.tsx | 31 +-- web/src/types/shell.ts | 4 +- 19 files changed, 578 insertions(+), 215 deletions(-) create mode 100644 web/src/components/language-switcher.tsx create mode 100644 web/src/i18n/i18n.ts create mode 100644 web/src/i18n/translations.ts diff --git a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java index 14390270..c0fb508d 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java @@ -10,7 +10,10 @@ import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -48,13 +51,7 @@ public class ConfigController { .collect(Collectors.toList()) ); config.setCore(coreMap); - config.setPackers(Arrays.stream(Packer.INSTANCE.values()) - .collect(Collectors.toMap( - Packer.INSTANCE::getDesc, - Packer.INSTANCE::name, - (e1, e2) -> e1, - LinkedHashMap::new - ))); + config.setPackers(Arrays.stream(Packer.INSTANCE.values()).map(Packer.INSTANCE::name).toList()); return ResponseEntity.ok(config); } } \ No newline at end of file diff --git a/boot/src/main/java/com/reajason/javaweb/boot/entity/Config.java b/boot/src/main/java/com/reajason/javaweb/boot/entity/Config.java index 959dd951..6f91466a 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/entity/Config.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/entity/Config.java @@ -13,5 +13,5 @@ import java.util.Map; public class Config { private List servers; private Map> core; - private Map packers; + private List packers; } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/Packer.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/Packer.java index f8751b76..7a70c29e 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/Packer.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/Packer.java @@ -42,49 +42,46 @@ public interface Packer { /** * Base64 */ - Base64("Base64", new Base64Packer()), + Base64(new Base64Packer()), /** * BCEL */ - BCEL("BCEL", new BCELPacker()), + BCEL(new BCELPacker()), /** * JSP 打包器 */ - JSP("JSP", new JspPacker()), + JSP(new JspPacker()), /** * 脚本引擎打包器 */ - ScriptEngine("脚本引擎", new ScriptEnginePacker()), + ScriptEngine(new ScriptEnginePacker()), /** * 反序列化打包器 */ - Deserialize("反序列化(Only CB4, 1.9.x)", new DeserializePacker()), + Deserialize(new DeserializePacker()), /** * EL */ - EL("EL 表达式", new ELPacker()), + EL(new ELPacker()), - OGNL("OGNL 表达式", new OGNLPacker()), + OGNL(new OGNLPacker()), - SpEL("SpEL 表达式", new SpELPacker()), + SpEL(new SpELPacker()), - Freemarker("Freemarker", new FreemarkerPacker()), + Freemarker(new FreemarkerPacker()), - Velocity("Velocity", new VelocityPacker()), + Velocity(new VelocityPacker()), - AgentJar("AgentJar", new AgentJarPacker()), + AgentJar(new AgentJarPacker()), ; - - private final String desc; private final Packer packer; - INSTANCE(String desc, Packer packer) { - this.desc = desc; + INSTANCE(Packer packer) { this.packer = packer; } } diff --git a/web/bun.lockb b/web/bun.lockb index c7910757afc4438062b3add59e371df29163f278..79dccd119efcf21befc284711810a4f06ebacc3b 100755 GIT binary patch delta 28609 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 MQ(#F9o+!`%FQOq96aWAK delta 27456 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 { + const newLang = i18n.language === "en" ? "zh" : "en"; + i18n.changeLanguage(newLang); + }; + + return ( + + ); +} diff --git a/web/src/components/main-config-card.tsx b/web/src/components/main-config-card.tsx index 59ef91ed..fe691e0a 100644 --- a/web/src/components/main-config-card.tsx +++ b/web/src/components/main-config-card.tsx @@ -14,6 +14,7 @@ import { cn } from "@/lib/utils.ts"; import { ArrowUpRightIcon, ServerIcon } from "lucide-react"; import { useState } from "react"; import { FormProvider, UseFormReturn } from "react-hook-form"; +import { useTranslation } from "react-i18next"; const JDKVersion = [ { name: "Java6", value: "50" }, @@ -34,9 +35,17 @@ export function MainConfigCard({ servers?: string[]; }) { const [shellToolMap, setShellToolMap] = useState<{ [toolName: string]: string[] }>(); - const [shellTools, setShellTools] = useState(["Behinder", "Godzilla", "Command"]); + const [shellTools, setShellTools] = useState([ + "Behinder", + "Godzilla", + "Command", + "AntSword", + "Suo5", + "Neo-reGeorg", + ]); const [shellTypes, setShellTypes] = useState([]); const shellTool = form.watch("shellTool"); + const { t } = useTranslation(); const handleServerChange = (value: string) => { if (mainConfig) { @@ -97,7 +106,7 @@ export function MainConfigCard({ - 生成配置 + {t("configs.main-config")} @@ -107,7 +116,7 @@ export function MainConfigCard({ name="server" render={({ field }) => ( - 目标服务 + {t("mainConfig.server")} - 下拉列表找不到目标服务 ? + {t("tips.targetServerNotFound")}  - 请求适配 + {t("tips.targetServerRequest")} @@ -149,7 +158,7 @@ export function MainConfigCard({ render={({ field }) => ( - + @@ -252,7 +267,7 @@ function ShellTypeFormField({ form, shellTypes }: { form: UseFormReturn )) ) : ( - 请先选择内存马工具类型 + {t("tips.shellToolNotSelected")} )} @@ -264,6 +279,7 @@ function ShellTypeFormField({ form, shellTypes }: { form: UseFormReturn }) { + const { t } = useTranslation(); return ( }) { render={({ field }) => ( - + )} /> @@ -283,6 +299,7 @@ function UrlPatternFormField({ form }: { form: UseFormReturn }) { } function OptionalClassFormField({ form }: { form: UseFormReturn }) { + const { t } = useTranslation(); return ( }) { name="shellClassName" render={({ field }) => ( - 内存马类名(可选) - + + {t("mainConfig.shellClassName")} {t("optional")} + + )} /> @@ -300,8 +319,10 @@ function OptionalClassFormField({ form }: { form: UseFormReturn }) { name="injectorClassName" render={({ field }) => ( - 注入器类名(可选) - + + {t("mainConfig.injectorClassName")} {t("optional")} + + )} /> @@ -310,6 +331,7 @@ function OptionalClassFormField({ form }: { form: UseFormReturn }) { } function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn; shellTypes: Array }) { + const { t } = useTranslation(); return ( @@ -324,8 +346,8 @@ function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn ( - 连接密码 - + {t("shellToolConfig.behinderPass")} + )} /> @@ -335,8 +357,8 @@ function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn ( - 请求头键 - + {t("shellToolConfig.headerName")} + )} /> @@ -345,8 +367,8 @@ function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn ( - 请求头值 - + {t("shellToolConfig.headerValue")} + )} /> @@ -360,6 +382,7 @@ function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn; shellTypes: Array }) { + const { t } = useTranslation(); return ( @@ -375,8 +398,8 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn ( - 密码 - + {t("shellToolConfig.pass")} + )} /> @@ -385,8 +408,8 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn ( - 密钥 - + {t("shellToolConfig.key")} + )} /> @@ -395,8 +418,8 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn ( - 请求头键 - + {t("shellToolConfig.headerName")} + )} /> @@ -405,8 +428,8 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn ( - 请求头值 - + {t("shellToolConfig.headerValue")} + )} /> @@ -420,6 +443,7 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn; shellTypes: Array }) { + const { t } = useTranslation(); return ( @@ -434,11 +458,10 @@ function CommandTabContent({ form, shellTypes }: { form: UseFormReturn ( - 请求参数 + {t("shellToolConfig.paramName")} - + - 填写接收命令的请求参数,例如填 cmd 即 `?cmd=whoami` 来执行命令 )} /> @@ -449,3 +472,45 @@ function CommandTabContent({ form, shellTypes }: { form: UseFormReturn ); } + +function AntSwordTabContent() { + return ( + + + +
+ WIP +
+
+
+
+ ); +} + +function Suo5TabContent() { + return ( + + + +
+ WIP +
+
+
+
+ ); +} + +function NeoreGeorgTabContent() { + return ( + + + +
+ WIP +
+
+
+
+ ); +} diff --git a/web/src/components/package-config-card.tsx b/web/src/components/package-config-card.tsx index a1afdd80..9358f370 100644 --- a/web/src/components/package-config-card.tsx +++ b/web/src/components/package-config-card.tsx @@ -6,6 +6,12 @@ import { PackerConfig } from "@/types/shell.ts"; import { PackageIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { FormProvider, UseFormReturn } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +type Option = { + name: string; + value: string; +}; export function PackageConfigCard({ packerConfig, @@ -14,12 +20,13 @@ export function PackageConfigCard({ packerConfig: PackerConfig | undefined; form: UseFormReturn; }) { - const [options, setOptions] = useState>>([]); + const [options, setOptions] = useState>([]); const shellType = form.watch("shellType"); + const { t } = useTranslation(); useEffect(() => { - const filteredOptions = Object.entries(packerConfig ?? {}).filter(([name, _]) => { + const filteredOptions = (packerConfig ?? []).filter((name) => { if (!shellType || shellType === " ") { return true; } @@ -28,18 +35,25 @@ export function PackageConfigCard({ } return !name.startsWith("Agent"); }); - setOptions(filteredOptions); + setOptions( + filteredOptions.map((name) => { + return { + name: t(`packageConfig.packer.${name}`), + value: name, + }; + }), + ); if (filteredOptions.length > 0) { - form.setValue("packingMethod", filteredOptions[0][0]); + form.setValue("packingMethod", filteredOptions[0]); } - }, [form, packerConfig, shellType]); // Add shellType to the dependency array + }, [form, packerConfig, shellType, t]); return ( - 打包配置 + {t("configs.package-config")} @@ -49,10 +63,10 @@ export function PackageConfigCard({ name="packingMethod" render={({ field }) => ( - 打包方式 + {t("packageConfig.title")} - {options.map(([name, value]) => ( + {options.map(({ name, value }) => ( diff --git a/web/src/components/quick-usage.tsx b/web/src/components/quick-usage.tsx index 8cf06ca9..57691182 100644 --- a/web/src/components/quick-usage.tsx +++ b/web/src/components/quick-usage.tsx @@ -1,18 +1,20 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx"; +import { useTranslation } from "react-i18next"; export function QuickUsage() { + const { t } = useTranslation(); return ( - 快速使用 + {t("quickUsage.title")}
    -
  1. 选择目标服务
  2. -
  3. 选择内存马功能,Godzilla、Behinder 或者其他
  4. -
  5. 选择内存马挂载类型,Filter、Listener 或者其他
  6. -
  7. 选择打包方式
  8. -
  9. 点击生成内存马
  10. +
  11. {t("quickUsage.step1")}
  12. +
  13. {t("quickUsage.step2")}
  14. +
  15. {t("quickUsage.step3")}
  16. +
  17. {t("quickUsage.step4")}
  18. +
  19. {t("quickUsage.step5")}
diff --git a/web/src/components/shell-result.tsx b/web/src/components/shell-result.tsx index aeaa14f3..226e7240 100644 --- a/web/src/components/shell-result.tsx +++ b/web/src/components/shell-result.tsx @@ -26,6 +26,8 @@ import { } from "@/types/shell.ts"; import { CircleHelpIcon, TriangleAlertIcon } from "lucide-react"; import { Fragment } from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; function AgentResult({ packResult, generateResult }: { packResult: string; generateResult?: GenerateResult }) { return ( @@ -77,25 +79,26 @@ function AgentResult({ packResult, generateResult }: { packResult: string; gener } function FeedbackAlert() { + const { t } = useTranslation(); return ( - 内存马利用失败 ? + {t("shellNotWork.title")}
    -
  1. 1. 尝试开启调试模式,重新生成内存马并注入,查看控制台或日志
  2. -
  3. 2. 如果出现异常堆栈信息,或未见异常,请截图当前生成界面以及异常日志,并尽可能描述目标环境进行反馈
  4. +
  5. {t("shellNotWork.step1")}
  6. +
  7. {t("shellNotWork.step2")}
- 取消 + {t("cancel")} window.open( @@ -103,7 +106,7 @@ function FeedbackAlert() { ) } > - 反馈 + {t("feedback")}
@@ -112,36 +115,40 @@ function FeedbackAlert() { } function BasicInfo({ generateResult }: { generateResult?: GenerateResult }) { + const { t } = useTranslation(); return ( - 基础信息 + {t("generateResult.basicInfo")}
- - + +
- + {generateResult?.shellConfig.shellTool === "Behinder" && ( - - + + @@ -150,19 +157,19 @@ function BasicInfo({ generateResult }: { generateResult?: GenerateResult }) { {generateResult?.shellConfig.shellTool === "Godzilla" && ( - - + + @@ -171,19 +178,19 @@ function BasicInfo({ generateResult }: { generateResult?: GenerateResult }) { {generateResult?.shellConfig.shellTool === "Command" && ( )} @@ -199,81 +206,95 @@ export function ShellResult({ }: { packResult: string; packMethod: string; generateResult?: GenerateResult }) { const showCode = packMethod === "JSP"; const isAgent = packMethod.startsWith("Agent"); + const { t } = useTranslation(); return ( - - - 打包结果 - 内存马类 - 注入器类 - - -
- {generateResult && } - {!generateResult && } -
- {!isAgent && ( - - )} - {isAgent && } -
- - - - Warning - 反编译还在开发中,因此当前仅能看到 base64 编码格式 - -
- {generateResult && ( - - )} -
- -
- - - - Warning - 反编译还在开发中,因此当前仅能看到 base64 编码格式 - -
- {generateResult && ( - - )} -
- -
-
+ + {generateResult ? ( + + + {t("generateResult.title1")} + {t("generateResult.title2")} + {t("generateResult.title3")} + + +
+ +
+ {!isAgent && ( + + )} + {isAgent && } +
+ + + + Warning + {t("tips.decompileTip")} + +
+ +
+ +
+ + + + Warning + {t("tips.decompileTip")} + +
+ +
+ +
+
+ ) : ( + + )} +
); } diff --git a/web/src/components/tips/jre-tip.tsx b/web/src/components/tips/jre-tip.tsx index 2fdad5ed..68cb20cf 100644 --- a/web/src/components/tips/jre-tip.tsx +++ b/web/src/components/tips/jre-tip.tsx @@ -1,7 +1,9 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx"; import { InfoIcon } from "lucide-react"; +import { useTranslation } from "react-i18next"; export function JreTip() { + const { t } = useTranslation(); return ( @@ -9,8 +11,8 @@ export function JreTip() { -

目标 JRE 版本,一般而言为了最大的兼容性,默认 Java 6 即可,Java 高版本能加载低版本的字节码。

-

特定情况下,例如 JDK8 才能使用 lambda 表达式,JDK9 以上存在模块限制时才需要选择特定的版本。

+

{t("tips.jreTip")}

+

{t("tips.jreTip2")}

diff --git a/web/src/components/tips/url-pattern-tip.tsx b/web/src/components/tips/url-pattern-tip.tsx index 2f3985a9..b779bd90 100644 --- a/web/src/components/tips/url-pattern-tip.tsx +++ b/web/src/components/tips/url-pattern-tip.tsx @@ -1,7 +1,9 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx"; import { InfoIcon } from "lucide-react"; +import { useTranslation } from "react-i18next"; export function UrlPatternTip() { + const { t } = useTranslation(); return ( @@ -9,10 +11,9 @@ export function UrlPatternTip() { -

当使用 Servlet 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用

-

当使用 SpringMVC ControllerHandler 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用

-

当使用 SpringWebFlux HandlerMethod 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用

-

当使用 SpringWebFlux HandlerFunction 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用

+

{t("tips.servletUrlPattern")}

+

{t("tips.controllerUrlPattern")}

+

{t("tips.handlerUrlPattern")}

diff --git a/web/src/i18n/i18n.ts b/web/src/i18n/i18n.ts new file mode 100644 index 00000000..d7c8cc1d --- /dev/null +++ b/web/src/i18n/i18n.ts @@ -0,0 +1,32 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import { resources } from "./translations"; + +const getStoredLanguage = () => { + const storedLang = localStorage.getItem("i18nextLng"); + if (storedLang && ["en", "zh"].includes(storedLang)) { + return storedLang; + } + const browserLang = navigator.language.split("-")[0]; + return ["en", "zh"].includes(browserLang) ? browserLang : "en"; +}; + +i18n.use(initReactI18next).init({ + resources, + lng: getStoredLanguage(), + fallbackLng: "en", + interpolation: { + escapeValue: false, + }, + detection: { + order: ["localStorage", "navigator"], + }, + saveMissing: true, + load: "languageOnly", +}); + +i18n.on("languageChanged", (lng) => { + localStorage.setItem("i18nextLng", lng); +}); + +export default i18n; diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts new file mode 100644 index 00000000..182d1f4b --- /dev/null +++ b/web/src/i18n/translations.ts @@ -0,0 +1,217 @@ +export const resources = { + en: { + translation: { + 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", + }, + packageConfig: { + title: "Package Method", + packer: { + Base64: "Base64", + BCEL: "BCEL", + JSP: "JSP", + JAR: "JAR", + EL: "EL", + SpEL: "SpEL", + OGNL: "OGNL", + MVEL: "MVEL", + Freemarker: "Freemarker", + Velocity: "Velocity", + Groovy: "Groovy", + AgentJar: "AgentJar", + Deserialize: "Deserialize(Only CB4, 1.9.x)", + ScriptEngine: "ScriptEngine", + }, + }, + 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", + }, + buttons: { + generate: "Generate Shell", + }, + success: { + generated: "Generation successful", + }, + errors: { + generationFailed: "Generation failed, {{error}}", + }, + }, + }, + zh: { + translation: { + 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: "请求参数", + }, + packageConfig: { + title: "打包方式", + packer: { + Base64: "Base64", + BCEL: "BCEL", + JSP: "JSP", + JAR: "JAR", + EL: "EL 表达式", + SpEL: "SpEL 表达式", + OGNL: "OGNL 表达式", + MVEL: "MVEL 表达式", + Freemarker: "Freemarker", + Velocity: "Velocity", + Groovy: "Groovy", + AgentJar: "AgentJar", + Deserialize: "反序列化(仅支持 CB4, 1.9.x)", + ScriptEngine: "脚本引擎", + }, + }, + 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. 如果出现异常堆栈信息,或未见异常,请截图当前生成界面以及异常日志,并尽可能描述目标环境进行反馈", + }, + buttons: { + generate: "生成内存马", + }, + success: { + generated: "生成成功", + }, + errors: { + generationFailed: "生成失败,{{error}}", + }, + }, + }, +}; diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index 6b43cf07..050b799d 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -1,16 +1,11 @@ import { type ClassValue, clsx } from "clsx"; -import { toast } from "sonner"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export function downloadBytes(base64String?: string, className?: string, jarName?: string) { - if (!base64String) { - toast.warning("字节码为空,无法下载, 请先生成内存马"); - return; - } +export function downloadBytes(base64String: string, className?: string, jarName?: string) { const byteCharacters = atob(base64String); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { diff --git a/web/src/main.tsx b/web/src/main.tsx index 969f6d6e..e41a48fc 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -6,6 +6,8 @@ import { TailwindIndicator } from "@/components/tailwind-indicator.tsx"; import { Toaster } from "@/components/ui/sonner.tsx"; import { env } from "@/config.ts"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { I18nextProvider } from "react-i18next"; +import i18n from "./i18n/i18n"; const queryClient = new QueryClient(); @@ -26,9 +28,11 @@ if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( - - - {env.MODE !== "production" && } + + + + {env.MODE !== "production" && } + , ); } diff --git a/web/src/routes/__root.tsx b/web/src/routes/__root.tsx index 75dfad69..9f7abae0 100644 --- a/web/src/routes/__root.tsx +++ b/web/src/routes/__root.tsx @@ -1,3 +1,4 @@ +import { LanguageSwitcher } from "@/components/language-switcher"; import { ModeToggle } from "@/components/mode-toggle.tsx"; import { ThemeProvider } from "@/components/theme-provider.tsx"; import { Button } from "@/components/ui/button.tsx"; @@ -17,6 +18,7 @@ function RootComponent() {

MemShellParty - JavaWeb

+
diff --git a/web/src/types/shell.ts b/web/src/types/shell.ts index 2146e9ed..7047ec41 100644 --- a/web/src/types/shell.ts +++ b/web/src/types/shell.ts @@ -58,9 +58,7 @@ export interface MainConfig { }; } -export interface PackerConfig { - [packerName: string]: string; -} +export type PackerConfig = Array; export interface GenerateResponse { packResult: string;