前言#
一种数据传输协议 2023年的国赛初赛考了一题基于这个堆题 需要使用其才能完成菜单交互 其中几个用来pack和unpack的函数代码审计起来很恶心 如果不知道这个协议的 看到以后会无从下手 挺恶心人的 刚好我有出题的需要 就记录一下相关的
环境配置#
首先需要安装protoc 便于以后我们利用protoc文件生成py文件
sudo apt install python3-pip git openjdk-11-jre libqt5x11extras5 python3-pyqt5.qtwebengine python3-pyqt5sudo pip3 install protobuf pyqt5 pyqtwebengine requests websocket-clientsudo apt-get install autoconf automake libtoolsudo apt-get install autoconf automake libtool curl make g++ unzipgit clone https://gitclone.com/github.com/protocolbuffers/protobuf.gitcd protobuf./autogen.sh./configuremakesudo make installsudo ldconfig如果有出题需求的话 还需要安装protobuf-c 才能编译
git clone https://gitclone.com/github.com/protobuf-c/protobuf-c.gitcd protobuf-c/./autogen.sh && ./configure && make && make install编译#
先从利用proto文件生成c语言编译所需要的pb-c.h文件说起
诸如这样写好一个proto文件后
syntax = "proto2";
package test;
message Student { optional string name = 1; //这个1无特殊含义 是表示顺序 如果还需要新增一个变量就=2}optional和request是代表是否强制要求该数据 数据类型这个参照其他教程 我复制粘贴供哪天我本地查询
protoc-c --c_out=./ ./pwn.proto使用上面的指令会生成两个文件

一个是在gcc编译时需要使用 一个是在源码编写中需要作为头文件导入
同时需要注意 proto文件中变量名不区分大小写
这个时候差不多就可以编写c语言源码了 直接上一个demo
#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文件
gcc pwn.pb-c.c test.c -o pwn -lprotobuf-c此时 直接运行会出现如下的报错

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

可以看到成功输出hello 剩下的就没什么好说了 如果要想恶心一点就删符号表 静态编译 代码审计量巨大
解题#
拿今年CISCN初赛的一题来举例 我也是通过这题了解到protobuf 抛开交互这道题是一个堆 我估计他的源码是用malloc开空间 所以在后面堆的时候 堆结构会改变 就很烦 这里就不提了 可以去看我的wp
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查看一下字符串 看看是否有可疑的

看变量名大概可以猜出来 actionid 代表了要操作的函数索引 msgidx是chunk索引 msgsize是malloc的大小 content则是内容
至于Devicemsg则是包名 刚才也说了 不区分大小写的 唯一要注意的是 proto的编写也可以不要包名 但是这个我就没研究过了 感兴趣的自己看吧
syntax = "proto2";
package Devicemsg;
message pwn { optional int64 actionid = 1; optional int64 msgidx = 2; optional int64 msgsize = 3; optional bytes msgcontent = 4;}protoc --python_out=./ ./pwn.proto编写好proto文件后 生成py文件
import导入exp 最后按照如下编写即可
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名称