原创声明:转载本文请标注出处和作者,望尊重作者劳动成果!感谢!
前言:文章内容源于本月团队内部技术分享会的议题之一。
一、gRPC介绍
RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC协议的主要目的是做到不同服务间调用方法像同一服务间调用本地方法一样。grpc是一个高性能、开源、通用的RPC框架,由Google推出,基于HTTP2协议标准设计开发,默认采用Protocol Buffers数据序列化协议,支持多种开发语言。简单来说proto3是grpc的协议定义,使用的文件后缀为.proto。在客户端调用服务端提供的远程接口前,双方必须进行一些约定,比如接口的方法签名,请求和响应的数据结构等,这个过程称为服务定义。服务定义需要特定的接口定义语言(IDL)来完成,gRPC中默认使用protocol buffers。通过protobug定义服务后,不同语言通过不同的语言转换为代码,进而创建客户端和服务端。
二、gRPC的简单测试
测试demo:https://github.com/Snailll/gRPCDemo,使用maven加载环境,详情查看 github靶场的pom.xml,服务端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) {}
}
加载maven后,使用mvn命令生成java代码
mvn protobuf:compile
mvn protobuf:compile-custom
生成的代码
编写server端和client端的代码:
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();
}
}
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();
}
}
还需定义服务端的实现方法:
启动Server端再启动Client端,调用了getuser()方法,实现了远程调用Server的方法
三、内存马的实现
内存马的分析如下,定义了webshell.proto文件,和webshell的实现方法,webshell.proto
syntax = "proto3";
package protocol;
option go_package = "protocol";
option java_multiple_files = true;
option java_package = "com.demo.shell.protocol";
message Webshell {
string pwd = 1;
string cmd = 2;
}
service WebShellService {
rpc exec (Webshell) returns (Webshell) {}
}
可以看到,该方法定义了一个"x"为密码,Runtime.getRuntime().exec(cmdStrings)的执行方法
由于测试是研究如何防御,所以实现方式是在Server端new一个WebshellServiceImpl的对象
原因是gRPC目前能添加的方式很少,而且grpc需要提前协商proto
运行测试:打开server端后,内存马已经实现注入,运行TestShell是命令执行的代码
分析server端,addService()是注入的关键,这里把UserServiceImpl()当成参数添加到service中,在debug时,可以看到service正是生成的UserServiceGrpc的类,而addService()的方法是调用了内部类InternalHandlerRegistry,那内存马注入的思路就是:
生成一个新的service,调用InternalHandlerRegistry类替换service,从而达到注入的效果
查看添加的方法,此方法用来替换service
可以看到,这个方法是利用反射取得当前类的属性,获得services的数组,之后再新建一个webshell的类
同时,新建一个数组,把恶意的service放在数组里,再进行替换,在此处完成了替换,至此,内存马的注入完成
内存马查杀
https://github.com/sf197/MemoryShellHunter/releases/tag/v1.2
打包项目的NsServer类之后会生成shell.jar文件,使用方法:
Java -javaagent:./findMemShell-1.2 -jar shell.jar
已经打包好放在 gRPCDemooutartifactsshell_jar 目录下,可直接测试运行。
分析:查看当前类是否是继承了io.grpc.BindableService,如果是则存储起来,判断是否有危险函数,这里定义了一个
处置的方式是重新加载字节码
END
公众号回复“20230415”,获取原格式PDF文章和相关工具或源码。
原文始发于微信公众号(Fighter安全团队):gRPC内存马分析与查杀