Tutorial How to Bypass Fairplaykd.sys MTA:SA Anticheat

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

iPower

Piece of shit
Escobar Tier VIP
Fleep Tier Donator
Jun 29, 2017
598
21,108
67
Game Name
MTA: SA
Anticheat
MTA: SA Kernel Mode Anticheat
How long you been coding/hacking?
Idk
Coding Language
None
So in the past few days I've been reversing MTA: SA's anti cheat and I decided to start out with the driver FairplayKD.sys because I wanted to be able to inject my stuff without any problem. Here I'm gonna show you why the Fairplaykd.sys driver is a joke.

Multi Theft Auto (MTA) is a multiplayer modification for Grand Theft Auto: San Andreas that adds online multiplayer. For Grand Theft Auto: San Andreas, the mod also serves as a derivative engine to Rockstar's interpretation of RenderWare.

To dynamically import functions, the driver builds encrypted stack strings, decrypts them and convert them to Unicode and calls MmGetSystemRoutineAddress, which get the address of exported functions from ntoskrnl.exe (the kernel and executive) and hal.dll (HAL).

Example:

1598044617790.png


String decryption code:
C++:
size_t i = 0;
char random_shit = 0;

do
{
    random_shit = ( ( 3 - i ) ^ *pString & 0x7F ) - i * i;
    ++i;
    *pString++ = random_shit & 0x7F;
}
while ( i < strlen( pString ) );
So after knowing about that, I easily found where it grabs the address of ObRegisterCallbacks:

1598044638913.png

(DecryptStringAndGetRoutineAddress is a function that does exactly what I said)

Here's where the driver registers the call-backs:

1598044643873.png


(you can also see PsSetCreateProcessNotifyRoutine there).

Inside RegisterShittyCallbacks:

1598044660451.png


You can see they register 2 pre-operation call-backs - which are called by ObpCallPreOperationCallbacks, one for process and the other for thread. I'm gonna only show the process one since both call-backs are basically the same shit.

Before getting into the pre-operation call-back, let's see how the driver store information about process like itself. MTA: SA's driver stores information about some processes in a global array that I called SpecialProcessesInfo and. Example of it being accesed:

1598044670455.png


Each entry in that array is represented by this structure:

C++:
struct _PROCESS_INFO
{
  DWORD ProcessId;
  DWORD Unknown;
  DWORD Type;
  DWORD Flags;
};
The type member can be one of the following numbers:

Code:
TYPE    PROCESS

1          Normal Process
2          csrss.exe
3          lsass.exe
4          svchost.exe
5          Multi Theft Auto.exe or MULTIT~1.EXE
6          mta_sa.exe or proxy_sa.exe
7          raidcall.exe
8          LVPrcSrv.exe or LWEMon.exe
9          Action_x86.bin or Action_x64.bin
I named that global array as "SpecialProcessesInfo" because type 1 processes (normal processes) won't be added to the list. From PcreateProcessNotifyRoutine (the callback set by PsSetCreateProcessNotifyRoutine):

1598044698344.png


Now that I explained about this stuff, let's go to the PreOperation Callback:
1598044704831.png


What basically happens here is this:
  1. Check if target is gta_sa.exe or proxy_sa.exe
  2. Check‬ if it isn't gta_sa/proxy_sa that's doing the operation
  3. Check the operation (create/duplicate)
  4. Check if some bits representing write access or other operations are set. Go to step 5 if true.
  5. Check if the process that's creating/duplicating the handle is of type 1, 5, or 6. Go to step 5 if true.
  6. Strip handle..

That means we can use type 7 (raidcall.exe) to inject our stuff in there. I've coded a basic manual mapping injector (thx @Broihon) to test it and look what happened:

1598044687165.png


Get rekt shitty driver.

Moral of the story: raidcall is the real MVP

FairplayKD.sys IDA Database
Found this old IDB for FairplayKD.sys in my PC so I'm posting it. It's not fully reversed (I've lost my fully reversed one) but I'm sure this will help someone as the driver didn't change a lot.

That's it for fairplaykd. Still gotta see the user mode part but at least I can inject my shit. Rake posted some stuff below so keep reading.
 

Attachments

Last edited by a moderator:

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,380
78,998
2,414
I was just poking around, I don't have GTA San Andreas so I can't even install the game or play it, but I was interested in taking a look at the MTA: SA Anticheat

I downloaded MTA: SA and it wouldn't let me install it cuz I don't have the game, so first I had to figure out how to bypass that part of the install.

Then peaking around, the installer doesn't install fairplaykd.sys, it doesn't exist anywhere on disk.

If you grep your MTA:SA folder for "fairplay" you get nothing, also did a recursive unicode strings scan on the game folder, on luck. So it's either downloaded when you install or it's embedded in one of the game files and obfuscated. I looked at the resources in all the files, no simple resource embeds :(

Nothing obvious in the main executable but inside loader.dll:
1598072762807.png


You can find that easily by looking for that string, that function includes a few other / commands as well:
  • /kdinstall
  • /kduninstall
  • /L5
  • /nolaunch
I didn't figure out how they decrypt/retrieve the fairplaykd.sys but I did find some other stuff.

Here is there GetProcAddress import resolution at runtime
C++:
int RuntimeGetProcAddr()
{

  v0 = GetProcAddrWrapper("Kernel32.dll", "CreateFileA");
  dword_100E2314 = sub_10018D80(v0, (int)sub_1003B5A0);
  if ( !dword_100E2314 )
    MEMORY[0] = 0;
  v1 = GetProcAddrWrapper("Kernel32.dll", "LoadLibraryA");
  dword_100E2318 = sub_10018D80(v1, (int)sub_1003BBD0);
  if ( !dword_100E2318 )
    MEMORY[0] = 0;
  v2 = GetProcAddrWrapper("Kernel32.dll", "LoadLibraryExA");
  dword_100E231C = sub_10018D80(v2, (int)sub_1003BCD0);
  if ( !dword_100E231C )
    MEMORY[0] = 0;
  v3 = GetProcAddrWrapper("Kernel32.dll", "SetDllDirectoryA");
  dword_100E2320 = sub_10018D80(v3, (int)sub_1003C3D0);
  if ( !dword_100E2320 )
    MEMORY[0] = 0;
  v4 = GetProcAddrWrapper("Kernel32.dll", "SetCurrentDirectoryA");
  dword_100E2324 = sub_10018D80(v4, (int)sub_1003C270);
  if ( !dword_100E2324 )
    MEMORY[0] = 0;
  v5 = GetProcAddrWrapper("Gdi32.dll", "AddFontResourceExA");
  dword_100E2328 = sub_10018D80(v5, (int)sub_1003B160);
  if ( !dword_100E2328 )
    MEMORY[0] = 0;
  v6 = GetProcAddrWrapper("Gdi32.dll", "RemoveFontResourceExA");
  dword_100E232C = sub_10018D80(v6, (int)sub_1003C160);
  if ( !dword_100E232C )
    MEMORY[0] = 0;
  v7 = GetProcAddrWrapper("Kernel32.dll", "RemoveDirectoryA");
  dword_100E2330 = sub_10018D80(v7, (int)sub_1003BFA0);
  if ( !dword_100E2330 )
    MEMORY[0] = 0;
  v8 = GetProcAddrWrapper("Kernel32.dll", "GetDiskFreeSpaceExA");
  dword_100E2334 = sub_10018D80(v8, (int)sub_1003B890);
  if ( !dword_100E2334 )
    MEMORY[0] = 0;
  v9 = GetProcAddrWrapper("Kernel32.dll", "GetFileAttributesA");
  dword_100E2338 = sub_10018D80(v9, (int)sub_1003B9A0);
  if ( !dword_100E2338 )
    MEMORY[0] = 0;
  v10 = GetProcAddrWrapper("Kernel32.dll", "SetFileAttributesA");
  dword_100E233C = sub_10018D80(v10, (int)sub_1003C4D0);
  if ( !dword_100E233C )
    MEMORY[0] = 0;
  v11 = GetProcAddrWrapper("Shell32.dll", "ShellExecuteA");
  dword_100E2340 = sub_10018D80(v11, (int)sub_1003C630);
  if ( !dword_100E2340 )
    MEMORY[0] = 0;
  v12 = GetProcAddrWrapper("Kernel32.dll", "CreateDirectoryA");
  dword_100E2344 = sub_10018D80(v12, (int)sub_1003B440);
  if ( !dword_100E2344 )
    MEMORY[0] = 0;
  v13 = GetProcAddrWrapper("Kernel32.dll", "CopyFileA");
  dword_100E2348 = sub_10018D80(v13, (int)sub_1003B270);
  if ( !dword_100E2348 )
    MEMORY[0] = 0;
  v14 = GetProcAddrWrapper("Kernel32.dll", "MoveFileA");
  dword_100E234C = sub_10018D80(v14, (int)sub_1003BDE0);
  if ( !dword_100E234C )
    MEMORY[0] = 0;
  v15 = GetProcAddrWrapper("Kernel32.dll", "DeleteFileA");
  dword_100E2350 = sub_10018D80(v15, (int)sub_1003B730);
  if ( !dword_100E2350 )
    MEMORY[0] = 0;
  v16 = GetProcAddrWrapper("Kernel32.dll", "GetModuleHandleA");
  result = sub_10018D80(v16, (int)sub_1003BB00);
  dword_100E2354 = result;
  if ( !result )
    MEMORY[0] = 0;
  return result;
}

This whole area of surrounding the /kdinstall is obfuscated and has multiple antidebug checks, like lots of them, there are multiple copies of the same function, in case you nullify the first couple

C++:
void __usercall IsDebuggerPresentWrap(int a1@<ebx>, int a2@<edi>, int a3@<esi>, int a4, int a5, int a6)

if ( a4 != 0xFFFFFFFF )
    sub_10097834();
  sub_100990B0(&v11, 0, 0x50);
  sub_100990B0(&v14, 0, 0x2CC);
  ExceptionInfo.ExceptionRecord = (PEXCEPTION_RECORD)&v11;
  ExceptionInfo.ContextRecord = (PCONTEXT)&v14;
  v24 = &v14;
  v23 = v6;
  v22 = v7;
  v21 = a1;
  v20 = a3;
  v19 = a2;
  v30 = __SS__;
  v27 = __CS__;
  v18 = __DS__;
  v17 = __ES__;
  v16 = __FS__;
  v15 = __GS__;
  v8 = __readeflags();
  v28 = v8;
  v26 = retaddr;
  v29 = &retaddr;
  v14 = 0x10001;
  v25 = savedregs;
  v11 = a5;
  v12 = a6;
  v13 = retaddr;
  v9 = IsDebuggerPresent();
  SetUnhandledExceptionFilter(0);
  if ( !UnhandledExceptionFilter(&ExceptionInfo) && !v9 && a4 != 0xFFFFFFFF )
    sub_10097834();
}

BOOL __usercall IsDebuggerPresentWrap2@<eax>(int a1@<ebx>, int a2@<edi>)
{
  HANDLE v2; // eax

  if ( IsProcessorFeaturePresent(0x17u) )
    __fastfail(5u);
  IsDebuggerPresentWrap(a1, a2, 0xC0000417, 2, 0xC0000417, 1);
  v2 = GetCurrentProcess();
  return TerminateProcess(v2, 0xC0000417);
}
A lot of strings are encrypted too. I was just doing this for fun for a half hour, once I saw all the obfuscation and ridiculously large XORing algorithms I just gave up :p

this also looked interesting, connected with the /kdinstall code:
1598073625698.png


Well anyways there is a lot of usermode antidebug stuff in there too.

From just searching around on the internet it sounds like they have a pretty tough HWID ban system.

But I did read that if you use handle hijacking to manually map a DLL, that bypasses the anticheat completely
 
Last edited:

Thiago

Newbie
Full Member
Nobleman
Feb 28, 2017
57
508
3
Interesting. Their anti cheat is shit indeed, you can just rename ReClass.exe to csrss.exe and have rpm/wpm access. I would like to see someone trying to evade their global ban tho, it's another story. I succesfully did it, but they log every one of your fails so the driver I was using got detected and I gave up :p
 
Apr 21, 2018
1
14
0
So in the past few days I've been reversing MTA: SA's anti cheat and I decided to start out with the driver (FairplayKD.sys) because I wanted to be able to inject my stuff without any problem. Here I'm gonna show you why this driver is a joke.

What basically happens here is this:

1-Check if target is gta_sa.exe or proxy_sa.exe
2- Check‬ if it isn't gta_sa/proxy_sa that's doing the operation
3- Check the operation (create/duplicate)
4.1- Check if some bits representing write access or other operations are set. Go to step 5 if true.
4.2- Check if the process that's creating/duplicating the handle is of type 1, 5, or 6. Go to step 5 if true.
5- Strip handle..

That means we can use type 7 (raidcall.exe) to inject our stuff in there. I've coded a basic manual mapping injector (thx @Broihon) to test it and look what happened: Screenshot (Injected random dll lul)

Get rekt shitty driver.

Moral of the story: raidcall is the real MVP
Just.. lol.

pd. do you have discord? pm me if yes pls <3
 

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,380
78,998
2,414
This is a very impressive writeup regarding Multi Theft Auto's fairplaykd.sys driver, considering you really just got into this stuff 1 year ago, from RPM/WPM game trainers last June to reversing drivers this August. Everyone take notice, if you just work hard and follow our tuts you can be dank too
 
Last edited:

Kleon742

Feature Enthusiast
Dank Tier VIP
Dank Tier Donator
Sep 2, 2018
357
16,058
42
Well done! I appreciate your work and the idea of sharing this with us!
 

iPower

Piece of shit
Escobar Tier VIP
Fleep Tier Donator
Jun 29, 2017
598
21,108
67
Got more information to share here. Idk when I'm posting it because I need to do some testing and I'm working on other projects rn. Gonna update this post when I'm finished with it.

It seems like they use IOCTL requests to get information about the state of the driver like if the callbacks are present and some other stuff. Gotta see where they call DeviceIoControl to see what they do with this information.
 
  • Like
Reactions: Kleon742 and Rake

Thiago

Newbie
Full Member
Nobleman
Feb 28, 2017
57
508
3
Are you working on netc.dll? I'd love to see how they detect basic stuff.
 

iPower

Piece of shit
Escobar Tier VIP
Fleep Tier Donator
Jun 29, 2017
598
21,108
67
Are you working on netc.dll? I'd love to see how they detect basic stuff.
Yeah I'm figuring out the structures it uses for IOCTL requests. Also it seems they hook some stuff but Idk what they are doing exactly.
 

Thiago

Newbie
Full Member
Nobleman
Feb 28, 2017
57
508
3
Hi Sir Can you add me on dc please? Skyflux#2148 or you can send ur dc via pm no matter. I have a question about mta but i can't ask from here
Are you the same guy who asked me about MTA in UC?
What's so important that has to be privately shared?
 

dretax

CIL Expert
Dank Tier VIP
Fleep Tier Donator
Mar 28, 2020
111
8,673
0
I have been looking around in iPower's reversed code, and I am mainly interested in finding the driver's altitude that It uses to register callbacks.
(Don't go hard on me, I'm still new to C, and IDA)
Here is what it looks like:

1588530701626.png


So as I can "tell" they are trying to make the altitude string generated by this code:
C:
    UNICODE_STRING DestinationString;
    STRING SourceString;
    char v22;

    DestinationString.Length = 0;
    DestinationString.MaximumLength = 12;
    DestinationString.Buffer = (PWSTR)&v22;

    int v2 = 0;
    int v15 = -73;
    char* v3 = &v15;
    char v4;
    do
    {
        v4 = ((3 - v2) ^ *v3 & 0x7F) - v2 * v2;
        ++v2;
        *v3++ = v4 & 0x7F;
    } while (v2 < 5u);

    SourceString.Length = 5;
    SourceString.MaximumLength = 5;
    SourceString.Buffer = &v15;
    RtlAnsiStringToUnicodeString(&DestinationString, &SourceString, 0);
I have been making my driver print out DestinationString, but It really doesn't show a correct value due in debugview to being unicode.
I'm also assuming that v15 is the number that I'm looking for. It's value is 1987738676.

Is that the altitude number that im looking for?
 

mambda

headass
Escobar Tier VIP
Trump Tier Donator
Jun 25, 2014
2,305
37,938
270
remember altitude is meant to be a string, so the numbers youre seeing are just chars on the stack ( that are obfuscated )

so what you want is v15 sure, but you want it after its been decrypted, i felt bored so i did a python script for it

Python:
# for the boys
alt = [ -73, -77, -76, -71, -65 ]
out_alt = [0,0,0,0,0]
for i in range(len(alt)):
    alt_val = alt[i]
    temp = ((3 - i) ^ alt_val & 0x7F) - i ** 2
    out_alt[i] = chr(temp & 0x7F)
print(''.join(out_alt))
prints out 40100. double check if i got anything wrong because i believe im missing a digit, but could be right
 
  • Like
Reactions: Rake and XdarionX

dretax

CIL Expert
Dank Tier VIP
Fleep Tier Donator
Mar 28, 2020
111
8,673
0
remember altitude is meant to be a string, so the numbers youre seeing are just chars on the stack ( that are obfuscated )

so what you want is v15 sure, but you want it after its been decrypted, i felt bored so i did a python script for it

Python:
# for the boys
alt = [ -73, -77, -76, -71, -65 ]
out_alt = [0,0,0,0,0]
for i in range(len(alt)):
    alt_val = alt[i]
    temp = ((3 - i) ^ alt_val & 0x7F) - i ** 2
    out_alt[i] = chr(temp & 0x7F)
print(''.join(out_alt))
prints out 40100. double check if i got anything wrong because i believe im missing a digit, but could be right
Yeah, I know It's a string made out of digits, but I couldn't really figure out that these are supposed to be characters on the stack.
That's new for me O_O

It's indeed strange that it's only 5 digits, but the source string's max length is also 5, so maybe It's indeed 5?
while ( v2 < 5u ); could give the answer, I would interpret It as a variable, but hence IDA tells its an unsigned int, I suppose It's just 5 as unsigned.

I'll test It later today, see if they are still using the altitude number stated here.
 
Last edited:

mambda

headass
Escobar Tier VIP
Trump Tier Donator
Jun 25, 2014
2,305
37,938
270
Yeah, I know It's a string made out of digits, but I couldn't really figure out that these are supposed to be characters on the stack.
That's new for me O_O

It's indeed strange that it's only 5 digits, but the source string's max length is also 5, so maybe It's indeed 5?
while ( v2 < 5u ); could give the answer, I would interpret It as a variable, but hence IDA tells its an unsigned int, I suppose It's just 5 as unsigned.

I'll test It later today, see if they are still using the altitude number stated here.
also how were you attempting to print it from your driver? printing a unicode string requires you to use proper format specifications. such as %S (not %s), or %wz i think works as well
 

dretax

CIL Expert
Dank Tier VIP
Fleep Tier Donator
Mar 28, 2020
111
8,673
0
Extra information will be merged to this post with funtime comments from the creator of fairplay.
The anti-cheat is dumb as fuck, the only strength they have is a non gdpr friendly HWID system.
Stay tuned, I'm making this post here today.
 
  • Like
Reactions: Kekz
Attention! Before you post:

Read the How to Ask Questions Guide
99% of questions are answered in the Beginner's Guide, do it before asking a question.

No Hack Requests. Post in the correct section.  Search the forum first. Read the rules.

How to make a good post:

  • Fill out the form correctly
  • Tell us the game name & coding language
  • Post everything we need to know to help you
  • Ask specific questions, be descriptive
  • Post errors, line numbers & screenshots
  • Post code snippets using code tags
  • If it's a large project, zip it up and attach it

If you do not comply, your post may be deleted.  We want to help, please make a good post and we will do our best to help you.

Community Mods