← 返回文章归档
FIELD NOTES / Reverse11 MIN READ

[pwn] protobuf学习与逆向

介绍

一个轻量的跨平台数据结构协议。

说人话就是json-pro,或者说zip-pro。zip不也是不论在手机、电脑、macos都能解压吗,,

当然运转起来和zip不同,毕竟这个是为了程序间收发信息而发明的,而zip只是为了打包数据,只是一个不太恰当的类比。

快速安装

1
sudo apt install protobuf-compiler libprotobuf-dev

protobuf支持c++、java、python、go等多种语言,但是不支持c语言。

https://github.com/protobuf-c/protobuf-c

不过这里有一个社区成员自发维护的proto-c实现,要支持c语言额外安装这个即可

(注意make之前先checkout到最新的release tag)。

基本语法

定义消息类型

1
2
3
4
5
6
7
syntax = "proto2";

message SearchRequest {
optional string query = 1;
optional int32 page_number = 2;
optional int32 results_per_page = 3;
}
  • 文件的第一行指定您正在使用 protobuf 语言规范的 2023 版。

    • edition(或 proto2/proto3 的 syntax)必须是文件的第一个非空、非注释行。
    • 如果未指定 edition 或 syntax,协议缓冲区编译器将假定您正在使用 proto2。
  • SearchRequest 消息定义指定了三个字段(名称/值对),每种你想包含在此类消息中的数据都对应一个字段。每个字段都有一个名称和一个类型。

定义字段

定义字段类型

e7fa3d288c2ad8c00cb4004f60bdcec0\_MD5

这里有一些类型,总之就是整数,浮点数,布尔和字符串。
除了前面那个例子里定义的标量类型,也可以是枚举或者复合类型。
在proto3里面这个有些许扩展,具体可以上官网查看。

分配字段编号

1
2
3
4
5
6
7
syntax = "proto2";

message SearchRequest {
optional string query = 1;
optional int32 page_number = 2;
optional int32 results_per_page = 3;
}

依然是这个例子,其中的=1和=2就是字段编号。每个字段编号必须唯一,范围是从0到2^29

指定字段基数

翻译成基数很奇怪,感觉应该类似字段属性的意思。

  • 单一 (Singular):
    • optional:(推荐)optional 字段处于两种可能状态之一
    • 你可以用接口查询发送方有没有显式设置值,比如说接收到了默认值,但你不清楚发送方是没设置,还是设置了,刚好就是这个值。
    • 建议使用 optional 而不是隐式字段,以实现与 protobuf 版本和 proto2 的最大兼容性。
    • 隐式:(不推荐)隐式字段没有明确的基数标签,其行为如下
    • 如果字段是消息类型,则其行为与 optional 字段完全相同。
    • 如果字段不是消息,它有两种状态
    • 字段设置为非默认(非零)值,该值已显式设置或从数据线解析。它将被序列化到数据线。字段设置为默认(零)值。它不会被序列化到数据线。事实上,您无法确定默认(零)值是已设置还是从数据线解析,还是未提供。有关此主题的更多信息,请参阅字段存在。
    • (翻译够怪的,总之参考上面option的意思,这里和option相反)
  • repeated:此字段类型在一个格式良好的消息中可以重复零次或多次。重复值的顺序将被保留。
  • map:这是一个键值对字段类型。有关此字段类型的更多信息,请参阅映射。

其实就是普通的optional,数组repeated,哈希表map
还有一个require被废弃了,官网上写不建议使用

字段默认值

  • 对于字符串,默认值是空字符串。
  • 对于字节,默认值是空字节。
  • 对于布尔值,默认值是 false。
  • 对于数值类型,默认值是零。
  • 对于消息字段,该字段未设置。其确切值取决于语言。
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为 0。

当然你可以自己指定默认值。

1
2
3
4
5
6
7
syntax = "proto2";

message SearchRequest {
optional string query = 1;
optional int32 page_number = 2 [default = 10];
optional int32 results_per_page = 3;
}

如果发送方未指定 result_per_page,接收方将观察到以下状态

  • result_per_page 字段不存在。也就是说,has_result_per_page()(存在器方法)方法将返回 false。
  • result_per_page 的值(从“getter”返回)为 10。

这里有点特别,虽然有值,但是查询这个值存不存在返回false,直接获取值又能获取到,感觉容易出小错误。

枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}

message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
Corpus corpus = 4;
}

文档建议枚举类型的第一个值必须为0且不包含任何语义,因为兼容性问题。

像 SearchRequest.corpus 这样的枚举字段的默认值可以像这样显式覆盖

1
Corpus corpus = 4 [default = CORPUS_UNIVERSAL];

枚举也可以有别名,就是一个值有两个名字,像下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}

enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
ENAA_RUNNING = 1; // Uncommenting this line will cause a warning message.
ENAA_FINISHED = 2;
}

不过如果这么用的话,protocol buffer 编译器会生成一条警告消息(可设置allow_alias 为 true来关闭)。以及虽然所有别名值在序列化时都有效,但在反序列化时只使用第一个值。

删除枚举条目

1
2
3
4
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved FOO, BAR;
}

要用reserved。
比如说某项目里,字段3是AAA,一次更新以后字段3被删掉,但是没有reversed。过了一段时间另一个人要添加一个新字段,发现3空着的,就把3用给BBB。但是有其他模块没有及时更新到这个信息,结果就是本来接收BBB的接口会收到AAA,造成严重的问题。

用reversed把被删除的flag标记,这样如果后来的维护者重用了字段,编译就会报错。

proto序列化的不规范性

意思是对于同样的数据,proto不能保证其被序列化为相同的结果。

比如说哈希密钥验证等流程,不应该直接使用proto序列化出来的数据,因为它们可能随着版本更新发生变化。正确的姿势是反序列化提取其中的信息,然后再拿去用。

使用教程

参考了下这个
https://github.com/protobuf-c/protobuf-c/wiki/Examples

这里是c的使用教材,如果需要python或者C++可以区看官方文档

1
2
3
4
5
6
7
syntax = "proto2";

message msg {
optional uint32 id = 1;
optional string name = 2;
optional bytes key = 3;
}

写一个这个,然后输入protoc-c --c_out=. amessage.proto

接下来在自己的代码中引用生成的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "message.pb-c.h"
#include <stdio.h>
#include <stdlib.h>

int main() {
Msg msg = MSG__INIT;
msg.has_id = 1;
msg.id = 12345;
msg.name = "Hello, Protobuf-C!";
msg.has_key = 1;
uint8_t key_data[] = {0xDE, 0xAD, 0xBE, 0xEF};
msg.key.data = key_data;
msg.key.len = sizeof(key_data);

// Serialize the message
size_t packed_size = msg__get_packed_size(&msg);
uint8_t *buffer = malloc(packed_size);
if (!buffer) {
fprintf(stderr, "Failed to allocate memory for buffer\n");
return EXIT_FAILURE;
}
msg__pack(&msg, buffer);

// Print the serialized data
printf("Serialized Msg (hex): ");
for (size_t i = 0; i < packed_size; i++) {
printf("%02X ", buffer[i]);
}
printf("\n");

// Unpack the message
Msg *unpacked_msg = msg__unpack(NULL, packed_size, buffer);
if (!unpacked_msg) {
fprintf(stderr, "Failed to unpack message\n");
free(buffer);
return EXIT_FAILURE;
}

// Print the unpacked message
printf("Unpacked Msg:\n");
printf(" ID: %u\n", unpacked_msg->id);
printf(" Name: %s\n", unpacked_msg->name);
printf(" Key (hex): ");
for (size_t i = 0; i < unpacked_msg->key.len; i++) {
printf("%02X ", unpacked_msg->key.data[i]);
}
printf("\n");

msg__free_unpacked(unpacked_msg, NULL);

free(buffer);
return EXIT_SUCCESS;
}

pack和unpack,然后unpack可以传allocator,可以传自定义堆管理器,传null就是用malloc。

编译指令

1
gcc ./main.c ./message.pb-c.c -o ./main -lprotobuf-c

如果你用syntax = "proto3"会发现生成不了,因为神秘的“咕勾”在3.12版本把optional扬了,引发了设区的强烈反对,后来又加了回来。

而proto-c是社区人员维护的,最新的release版本还没对齐这个需求。

我们可以看看生成的文件里面有什么,其实也很简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/* Generated by the protocol buffer compiler.  DO NOT EDIT! */
/* Generated from: message.proto */

/* Do not generate deprecated warnings for self */
#ifndef PROTOBUF_C__NO_DEPRECATED
#define PROTOBUF_C__NO_DEPRECATED
#endif

#include "message.pb-c.h"
void msg__init
(Msg *message)
{
static const Msg init_value = MSG__INIT;
*message = init_value;
}
size_t msg__get_packed_size
(const Msg *message)
{
assert(message->base.descriptor == &msg__descriptor);
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
}
size_t msg__pack
(const Msg *message,
uint8_t *out)
{
assert(message->base.descriptor == &msg__descriptor);
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
}
size_t msg__pack_to_buffer
(const Msg *message,
ProtobufCBuffer *buffer)
{
assert(message->base.descriptor == &msg__descriptor);
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
}
Msg *
msg__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data)
{
return (Msg *)
protobuf_c_message_unpack (&msg__descriptor,
allocator, len, data);
}
void msg__free_unpacked
(Msg *message,
ProtobufCAllocator *allocator)
{
if(!message)
return;
assert(message->base.descriptor == &msg__descriptor);
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
}
static const ProtobufCFieldDescriptor msg__field_descriptors[3] =
{
{
"id",
1,
PROTOBUF_C_LABEL_OPTIONAL,
PROTOBUF_C_TYPE_UINT32,
offsetof(Msg, has_id),
offsetof(Msg, id),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"name",
2,
PROTOBUF_C_LABEL_OPTIONAL,
PROTOBUF_C_TYPE_STRING,
0, /* quantifier_offset */
offsetof(Msg, name),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"key",
3,
PROTOBUF_C_LABEL_OPTIONAL,
PROTOBUF_C_TYPE_BYTES,
offsetof(Msg, has_key),
offsetof(Msg, key),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
};
static const unsigned msg__field_indices_by_name[] = {
0, /* field[0] = id */
2, /* field[2] = key */
1, /* field[1] = name */
};
static const ProtobufCIntRange msg__number_ranges[1 + 1] =
{
{ 1, 0 },
{ 0, 3 }
};
const ProtobufCMessageDescriptor msg__descriptor =
{
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
"msg",
"Msg",
"Msg",
"",
sizeof(Msg),
3,
msg__field_descriptors,
msg__field_indices_by_name,
1, msg__number_ranges,
(ProtobufCMessageInit) msg__init,
NULL,NULL,NULL /* reserved[123] */
};

程序输出结果:

98e8de5de4b86f3d0078042f42968d4f\_MD5

rpc协议支持

1
2
3
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}

rpc(Remote Produce Call)是一种通信协议,让你可以调用远程服务器上的函数,就如同调用本地的函数一样。protobuf支持这个,但是你需要自己提供rpc框架。

后面可以跟gRPC结合,详细放到新的文档写吧。

逆向

定位结构体,逆向通信协议,然后根据结构体把.proto文件重新写出来,然后用proto生成.py接口就行了。

这里有魔数可以定位。

1aa7578a02f006650c2fb20506019b5b\_MD5

然后就是把类型一个一个套上去,根据宏的名字把.proto反推就行了ovo

叽里咕噜说什么呢跟我结构体板子说去吧

右键parse statements全部导入,然后想要恢复哪个结构体自己填

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
typedef enum {
PROTOBUF_C_FIELD_FLAG_PACKED = (1 << 0),
PROTOBUF_C_FIELD_FLAG_DEPRECATED = (1 << 1),
PROTOBUF_C_FIELD_FLAG_ONEOF = (1 << 2),
} ProtobufCFieldFlag;
typedef enum {
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_LABEL_OPTIONAL,
PROTOBUF_C_LABEL_REPEATED,
PROTOBUF_C_LABEL_NONE,
} ProtobufCLabel;
typedef enum {
PROTOBUF_C_TYPE_INT32,
PROTOBUF_C_TYPE_SINT32,
PROTOBUF_C_TYPE_SFIXED32,
PROTOBUF_C_TYPE_INT64,
PROTOBUF_C_TYPE_SINT64,
PROTOBUF_C_TYPE_SFIXED64,
PROTOBUF_C_TYPE_UINT32,
PROTOBUF_C_TYPE_FIXED32,
PROTOBUF_C_TYPE_UINT64,
PROTOBUF_C_TYPE_FIXED64,
PROTOBUF_C_TYPE_FLOAT,
PROTOBUF_C_TYPE_DOUBLE,
PROTOBUF_C_TYPE_BOOL,
PROTOBUF_C_TYPE_ENUM,
PROTOBUF_C_TYPE_STRING,
PROTOBUF_C_TYPE_BYTES,
PROTOBUF_C_TYPE_MESSAGE,
} ProtobufCType;
typedef enum {
PROTOBUF_C_WIRE_TYPE_VARINT = 0,
PROTOBUF_C_WIRE_TYPE_64BIT = 1,
PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED = 2,
PROTOBUF_C_WIRE_TYPE_32BIT = 5,
} ProtobufCWireType;
struct ProtobufCAllocator;
struct ProtobufCBinaryData;
struct ProtobufCBuffer;
struct ProtobufCBufferSimple;
struct ProtobufCEnumDescriptor;
struct ProtobufCEnumValue;
struct ProtobufCEnumValueIndex;
struct ProtobufCFieldDescriptor;
struct ProtobufCIntRange;
struct ProtobufCMessage;
struct ProtobufCMessageDescriptor;
struct ProtobufCMessageUnknownField;
struct ProtobufCMethodDescriptor;
struct ProtobufCService;
struct ProtobufCServiceDescriptor;
typedef struct ProtobufCAllocator ProtobufCAllocator;
typedef struct ProtobufCBinaryData ProtobufCBinaryData;
typedef struct ProtobufCBuffer ProtobufCBuffer;
typedef struct ProtobufCBufferSimple ProtobufCBufferSimple;
typedef struct ProtobufCEnumDescriptor ProtobufCEnumDescriptor;
typedef struct ProtobufCEnumValue ProtobufCEnumValue;
typedef struct ProtobufCEnumValueIndex ProtobufCEnumValueIndex;
typedef struct ProtobufCFieldDescriptor ProtobufCFieldDescriptor;
typedef struct ProtobufCIntRange ProtobufCIntRange;
typedef struct ProtobufCMessage ProtobufCMessage;
typedef struct ProtobufCMessageDescriptor ProtobufCMessageDescriptor;
typedef struct ProtobufCMessageUnknownField ProtobufCMessageUnknownField;
typedef struct ProtobufCMethodDescriptor ProtobufCMethodDescriptor;
typedef struct ProtobufCService ProtobufCService;
typedef struct ProtobufCServiceDescriptor ProtobufCServiceDescriptor;
typedef int protobuf_c_boolean;
typedef void (*ProtobufCClosure)(const ProtobufCMessage *, void *closure_data);
typedef void (*ProtobufCMessageInit)(ProtobufCMessage *);
typedef void (*ProtobufCServiceDestroy)(ProtobufCService *);
struct ProtobufCAllocator {
void *(*alloc)(void *allocator_data, size_t size);
void (*free)(void *allocator_data, void *pointer);
void *allocator_data;
};
struct ProtobufCBinaryData {
size_t len;
uint8_t *data;
};
struct ProtobufCBuffer {
void (*append)(ProtobufCBuffer *buffer,
size_t len,
const uint8_t *data);
};
struct ProtobufCBufferSimple {
ProtobufCBuffer base;
size_t alloced;
size_t len;
uint8_t *data;
protobuf_c_boolean must_free_data;
ProtobufCAllocator *allocator;
};
struct ProtobufCEnumDescriptor {
uint32_t magic;
const char *name;
const char *short_name;
const char *c_name;
const char *package_name;
unsigned n_values;
const ProtobufCEnumValue *values;
unsigned n_value_names;
const ProtobufCEnumValueIndex *values_by_name;
unsigned n_value_ranges;
const ProtobufCIntRange *value_ranges;
void *reserved1;
void *reserved2;
void *reserved3;
void *reserved4;
};
struct ProtobufCEnumValue {
const char *name;
const char *c_name;
int value;
};
struct ProtobufCEnumValueIndex {
const char *name;
unsigned index;
};
struct ProtobufCFieldDescriptor {
const char *name;
uint32_t id;
ProtobufCLabel label;
ProtobufCType type;
unsigned quantifier_offset;
unsigned offset;
const void *descriptor;
const void *default_value;
uint32_t flags;
unsigned reserved_flags;
void *reserved2;
void *reserved3;
};
struct ProtobufCIntRange {
int start_value;
unsigned orig_index;
};
struct ProtobufCMessage {
const ProtobufCMessageDescriptor *descriptor;
unsigned n_unknown_fields;
ProtobufCMessageUnknownField *unknown_fields;
};
struct ProtobufCMessageDescriptor {
uint32_t magic;
const char *name;
const char *short_name;
const char *c_name;
const char *package_name;
size_t sizeof_message;
unsigned n_fields;
const ProtobufCFieldDescriptor *fields;
const unsigned *fields_sorted_by_name;
unsigned n_field_ranges;
const ProtobufCIntRange *field_ranges;
ProtobufCMessageInit message_init;
void *reserved1;
void *reserved2;
void *reserved3;
};
struct ProtobufCMessageUnknownField {
uint32_t tag;
ProtobufCWireType wire_type;
size_t len;
uint8_t *data;
};
struct ProtobufCMethodDescriptor {
const char *name;
const ProtobufCMessageDescriptor *input;
const ProtobufCMessageDescriptor *output;
};
struct ProtobufCService {
const ProtobufCServiceDescriptor *descriptor;
void (*invoke)(ProtobufCService *service,
unsigned method_index,
const ProtobufCMessage *input,
ProtobufCClosure closure,
void *closure_data);
void (*destroy)(ProtobufCService *service);
};
struct ProtobufCServiceDescriptor {
uint32_t magic;
const char *name;
const char *short_name;
const char *c_name;
const char *package;
unsigned n_methods;
const ProtobufCMethodDescriptor *methods;
const unsigned *method_indices_by_name;
};

叽里咕噜说什么呢跟我自动化脚本说去吧

有pbtk,实测识别不太出来

也有auto-re,实测太久没人维护(6years ago)

// todo

FROM THE ARCHIVE

这篇文章迁移自我的博客园,保留最初的记录。

阅读原文 ↗
/ END✳∷ — 00
SEARCH / 00

搜索文章

输入关键词,搜索全部文章。

标题 / 标签 / 全文ESC 关闭