某 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
...

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::Value::null 对象,对象中的空数据被作为指针解引用,从而触发了异常。

复现异常

将上面的 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 的成员对象,并将成员对象加入到队列中等待处理。

Value::operator[] 函数在检索 shareTemplateData 时会返回 Json::Value::null 对象,正是这个对象触发了后续的异常,相关函数实现如下:

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

Value &
Value::resolveReference( const char *key, 
                         bool isStatic )
{
   JSON_ASSERT( type_ == nullValue  ||  type_ == 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
}

随后 Json::Value::null 对象从队列中取出,并传入 Json::Value::getMemberNames 函数进行处理。

然而 KernelUtil 中使用的是早期版本的 JsonCpp,相关函数实现如下:

Value::Members 
Value::getMemberNames() const
{
   JSON_ASSERT( type_ == nullValue  ||  type_ == objectValue );
   Members members;
   members.reserve( value_.map_->size() );
#ifndef JSON_VALUE_USE_INTERNAL_MAP
   ObjectValues::const_iterator it = value_.map_->begin();
   ObjectValues::const_iterator itEnd = value_.map_->end();
   for ( ; it != itEnd; ++it )
      members.push_back( std::string( (*it).first.c_str() ) );
#else
   ValueInternalMap::IteratorState it;
   ValueInternalMap::IteratorState itEnd;
   value_.map_->makeBeginIterator( it );
   value_.map_->makeEndIterator( itEnd );
   for ( ; !ValueInternalMap::equals( it, itEnd ); ValueInternalMap::increment(it) )
      members.push_back( std::string( ValueInternalMap::key( it ) ) );
#endif
   return members;
}

其中 Json::Value::getMemberNames 并没有检查对象的 type_ 是否为 nullValue

导致 Json::Value::null 对象中的空数据被作为 value_.map 指针解引用,从而触发了异常。

JsonCpp 的这个问题已经在后续的 commit 中被修复。

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

posted @ 2021-09-14 00:51  Byaidu  阅读(574)  评论(0编辑  收藏  举报