/OneByteLdr

Bypass for CS:GO's LoadLibrary injection prevention mechanism, achieved by patching one byte of game memory.

Primary LanguagePythonMIT LicenseMIT

OneByteLdr

Bypass for CS:GO's LoadLibrary injection prevention mechanism, achieved by patching one byte of game memory.

How it works

The game hooks NtOpenFile function from ntdll.dll. The disassembly of the replacement function is listed below:

push    ebp
mov     ebp, esp
push    esi
mov     esi, [ebp+arg_8]
mov     eax, [esi+8]
mov     eax, [eax+4]
test    eax, eax ; is ObjectAttributes->ObjectName->Buffer not null
jz      short loc_4095BB ; we patch this with 'jmp' to skip loc_4095A1

loc_4095A1:

test    byte ptr [ebp+arg_4], 20h ; check if DesiredAccess has FILE_EXECUTE flag set (whether we're loading a dll)
jz      short loc_4095BB ; if it's not a dll, call original
push    eax ; ObjectAttributes->ObjectName->Buffer
call    sub_40D460 ; verify the dll
test    al, al ; check if dll is allowed to load
jnz     short loc_4095BB ; if the dll passed verification call original
mov     eax, 0C0000034h ; return STATUS_OBJECT_NAME_NOT_FOUND
pop     esi
pop     ebp
retn    18h

loc_4095BB:

push    [ebp+arg_14]
push    [ebp+arg_10]
push    [ebp+arg_C]
push    esi
push    [ebp+arg_4]
push    [ebp+arg_0]
call    originalNtOpenFile
pop     esi
pop     ebp
retn    18h

Alternative approach

An alternative approach which also bypasses anti-loadlibrary protection is to restore 5 first bytes of original NtOpenFile. Below is an example of that coded in C:

// Restore original NtOpenFile from external process
LPVOID ntOpenFile = GetProcAddress(LoadLibraryW(L"ntdll"), "NtOpenFile");
if (ntOpenFile) {
    char originalBytes[5];
    memcpy(originalBytes, ntOpenFile, 5);
    WriteProcessMemory(csgoProcessHandle, ntOpenFile, originalBytes, 5, NULL);
}

For increased safety you can backup the patched first five bytes of NtOpenFile and restore them after injection.

Thread creation detection

Many Manual Mapping dll injectors create thread in target process to load dll or perform initialization. This is what CS:GO devs target in addition to LoadLibrary detection. Thread detection doesn't affect LoadLibrary injectors.

DllMain function of client.dll contains code that on DLL_THREAD_ATTACH calls NtQueryInformationThread function from ntdll.dll to get start address of newly created thread:

push    ebp
mov     ebp, esp
mov     eax, [ebp+fdwReason]
sub     esp, 20h
cmp     eax, 1
jz      loc_106390D3 ; if fdwReason is DLL_PROCESS_ATTACH, skip
test    eax, eax
jz      loc_106390D3 ; if fdwReason is DLL_PROCESS_DETACH, skip
cmp     eax, 2
jnz     loc_106390D3 ; if fdwReason is not DLL_THREAD_ATTACH, skip
push    esi
push    edi
mov     [ebp+phModule], 0
call    ds:GetCurrentThreadId
push    offset aNtqueryinforma ; "NtQueryInformationThread"
push    offset aNtdllDll ; "ntdll.dll"
mov     edi, eax
call    ds:GetModuleHandleA
push    eax
call    ds:GetProcAddress
mov     esi, eax
test    esi, esi
jz      short loc_106390C6 ; we patch this with 'jmp' to skip loc_106390B2
push    0
push    4
lea     eax, [ebp+fdwReason]
push    eax
push    9 ; ThreadQuerySetWin32StartAddress
call    ds:GetCurrentThread
push    eax
call    esi ; get thread start address from NtQueryInformationThread
test    eax, eax
jnz     short loc_106390C6
push    1Ch
lea     eax, [ebp+Buffer]
push    eax
push    [ebp+fdwReason]
call    ds:VirtualQuery
lea     eax, [ebp+phModule]
push    eax
push    [ebp+fdwReason]
push    6
call    ds:GetModuleHandleExA
mov     ecx, [ebp+Buffer.Protect]
test    eax, eax ; check if the address leads to a valid module
jz      short loc_106390B2 ; if the code's been manually mapped save thread's characteristics
cmp     ecx, 40h
jnz     short loc_106390C6

loc_106390B2:
mov     eax, [ebp+fdwReason]
mov     dword_1528625C, eax ; save thread start address
mov     dword_15286260, edi ; save thread id
mov     dword_15286264, ecx ; save protection of memory at thread start address

loc_106390C6:
pop     edi
mov     eax, 1
pop     esi
mov     esp, ebp
pop     ebp
retn    0Ch

loc_106390D3:
mov     eax, 1
mov     esp, ebp
pop     ebp
retn    0Ch