通过 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[]
函数会返回一个 static
的 Json::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
函数中空指针被解引用触发了异常
至此消息卡片异常分析完毕