某 IM 软件消息卡片异常分析

日志分析

通过 EXCEPTION_ACCESS_VIOLATION 可以判断异常类型是非法内存访问

触发异常的指令地址位于 EIP=6931A5CC,对应的汇编指令片段如下

mov     eax, [esi]
push    dword ptr [eax+4]
call    sub_56BCA6A8

栈帧底部地址位于 EBP=00CFBAD8,通过栈帧地址可以帮助恢复函数调用栈的结构

Type: EXCEPTION_ACCESS_VIOLATION
Error: Read address 0x00000004
Address: 6931A5CC

CallStack:
KernelUtil + 14A5CC
KernelUtil + DB3AB
MsgMgr + 5FA6A
MsgMgr + 5A6D8
MsgMgr + 55783
AppUtil + 2B176
GroupApp + 1F17C4
GroupApp + 1F0F52
GroupApp + 1EC9BB
GroupApp + 1EBB82
GroupApp + 7909
AppFramework + FC6B3
AppFramework + 106F37
AppFramework + 1178DC
GroupApp + 19D2AF
GroupApp + 1A76FE
AppFramework + FEB90
AppFramework + 107150
GroupApp + 19D2F7
GroupApp + 1A8771
AppFramework + FC51A
AppFramework + FAD77
AppFramework + 23B3F
GroupApp + 19AA5B
AppFramework + 2B032
GroupApp + 199B80
GroupApp + 1A58FC
AppFramework + 13ADC4
ChatFrameApp + 5C20B
ChatFrameApp + E545D
GroupApp + 519AA
GroupApp + 1A0E03
AppFramework + 15592C
GroupApp + 51CA6
GroupApp + 1DAD8B
MsgMgr + 13E76E
MsgMgr + 13AD04
MsgMgr + 1758B
MsgMgr + 17C73
MsgMgr + 1807F
MsgMgr + 13BBB3
MsgMgr + 137DAC
MsgMgr + 1388A4
MsgMgr + 1807F
IM + 133F53
IM + 55448
AsyncTask + 24EE
AsyncTask + 2591
AsyncTask + 27CF
AsyncTask + 4321

Regs:
EAX=00000000, EBX=00000000, ECX=00CFBBB0, EDX=00000001
ESI=1B9E13F8, EDI=00CFBBB0, EBP=00CFBAD8, ESP=00CFBAAC, EIP=6931A5CC

堆栈分析

函数调用链

通过日志记录的调用栈可以恢复函数调用链

CTXMsgReplyOleCtrl::ParserSourceMsg()
    Util::Msg::GetJumpUrlFromArkMeta()
        Json::Value::getMemberNames()

CTXMsgReplyOleCtrl::ParserSourceMsg 负责对 json 消息卡片进行解析

Util::Msg::GetJumpUrlFromArkMeta 负责对 json 消息卡片中的 meta 数据进行解析

Json::Value::getMemberNames 负责获取 json 的所有成员名称

函数参数

使用逆向工具加载 dmp 文件检查内存快照,通过回溯栈帧可以分析异常发生时各个函数传入的参数

其中 Util::Msg::GetJumpUrlFromArkMeta 传入的参数是一个字符串,通过栈上的引用可以在堆中找到完整的 json 文本,内容如下

{ "detail_1": { "appid": "1109937557", "desc": "0", "gamePoints": "", "gamePointsUrl": "", "host": { "nick": "0", "uin": 0 }, "icon": "https:\/\/open.gtimg.cn\/open\/app_icon\/00\/95\/17\/76\/100951776_100_m.png?t=1631089970", "preview": "pubminishare-30161.picsz.qpic.cn\/492935bc-2abe-47ee-a50b-75e64278ab80", "qqdocurl": "https:\/\/b23.tv\/uRFc0c?share_medium=android&share_source=qq&bbid=XX463414F6720F0D766BD7EB79936116E8EF7&ts=1631426689501", "scene": 1036, "shareTemplateData": null, "shareTemplateId": "8C8E89B49BE609866298ADDFF2DBABA4", "showLittleTail": "", "title": "哔哩哔哩", "url": "m.q.qq.com\/a\/s\/0c3a31e2186f75749bc4e045d273d33e" } }

Json::Value::getMemberNames 传入的参数是一个空对象,随后空对象中的空指针被解引用触发了异常

复现异常

将上面的 json 文本传入 Util::Msg::GetJumpUrlFromArkMeta 可以复现异常,方便后续的调试过程

HMODULE hDll = LoadLibraryW(L"KernelUtil.dll");
PVOID pGetJumpUrlFromArkMeta = (PVOID)GetProcAddress(hDll, "?GetJumpUrlFromArkMeta@Msg@Util@@YAHVCTXBSTR@@PAVCTXStringW@@@Z");
HGetJumpUrlFromArkMeta GetJumpUrlFromArkMeta = (HGetJumpUrlFromArkMeta)pGetJumpUrlFromArkMeta;
GetJumpUrlFromArkMeta((PWCHAR)jsonString);

代码分析

通过跟踪 Util::Msg::GetJumpUrlFromArkMeta 流程,可知其调用 Value::operator[] 来获取 json 的成员对象,而触发异常的空对象正是出自于这里

JsonCpp 中相关函数实现如下

Value& Value::operator[](const char* key) {
  return resolveReference(key, false);
}

Value& Value::resolveReference(const char* key, bool isStatic) {
  JSON_ASSERT_MESSAGE(
      type_ == nullValue || type_ == objectValue,
      "in Json::Value::resolveReference(): requires objectValue");
  if (type_ == nullValue)
    *this = Value(objectValue);
#ifndef JSON_VALUE_USE_INTERNAL_MAP
  CZString actualKey(
      key, isStatic ? CZString::noDuplication : CZString::duplicateOnCopy);
  ObjectValues::iterator it = value_.map_->lower_bound(actualKey);
  if (it != value_.map_->end() && (*it).first == actualKey)
    return (*it).second;

  ObjectValues::value_type defaultValue(actualKey, null);
  it = value_.map_->insert(it, defaultValue);
  Value& value = (*it).second;
  return value;
#else
  return value_.map_->resolveReference(key, isStatic);
#endif
}

使用逆向工具进行调试,可以发现当其索引到 "shareTemplateData": null 的时候,Value::operator[] 函数会返回一个 staticJson::Value::null 对象指针

正常情况下 Json::Value::null 对象在初始化过程中会被写入一个非空指针,且该指针指向 Json::kNull,相关代码如下

static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 };
const unsigned char& kNullRef = kNull[0];
const Value& Value::null = reinterpret_cast<const Value&>(kNullRef);

但是这个初始化过程好像被腾讯的工程师给删掉了,所以返回的是一个没有经过初始化的包含空指针的空对象

随后在 Json::Value::getMemberNames 函数中空指针被解引用触发了异常

至此消息卡片异常分析完毕

上一篇:JAVA集成腾讯云即时通讯IM服务端


下一篇:Python图像处理