Who crashed?
7/May 2017
Last week I was investigating a crash originating somewhere in a code that looked like this (GPF in this C++ line):
obj->GetFoo()->GetBar().Call(player->GetCat(), this, &MyType::SomeFunc, moreArgs
We could discuss the number of indirections or the fact that if this code had been split into multiple lines it’d be obvious, but that’s not the main point here. I didn’t have this luxury, I had to find out which pointer exactly was NULL here.
Let’s take a look at the assembly first, this should give us more info:
1lea rdx,[rsp+58h]
2mov rcx,qword ptr [rax]
3mov rax,qword ptr [r14+0B20h]
4mov rdi,qword ptr [rcx]
5mov rcx,qword ptr [rax]
6call Object::GetFoo (07FF7A08AA2C0h)
7mov rcx,rdi
8mov rdx,qword ptr [rax]
9mov rax,qword ptr [rdi]
10mov rbx,qword ptr [rdx]
11call qword ptr [rax+0E8h] *** crash here
12mov rdx,qword ptr [rbx]
13mov rcx,rbx
14mov rdi,rax
15call qword ptr [rdx+1E0h]
16mov dword ptr [rsp+38h],3
17lea r9,[MyType::SomeFunc (07FF7A0DE7C6)]
18mov byte ptr [rsp+30h],0
19mov rcx,rax
20mov dword ptr [rsp+28h],3
21mov r8,r14
22mov rdx,rdi
23mov qword ptr [rsp+20h],r15
24call Mgr::Call(07FF7A0DD87C0h)
At the first glance, it might seem like the result of Object::GetFoo is null, as GPF occurs almost immediately after the call. Looking closer at the assembly, it doesn’t look like the result (RAX) is being used there, though. Is it possible it’s actually player pointer that’s invalid? We could analyze the assembly in more depth, but another way would be to determine the types of our pointers. We seem to be calling a bunch of virtual functions here, so if we know what method resides under [rax+0xE8], we’d be able to tell the type. If it’s GetBar() then it’s indeed result of GetFoo causing issues, if it’s GetCat -> it’s player.
How do we find the vtable for a given type, though? I’m not sure how to do it in Visual Studio, to be honest (my best bet would be to search memory for an address of one of the virtual methods, but the search memory command seems to be removed/well hidden in 2015), but it’s trivial in my old friend WinDbg:
0:000> x *!Foo*`vftable'
00007ff7`a1c70f78 App_x64!Foo::`vftable' = <no type information>
All we have to do is to check what’s at 0xE8:
:000> dqs (00007ff7a1c70f78 + 0xE8) L1
00007ff7`a1c71060 00007ff7`a0e90b60 App_x64!Foo::GetAnimationMgr
Hmm, OK, doesn’t seem like it’s Foo that’s null then, if it was we’d see GetBar at offset 0xE8. Let’s try Player class now:
0:000> x *!Player*`vftable'
00007ff7`a1bd2870 App_x64!Player::`vftable' = <no type information>
0:000> dqs 00007ff7a1bd2870+0x0E8 L1
00007ff7`a1bd2958 00007ff7`a0909310 App_x64!Player::GetCat
Boom, that’s what we wanted to see. Application has crashed while trying to jump to GetCat using Player’s vtable, so it was the player pointer that was null. Now to find wht it’s the case, but that’s another story…