zoukankan      html  css  js  c++  java
  • 某 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 中被修复。

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

  • 相关阅读:
    ubuntu中apt-get安装与默认路径
    css计数器
    jq实现多级菜单
    video文件格式说明(笔记)
    css文字闪烁效果
    video设置视频的播放位置(本例中实现效果是视频第一次播放完成后,接下来中从视频的中间部位开始循环播放)
    css3鼠标经过出现转圈菜单(仿)
    jq弹框 (1)内容自适应宽度 2(内容框显示,几秒后自动消失)
    jq实现 元素显示后 点击页面的任何位置除元素本身外 隐藏元素
    nginx https配置记录
  • 原文地址:https://www.cnblogs.com/algonote/p/15264397.html
Copyright © 2011-2022 走看看