feat: support staticInitialize

This commit is contained in:
ReaJason
2025-11-20 00:36:25 +08:00
parent 6a6e7e1ec7
commit 65d9a3c672
19 changed files with 109 additions and 24 deletions
@@ -46,4 +46,9 @@ public class InjectorConfig {
* 内存马类字节
*/
private byte[] shellClassBytes;
/**
* 添加静态代码块调用构造方法初始化
*/
private boolean staticInitialize;
}
@@ -3,6 +3,7 @@ package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.asm.InnerClassDiscovery;
import com.reajason.javaweb.buddy.*;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.utils.CommonUtil;
@@ -59,6 +60,10 @@ public class InjectorGenerator {
if (shellConfig.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder);
}
if (injectorConfig.isStaticInitialize() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
builder = StaticBlockSelfConstructorCall.extend(builder);
}
return builder;
}
@@ -32,13 +32,20 @@ public class ValveGenerator {
public static DynamicType.Builder<?> build(DynamicType.Builder<?> builder, AbstractServer shell, String serverVersion) {
String packageName = null;
if (serverVersion.equals("6")) {
if (serverVersion != null) {
switch (serverVersion) {
case "6":
packageName = TONGWEB6_VALVE_PACKAGE;
} else if (serverVersion.equals("7")) {
break;
case "7":
packageName = TONGWEB7_VALVE_PACKAGE;
} else if (serverVersion.equals("8")) {
break;
case "8":
packageName = TONGWEB8_VALVE_PACKAGE;
} else if (shell instanceof Bes) {
break;
}
}
if (shell instanceof Bes) {
packageName = BES_VALVE_PACKAGE;
}
if (StringUtils.isEmpty(packageName)) {
@@ -47,6 +47,12 @@ public class ProbeConfig {
@Builder.Default
private boolean shrink = false;
/**
* 是否添加静态代码块调用构造方法
*/
@Builder.Default
private boolean staticInitialize = false;
public boolean isDebugOff() {
return !debug;
}
@@ -4,6 +4,7 @@ import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.ShellGenerator;
import com.reajason.javaweb.buddy.ByPassJavaModuleInterceptor;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.StaticBlockSelfConstructorCall;
import com.reajason.javaweb.probe.config.ProbeConfig;
import com.reajason.javaweb.probe.config.ProbeContentConfig;
import net.bytebuddy.ByteBuddy;
@@ -27,13 +28,19 @@ public abstract class ByteBuddyShellGenerator<T extends ProbeContentConfig> impl
@Override
public byte[] getBytes() {
DynamicType.Builder<?> builder = build(new ByteBuddy());
if (probeConfig.needByPassJavaModule()) {
builder = ByPassJavaModuleInterceptor.extend(builder);
}
if (probeConfig.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder);
}
if (probeConfig.isStaticInitialize()) {
builder = StaticBlockSelfConstructorCall.extend(builder);
}
try (DynamicType.Unloaded<?> unloaded = builder.make()) {
return ClassBytesShrink.shrink(unloaded.getBytes(), probeConfig.isShrink());
}
@@ -29,6 +29,7 @@ public class ProbeAssertion {
.targetJreVersion(targetJreVersion)
.debug(true)
.shrink(true)
.staticInitialize(true)
.build();
String reqParamName = "payload";
ResponseBodyConfig responseBodyConfig = ResponseBodyConfig.builder()
@@ -57,6 +58,7 @@ public class ProbeAssertion {
.targetJreVersion(targetJreVersion)
.debug(true)
.shrink(true)
.staticInitialize(true)
.build();
String reqParamName = "payload";
ResponseBodyConfig responseBodyConfig = ResponseBodyConfig.builder()
@@ -84,6 +86,7 @@ public class ProbeAssertion {
.probeContent(ProbeContent.Command)
.debug(true)
.shrink(true)
.staticInitialize(true)
.targetJreVersion(targetJreVersion)
.build();
String headerName = "X-Header";
@@ -84,13 +84,20 @@ public class ShellAssertion {
@SneakyThrows
public static void shellInjectIsOk(String url, String server, String shellType, String shellTool, int targetJdkVersion, Packers packer, GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
shellInjectIsOk(url, server, null, shellType, shellTool, targetJdkVersion, packer, appContainer, pythonContainer);
}
@SneakyThrows
public static void shellInjectIsOk(String url, String server, String serverVersion, String shellType, String shellTool,
int targetJdkVersion, Packers packer,
GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
Pair<String, String> urls = getUrls(url, shellType, shellTool, packer);
String shellUrl = urls.getLeft();
String urlPattern = urls.getRight();
ShellToolConfig shellToolConfig = getShellToolConfig(shellType, shellTool, packer);
MemShellResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, shellToolConfig, packer);
MemShellResult generateResult = generate(urlPattern, server, serverVersion, shellType, shellTool, targetJdkVersion, shellToolConfig, packer);
packerResultAndInject(generateResult, url, shellTool, shellType, packer, appContainer);
@@ -107,7 +114,7 @@ public class ShellAssertion {
String jarPath = "/" + shellTool + shellType + packer.name() + ".jar";
appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath);
FileUtils.deleteQuietly(tempJar.toFile());
String pidInContainer = appContainer.execInContainer("bash", "/fetch_pid.sh").getStdout();
String pidInContainer = appContainer.execInContainer("bash", "/fetch_pid.sh").getStdout().trim();
assertDoesNotThrow(() -> Long.parseLong(pidInContainer));
String stdout = appContainer.execInContainer("/jattach", pidInContainer, "load", "instrument", "false", jarPath).getStdout();
log.info("attach result: {}", stdout);
@@ -291,8 +298,8 @@ public class ShellAssertion {
return shellToolConfig;
}
public static MemShellResult generate(String urlPattern, String server, String shellType, String shellTool, int targetJdkVersion, ShellToolConfig shellToolConfig, Packers packer) {
InjectorConfig injectorConfig = new InjectorConfig();
public static MemShellResult generate(String urlPattern, String server, String serverVersin, String shellType, String shellTool, int targetJdkVersion, ShellToolConfig shellToolConfig, Packers packer) {
InjectorConfig injectorConfig = InjectorConfig.builder().staticInitialize(true).build();
if (StringUtils.isNotBlank(urlPattern)) {
injectorConfig.setUrlPattern(urlPattern);
}
@@ -304,6 +311,7 @@ public class ShellAssertion {
ShellConfig shellConfig = ShellConfig.builder()
.server(server)
.serverVersion(serverVersin)
.shellTool(shellTool)
.shellType(shellType)
.targetJreVersion(targetJdkVersion)
BIN
View File
Binary file not shown.
+13 -13
View File
@@ -15,27 +15,27 @@
},
"devDependencies": {
"@biomejs/biome": "2.1.4",
"@react-router/dev": "^7.9.5",
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
"@react-router/dev": "^7.9.6",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^19.2.2",
"@types/react-dom": "^19.2.3",
"@types/react-syntax-highlighter": "^15.5.13",
"@vitejs/plugin-react": "^5.1.0",
"@vitejs/plugin-react": "^5.1.1",
"rimraf": "^6.1.0",
"tailwindcss": "^4.1.16",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"vite": "^7.1.12",
"vite": "^7.2.2",
"vite-bundle-visualizer": "^1.2.1"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@tailwindcss/vite": "^4.1.16",
"@tanstack/react-query": "^5.90.6",
"@tailwindcss/vite": "^4.1.17",
"@tanstack/react-query": "^5.90.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"i18next": "^25.6.0",
"i18next": "^25.6.2",
"lucide-react": "^0.539.0",
"motion": "^12.23.24",
"radix-ui": "^1.4.3",
@@ -44,11 +44,11 @@
"react-dom": "^19.2.0",
"react-hook-form": "^7.66.0",
"react-i18next": "^15.7.4",
"react-router": "^7.9.5",
"react-router-dom": "^7.9.5",
"react-router": "^7.9.6",
"react-router-dom": "^7.9.6",
"react-syntax-highlighter": "^15.6.6",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwind-merge": "^3.4.0",
"tw-animate-css": "^1.4.0",
"yup": "^1.7.1"
},
@@ -383,6 +383,22 @@ export default function MainConfigCard({
</FormItem>
)}
/>
<FormField
control={form.control}
name="staticInitialize"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="staticInitialize">{t("common:staticInitialize")}</Label>
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
@@ -324,6 +324,24 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
</FormItem>
)}
/>
<FormField
control={form.control}
name="staticInitialize"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
</FormItem>
)}
/>
</div>
),
[form.control, t],
+1
View File
@@ -28,6 +28,7 @@
"server": "Server",
"serverVersion": "Server Version",
"shrink": "Shrink",
"staticInitialize": "StaticInitialize",
"toast.generateError": "Generation failed, {{error}}",
"toast.generateSuccess": "Generation successful",
"urlPattern": "URL Pattern",
+1
View File
@@ -28,6 +28,7 @@
"server": "服务类型",
"serverVersion": "服务版本",
"shrink": "缩小字节码",
"staticInitialize": "静态初始化",
"toast.generateError": "生成失败,{{error}}",
"toast.generateSuccess": "生成成功",
"urlPattern": "请求路径",
+1
View File
@@ -77,6 +77,7 @@ export default function MemShellPage() {
injectorClassName: urlParams.injectorClassName ?? "",
packingMethod: urlParams.packingMethod ?? "",
shrink: urlParams.shrink ?? true,
staticInitialize: true,
shellClassBase64: urlParams.shellClassBase64 ?? "",
},
});
+1
View File
@@ -57,6 +57,7 @@ export default function ProbeShellGenerator() {
seconds: 5,
sleepServer: "Tomcat",
shrink: true,
staticInitialize: true
},
});
+1
View File
@@ -67,6 +67,7 @@ export interface InjectorConfig {
injectorClassName?: string;
classInheritance?: string;
urlPattern?: string;
staticInitialize?: boolean;
}
export interface ConfigResponseType {
+1
View File
@@ -16,6 +16,7 @@ export interface ProbeConfig {
debug?: boolean;
byPassJavaModule?: boolean;
shrink?: boolean;
staticInitialize?: boolean;
}
export interface ProbeContentConfig {
+2
View File
@@ -10,6 +10,7 @@ export const memShellFormSchema = yup.object({
targetJdkVersion: yup.string().optional(),
debug: yup.boolean().optional(),
byPassJavaModule: yup.boolean().optional(),
staticInitialize: yup.boolean().optional(),
shellClassName: yup.string().optional(),
shellTool: yup.string().required().min(1),
shellType: yup.string().required().min(1),
@@ -152,6 +153,7 @@ export const probeShellFormSchema = yup.object().shape({
debug: yup.boolean().optional(),
byPassJavaModule: yup.boolean().optional(),
shrink: yup.boolean().optional(),
staticInitialize: yup.boolean().optional()
});
type ProbeValidationResult = ResolverResult<ProbeShellFormSchema>;
+2
View File
@@ -37,6 +37,7 @@ export function transformToPostData(formValue: MemShellFormSchema) {
const injectorConfig: InjectorConfig = {
urlPattern: formValue.urlPattern,
injectorClassName: formValue.injectorClassName,
staticInitialize: formValue.staticInitialize
};
return {
shellConfig,
@@ -54,6 +55,7 @@ export function transformToProbePostData(formValue: ProbeShellFormSchema) {
shrink: formValue.shrink,
debug: formValue.debug,
byPassJavaModule: formValue.byPassJavaModule,
staticInitialize: formValue.staticInitialize
};
const probeContentConfig: ProbeContentConfig = {
host: formValue.host,