前言 一种数据传输协议 2023年的国赛初赛考了一题基于这个堆题 需要使用其才能完成菜单交互 其中几个用来pack和unpack的函数代码审计起来很恶心 如果不知道这个协议的 看到以后会无从下手 挺恶心人的 刚好我有出题的需要 就记录一下相关的
环境配置 首先需要安装protoc 便于以后我们利用protoc文件生成py文件
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
git clone https://gitclone.com/github.com/protocolbuffers/protobuf.git cd protobuf ./autogen.sh ./configure make sudo make install sudo ldconfig
如果有出题需求的话 还需要安装protobuf-c 才能编译
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文件后
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
此时 直接运行会出现如下的报错
该指令可以搜寻共享动态库 创建出动态装入程序所需的连接和缓存文件
可以看到成功输出hello 剩下的就没什么好说了 如果要想恶心一点就删符号表 静态编译 代码审计量巨大
解题 拿今年CISCN初赛的一题来举例 我也是通过这题了解到protobuf 抛开交互这道题是一个堆 我估计他的源码是用malloc开空间 所以在后面堆的时候 堆结构会改变 就很烦 这里就不提了 可以去看我的wp
void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { ssize_t v3; __int64 *v4; sub_1763(a1, a2, a3); while ( 1 ) { memset (&unk_A060, 0 , 0x400 uLL); puts ("You can try to have friendly communication with me now: " ); v3 = read(0 , &unk_A060, 0x400 uLL); 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 = 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名称