Skip to main content

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);
}