mqqm文件函数CQmPacket-CQmPacket分析
字数 1734 2025-08-22 12:22:24
MSMQ协议CQmPacket::CQmPacket函数分析与漏洞研究
1. 概述
本文档详细分析MSMQ协议中CQmPacket::CQmPacket函数的实现原理、数据结构处理机制以及其中存在的安全漏洞。该函数主要用于处理UserMessage数据包,包含多个必需和可选的消息头结构。
2. 函数参数分析
CQmPacket::CQmPacket函数主要参数:
- 参数一:原始数据包指针
- 参数二:BaseHeader头指针
- 返回值:处理后的数据包结构
3. BaseHeader结构定义
根据微软官方文档定义,BaseHeader结构如下:
typedef struct {
uint8_t VersionNumber; // 版本号
uint8_t Reserved; // 保留字段
uint16_t Flags; // 标志位
uint32_t Signature; // 签名(固定值0x524F494C)
uint32_t PacketSize; // 数据包大小
uint32_t TimeToReachQueue; // 到达队列时间
// 其他字段...
} BaseHeader;
4. UserMessage数据包结构
UserMessage数据包包含以下部分:
必需头:
- BaseHeader
- UserHeader
- MessagePropertiesHeader
可选头:
- TransactionHeader
- SecurityHeader
- DebugHeader
- SoapHeader
- MultiQueueFormatHeader
- SessionHeader
5. 头结构验证机制
5.1 CBaseHeader::SectionIsValid验证流程
验证函数接收三个参数:
- baseHeader指针
- 消息最大限制值
- 固定布尔值1
验证步骤:
- 检查BaseHeader大小是否超过最大限制
- 验证BaseHeader数据体是否超出范围
- 验证签名是否为固定值0x524F494C
- 验证版本号是否为0x10
5.2 其他头结构验证
类似验证流程,包括:
- 固定值验证
- 范围检查
- 合理性检查
6. GetNextSection函数
用于获取下一个节(头结构):
void* GetNextSection(void* prevHeader, int param=0);
工作原理:
- 从上一个头结构中获取数据大小
- 在当前头结构地址上偏移该大小
- 返回下一个头结构的地址
7. CQmPacket结构定义
完整结构定义如下:
typedef struct {
int64_t field1; // 'this'指针
int64_t field2; // 跳过初始化
int64_t field3; // 跳过初始化
int64_t pPacket; // 原始数据包指针(由a3参数赋值)
int64_t pBaseHeader; // BaseHeader指针
int64_t pUserHeader; // UserHeader指针(this+6)
int64_t pXactHeader; // 事务头(this+7)
int64_t pSecurityHeader; // 安全头(this+8)
int64_t pPropertyHeader; // 属性头(this+9)
int64_t pDebugSection; // 调试节(this+10)
int64_t pBaseMqfHeader; // MQF基础头(this+11)
int64_t pBaseMqfHeader2; // MQF基础头(this+12)
int64_t pBaseMqfHeader3; // MQF基础头(this+13)
int64_t pMqfSignatureHeader; // MQF签名头(this+14)
int64_t pSRMPEnvelopeHeader; // SRMP信封头(this+15,未验证)
int64_t pCompoundMessageHeader; // 复合消息头(this+16,未验证)
int64_t unKnownReservedX31; // 保留位X31(this+17,未验证)
int64_t unKnownReservedX32; // 保留位X32(this+18,未验证)
int64_t SoapHeader; // SOAP头(this+19,未验证)
int64_t SoapHeaderBody; // SOAP头体(this+20,未验证)
int64_t unKnownReservedX41; // 保留位X41(this+21,未验证)
int64_t unKnown22; // 未知头22(this+22,未验证)
int64_t pMsgGroupHeader; // 消息组头(this+23,已验证)
int64_t pExtensionHeader; // 扩展头(this+24,未验证)
int64_t pMsgDeadletterHeader; // 死信头(this+25,未验证)
int64_t pSubqueueHeader; // 子队列头(this+26,未验证)
int64_t pExtendedAddressHeader; // 扩展地址头(this+27,未验证)
int64_t pSessionHeader; // 会话头(this+28,未验证)
} CQmPacket;
8. 标志位解析
通过UserHeader的标志位识别其他数据结构:
-
0x2000000 (J标志位):指示数据包是否通过HTTP发送
- 设置:使用HTTP(SRMP消息)
- 未设置:使用二进制协议
- 设置时会多出SRMPEnvelopeHeader和CompoundMessageHeader
-
0x4000000 (X3标志位):保留位1
-
0x8000000 (X3标志位):保留位2
-
0x10000000 (K标志位):对应SoapHeader
-
0x20000000 (X4标志位):保留位第一位
9. 漏洞原理
9.1 漏洞成因
部分头结构未进行验证,攻击者可:
- 伪造结构体大小与实际大小的差异
- 误导后续节获取的指针地址
- 导致访问未分配内存而崩溃
9.2 漏洞利用
选择第三个未验证的结构体(unKnownReservedX31)进行伪造:
- 需要使if语句为真进入循环
- this+6(UserHeader)偏移44对应Flags字段(16进制数据中04字节)
- 指针地址计算公式:
指针地址 + (指针地址+4) + (指针地址+8) + 15 - 可随意设置(指针地址+4)和(指针地址+8)处的值
- 最终导致访问未分配地址而崩溃
10. POC构造
10.1 必要通信流程
- 发送EstablishConnection Packet
- 发送ConnectionParameters Packet
- 发送特制的User Message包
10.2 User Message包构造要点
- 保留必选头(BaseHeader, UserHeader, MessagePropertiesHeader)
- 添加触发漏洞的头结构(unKnownReservedX31)
- 设置UserHeader Flags字段的特定标志位(0x2000000等)
10.3 参考数据包
11. 防御建议
- 对所有头结构进行完整验证
- 检查指针计算过程中的边界条件
- 更新MSMQ协议实现,修复未验证头结构的问题
- 实施输入数据的完整性检查