当前位置:网站首页>Easyanticheat uses to inject unsigned code into a protected process (1)

Easyanticheat uses to inject unsigned code into a protected process (1)

2022-06-24 04:11:00 franket

Responsibility statement


It's not right EasyAntiCheat The attack of .EasyAntiCheat Doing a great job in protecting the game , And will continue to do so in the coming years . I passed on EasyAntiCheat The module's private research gathered these elements , It has nothing to do with the work of public game hacking publishers or other entities . I am not interested in writing secret scripts , Everything here is just for educational purposes . Please do not contact me for help with any cheating related problems , Because I will not respond to any such requests .

It is also important to pay attention to , In the whole article , Yes EasyAntiCheat The internal structure of . I didn't reverse engineer anti cheating from top to bottom , So I can't confidently say whether this will allow you to create undiscovered cheating . Best hypothesis EasyAntiCheat The detection mechanism has been implemented . There are some items with similar attack vectors , For example, those who have achieved similar goals modmap.

Introduce


EasyAntiCheat yes Epic Games Have a commercial anti cheating solution , At present, it claims that ( And as we all know ) It was prevented by game hackers “ Industry leader ” Solution . For game developers , This allows for the smooth implementation of anti cheating in their games , Prevent multiple forms of game manipulation . from AHK Script to cheating hidden in the game ,EasyAntiCheat Has been firmly established in the anti cheating industry , Pave the way for a more honest gaming experience .

For the attacker , An important challenge is to understand how anti cheating works . therefore , Knowing what happens inside anti cheating can hide your tracks ( Or place hooks and attacks ). Let's see EasyAntiCheat How to build a bridge between the kernel and the game through its module set . This will reveal how an overlooked design flaw in the driver could allow an attacker to attack any victim EasyAntiCheat The game of protection ( Or games that may be protected by the services of other competitors ) Unlimited execution of unsigned code in .

This effectively entices the anti cheating program to protect your memory as your own memory , And endow it with various abilities , For example, create a thread 、 Deliberately place hooks, etc . That being the case ,EasyAntiCheat The design of contains a series of executable files , We will examine only the three main modules in this exploit .

Before we start , The following figure shows some of the standard procedures responsible for EasyAntiCheat Initialized module , Its operation mode is briefly explained .

Be careful : These are not EasyAntiCheat The only module used , But these are the only modules necessary to understand what is about to happen .

x86 modular


As shown above , The anti cheating program injects a tag called EasyAntiCheat.dll Module . This module is one of the main modules of the service , Used to send data to the server for background analysis . Don't forget its own set of heuristic data collection routines . But this DLL How is it injected ? consider x86 The set of functions in the module :

using LauncherCallback = VOID( __stdcall* )( INT, ULONG*, UINT );


enum EasyAntiCheatStatus 
{
        Successful = 0,
        FailedDriverHandle = 1,
        IncompatibleEasyAntiCheatVersion = 2,
        LauncherAlreadyOpen = 3,
        DebuggerDetected = 4,
        WindowsSafeMode = 5,
        WindowsSignatureEnforcement = 6,
        InsufficientMemory = 7,
        DisallowedTool = 8,
        PatchGuardDisabled = 11,
        KernelDebugging = 12,
        UnexpectedError = 13,
        PatchedBootloader = 15,
        GameRunning = 16,
};


const EasyAntiCheatStatus SetupEasyAntiCheatModule( PVOID InternalModule, SIZE_T InternalModuleSize )
{
     // The current value is 0x3C but is subject to change....
        if ( GetDriverVersion( this->DriverHandle ) != CurrentVersion )
                return EasyAntiCheatStatus::FailedDriverHandle;


    // sizeof( MapModuleStructure ) == 0x140
        SIZE_T BufferSize = InternalModuleSize + sizeof( MapModuleStructure );
        MODULE_MAP_STRUCTURE* Buffer = static_cast< MODULE_MAP_STRUCTURE* >( new UINT8[ BufferSize ] );


        // Copy the image into the heap allocation....
        // Currently Heap+0x140
        memcpy( Buffer->Image, InternalModule, InternalModuleSize );


        // Game initialization data such as the name are then copied over...
        // Do note that although this buffer is encrypted with XTEA, the module is also encrypted with its own algo...
        // The following DeviceIoControl tells the driver where to map the DLL (the game).


        XTEA_ENCRYPT( Buffer, InternalModuleSize + sizeof( MapModuleStructure ), -1 );


        SIZE_T ReturnedSize = 0;
        const BOOL Result = DeviceIoControl( this->DriverHandle, MAP_INTERNAL_MODULE, Buffer, BufferSize, &Buffer, BufferSize, &ReturnedSize, nullptr );
        if ( Result && ReturnedSize == BufferSize )
        {
                // Some processing comes here....
                return EasyAntiCheatStatus::Successful;
        }


        // Other data processing occurs and error handling....


        return EasyAntiCheatStatus::UnexpectedError;
}


// The exported name of this function is called "a" inside the x86 package but I have chosen a more fit name for reference.
__declspec( dllexport ) UINT InitEasyAntiCheat( LauncherCallback CallOnStatus , PVOID SharedMemoryBuffer, UINT Num )
{
        // 
        // Sends EasyAntiCheat.sys through an open shared memory buffer "Global\EasyAntiCheatBin"
        // This code is chopped off due to its irrelevance
        // ...
        //


        const EasyAntiCheatStatus Status = SetupEasyAntiCheatModule( InternalModule, sizeof InternalModule /* Some arguments are redacted as they are irrelevant */ );
        switch ( Status )
        {
                case EasyAntiCheatStatus::Successful:
                {
                        SetEventStatus("Easy Anti-Cheat successfully loaded in-game");
                        LoadEvent("launcher_error.success_loaded");
                        break;
                }


                // Handles error codes and generates an error log...
        }


        // ...
}

As you can see from the following set of code ,EasyAntiCheat adopt XTEA The encryption buffer will EasyAntiCheat.dll Together with other necessary information ( Such as GameID、 Process name, etc ) Send to driver .

Does this seem hateful to you ? Because it really works for me . At first glance , You will notice that they also use their own algorithms to encrypt the modules , Because the first few bytes A7 ED 96 0C 0F.... Not expected Windows PE header format . Considering that the driver module seems to follow the same format , reverse EasyAntiCheat.exe Will allow us to locate decryption . The current situation is as follows :

Image encryption


VOID DecryptModule( PVOID ModuleBase, ULONG ModuleSize )
{
        if ( !ModuleSize ) 
                return;


        UINT8* Module = static_cast< UINT8* >( ModuleBase );
        ULONG DecryptionSize = ModuleSize - 2;


        while ( DecryptionSize )
        {
                Module[ DecryptionSize ] += -3 * DecryptionSize - Module[ DecryptionSize + 1];
                --DecryptionSize;
        }


        Module[ 0 ] -= Module[ 1 ];
        return;
}

So the reverse is true ,

VOID EncryptModule( PVOID ModuleBase, ULONG ModuleSize )
{
        UINT8* Module = static_cast< UINT8* >( ModuleBase );
        ULONG Iteration = 0;


        Module[ ModuleSize - 1 ] += 3 - 3 * ModuleSize;


        while ( Iteration < ModuleSize )
        {
                Module[ Iteration ] -= -3 * Iteration - Module[ Iteration + 1];
                ++Iteration;
        }


        return;
}

In view of this code , People can easily decrypt modules , And operate it as they see fit . for example , You can choose to inject an older version of this module , this Probably Allow users to avoid adding anything to EasyAntiCheat.dll Module . Or even modify its content to map its own image . however , It's best to stay away from assumptions . Since there is not much information disclosed in this module , Our new focus should be on checking EasyAntiCheat.sys To understand what happens when the module is delivered .

EasyAntiCheat.sys


once EasyAntiCheat.sys Receive module , It will decrypt XTEA buffer , Then decrypt the encrypted PE image . after ,KeStackAttachProcess Before running the following code , It switches the context to the protected game ( Use ) To prepare for manual mapping .

Manual mapping


The following code is used to map images into the game :

BOOLEAN MapSections( PVOID ModuleBase, PVOID ImageBuffer, PIMAGE_NT_HEADERS NtHeaders )
{
        if ( !ModuleBase || !ImageBuffer )
            return FALSE;


        UINT8* MappedModule = static_cast< UINT8* >( ModuleBase );
        UINT8* ModuleBuffer = static_cast< UINT8* >( ImageBuffer );
        ULONG SectionCount = NtHeaders->FileHeader.NumberOfSections;


        const PIMAGE_SECTION_HEADER SectionHeaders = IMAGE_FIRST_SECTION( NtHeaders );
        const ULONG PEHeaderSize = SectionHeaders->VirtualAddress;


        // Copy the PE header information.....
        memcpy( ModuleBase, ImageBuffer, PEHeaderSize );


        while( SectionCount )
        {
                const PIMAGE_SECTION_HEADER SectionHeader = &SectionHeaders[ SectionCount ];
                if ( SectionHeader->SizeOfRawData )
                        memcpy( &MappedModule[ SectionHeader->VirtualAddress ], &ModuleBuffer[ SectionHeader->PointerToRawData ], SectionHeader->SizeOfRawData );


                --SectionCount;
        }


        return TRUE;
}


BOOLEAN MapImage( PVOID ImageBase, SIZE_T ImageSize, PVOID* MappedBase, SIZE_T* MappedSize, PVOID* MappedEntryPoint, /* x86 only */ OPTIONAL ULONG* ExceptionDirectory, /* x86 only */ OPTIONAL ULONG* ExceptionDirectorySize )
{
        if ( !ImageBase || !ImageSize || !MappedBase || !MappedSize || !MappedEntryPoint )
                return FALSE;


        *MappedBase = nullptr;
        *MappedSize = 0;
        *MappedEntryPoint = nullptr;


        if ( ExceptionDirectory && ExceptionDirectorySize )
        {
                // These parameters are only used to resolve the exception directory if the DllHost module is being mapped into Dllhost.exe....
                *ExceptionDirectory = 0;
                *ExceptionDirectorySize = 0;
        }


        ImageType ModuleType;
        const PIMAGE_NT_HEADERS NtHeaders = RtlImageNtHeader( ImageBase );
        if ( NtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 )
        {
                ModuleType = ImageType::Image64;
        } 
        else if ( NtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 )
        {
                ModuleType = ImageType::Image86;
        }


        PVOID MemBuffer = ExAllocatePool( ImageSize );
        if ( MemBuffer )
        {
                // This will be used to effectively "hide" the module within the process...
                const ULONG RandomSizeStart = RandomSeed( 4, 16 ) << 12UL;
                const ULONG RandomSizeEnd = RandomSeed( 4, 16 ) << 12UL;


                memcpy( MemBuffer, ImageBase, ImageSize );


                ULONG64 SizeOfImage = NtHeaders->OptionalHeader.SizeOfImage + ( RandomSizeEnd + RandomSizeStart );


                BOOLEAN VirtualApiResult = 
                        NT_SUCCESS( NtAllocateVirtualMemory( NtCurrentProcess(), MappedBase, 0, &SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE );


                if ( VirtualApiResult )
                {
                        ULONG OldProtect = 0;
                        VirtualApiResult = NT_SUCCESS( NtProtectVirtualMemory( NtCurrentProcess(), MappedBase, SizeOfImage, PAGE_EXECUTE_READWRITE, &OldProtect ) );
                        if ( VirtualApiResult )
                        {
                                // This region is used to throw people off from the module.
                                RandomizeRegion( *MappedBase, RandomSizeStart );
                                VirtualApiResult = NT_SUCCESS( NtProtectVirtualMemory( NtCurrentProcess(), MappedBase, RandomSizeStart, PAGE_READWRITE, &OldProtect ) );


                                if ( VirtualApiResult )
                                {
                                        PVOID ModuleEnd = static_cast< UINT8* >( *MappedBase ) + ( SizeOfImage - RandomSizeEnd );
                                        RandomizeRegion( ModuleEnd,  RandomSizeEnd );
                                        VirtualApiResult = NT_SUCCESS( NtProtectVirtualMemory( NtCurrentProcess(), &ModuleEnd, RandomSizeEnd, PAGE_READONLY, &OldProtect ) );


                                        if ( VirtualApiResult )
                                        {
                                                PVOID RealModule = static_cast< UINT8* >( *MappedBase ) + RandomSizeStart;
                                                ResolveRelocations( RealModule, MemBuffer, ModuleType, NtHeaders );
                                                NtHeaders->OptionalHeader.ImageBase = RealModule;


                                                if ( MapSections( RealModule, MemBuffer, NtHeaders ))
                                                {
                                                        // Applies the correct memory attributes for each section (.text = RX, .data = RW, .rdata = R, etc)
                                                        CorrectSectionProtection( RealModule, NtHeaders );


                            *MappedBase = RealModule;
                                                        *MappedSize = NtHeaders->OptionalHeader.SizeOfImage;
                                                        *MappedEntryPoint = static_cast< UINT8* >( RealModule ) + NtHeaders->OptionalHeader.AddressOfEntryPoint;


                                                        if ( ExceptionDirectory && ExceptionDirectorySize )
                                                        {
                                                                *ExceptionDirectory = NtHeaders->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXCEPTION ].VirtualAddress;
                                                                *ExceptionDirectorySize = NtHeaders->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXCEPTION ].Size;
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }


        if ( MemBuffer )
        {
                ExFreePool( MemBuffer );
                MemBuffer = nullptr;
        }


        return *MappedEntryPoint != NULL;
}
原网站

版权声明
本文为[franket]所创,转载请带上原文链接,感谢
https://yzsam.com/2021/09/20210910165620869h.html