Control Flow Analysis
R5AC is planting control flow enumeration helpers into important game mode.
Just because a module is signed, doesn't mean that R5AC will give it a pass.
It follows a strict whitelist which at the time of this writing, consisted of the following ranges.
| r5apex_dx12.exe | (+1000 → +13d2000) ; '.text' |
| KERNEL32.DLL | (+1000 → +7f4bb) ; '.text' |
| ntdll.dll | (+1000 → +119f1e) ; '.text' |
| ntdll.dll | +11a000 → +11a592) ; 'PAGE' |
| ntdll.dll | (+11b000 → +11b1f9) ; 'RT' |
| steamnetworkingsockets.dll | (+1000 → +2f63a8) ; '.text |
| steam_api64.dll | (+1000 → +243de) ; '.text' |
| EOSSDK-Win64-Shipping.dll | (+1000 → +d8117c) ; '.text |
An example of this instance of detection can be found in C_ViewRender::GetMostRecentClipTransform.
__int64 C_ViewRender::GetMostRecentClipTransform(__int64 a1, char frameIndex)
{
v152 = frameIndex; // Prime RNG (seems unrelated to stack walker)
R5AC::Prime = 214013 * R5AC::Prime + 2531011; // Early exit: check stack walker enablement
if (!R5AC::IsStackWalkerEnabled)
return *(_QWORD *)(a1 + 8i64 * v152 + 1155920); __int64 stackFrames[2] = { 0 };
__int128 stackData[2] = { 0 }; unsigned short numFrames = R5AC::Imp::RtlCaptureSBT(1, 5, stackData);
if (!numFrames)
return *(_QWORD *)(a1 + 8i64 * v152 + 1155920); // Compute simple hash of the captured stack
int hashA = 5381, hashB = 0;
char* p = reinterpret_cast<char*>(stackData);
for (size_t i = 0; i < 8 * numFrames; ++i)
{
unsigned char val = *p++;
hashA = val + 33 * hashA;
hashB = val + 65599 * hashB;
}
int stackHash = hashA ^ hashB; // Lock to check hash
MEMORY[0x7FFE8D571760](&R5AC::SoemLock, 0);
char hasChanged = R5AC::HashmapHasChangedData((__int64)&R5::HashedCodeExecutionFrames, &stackHash);
MEMORY[0x7FFE8D571920](&R5AC::SoemLock); if (hasChanged != -1)
{
// If hash is known, return cached transform
return *(_QWORD *)(a1 + 8i64 * v152 + 1155920);
} // Iterate captured frames
for (unsigned int i = 0; i < numFrames; ++i)
{
__int64 frameAddr = *reinterpret_cast<__int64*>(stackData + i);
if (!frameAddr)
break; // Check if frame is in whitelisted regions
bool inWhitelist = false;
for (int j = 0; j < R5AC::WhitelistedSectionCount; ++j)
{
_WHITELISTED_SEGMENT_* region = &R5AC::WhitelistedSections[j];
if (frameAddr >= region->Begin && frameAddr <= region->End)
{
inWhitelist = true;
break;
}
} if (inWhitelist)
continue; // If not whitelisted, fetch mapped module and push violation
char moduleName[272] = { 0 };
((void(__fastcall*)(__int64, __int64*, char*, __int64))0)(-1, frameAddr, moduleName, 260); if (moduleName[0])
{
R5AC::PushViolation(nullptr, 1, frameAddr, reinterpret_cast<__int64>(moduleName));
return *(_QWORD *)(a1 + 8i64 * v152 + 1155920);
}
} // If no violations, store new hash
MEMORY[0x7FFE8D5790A0](&R5AC::SoemLock, R5AC::WhitelistedSectionCount, numFrames, R5AC::WhitelistedSections);
sub_2345E0(&R5::HashedCodeExecutionFrames, &stackHash);
MEMORY[0x7FFE8D562C70](&R5AC::SoemLock); return *(_QWORD *)(a1 + 8i64 * v152 + 1155920);
}