feat: support i18n [skip ci]

This commit is contained in:
ReaJason
2025-01-20 00:34:10 +08:00
parent a64238eea0
commit f90f0f0713
19 changed files with 578 additions and 215 deletions
@@ -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);
}
}
@@ -13,5 +13,5 @@ import java.util.Map;
public class Config {
private List<String> servers;
private Map<String, Map<?, ?>> core;
private Map<String, String> packers;
private List<String> packers;
}
@@ -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;
}
}
BIN
View File
Binary file not shown.
+6 -4
View File
@@ -13,7 +13,7 @@
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tanstack/router-plugin": "^1.97.1",
"@tanstack/router-plugin": "^1.97.3",
"@types/node": "^22.10.7",
"@types/react": "^19.0.7",
"@types/react-dom": "^19.0.3",
@@ -39,17 +39,19 @@
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.6",
"@tanstack/react-query": "^5.64.1",
"@tanstack/react-router": "^1.97.1",
"@tanstack/router-devtools": "^1.97.1",
"@tanstack/react-query": "^5.64.2",
"@tanstack/react-router": "^1.97.3",
"@tanstack/router-devtools": "^1.97.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"i18next": "^24.2.1",
"lucide-react": "^0.473.0",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"react-i18next": "^15.4.0",
"react-syntax-highlighter": "^15.6.1",
"sonner": "^1.7.2",
"tailwind-merge": "^2.6.0",
+23
View File
@@ -0,0 +1,23 @@
import { LanguagesIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "./ui/button";
export function LanguageSwitcher() {
const { i18n } = useTranslation();
const toggleLanguage = () => {
const newLang = i18n.language === "en" ? "zh" : "en";
i18n.changeLanguage(newLang);
};
return (
<Button
variant="ghost"
size="icon"
onClick={toggleLanguage}
title={i18n.language === "en" ? "Switch to Chinese" : "切换到英文"}
>
<LanguagesIcon className="h-5 w-5" />
</Button>
);
}
+102 -37
View File
@@ -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<string[]>(["Behinder", "Godzilla", "Command"]);
const [shellTools, setShellTools] = useState<string[]>([
"Behinder",
"Godzilla",
"Command",
"AntSword",
"Suo5",
"Neo-reGeorg",
]);
const [shellTypes, setShellTypes] = useState<string[]>([]);
const shellTool = form.watch("shellTool");
const { t } = useTranslation();
const handleServerChange = (value: string) => {
if (mainConfig) {
@@ -97,7 +106,7 @@ export function MainConfigCard({
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<ServerIcon className="h-5" />
<span></span>
<span>{t("configs.main-config")}</span>
</CardTitle>
</CardHeader>
<CardContent>
@@ -107,7 +116,7 @@ export function MainConfigCard({
name="server"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<FormLabel>{t("mainConfig.server")}</FormLabel>
<Select
onValueChange={(v) => {
field.onChange(v);
@@ -117,7 +126,7 @@ export function MainConfigCard({
>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
@@ -129,14 +138,14 @@ export function MainConfigCard({
</SelectContent>
</Select>
<FormDescription className="flex items-center">
{t("tips.targetServerNotFound")}&nbsp;
<a
href="https://github.com/ReaJason/MemShellParty/issues/new?template=%E8%AF%B7%E6%B1%82%E9%80%82%E9%85%8D.md"
target="_blank"
rel="noreferrer"
className="flex items-center underline"
>
{t("tips.targetServerRequest")}
<ArrowUpRightIcon className="h-4" />
</a>
</FormDescription>
@@ -149,7 +158,7 @@ export function MainConfigCard({
render={({ field }) => (
<FormItem className="flex flex-col mt-1">
<Label className="flex items-center">
JRE () <JreTip />
{t("mainConfig.jre")} {t("optional")} <JreTip />
</Label>
<Select
onValueChange={(v) => {
@@ -164,7 +173,7 @@ export function MainConfigCard({
>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
@@ -188,7 +197,7 @@ export function MainConfigCard({
<FormControl>
<Switch id="debug" checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<FormLabel htmlFor="debug"></FormLabel>
<FormLabel htmlFor="debug">{t("mainConfig.debug")}</FormLabel>
</FormItem>
)}
/>
@@ -200,7 +209,7 @@ export function MainConfigCard({
<FormControl>
<Switch id="bypassJavaModule" checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<Label htmlFor="bypassJavaModule"> Java </Label>
<Label htmlFor="bypassJavaModule">{t("mainConfig.bypassJavaModule")}</Label>
</FormItem>
)}
/>
@@ -214,7 +223,9 @@ export function MainConfigCard({
}}
className="w-full"
>
<TabsList className={cn("grid w-full", `grid-cols-${shellTools.length}`)}>
<TabsList
className={cn("grid w-full", shellTools.length > 3 ? "grid-flow-col" : `grid-cols-${shellTools.length}`)}
>
{shellTools.map((shellTool) => (
<TabsTrigger key={shellTool} value={shellTool}>
{shellTool}
@@ -224,12 +235,16 @@ export function MainConfigCard({
<BehinderTabContent form={form} shellTypes={shellTypes} />
<GodzillaTabContent form={form} shellTypes={shellTypes} />
<CommandTabContent form={form} shellTypes={shellTypes} />
<AntSwordTabContent />
<Suo5TabContent />
<NeoreGeorgTabContent />
</Tabs>
</FormProvider>
);
}
function ShellTypeFormField({ form, shellTypes }: { form: UseFormReturn<FormSchema>; shellTypes: Array<string> }) {
const { t } = useTranslation();
return (
<FormProvider {...form}>
<FormField
@@ -237,11 +252,11 @@ function ShellTypeFormField({ form, shellTypes }: { form: UseFormReturn<FormSche
name="shellType"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<FormLabel>{t("mainConfig.shellMountType")}</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent key={shellTypes.join(",")}>
@@ -252,7 +267,7 @@ function ShellTypeFormField({ form, shellTypes }: { form: UseFormReturn<FormSche
</SelectItem>
))
) : (
<SelectItem value=" "></SelectItem>
<SelectItem value=" ">{t("tips.shellToolNotSelected")}</SelectItem>
)}
</SelectContent>
</Select>
@@ -264,6 +279,7 @@ function ShellTypeFormField({ form, shellTypes }: { form: UseFormReturn<FormSche
}
function UrlPatternFormField({ form }: { form: UseFormReturn<FormSchema> }) {
const { t } = useTranslation();
return (
<FormProvider {...form}>
<FormField
@@ -272,9 +288,9 @@ function UrlPatternFormField({ form }: { form: UseFormReturn<FormSchema> }) {
render={({ field }) => (
<FormItem className="flex flex-col mt-1">
<Label className="flex items-center">
<UrlPatternTip />
{t("mainConfig.urlPattern")} <UrlPatternTip />
</Label>
<Input {...field} placeholder="请输入" className="h-8" />
<Input {...field} placeholder={t("placeholders.input")} className="h-8" />
</FormItem>
)}
/>
@@ -283,6 +299,7 @@ function UrlPatternFormField({ form }: { form: UseFormReturn<FormSchema> }) {
}
function OptionalClassFormField({ form }: { form: UseFormReturn<FormSchema> }) {
const { t } = useTranslation();
return (
<FormProvider {...form}>
<FormField
@@ -290,8 +307,10 @@ function OptionalClassFormField({ form }: { form: UseFormReturn<FormSchema> }) {
name="shellClassName"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input id="shellClassName" {...field} placeholder="请输入" className="h-8" />
<FormLabel>
{t("mainConfig.shellClassName")} {t("optional")}
</FormLabel>
<Input id="shellClassName" {...field} placeholder={t("placeholders.input")} className="h-8" />
</FormItem>
)}
/>
@@ -300,8 +319,10 @@ function OptionalClassFormField({ form }: { form: UseFormReturn<FormSchema> }) {
name="injectorClassName"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input id="injectorClassName" {...field} placeholder="请输入" className="h-8" />
<FormLabel>
{t("mainConfig.injectorClassName")} {t("optional")}
</FormLabel>
<Input id="injectorClassName" {...field} placeholder={t("placeholders.input")} className="h-8" />
</FormItem>
)}
/>
@@ -310,6 +331,7 @@ function OptionalClassFormField({ form }: { form: UseFormReturn<FormSchema> }) {
}
function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn<FormSchema>; shellTypes: Array<string> }) {
const { t } = useTranslation();
return (
<FormProvider {...form}>
<TabsContent value="Behinder">
@@ -324,8 +346,8 @@ function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
name="behinderPass"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input {...field} placeholder="Pass" className="h-8" />
<FormLabel>{t("shellToolConfig.behinderPass")}</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.pass")} className="h-8" />
</FormItem>
)}
/>
@@ -335,8 +357,8 @@ function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
name="behinderHeaderName"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input {...field} placeholder="Header Name" className="h-8" />
<FormLabel>{t("shellToolConfig.headerName")}</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerName")} className="h-8" />
</FormItem>
)}
/>
@@ -345,8 +367,8 @@ function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
name="behinderHeaderValue"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input {...field} placeholder="Header Value" className="h-8" />
<FormLabel>{t("shellToolConfig.headerValue")}</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerValue")} className="h-8" />
</FormItem>
)}
/>
@@ -360,6 +382,7 @@ function BehinderTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
}
function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn<FormSchema>; shellTypes: Array<string> }) {
const { t } = useTranslation();
return (
<FormProvider {...form}>
<TabsContent value="Godzilla">
@@ -375,8 +398,8 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
name="godzillaPass"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input {...field} placeholder="Pass" className="h-8" />
<FormLabel>{t("shellToolConfig.pass")}</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.pass")} className="h-8" />
</FormItem>
)}
/>
@@ -385,8 +408,8 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
name="godzillaKey"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input {...field} placeholder="Key" className="h-8" />
<FormLabel>{t("shellToolConfig.key")}</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.key")} className="h-8" />
</FormItem>
)}
/>
@@ -395,8 +418,8 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
name="godzillaHeaderName"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input {...field} placeholder="Header Name" className="h-8" />
<FormLabel>{t("shellToolConfig.headerName")}</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerName")} className="h-8" />
</FormItem>
)}
/>
@@ -405,8 +428,8 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
name="godzillaHeaderValue"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Input {...field} placeholder="Header Value" className="h-8" />
<FormLabel>{t("shellToolConfig.headerValue")}</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerValue")} className="h-8" />
</FormItem>
)}
/>
@@ -420,6 +443,7 @@ function GodzillaTabContent({ form, shellTypes }: { form: UseFormReturn<FormSche
}
function CommandTabContent({ form, shellTypes }: { form: UseFormReturn<FormSchema>; shellTypes: Array<string> }) {
const { t } = useTranslation();
return (
<FormProvider {...form}>
<TabsContent value="Command">
@@ -434,11 +458,10 @@ function CommandTabContent({ form, shellTypes }: { form: UseFormReturn<FormSchem
name="commandParamName"
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<FormLabel>{t("shellToolConfig.paramName")}</FormLabel>
<FormControl>
<Input {...field} placeholder="请输入" className="h-8" />
<Input {...field} placeholder={t("shellToolConfig.paramName")} className="h-8" />
</FormControl>
<FormDescription> cmd `?cmd=whoami` </FormDescription>
</FormItem>
)}
/>
@@ -449,3 +472,45 @@ function CommandTabContent({ form, shellTypes }: { form: UseFormReturn<FormSchem
</FormProvider>
);
}
function AntSwordTabContent() {
return (
<TabsContent value="AntSword">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="flex items-center justify-center">
<span className="text-gray-500">WIP</span>
</div>
</CardContent>
</Card>
</TabsContent>
);
}
function Suo5TabContent() {
return (
<TabsContent value="Suo5">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="flex items-center justify-center">
<span className="text-gray-500">WIP</span>
</div>
</CardContent>
</Card>
</TabsContent>
);
}
function NeoreGeorgTabContent() {
return (
<TabsContent value="Neo-reGeorg">
<Card>
<CardContent className="space-y-2 mt-4">
<div className="flex items-center justify-center">
<span className="text-gray-500">WIP</span>
</div>
</CardContent>
</Card>
</TabsContent>
);
}
+22 -8
View File
@@ -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<FormSchema>;
}) {
const [options, setOptions] = useState<Array<Array<string>>>([]);
const [options, setOptions] = useState<Array<Option>>([]);
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 (
<Card className="w-full">
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<PackageIcon className="h-5" />
<span></span>
<span>{t("configs.package-config")}</span>
</CardTitle>
</CardHeader>
<CardContent>
@@ -49,10 +63,10 @@ export function PackageConfigCard({
name="packingMethod"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel></FormLabel>
<FormLabel>{t("packageConfig.title")}</FormLabel>
<FormControl>
<RadioGroup onValueChange={field.onChange} value={field.value} className="grid grid-cols-3">
{options.map(([name, value]) => (
{options.map(({ name, value }) => (
<FormItem key={value} className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value={value} id={value} />
+8 -6
View File
@@ -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 (
<Card>
<CardHeader>
<CardTitle>使</CardTitle>
<CardTitle>{t("quickUsage.title")}</CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm">
<li></li>
<li>GodzillaBehinder </li>
<li>FilterListener </li>
<li></li>
<li></li>
<li>{t("quickUsage.step1")}</li>
<li>{t("quickUsage.step2")}</li>
<li>{t("quickUsage.step3")}</li>
<li>{t("quickUsage.step4")}</li>
<li>{t("quickUsage.step5")}</li>
</ol>
</CardContent>
</Card>
+119 -98
View File
@@ -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 (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline" type="button">
<CircleHelpIcon />
<CircleHelpIcon /> {t("shellNotWork.title")}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogTitle>{t("shellNotWork.title")}</AlertDialogTitle>
<AlertDialogDescription>
<ol>
<li>1. </li>
<li>2. </li>
<li>{t("shellNotWork.step1")}</li>
<li>{t("shellNotWork.step2")}</li>
</ol>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={() =>
window.open(
@@ -103,7 +106,7 @@ function FeedbackAlert() {
)
}
>
{t("feedback")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
@@ -112,36 +115,40 @@ function FeedbackAlert() {
}
function BasicInfo({ generateResult }: { generateResult?: GenerateResult }) {
const { t } = useTranslation();
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
{t("generateResult.basicInfo")}
<FeedbackAlert />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2">
<CopyableField label="目标服务" text={generateResult?.shellConfig.server} />
<CopyableField label="内存马功能" text={generateResult?.shellConfig.shellTool} />
<CopyableField label={t("mainConfig.server")} text={generateResult?.shellConfig.server} />
<CopyableField label={t("mainConfig.shellTool")} text={generateResult?.shellConfig.shellTool} />
</div>
<CopyableField label="内存马挂载类型" text={generateResult?.shellConfig.shellType} />
<CopyableField label={t("mainConfig.shellMountType")} text={generateResult?.shellConfig.shellType} />
<CopyableField
label="请求路径"
label={t("mainConfig.urlPattern")}
text={generateResult?.injectorConfig.urlPattern}
value={generateResult?.injectorConfig.urlPattern}
/>
{generateResult?.shellConfig.shellTool === "Behinder" && (
<Fragment>
<CopyableField label="脚本类型" text="jsp" />
<CopyableField label="加密类型" text="默认" />
<CopyableField label={t("shellToolConfig.behinderScriptType")} text="jsp" />
<CopyableField
label="连接密码"
label={t("shellToolConfig.behinderEncryptType")}
text={t("shellToolConfig.behinderDefaultEncryptType")}
/>
<CopyableField
label={t("shellToolConfig.behinderPass")}
text={(generateResult?.shellToolConfig as BehinderShellToolConfig).pass}
value={(generateResult?.shellToolConfig as BehinderShellToolConfig).pass}
/>
<CopyableField
label="自定义请求头"
label={t("shellToolConfig.customHeader")}
text={`${(generateResult?.shellToolConfig as BehinderShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as BehinderShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as BehinderShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as BehinderShellToolConfig).headerValue}`}
/>
@@ -150,19 +157,19 @@ function BasicInfo({ generateResult }: { generateResult?: GenerateResult }) {
{generateResult?.shellConfig.shellTool === "Godzilla" && (
<Fragment>
<CopyableField
label="密码"
label={t("shellToolConfig.pass")}
text={(generateResult?.shellToolConfig as GodzillaShellToolConfig).pass}
value={(generateResult?.shellToolConfig as GodzillaShellToolConfig).pass}
/>
<CopyableField
label="密钥"
label={t("shellToolConfig.key")}
text={(generateResult?.shellToolConfig as GodzillaShellToolConfig).key}
value={(generateResult?.shellToolConfig as GodzillaShellToolConfig).key}
/>
<CopyableField label="有效载荷" text="JavaDynamicPayload" />
<CopyableField label="加密器" text="JAVA_AES_BASE64" />
<CopyableField label={t("shellToolConfig.godzillaPayload")} text="JavaDynamicPayload" />
<CopyableField label={t("shellToolConfig.godzillaEncryptor")} text="JAVA_AES_BASE64" />
<CopyableField
label="请求配置 -> 请求头"
label={t("shellToolConfig.godzillaHeader")}
text={`${(generateResult?.shellToolConfig as GodzillaShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as GodzillaShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as GodzillaShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as GodzillaShellToolConfig).headerValue}`}
/>
@@ -171,19 +178,19 @@ function BasicInfo({ generateResult }: { generateResult?: GenerateResult }) {
{generateResult?.shellConfig.shellTool === "Command" && (
<Fragment>
<CopyableField
label="接收命令请求参数"
label={t("shellToolConfig.paramName")}
text={(generateResult?.shellToolConfig as CommandShellToolConfig).paramName}
value={(generateResult?.shellToolConfig as CommandShellToolConfig).paramName}
/>
</Fragment>
)}
<CopyableField
label="注入器类名"
label={t("mainConfig.injectorClassName")}
value={generateResult?.injectorClassName}
text={`${generateResult?.injectorClassName} (${generateResult?.injectorSize} bytes)`}
/>
<CopyableField
label="内存马类名"
label={t("mainConfig.shellClassName")}
value={generateResult?.shellClassName}
text={`${generateResult?.shellClassName} (${generateResult?.shellSize} bytes)`}
/>
@@ -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 (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="packResult"></TabsTrigger>
<TabsTrigger value="shell"></TabsTrigger>
<TabsTrigger value="injector"></TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="my-4">
<div className="mb-4">
{generateResult && <BasicInfo generateResult={generateResult} />}
{!generateResult && <QuickUsage />}
</div>
{!isAgent && (
<CodeViewer
code={packResult}
wrapLongLines={!showCode}
showLineNumbers={showCode}
language={showCode ? "java" : "text"}
height={400}
/>
)}
{isAgent && <AgentResult packResult={packResult} generateResult={generateResult} />}
</TabsContent>
<TabsContent value="shell" className="mt-4">
<Alert>
<TriangleAlertIcon className="h-4 w-4" />
<AlertTitle>Warning</AlertTitle>
<AlertDescription> base64 </AlertDescription>
</Alert>
<div className="gap-4 my-2 flex items-center justify-end">
{generateResult && (
<Button
size="sm"
variant="outline"
className="w-28"
type="button"
onClick={() => downloadBytes(generateResult?.shellBytesBase64Str, generateResult?.shellClassName)}
>
Class
</Button>
)}
</div>
<CodeViewer
showLineNumbers={false}
wrapLongLines={true}
code={generateResult?.shellBytesBase64Str ?? ""}
language="text"
/>
</TabsContent>
<TabsContent value="injector" className="mt-4">
<Alert>
<TriangleAlertIcon className="h-4 w-4" />
<AlertTitle>Warning</AlertTitle>
<AlertDescription> base64 </AlertDescription>
</Alert>
<div className="gap-4 my-2 flex items-center justify-end">
{generateResult && (
<Button
size="sm"
className="w-28"
variant="outline"
type="button"
onClick={() => downloadBytes(generateResult?.injectorBytesBase64Str, generateResult?.injectorClassName)}
>
Class
</Button>
)}
</div>
<CodeViewer
showLineNumbers={false}
wrapLongLines={true}
code={generateResult?.injectorBytesBase64Str ?? ""}
language="text"
/>
</TabsContent>
</Tabs>
<Fragment>
{generateResult ? (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="packResult">{t("generateResult.title1")}</TabsTrigger>
<TabsTrigger value="shell">{t("generateResult.title2")}</TabsTrigger>
<TabsTrigger value="injector">{t("generateResult.title3")}</TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="my-4">
<div className="mb-4">
<BasicInfo generateResult={generateResult} />
</div>
{!isAgent && (
<CodeViewer
code={packResult}
wrapLongLines={!showCode}
showLineNumbers={showCode}
language={showCode ? "java" : "text"}
height={400}
/>
)}
{isAgent && <AgentResult packResult={packResult} generateResult={generateResult} />}
</TabsContent>
<TabsContent value="shell" className="mt-4">
<Alert>
<TriangleAlertIcon className="h-4 w-4" />
<AlertTitle>Warning</AlertTitle>
<AlertDescription>{t("tips.decompileTip")}</AlertDescription>
</Alert>
<div className="gap-4 my-2 flex items-center justify-end">
<Button
size="sm"
variant="outline"
className="w-28"
type="button"
onClick={() => {
if (!generateResult?.shellBytesBase64Str) {
toast.warning(t("tips.shellBytesEmpty"));
return;
}
downloadBytes(generateResult?.shellBytesBase64Str, generateResult?.shellClassName);
}}
>
{t("download")} Class
</Button>
</div>
<CodeViewer
showLineNumbers={false}
wrapLongLines={true}
code={generateResult?.shellBytesBase64Str ?? ""}
language="text"
/>
</TabsContent>
<TabsContent value="injector" className="mt-4">
<Alert>
<TriangleAlertIcon className="h-4 w-4" />
<AlertTitle>Warning</AlertTitle>
<AlertDescription>{t("tips.decompileTip")}</AlertDescription>
</Alert>
<div className="gap-4 my-2 flex items-center justify-end">
<Button
size="sm"
className="w-28"
variant="outline"
type="button"
onClick={() => {
if (!generateResult?.injectorBytesBase64Str) {
toast.warning(t("tips.shellBytesEmpty"));
return;
}
downloadBytes(generateResult?.injectorBytesBase64Str, generateResult?.injectorClassName);
}}
>
{t("download")} Class
</Button>
</div>
<CodeViewer
showLineNumbers={false}
wrapLongLines={true}
code={generateResult?.injectorBytesBase64Str ?? ""}
language="text"
/>
</TabsContent>
</Tabs>
) : (
<QuickUsage />
)}
</Fragment>
);
}
+4 -2
View File
@@ -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 (
<TooltipProvider>
<Tooltip>
@@ -9,8 +11,8 @@ export function JreTip() {
<InfoIcon className="cursor-pointer h-4" />
</TooltipTrigger>
<TooltipContent>
<p> JRE Java 6 Java </p>
<p> JDK8 使 lambda JDK9 </p>
<p>{t("tips.jreTip")}</p>
<p>{t("tips.jreTip2")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
+5 -4
View File
@@ -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 (
<TooltipProvider>
<Tooltip>
@@ -9,10 +11,9 @@ export function UrlPatternTip() {
<InfoIcon className="cursor-pointer h-4" />
</TooltipTrigger>
<TooltipContent>
<p>使 Servlet urlPattern使 /*使</p>
<p>使 SpringMVC ControllerHandler urlPattern使 /*使</p>
<p>使 SpringWebFlux HandlerMethod urlPattern使 /*使</p>
<p>使 SpringWebFlux HandlerFunction urlPattern使 /*使</p>
<p>{t("tips.servletUrlPattern")}</p>
<p>{t("tips.controllerUrlPattern")}</p>
<p>{t("tips.handlerUrlPattern")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
+32
View File
@@ -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;
+217
View File
@@ -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}}",
},
},
},
};
+1 -6
View File
@@ -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++) {
+7 -3
View File
@@ -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(
<QueryClientProvider client={queryClient}>
<Toaster />
<RouterProvider router={router} />
{env.MODE !== "production" && <TailwindIndicator />}
<I18nextProvider i18n={i18n}>
<Toaster />
<RouterProvider router={router} />
{env.MODE !== "production" && <TailwindIndicator />}
</I18nextProvider>
</QueryClientProvider>,
);
}
+2
View File
@@ -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() {
<h2 className="text-lg font-semibold">MemShellParty - JavaWeb</h2>
</div>
<div className="flex gap-1 mr-4">
<LanguageSwitcher />
<Button
variant="ghost"
size="icon"
+11 -20
View File
@@ -13,6 +13,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { LoaderCircle, WandSparklesIcon } from "lucide-react";
import { useState, useTransition } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
export const Route = createFileRoute("/")({
@@ -27,6 +28,7 @@ function IndexComponent() {
return await response.json();
},
});
const { t } = useTranslation();
const form = useForm<FormSchema>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -51,19 +53,19 @@ function IndexComponent() {
},
});
const [packResult, setPackResult] = useState<string>("// 等待填写参数生成中");
const [packResult, setPackResult] = useState<string>();
const [generateResult, setGenerateResult] = useState<GenerateResult>();
const [packMethod, setPackMethod] = useState<string>("");
const [isActionPending, startTransition] = useTransition();
function customValidation(values: FormSchema) {
if (values.shellType.endsWith("Servlet") && (values.urlPattern === "/*" || !values.urlPattern)) {
toast.warning("Servlet 类型的需要填写具体的 URL Pattern,例如 /hello_servlet");
toast.warning(t("tips.servletUrlPattern"));
return false;
}
if (values.shellType.endsWith("ControllerHandler") && (values.urlPattern === "/*" || !values.urlPattern)) {
toast.warning("ControllerHandler 类型的需要填写具体的 URL Pattern,例如 /hello_controller");
toast.warning(t("tips.controllerUrlPattern"));
return false;
}
@@ -71,20 +73,9 @@ function IndexComponent() {
(values.shellType === "HandlerMethod" || values.shellType === "HandlerFunction") &&
(values.urlPattern === "/*" || !values.urlPattern)
) {
toast.warning("HandlerMethod/HandlerFunction 类型的需要填写具体的 URL Pattern,例如 /hello_handler");
toast.warning(t("tips.handlerUrlPattern"));
return false;
}
if (values.shellType.startsWith("Agent") && values.packingMethod !== "AgentJar") {
toast.warning("Agent 注入方式当前仅支持 AgentJar 打包方式");
return false;
}
if (!values.shellType.startsWith("Agent") && values.packingMethod === "AgentJar") {
toast.warning("Agent 注入方式当前仅支持 Tomcat,只有 Agent 注入方式才可使用 AgentJar 打包方式");
return false;
}
return true;
}
@@ -103,20 +94,20 @@ function IndexComponent() {
},
body: JSON.stringify(postData),
});
await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 200));
if (response.ok) {
const json: GenerateResponse = await response.json();
setPackResult(json.packResult);
setGenerateResult(json.generateResult);
setPackMethod(values.packingMethod);
toast.success("生成成功");
toast.success(t("success.generated"));
} else {
const json: APIErrorResponse = await response.json();
toast.error(`生成失败,${json.error}`);
toast.error(t("errors.generationFailed", { error: json.error }));
}
} catch (err) {
const error = err as Error;
toast.error(`生成失败,${error.message}`);
toast.error(t("errors.generationFailed", { error: error.message }));
}
});
}
@@ -129,7 +120,7 @@ function IndexComponent() {
<PackageConfigCard packerConfig={data?.packers} form={form} />
<Button className="w-full" type="submit" disabled={isActionPending}>
{isActionPending ? <LoaderCircle className="animate-spin" /> : <WandSparklesIcon />}
{t("buttons.generate")}
</Button>
</div>
<div className="w-full xl:w-1/2 space-y-4">
+1 -3
View File
@@ -58,9 +58,7 @@ export interface MainConfig {
};
}
export interface PackerConfig {
[packerName: string]: string;
}
export type PackerConfig = Array<string>;
export interface GenerateResponse {
packResult: string;