日志分析
通过 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
函数中空指针被解引用触发了异常
至此消息卡片异常分析完毕