protobuf生成Go代码插件gogo/protobuf

VSole2022-08-01 11:01:00

从 JSON 开始

谈到序列化,大家最先想到的可能是 JSON 或者 XML,这两种序列化协议都是基于文本的编码方式进行数据传输。类似的还有 YAML 等。

JSON 拥有许多优点,使之成为最广泛使用的序列化协议之一。如 JSON 协议简单,人眼可读,序列化后十分简洁且解析速度快。此外,JSON 具备 JavaScript 的先天性支持,被广泛应用于 Web Browser 的应用场景中,并且是 Ajax 的事实标准协议。

JSON 的适用场景比较多,典型应用场景包括:

  • 公司外部之间传输数据量相对较小,实时性要求相对低的服务
  • 基于 Web browser 的 Ajax 请求
  • 接口经常发生变化,并对可调式性要求较高的场景,例如移动 App 与服务端的通信

然而,由于 JSON 本身的设计的一些特点,在一些场景下使用 JSON 仍然不是最优解。如:

  • 需要标准的 IDL ,增强参与各方业务约束的场景。由于 JSON 协议往往只能使用文档的方式来进行约定,这可能会给调试带来一些不便与不明确
  • 对性能和简洁性有较高要求的场景。JSON 在一些语言中的序列化和反序列化需要采用反射机制,所以在性能要求特别高场景下可能不是最优解
  • 对于大数据量服务或持久化场景。JSON 进行序列化的额外空间开销比较大,这也意味着较大的内存和磁盘开销

对于以上场景, 使用一些基于 IDL ,存储方案为二进制存储的序列化方案则更为合适, 如 ProtoBuf、Thrift、avro等。

IDL: 参与通讯的各方需要对通讯的内容需要做相关的约定。为了建立一个与语言和平台无关的约定,这个约定需要采用与具体开发语言、平台无关的语言来进行描述。这种语言被称为接口描述语言(IDL),采用IDL撰写的协议约定称之为IDL文件。

什么是 Protobuf

ProtoBuf 是 Protocol Buffers 的简称 ,是 Google 公司开源的一种语言无关、平台无关、可扩展的序列化结构数据的方案,它可用于(数据)通信协议、数据存储等。

ProtoBuf 是上述场景中比较适用的序列化方案之一。ProtoBuf 非常灵活,高效,我们可以通过定义 IDL (在这里是proto)文件,然后使用生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

上文提到,同类型的序列化方案还有 Thrift 和 Avro。其中 Thrift 并不仅仅是序列化协议,他被嵌入到 Thrift 框架中,这导致其很难和其他传输层协议共同使用。Avro 由于没有成熟的 JS 实现,不适合 Web 环境, 也 导致其使用场景也比较有限。

目前 gRPC 默认的序列化方式是 ProtoBuf。

ProtoBuf 包含序列化格式的定义、各种语言的库以及一个 IDL 编译器。正常情况下需要我们定义 proto 文件,然后使用IDL 编译器编译成需要的语言。

一个简单的 proto 例子 

syntax = "proto3";                // proto 版本,建议使用 proto3
option go_package = "main/proto"; // 包名声明符
message SearchRequestParam {      // message 类型
  enum Type {                     // 枚举类型
    PC = 0;
    Mobile = 1;
  }
  string query_text = 1;          // 字符串类型 | 后面的「1」为数字标识符,在消息定义中需要唯一
  int32 limit = 3;                // 整型
  Type type = 4;                  // 枚举类型
}
message SearchResultPage {
  repeated string result = 1;     // 「repeated」表示字段可以重复任意多次(包括0次)
  int32 num_results = 2;
}
// test.proto

代码中的只是一些比较普通的字段定义,还有一些复杂的一些字段定义,如OneofMapReserved等可以参考官方文档。

生成 Go 代码

在 .proto 文件中定义好需要处理的结构化数据后,可以通过 protoc 工具,将 .proto 文件转换为 C、C++、Golang、Java、Python 等多种语言的代码。我们这里尝试一下生成 Golang 语言代码。

首先需要安装 protoc 工具

# 下载安装包 (Mac)
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.15.6/protoc-3.15.6-osx-x86_64.zip
# 解压到 /usr/local 目录下
$ unzip protoc-3.15.6-osx-x86_64.zip -d protoc-3.15.6-osx-x86_64
$ mv protoc-3.5.0-osx-x86_64/bin/protoc /usr/local/bin/protoc
# 执行如下表示成功:
$ protoc --version
libprotoc 3.15.6

然后安装一个官方的生成 Golang 代码的插件 protoc-gen-go

$ go get -u github.com/golang/protobuf/protoc-gen-go

现在在 proto文件所在目录下执行以下命令以生成go文件

$ protoc --go_out=. test.proto

protoc 命令还可以使用-I参数指定搜索 import 的 proto 的文件夹。其他参数详情可以参考官方文档。

我们可以在目录下看到一个 test.pb.go 文件。其中主要结构体如下:

type SearchRequestParam struct {
 state         protoimpl.MessageState
 sizeCache     protoimpl.SizeCache
 unknownFields protoimpl.UnknownFields
 QueryText string                  `protobuf:"bytes,1,opt,name=query_text..."`    
 Limit     int32                   `protobuf:"varint,3,opt,name=limit,proto3"...."`                           
 Type      SearchRequestParam_Type `protobuf:"varint,4,opt,name=type,proto3..."`
}
type SearchResultPage struct {
 state         protoimpl.MessageState
 sizeCache     protoimpl.SizeCache
 unknownFields protoimpl.UnknownFields
 Result     []string `protobuf:"bytes,1,rep,name=result,proto3...."`
 NumResults int32    `protobuf:"varint,2,opt,name=num_results,json=numResults,proto3..."`

接下来,就可以在项目代码中直接使用了。

gogo/protobuf 是什么

在上文中,我们安装了一个「生成 Golang 代码的插件 protoc-gen-go」,这个插件其实是 golang 官方提供的 一个Protobuf api 实现。而我们的主角gogo/protobuf是基于 golang/protobuf 的一个增强版实现。

gogo 库基于官方库开发,增加了很多的功能,包括:

  • 快速的序列化和反序列化
  • 更规范的Go数据结构
  • goprotobuf 兼容
  • 可选择的产生一些辅助方法,减少使用中的代码输入
  • 可以选择产生测试代码和 benchmark 代码
  • 其它序列化格式

目前很多知名的项目都在使用该库,如 etcd、k8s、tidb、docker swarmkit 等。

gogo/protobuf 如何使用

https://github.com/gogo/protobuf 根目录下我们可以看到有很多文件夹,其中「protoc-gen」为前缀的为生成代码的插件,其他「proto」、「protobuf」、「gogoproto」等为库文件。

gogo 库目前有三种生成代码的方式

  • gofast: 速度优先,但此方式不支持其它 gogoprotobuf 的扩展选项。
$ go get github.com/gogo/protobuf/protoc-gen-gofast
$ protoc --gofast_out=. myproto.proto
  • gogofastgogofastergogoslick: 更快的速度、会生成更多的代码。
$ go get github.com/gogo/protobuf/proto
$ go get github.com/gogo/protobuf/{binary} //protoc-gen-gogofast、protoc-gen-gogofaster 、protoc-gen-gogoslick 
$ go get github.com/gogo/protobuf/gogoproto
$ protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/gogo/protobuf/protobuf --{binary}_out=. myproto.proto // 这里的{binary}不包含「protoc-gen」前缀
  • gogofast类似gofast,但是会引入 gogoprotobuf 库。
  • gogofaster类似gogofast,但是不会产生XXX_unrecognized类的指针字段,可以减少垃圾回收时间。
  • gogoslick类似gogofaster,但是会增加一些额外的stringgostringequal method等。
  • protoc-gen-gogo: 最快的速度,最多的可定制化
$ go get github.com/gogo/protobuf/proto
$ go get github.com/gogo/protobuf/jsonpb
$ go get github.com/gogo/protobuf/protoc-gen-gogo
$ go get github.com/gogo/protobuf/gogoproto
  • 可以通过扩展选项高度定制序列化。

gogo/protobuf 提供了非常多的扩展选项,以便在产生代码的时候进行更多的控制。上文提到的扩展选项这里有一个全面的介绍:extensions,扩展选项里主要包含一些生成快速序列化反序列化代码的可选项、生成更规范的Golang 数据结构的可选项、goprotobuf 兼容的可选项,一些产生辅助方法的可选项、产生测试代码和benchmark 的可选项,还可以增加 jsontag 等。

有同学对以上多个生成方式的序列化性能做了一些压测,在一般需求下,性能差距并不是很大,protoc-gen-gofast方式基本可以满足大多数场景。

最后,生成的 go 语言代码在项目中使用就非常简单了,一般只需要使用proto.Marshal,proto.Unmarshal 方法就可以了,下面是一个例子:

package main
import (
 "fmt"
 "log"
 zaproto "git.xxxxx.com/data/za-proto/proto"
 "github.com/gogo/protobuf/proto"
)
func main() {
 req := &zaproto.SearchRequestParam{
  QueryText: "xxxxxx",
  Limit:     10,
  Type:      zaproto.SearchRequestParam_PC,
 }
 data, err := proto.Marshal(req)
 if err != nil {
  log.Fatal("Marshal err : err")
 }
 // send data
 fmt.Println(string(data))
 var respData []byte
 var result = zaproto.SearchResultPage{}
 if err = proto.Unmarshal(respData, &result); err == nil {
  fmt.Println(result)
 } else {
  log.Fatal("Unmarshal err : err")
 }
}
序列化protobuf
本作品采用《CC 协议》,转载必须注明作者和本文链接
protobuf协议逆向解析
2021-09-23 18:43:30
在app抓包的过程中,我们会发现有些app的数据也是存在加密的,抓到的数据是一堆乱码。无法来解析数据。
0x0概述 近年来,老式勒索病毒依旧活跃,而新型勒索病毒花样百出,深信服安全云脑就捕获到一款具有“地方特色”的勒索病毒,其加密后缀为.beijing。及lock_XXX文件; 勒索提示文档: 遍历目录下所有文件; 打开待加密文件; 重命名为*.beijing; 使用AES算法加密; 文件末尾写入二次加密后的AES密钥 完成加密后自删除。
JSON 拥有许多优点,使之成为最广泛使用的序列化协议之一。此外,JSON 具备 JavaScript 的先天性支持,被广泛应用于 Web Browser 的应用场景中,并且是 Ajax 的事实标准协议。这种语言被称为接口描述语言,采用IDL撰写的协议约定称之为IDL文件。
近日有研究人员发现,MMRat新型安卓银行恶意软件利用protobuf 数据序列化这种罕见的通信方法入侵设备窃取数据。
Dubbo Kryo & FST RCE
2022-11-25 15:31:47
影响版本Dubbo 2.7.0 to 2.7.8Dubbo 2.6.0 to 2.6.9Dubbo all 2.5.x versions 环境复现 安装zookeeper和dubbo-samples,用idea打开dubbo-samples-api,然后修改其中的pom.xml如下: 注意,dubbo-common必须 ≤2.7.3版本。在Dubbo<=2.7.3中fastjson的版本≤1.2.46 ,这也是我们这个洞的利用点,不过这里复现使用的更高版本所以需要添加依赖, com.alibabagroupId> fastjsonartifactId> 1.2.46version>dependency>. 案例漏洞分析 FTS反序列化FTS反序列化发生在RPC协议反序列化
随着企业云原生化进程的推进,系统架构也开始从传统架构向微服务架构转型。服务之间的API调用也从RESTful接口转向gRPC、GraphQL等新型的更高效的协议接口。新技术带来便利的同时,也为内存马攻击技术带来了新的攻击面。
GraphFuzz是一个用于构建结构感知、库API模糊器的实验性框架。
最近在学习Android APP客户端漏洞挖掘过程中,对Android APP端漏洞挖掘做了一个基本的梳理总结本节主要是在介绍Android APP漏洞挖掘过程中,使用常见的Android漏洞挖掘工具的安装和使用办法,帮助Android漏洞挖掘人员提供便利。本文里面一部分的介绍采摘与网络博客,大家可以点击对应的网址进行查看。
Java安全中Groovy组件从反序列化到命令注入及绕过和在白盒中的排查方法
最近两个月我一直在做拒绝服务漏洞相关的时间,并收获了Spring和Weblogic的两个CVE但DoS漏洞终归是鸡肋洞,并没有太大的意义,比如之前有人说我只会水垃圾洞而已,所以在以后可能打算做其他方向早上和pyn3rd师傅聊天
VSole
网络安全专家