Crash Dump Analysis Patterns (Part 56)
The case when a function pointer or a return address becomes a Wild Pointer and EIP or RIP value lies in a valid region of memory the execution path may continue through a region called Wild Code. This might loop on itself or eventually reach non-executable or invalid pages and produce an exception. Local Buffer Overflow might lead to this behavior and also data corruption that overwrites function pointers with valid memory addresses.
My favorite example is when a function pointer points to zeroed pages with EXECUTE page attribute. What will happen next when we dereference it? All zeroes are perfect x86/x64 code:
0:001> dd 0000000`771afdf0
00000000`771afdf0 00000000 00000000 00000000 00000000
00000000`771afe00 00000000 00000000 00000000 00000000
00000000`771afe10 00000000 00000000 00000000 00000000
00000000`771afe20 00000000 00000000 00000000 00000000
00000000`771afe30 00000000 00000000 00000000 00000000
00000000`771afe40 00000000 00000000 00000000 00000000
00000000`771afe50 00000000 00000000 00000000 00000000
00000000`771afe60 00000000 00000000 00000000 00000000
0:001> u
ntdll!DbgUserBreakPoint:
00000000`771afe00 0000 add byte ptr [rax],al
00000000`771afe02 0000 add byte ptr [rax],al
00000000`771afe04 0000 add byte ptr [rax],al
00000000`771afe06 0000 add byte ptr [rax],al
00000000`771afe08 0000 add byte ptr [rax],al
00000000`771afe0a 0000 add byte ptr [rax],al
00000000`771afe0c 0000 add byte ptr [rax],al
00000000`771afe0e 0000 add byte ptr [rax],al
Now if RAX points to a valid memory page with WRITE attribute the code will modify the first byte at that address:
0:001> dq @rax
000007ff`fffdc000 00000000`00000000 00000000`035a0000
000007ff`fffdc010 00000000`0359c000 00000000`00000000
000007ff`fffdc020 00000000`00001e00 00000000`00000000
000007ff`fffdc030 000007ff`fffdc000 00000000`00000000
000007ff`fffdc040 00000000`0000142c 00000000`00001504
000007ff`fffdc050 00000000`00000000 00000000`00000000
000007ff`fffdc060 000007ff`fffd8000 00000000`00000000
000007ff`fffdc070 00000000`00000000 00000000`00000000
Therefore the code will be perfectly executed:
0:001> t
ntdll!DbgBreakPoint+0x2:
00000000`771afdf2 0000 add byte ptr [rax],al ds:000007ff`fffdc000=00
0:001> t
ntdll!DbgBreakPoint+0x4:
00000000`771afdf4 0000 add byte ptr [rax],al ds:000007ff`fffdc000=00
0:001> t
ntdll!DbgBreakPoint+0x6:
00000000`771afdf6 0000 add byte ptr [rax],al ds:000007ff`fffdc000=00
0:001> t
ntdll!DbgBreakPoint+0x8:
00000000`771afdf8 0000 add byte ptr [rax],al ds:000007ff`fffdc000=00
0:001> t
ntdll!DbgBreakPoint+0xa:
00000000`771afdfa 0000 add byte ptr [rax],al ds:000007ff`fffdc000=00
- Dmitry Vostokov @ DumpAnalysis.org -
June 20th, 2008 at 1:06 pm
[…] provided specific recommendation hints. When looking at the crash point we see an instance of Wild Code […]
May 15th, 2009 at 7:19 pm
[…] the assembly code looks almost wild (not like generated by your favourite compiler). For example (that also shows .NET runtime native […]
September 4th, 2009 at 2:15 pm
[…] recently got a chance to see an instance of Wild Code pattern in kernel […]
March 12th, 2016 at 7:25 pm
Sometimes
0:000> k
ChildEBP RetAddr
03ced1b0 771d6aec ntdll!KiFastSystemCallRet
03ced1b4 75406a8e ntdll!NtWaitForMultipleObjects+0xc
03ced250 7734be76 KERNELBASE!WaitForMultipleObjectsEx+0x100
03ced298 7734bee4 kernel32!WaitForMultipleObjectsExImplementation+0xe0
03ced2b4 7736072f kernel32!WaitForMultipleObjects+0x18
03ced320 773609ca kernel32!WerpReportFaultInternal+0x186
03ced334 77360978 kernel32!WerpReportFault+0x70
03ced344 773608f3 kernel32!BasepReportFault+0x20
03ced3d0 7720820a kernel32!UnhandledExceptionFilter+0x1af
03ced3d8 771ae364 ntdll!__RtlUserThreadStart+0x62
03ced3ec 771ae1fc ntdll!_EH4_CallFilterFunc+0x12
03ced414 771d72b9 ntdll!_except_handler4+0x8e
03ced438 771d728b ntdll!ExecuteHandler2+0x26
03ced45c 771af9d7 ntdll!ExecuteHandler+0x24
03ced4e8 771d7117 ntdll!RtlDispatchException+0x127
03ced4e8 63050001 ntdll!KiUserExceptionDispatcher+0xf
03ceda64 771e73e2 ModuleA!FunctionA+0xc1
03ceda84 0141b848 ntdll!_SEH_epilog4_GS+0xa
03cedcb4 767cbbf4 ModuleB!FunctionB+0x188
03cedcc8 767cbcb5 gdi32!NtGdiOpenDCW+0xc
03cedf70 013bc7df gdi32!hdcCreateDCW+0x517
03cee0c8 7734c413 ModuleB!FunctionC+0xff
03cee0e0 7734c3c2 kernel32!WaitForSingleObjectExImplementation+0x75
03cee0f4 65e66c9c kernel32!WaitForSingleObject+0x12
WARNING: Frame IP not in any known module. Following frames may be wrong.
00000000 00000000 0x65e66c9c
We see Incorrect Stack Trace pattern since it doesn’t make sense that waiting functions call GDI and other modules. Also the return address for ModuleA!FunctionA looks coincidental: 63050001. Although we are able to see code we are not able to disassemble it backwards:
0:000> u 63050001
ModuleA!FunctionA+0xc1:
63050001 a1056383c4 mov eax,dword ptr ds:[C4836305h]
63050006 108d4c241051 adc byte ptr [ebp+5110244Ch],cl
6305000c ff15b4a00563 call dword ptr [ModuleA!_imp__OutputDebugStringA (6305a0b4)]
63050012 8b4e14 mov ecx,dword ptr [esi+14h]
63050015 85c9 test ecx,ecx
63050017 740e je ModuleA!FunctionA+0xe7 (63050027)
63050019 8b11 mov edx,dword ptr [ecx]
6305001b 8b4204 mov eax,dword ptr [edx+4]
0:000> ub 63050001
^ Unable to find valid previous instruction for 'ub 63050001'
0:000> ub 63050001-1
^ Unable to find valid previous instruction for 'ub 63050001-1'
0:000> ub 63050001-2
^ Unable to find valid previous instruction for 'ub 63050001-2'
0:000> ub 63050001-3
ModuleA!FunctionA+0xa4:
6304ffe4 ffd2 call edx
6304ffe6 84c0 test al,al
6304ffe8 7541 jne ModuleA!FunctionA+0xeb (6305002b)
6304ffea 6818c20563 push offset ModuleA!`string' (6305c218)
6304ffef 68bcc10563 push offset ModuleA!`string' (6305c1bc)
6304fff4 8d442418 lea eax,[esp+18h]
6304fff8 6800010000 push 100h
6304fffd 50 push eax