[RLS][TUT]Mscrc/ggcrc Bypasses

On domingo, 12 de septiembre de 2010 0 comentarios

This was NOT made by me credits go to XOR but still a thanks would be awesome
(I Strongly recommend to Sticky this mods)

example bypass for GGCRC is attached at bottom of tut


MapleStory's CRC Routine Analysis - Irwin/x0r

Since the MapleStory memory integrity routine has been scrutinized lately by many Tool communities I thought it would be fair that I post a 'deep' analysis of it, so there's no more need to rummage through tutorials to see how it works. Since the routine has already been bypassed I won't really focus on where to start, since it has already been bypassed to an extent. Instead I'll just focus on where the community currently is and what the current bypass is doing.
The Current Bypass

[enable]
alloc(newmem,2048)
alloc(dump,3670018) // Dump size
loadbinary(dump,crc.CEM) // Dump file

newmem:
cmp ecx,00400000 // Image base
jb @f
cmp ecx,00780000 // Image end
ja @f
add ecx, dump-00400000 // Dump offset
@@:
// Original code
mov eax,[ebp+10]
push esi
push edi
jmp 00451cbf
00451cba:
jmp newmem // Jump to hook

[disable]
00451cba:
mov eax,[ebp+10]
push esi
push edi

dealloc(newmem)
dealloc(blaaaa) As you can see, the current bypass just checks to see if ECX is within the range of memory to be checked, if it is then it is adjusted to point to the copy of the game's memory which is unmodified.
The Inner Workings Of The Bypass

The bypass works very simply and is generic per version of MapleStory, only needing to change the game dump when there's a patch of something of that manner, but how does it work? Putting it simply, the bypass utilizes an optimized & modified CRC32 algorithm on the memory, so we can use a PEiD plugin called KANAL to look for generic cryptography signatures:

KANAL's feedback on the analysis of an unpacked cop of MapleStory. 'CRC32b' being the table used for the integrity check.
So now we know where the CRC32 table is located, lets have a look at how it is used:
; =============== S U B R O U T I N E =======================================
; Attributes: bp-based frame
; ULONG __cdecl CalculateCRC32(LPVOID lpStartAddress, SIZE_T nSize, ULONG ulInitialHash, BOOL bUseHash)

CalculateCRC32 proc near
lpStartAddress = dword ptr 8
nSize = dword ptr 0Ch
ulInitialHash = dword ptr 10h
bUseHash = dword ptr 14h

push ebp
mov ebp, esp
cmp [ebp+bUseHash], 0
mov ecx, [ebp+lpStartAddress]
jz short loc_473980
xor [ebp+ulInitialHash], ecx

loc_473980:
mov eax, [ebp+ulInitialHash] ; Generic bypass hook-point
push esi
push edi
mov edi, [ebp+nSize]
cmp edi, 10h
jb loc_473B22
mov esi, edi
push ebx
shr esi, 4

CalculateCRC:
movzx ebx, byte ptr [ecx]
mov edx, eax
shr edx, 18h
xor edx, ebx
mov ebx, eax
mov eax, ds:CRC32Table[edx*4]
shl ebx, 8
xor eax, ebx
movzx ebx, byte ptr [ecx+1]
mov edx, eax
shr edx, 18h
xor edx, ebx
mov edx, ds:CRC32Table[edx*4]
shl eax, 8
xor edx, eax
mov eax, edx
shr eax, 18h
shl edx, 8
inc ecx
movzx ebx, byte ptr [ecx+1]
xor eax, ebx
mov eax, ds:CRC32Table[eax*4]
xor eax, edx
inc ecx
movzx ebx, byte ptr [ecx+1]
mov edx, eax
shr edx, 18h
xor edx, ebx
mov edx, ds:CRC32Table[edx*4]
shl eax, 8
xor edx, eax
inc ecx
movzx ebx, byte ptr [ecx+1]
mov eax, edx
shr eax, 18h
xor eax, ebx
mov eax, ds:CRC32Table[eax*4]
shl edx, 8
xor eax, edx
inc ecx
movzx ebx, byte ptr [ecx+1]
mov edx, eax
shr edx, 18h
xor edx, ebx
mov edx, ds:CRC32Table[edx*4]
shl eax, 8
xor edx, eax
inc ecx
movzx ebx, byte ptr [ecx+1]
mov eax, edx
shr eax, 18h
xor eax, ebx
mov eax, ds:CRC32Table[eax*4]
shl edx, 8
xor eax, edx
inc ecx
movzx ebx, byte ptr [ecx+1]
mov edx, eax
shr edx, 18h
xor edx, ebx
mov edx, ds:CRC32Table[edx*4]
inc ecx
movzx ebx, byte ptr [ecx+1]
shl eax, 8
xor edx, eax
mov eax, edx
inc ecx
shr eax, 18h
xor eax, ebx
mov eax, ds:CRC32Table[eax*4]
movzx ebx, byte ptr [ecx+1]
shl edx, 8
xor eax, edx
inc ecx
mov edx, eax
shr edx, 18h
xor edx, ebx
mov edx, ds:CRC32Table[edx*4]
shl eax, 8
xor edx, eax
inc ecx
movzx ebx, byte ptr [ecx]
mov eax, edx
shr eax, 18h
xor eax, ebx
mov eax, ds:CRC32Table[eax*4]
movzx ebx, byte ptr [ecx+1]
shl edx, 8
xor eax, edx
mov edx, eax
shr edx, 18h
xor edx, ebx
mov edx, ds:CRC32Table[edx*4]
shl eax, 8
xor edx, eax
inc ecx
movzx ebx, byte ptr [ecx+1]
mov eax, edx
shr eax, 18h
xor eax, ebx
mov eax, ds:CRC32Table[eax*4]
shl edx, 8
xor eax, edx
inc ecx
movzx ebx, byte ptr [ecx+1]
mov edx, eax
shr edx, 18h
xor edx, ebx
mov edx, ds:CRC32Table[edx*4]
inc ecx
movzx ebx, byte ptr [ecx+1]
shl eax, 8
xor edx, eax
inc ecx
mov eax, edx
shr eax, 18h
xor eax, ebx
mov eax, ds:CRC32Table[eax*4]
movzx ebx, byte ptr [ecx+1]
shl edx, 8
xor eax, edx
inc ecx
mov edx, eax
shr edx, 18h
xor edx, ebx
mov edx, ds:CRC32Table[edx*4]
shl eax, 8
xor eax, edx
inc ecx
sub edi, 10h
dec esi
jnz CalculateCRC
pop ebx

loc_473B22:
cmp edi, 1
jb short loc_473B41

loc_473B27:
movzx esi, byte ptr [ecx]
mov edx, eax
shr edx, 18h
xor edx, esi
mov edx, ds:CRC32Table[edx*4]
shl eax, 8
xor eax, edx
inc ecx
dec edi
jnz short loc_473B27

loc_473B41:
pop edi
pop esi
pop ebp
retn
CalculateCRC32 endp
This is fairly self-explanitory, the function works in this manner:
ULONG __cdecl CalculateCRC32(
__in LPVOID lpBuffer,
__in SIZE_T nSize,
__in_opt ULONG ulInitialHash,
__in BOOL bUseInitialHash
);

Parameters
lpBuffer - The buffer for which the CRC is to be calculated for.
nSize
- The size of the region to hashed / 16.
ulInitialHash - This parameter is only valid if bUseInitialHash is TRUE, ulInitialHash is added to the hash if it is specified.
bUseInitialHash - If this parameter is specified then ulInitialHash is added onto the return.

Return
The function returns the CRC fo the region specified.

So now we know how the CRC is calculated, what calls the calculation routine?
InitialHash:
push ebx
push esi
xor esi, esi
push esi ; bUseHash
push esi ; ulInitialHash
lea eax, [ebp+lpBuffer]
push 2 ; nSize
push eax ; lpStartAddress
call CalculateCRC32
add esp, 10h
mov ebx, eax

CalculateHash:
mov eax, [edi+1Ch]
test eax, eax
jz short SendHash
cmp esi, [eax-4]
jnb short SendHash
push 1 ; bUseHash
lea eax, [eax+esi*8]
push ebx ; ulInitialHash
push dword ptr [eax+4] ; nSize
push dword ptr [eax] ; lpStartAddress
call CalculateCRC32
add esp, 10h
mov ebx, eax
inc esi
jmp short CalculateHash Simple enough, right? The game first generates an initial hash which it then uses on the other hashes, according to the buffer size. The really great thing about this is that even though this is happening, the game calls completely static sizes. So it's very predictable.
Creating A Bypass

Creating a bypass for this is very easy, as seen with the public variant of the MapleStory CRC bypass. This is however not optimal, instead what you could do is build up a list of possible return CRCs and since the addresses and address sizes are predictable you can simply return the CRC of the region which is being requested instead of creating large dump. I've done this and my CRC lookup table was under 100 kilobytes whilst all generic CRC bypasses I've come across have an average of a 4 megabyte dump, this is a large tradeoff in both speed and size.
Expect an example source in the near future, I'm tired as **** at the moment.

Last modified: 28 Febuary 2008 07:12:15

__________________________________________________ _____________


GameGuard's CRC Routine Analysis - Irwin/x0r
GameGuard has a new function which has been enabled for MapleStory which is essentially a memory integrity check routine. It routinely checks MapleStory's memory and then closes the game if it detects a change. One must think that it was enabled due to the fact that MapleStory's own memory integrity routine was comprised to an unacceptable point. Anyway, it is fairly simple to enable and only needs one setting to be modified, since when GameGuard is loaded the game must call LoadGameGuard("SettingsFile.ini") and in the settings file there is a parameter called GAMECRC, apparently when it is set to '2' it will perform a routine check of MapleStory's memory spanning from the code base to the code end (meaning you can modify dynamic values like the Import Address Table and things of such a manner), here's a copy of MapleStory's INI configuration file:
[GAMEMON]
GAME_NAME=MapleStoryUS
UPDATE_SERVER=gameguard.mapleglobal.com
UPDATE_PATH=/nProtect/GameGuard/RealServer/
BACKUP_SERVER=63.251.217.184
BACKUP_PATH=/nProtect/GameGuard/RealServer/
OPTION_VALUE=0
SPEEDCHECK_INTERVAL=1000
SENDERL=1
GAMECRC=2
USE_GGSCAN=1
LOG_SERVER=211.218.237.113
REVISION=47 The Previous Bypass

There is currently no public bypass but the previous iteration of the GameGuard CRC routine used the ReadProcessMemory API to read the game's memory and then perform a check to the local copy GameMon.des had. Unfortunately this method has been patched and no longer works. There is more to it than meets the eye however...
GameGuard makes copies of all system files which it uses APIs with (e.g. kernel32, ntdll, user32, etc) so that we cannot hook their functions with ease. So instead of normally calling the real APIs they would generate a table of pointers to the functions and call those (reminiscent to a pseudo IAT). Since hooking the copies of these functions isn't really feasible the bypass instead modified the pointer to ReadProcessMemory and made it redirect to their hook. A pseudo bypass hook would work like this:
BOOL WINAPI ReadProcessMemoryHook(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
{
if ( GetProcessId(hProcess) == dwMapleStoryPID )
{
if ( (lpBaseAddress <= 0x00400000) || (lpBaseAddress+nSize >= 0x00800000) ) // See if memory being read is within game's image
lpBaseAddress += lpDumpOffset; // Adjust the base address to read the dump instead of the real game memory
}
return ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead);
} Once bypasses started hooking their ReadProcessMemory pointer they decided to drop using ReadProcessMemory directly, their function table still existed however. After GameGuard had patched the GGCRC bypass, their function table entry which held kernel32.ReadProcessMemory now held ntdll.NtDeviceIoControlFile, this held no relevance at first but after some members of the community decided to look at how it was used its usage started to become more apparent...
Examining GameMon would generally be hard since it is using a very strong packer; Themida. Fortunately GameMon does not utilize Themida too well and the functions are not completely encrypted at run-time. So using a small dumper I coded it is possible to create a dump of GameMon.des whilst it is running. Now when looking at what how NtDeviceIoControl is utilized, it is clearly a function wrapper:
/* NTSTATUS NtDeviceIoControlFile(
* HANDLE FileHandle,
* HANDLE Event,
* PIO_APC_ROUTINE ApcRoutine,
* PVOID ApcContext,
* PIO_STATUS_BLOCK IoStatusBlock,
* ULONG IoControlCode,
* PVOID InputBuffer,
* ULONG InputBufferLength,
* PVOID OutputBuffer,
* ULONG OutputBufferLength
*/ );

FileHandle = HANDLE ptr 4
Event = HANDLE ptr 8
ApcRoutine = PIO_APC_ROUTINE ptr 0Ch
ApcContext = PVOID ptr 10h
IoStatusBlock = PIO_STATUS_BLOCK ptr 14h
IoControlCode = ULONG ptr 18h
InputBuffer = PVOID ptr 1Ch
InputBufferLength = ULONG ptr 20h
OutputBuffer = PVOID ptr 24h
OutputBufferLength = ULONG ptr 28h


mov eax, ds:ServiceIndex
test eax, eax
jnz short loc_13882
mov eax, ds:ServiceID
test eax, eax
jnz short loc_13888
loc_13882:
jmp ds:NtDeviceIoControlFilePointer
; ---------------------------------------------------------------------------
loc_13888:
mov eax, ds:ServiceID
lea edx, [esp+FileHandle]
int 2Eh
retn 28h
NtDeviceIoControlFile end
The NtDeviceIoControlFile wrapper/substitute.
This is pretty straight forward, first the wrapper checks to see if the service index is initialized at all, if it isn't it will use the NtDeviceIoControlFile wrapper. If it is, it will then check to see if the individual NtDeviceIoControlFile service is in place, if it is then it will proceed to directly call the service using int 2E, a direct system call (ala SYSENTER). In pseudo-code:
NTSTATUS __declspec(naked) NtDeviceIoControlFile(
HANDLE FileHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG IoControlCode,
PVOID InputBuffer,
ULONG InputBufferLength,
PVOID OutputBuffer,
ULONG OutputBufferLength
)
{
if ( (ServiceIndex == 0) && (ServiceID == 0))
_asm jmp [NtDeviceIoControlFilePointer];
else
_asm
{
mov eax, [ServiceID]
lea edx, [esp+FileHandle]
int 0x2E
retn 0x28
}
} So now we must look at what uses it. After some quick snooping it's clear that one of these not only matches the preparation of a read function, it takes the exact same parameters as ReadProcessMemory. So now lets look at the ReadProcessMemory Clone...
; =============== S U B R O U T I N E =======================================
; Attributes: bp-based frame
; BOOL WINAPI ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten)

ReadProcessMemory proc near

var_6FC = byte ptr -6FCh
var_138 = byte ptr -138h
var_34 = dword ptr -34h
InputBuffer = byte ptr -30h
OutputBuffer = byte ptr -10h
hProcess = dword ptr 8
lpBaseAddress = dword ptr 0Ch
lpBuffer = dword ptr 10h
nSize = dword ptr 14h
lpNumberOfBytesWritten= dword ptr 18h


push ebp
mov ebp, esp
push 0FFFFFFFFh
push offset SEH_15440
mov eax, fs:SEH
push eax
mov fs:SEH, esp
sub esp, 6F0h
push ebx
push esi
push edi
push offset unk_4C5730
mov [ebp+var_34], offset unk_4C5730
nop
call near ptr 77722E69h
xor ecx, ecx
mov dword ptr [ebp+OutputBuffer+0Ch], ecx
push eax
mov eax, [ebp+4]
mov [ebp-1Ch], eax
pop eax
mov eax, ds:dword_4B1664
cmp eax, 0FFFFFFFFh
jz loc_155AE
mov ebx, [ebp+lpBaseAddress]
mov esi, [ebp+nSize]
mov dword ptr [ebp+OutputBuffer], ecx ; nSize
mov ecx, [ebp+lpBuffer]
mov dword ptr [ebp+InputBuffer+8], ecx ; lpBuffer
mov ecx, ds:dword_4B1668
lea edx, [ebx+esi]
mov edi, [ebp+hProcess]
add ecx, edx
push 4 ; OutputBufferLength
mov ds:dword_4B1668, ecx
lea ecx, [ebp+OutputBuffer]
push ecx ; OutputBuffer
lea edx, [ebp+InputBuffer]
push 20 ; InputBufferLength
push edx ; InputBuffer
lea ecx, [ebp-18h]
push 84020044h ; IoControlCode
push ecx ; IoStatusBlock
push 0 ; ApcContext
push 0 ; ApcRoutine
push 0 ; Event
push eax ; FileHandle
mov dword ptr [ebp+InputBuffer], ebx ; lpBaseAddress
mov dword ptr [ebp+InputBuffer+4], esi ; nSize
mov dword ptr [ebp+InputBuffer+0Ch], esi
mov dword ptr [ebp+InputBuffer+10h], edi ; hProcess
call NtDeviceIoControlFile
;-------------------- It's now apparent that there is a structure of sorts being passed on through IOCTL 0x84020044, this is the memory check function without a doubt... but how is it structured? (Note: this is updated, since the version of GameMon I analyzed is now outdated)
Entry

Usage
Offset lpBaseAddress The base address from which to read memory from.
0
hProcess A handle to the process which is to have its memory read.
4
lpBuffer The buffer to recieve the read memory.
16
This along with knowing that the first DWORD in the OutputBuffer is actually the number of bytes to copy allows us to effectively detour and reroute any memory being read.
Creating A Bypass

Creating a bypass for this is easy to do. All you need to do is locate the pointers for the service index & ID and make sure they stay NULL, since it will always call the user-mode alternative if you do so. Then all you need to do is modify the pointer for NtDeviceIoControl and then modify the memory being read. An example bypass is attached.

0 comentarios:

Publicar un comentario