Protobuf协议

文章发布时间:

最后更新时间:

文章总字数:
1.2k

预计阅读时间:
5 分钟

前言

一种数据传输协议 2023年的国赛初赛考了一题基于这个堆题 需要使用其才能完成菜单交互 其中几个用来pack和unpack的函数代码审计起来很恶心 如果不知道这个协议的 看到以后会无从下手 挺恶心人的 刚好我有出题的需要 就记录一下相关的

环境配置

首先需要安装protoc 便于以后我们利用protoc文件生成py文件

1
2
3
4
sudo apt install python3-pip git openjdk-11-jre libqt5x11extras5 python3-pyqt5.qtwebengine python3-pyqt5
sudo pip3 install protobuf pyqt5 pyqtwebengine requests websocket-client
sudo apt-get install autoconf automake libtool
sudo apt-get install autoconf automake libtool curl make g++ unzip
1
2
3
4
5
6
7
git clone https://gitclone.com/github.com/protocolbuffers/protobuf.git 
cd protobuf
./autogen.sh
./configure
make
sudo make install
sudo ldconfig

如果有出题需求的话 还需要安装protobuf-c 才能编译

1
2
3
git clone https://gitclone.com/github.com/protobuf-c/protobuf-c.git
cd protobuf-c/
./autogen.sh && ./configure && make && make install

编译

先从利用proto文件生成c语言编译所需要的pb-c.h文件说起

诸如这样写好一个proto文件后

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

package test;

message Student {
optional string name = 1; //这个1无特殊含义 是表示顺序 如果还需要新增一个变量就=2
}
optional和request是代表是否强制要求该数据 数据类型这个参照其他教程 我复制粘贴供哪天我本地查询

img

1
protoc-c --c_out=./ ./pwn.proto

使用上面的指令会生成两个文件

image-20230531140134732

一个是在gcc编译时需要使用 一个是在源码编写中需要作为头文件导入

同时需要注意 proto文件中变量名不区分大小写

这个时候差不多就可以编写c语言源码了 直接上一个demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<stdlib.h>

#include "pwn.pb-c.h"
size_t my_pack(char *out){
Test__Student a;
test__student__init(&a);
a.name = "hello";
return test__student__pack(&a,out);
}
void my_unpack(size_t len,const uint8_t *data){
Test__Student *tmp = test__student__unpack(NULL,len,data);
test__student__free_unpacked(tmp, NULL);
}
int main(){
char buf[0x20];
unsigned int len = my_pack(buf);
Test__Student *tmp = test__student__unpack(NULL,len,buf);
printf("%s\n",tmp->name);
}

一个完整的流程是 初始化包 将其值打包 最后解包得到参数 最后还可以销毁包

这里的buf不一定要栈上 也可以malloc一块空间 总体应该还是很好理解的 这个时候我们就可以尝试编译了

还需要带上刚才生成的pb-c文件

1
gcc pwn.pb-c.c test.c -o pwn -lprotobuf-c

此时 直接运行会出现如下的报错

image-20230531224026544

1
sudo ldconfig

该指令可以搜寻共享动态库 创建出动态装入程序所需的连接和缓存文件

image-20230531224452012

可以看到成功输出hello 剩下的就没什么好说了 如果要想恶心一点就删符号表 静态编译 代码审计量巨大

解题

拿今年CISCN初赛的一题来举例 我也是通过这题了解到protobuf 抛开交互这道题是一个堆 我估计他的源码是用malloc开空间 所以在后面堆的时候 堆结构会改变 就很烦 这里就不提了 可以去看我的wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
ssize_t v3; // rsi
__int64 *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 = sub_192D(0LL, v3, &unk_A060);
if ( !v4 )
((&sub_1328 + 1))(0LL, v3);
sub_155D(v4[3], v4[4], v4[5], v4[6], v4[7]);
}
}

菜单交互题 需要我们利用protobuf来传输数据

第一步就是想办法复原出proto文件 f12查看一下字符串 看看是否有可疑的

image-20230531225508553

看变量名大概可以猜出来 actionid 代表了要操作的函数索引 msgidx是chunk索引 msgsize是malloc的大小 content则是内容

至于Devicemsg则是包名 刚才也说了 不区分大小写的 唯一要注意的是 proto的编写也可以不要包名 但是这个我就没研究过了 感兴趣的自己看吧

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

package Devicemsg;

message pwn {
optional int64 actionid = 1;
optional int64 msgidx = 2;
optional int64 msgsize = 3;
optional bytes msgcontent = 4;
}
1
protoc --python_out=./ ./pwn.proto

编写好proto文件后 生成py文件

import导入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
from pwn import*
from ctf_pb2 import *
from ctypes import *
#io = process("./pwn")
io = remote("123.57.248.214",16952)
elf = ELF("./pwn")
context.terminal = ['tmux','splitw','-h']
libc = ELF("./libc-2.31.so")

context.arch = "amd64"
context.log_level = "debug"
def debug():
gdb.attach(io)
pause()

def add(index,size,content):
global io
io.recvuntil("You can try to have friendly communication with me now: ")
chunk = pwn()
chunk.actionid = 2
chunk.msgidx = index*2
chunk.msgsize = size+0x10
chunk.msgcontent = content
io.send(chunk.SerializeToString())

pwn()是根据你proto中的message名称