(2015-06-15 분석 내용)
핵쉴드는 매우 오래 이어온 안랩의 Anti-Cheat 솔루션인데 어느날부터 자취를 감췄습니다. 시장에서 수익이 되지 않자 안철수님이 철수하신 것 같습니다.
우리나라에 대표적으로 X-trap , GameGuard, HackShield 가 존재 하였는데 대부분 찾아보기 힘듭니다. 요즘은 XignCode가 많이 이용되고 있는 것 같아요.
저는 해당 프로그램을 분석하기전에 이미 분석된 과거 버전을 많이 찾아보았습니다.
바이너리 게놈 지도가 존재하듯이 소형프로그램이 아니라면 몇 번 패치를 한다해서 코드 구조가 눈에 띄게 변하지 않기에 OPCODE들도 크게 변하지 않을거라 생각한점. 또한 라이브러리가 로딩되는 주소는 랜덤하더라도 오프셋은 같은점에 착안하여 기존에 공개된 오프셋부분의 OPCODE들을 패턴식으로 검색하여 새로운 New Offset을 알아내어 프로세스와 모듈 스캐너 부분을 우회하여주었습니다.
하지만 Ehsvc.dll(HackShield Main DLL)를 건드리다 보니 CRC에 잡혀 정상적인 구동이 불가능했습니다.
문제는 핵쉴드의 HeartBeat Packet이였는데, 핵쉴드는 메인으로 볼 수 있는 ServiceDisPatch에서 16번 함수가 13번(HeartBeat)를 불러 만들어 패킷을 보내줍니다. (서버와 계속 주고 받기에 13번은 지속적으로 호출ㅠㅠ)
핵쉴드의 기능인 Non-Client-Bot을 원천적으로 봉쇄하기 위한 것으로 보이는 이 기능이,
13번함수가 최근 버전부터 CRC를 각기 다른 코드로 3번 검사하는것 같았습니다.
(구글링을 해보니 이전 버전에서 Ehsvc를 꽁꽁 얼리고 강제로 13번함수만 호출 하던 것이 가능했던 것 같습니다. )
그런데 여기서 검사하는 CRC코드는 메모리를 할당해 REP로 쓴다음에 검사하는터라 오프셋을 이용한 정확한 주소를 알 수 도 없었습니다.
그래서 변조한 코드에 Hardware BP를 걸고 CRC 함수를 찾아냈고, 다음 CRC가 생기는 부분을 알기위해 메모리를 할당해하는코드에 후킹을 걸어 어떠한 코드를 썻는지 비교후에 Check변수에 1,2,3을 넣어 다시 그부분에 동적 후킹을 걸어 해결하였고 문제없이 작동하였습니다. (이해가 안된다면 코드를 보시라)
일단은 CRC부분이 해쉬화 되는 터라 간단히 점프문을 바꿔서는 해결 되지는 않으니 코드를 수정하기전에 메모리를 할당후 EHsvc.dll의 덤프해 FakeBaseAddr이란 메모리를 만들었습니다.
CRC검사를 하는 부분에 후킹을 하여 비교하는 주소가 Ehsvc의 영역이라면 Ehsvc.dll의 ImageBase를 빼고 FakeBaseAddr의 ImageBase를 더 해서 원본으로 계산하도록 수정.
해치웠나..? 이제 CRC는 문제없는것인가?
하지만 어림도 없지.. CRC가 하나만 있진 않았습니다.
첫 번째 CRC가 메인을 검사한 후라면 CRC2는 CRC1을 검사하고 CRC3은 CRC1,CRC2를 검사하는 방법인것 같았습니다.
위 글에서 밝혔듯이 랜덤하게 생성되는 CRC코드는 저를 고통스럽게하여 동적으로 후킹하는 함수 DHSCRC_HOOK~ 함수를 탄생시켰습니다..
분석은 어려웠으나 CRC를 우회하는 코드에 대해서는 그리 어렵진않았던것 같습니다. 아래를 참조하시면 될 것 같아요.
//HShield SDK Version 5.7.14.555
#include <stdio.h>
#include <windows.h>
void Start();
LPVOID FakeBaseAddr = 0;
DWORD CRCSize=0x500000;//FullSize 8D9000
DWORD dwEhsvc=0;
DWORD dwEhsvcEnd=0;
DWORD hscrc_return_1=0;DWORD hscrc_return_2=0;DWORD hscrc_return_3=0;DWORD hscrc_return_4=0;DWORD hscrc_return_5=0;
DWORD Dhscrc_return_1=0;
DWORD Dhscrc_return_2=0;
DWORD Daddr1=0;
DWORD Dcheck=0;
unsigned int HSCRC_CHECK1 = 0;unsigned int HSCRC_CHECK2 = 0;unsigned int HSCRC_CHECK3 = 0;unsigned int HSCRC_CHECK4 = 0;
BYTE jmp[6] = { 0xe9,0x00,0x00,0x00,0x00,0xc3 };
bool bCompare(const BYTE* pData, const BYTE* bMask, const char* szMask)
{
for(;*szMask;++szMask,++pData,++bMask)
if(*szMask=='x' && *pData!=*bMask) return 0;
return (*szMask) == NULL;
}
DWORD FindPattern(DWORD dwAddress,DWORD dwLen,BYTE *bMask,char * szMask)
{
for(DWORD i=0; i<dwLen; i++)
if (bCompare((BYTE*)(dwAddress+i),bMask,szMask)) return (DWORD)(dwAddress+i);
return 0;
}
BOOL MemoryEdit(VOID *lpMem, VOID *lpSrc, DWORD len)
{
DWORD lpflOldProtect, flNewProtect = PAGE_READWRITE;
unsigned char * pDst = (unsigned char *)lpMem,
*pSrc = (unsigned char *)lpSrc;
if (VirtualProtect(lpMem, len, flNewProtect, &lpflOldProtect))
{
while (len-- > 0) *pDst++ = *pSrc++;
return 0;
}
return 1;
}
DWORD Hook(LPVOID lpFunction,DWORD ret,DWORD offset)
{
DWORD dwAddr= ret - offset;
DWORD dwCalc = ((DWORD)lpFunction - dwAddr - 5);
memcpy(&jmp[1], &dwCalc, 4);
WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwAddr, jmp, 6, 0);
return dwAddr;
}
void __declspec(naked) HSCRC_HOOK1()
{
__asm
{
cmp ecx,dwEhsvc
jl nobypass
cmp ecx,dwEhsvcEnd
jg nobypass
sub ecx,dwEhsvc
add ecx,[FakeBaseAddr]
nobypass:
mov dl,[ecx]
xor eax,edx
mov ecx,[ebp+0x10]
jmp [hscrc_return_1]
}
}
void __declspec(naked) HSCRC_HOOK2()
{
__asm
{
cmp ecx,dwEhsvc
jl nobypass
cmp ecx,dwEhsvcEnd
jg nobypass
sub ecx,dwEhsvc
add ecx,[FakeBaseAddr]
nobypass:
add al,[ecx]
pop ecx
push edx
mov ch,0xDF
jmp [hscrc_return_2]
}
}
void __declspec(naked) HSCRC_HOOK3()
{
__asm
{
cmp edx,dwEhsvc
jl nobypass
cmp edx,dwEhsvcEnd
jg nobypass
cmp edx,[HSCRC_CHECK1]
jl nobypassa
cmp edx,[HSCRC_CHECK2]
jg nobypassa
jmp bypass
nobypassa:
cmp edx,[HSCRC_CHECK3]
jl nobypass
cmp edx,[HSCRC_CHECK4]
jg nobypass
bypass:
sub edx,dwEhsvc
add edx,[FakeBaseAddr]
nobypass:
push [edx]
jmp [hscrc_return_3]
}
}
void __declspec(naked) HSCRC_HOOK4()
{
__asm
{
cmp edi,dwEhsvc
jl nobypass
cmp edi,dwEhsvcEnd
jg nobypass
sub edi,dwEhsvc
add edi,[FakeBaseAddr]
nobypass:
mov bl,BYTE PTR DS:[EDX+EDI]
mov ECX,DWORD PTR SS:[EBP+0x10]
jmp [hscrc_return_4]
}
}
void __declspec(naked) HSCRC_HOOK5()
{
__asm
{
cmp esi,dwEhsvc
jl nobypass
cmp esi,dwEhsvcEnd
jg nobypass
sub esi,dwEhsvc
add esi,[FakeBaseAddr]
REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
MOV DWORD PTR SS:[EBP-0x4],-0x1
sub esi,[FakeBaseAddr]
add esi,dwEhsvc
jmp nobypassa
nobypass:
REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
MOV DWORD PTR SS:[EBP-0x4],-0x1
nobypassa:
jmp [hscrc_return_5]
}
}
void __declspec(naked) DHSCRC_HOOK2()
{
__asm
{
cmp Dcheck,1
jne nobypassa
cmp ecx, dwEhsvc
jl nobypass1
cmp ecx, dwEhsvcEnd
jg nobypass1
sub ecx, dwEhsvc
add ecx, [FakeBaseAddr]
nobypass1:
xor ebx, ebx
mov bl, byte ptr[ecx]
xor edx, ebx
jmp Dhscrc_return_2
nobypassa:
cmp Dcheck, 2
jne nobypassb
cmp eax, dwEhsvc
jl nobypassa1
cmp eax, dwEhsvcEnd
jg nobypassa1
sub eax, dwEhsvc
add eax, [FakeBaseAddr]
nobypassa1:
mov dl, byte ptr[EAX]
add dword ptr[ebp-0x24],edx
inc eax
jmp Dhscrc_return_2
nobypassb:
cmp Dcheck, 3
jne nobypassc
cmp eax, dwEhsvc
jl nobypassb1
cmp eax, dwEhsvcEnd
jg nobypassb1
sub eax, dwEhsvc
add eax, [FakeBaseAddr]
nobypassb1:
mov dl, byte ptr[EAX]
add dword ptr[ebp-0x20],edx
inc eax
jmp Dhscrc_return_2
nobypassc:
}
}
void __declspec(naked) DHSCRC_HOOK1()
{
{
__asm
{
pushad
mov eax, [ESP + 0x38]
mov Daddr1, eax
mov Dhscrc_return_2, eax
}
__asm
{
add Daddr1, 0x6f
mov eax,DWORD PTR [Daddr1]
cmp byte ptr [eax],0x33
jne nobypassa
add Dhscrc_return_2, 0x6f
add Dhscrc_return_2, 6
}
Dcheck = 1;
Hook(DHSCRC_HOOK2,Daddr1,0);
__asm
{
jmp nobypassc
}
__asm
{
nobypassa:
sub Daddr1, 0x1B
mov eax,DWORD PTR [Daddr1]
cmp byte ptr [eax],0x8a
jne nobypassb
add Dhscrc_return_2, 0x54
add Dhscrc_return_2, 6
}
Dcheck = 2;
Hook(DHSCRC_HOOK2,Daddr1,0);
__asm
{
jmp nobypassc
}
__asm
{
nobypassb:
add Daddr1, 0x7
mov eax,DWORD PTR [Daddr1]
cmp byte ptr [eax],0x8a
jne nobypassc
add Dhscrc_return_2, 0x5B
add Dhscrc_return_2, 6
}
Dcheck = 3;
Hook(DHSCRC_HOOK2,Daddr1,0);
__asm{
nobypassc:
}
__asm
{
popad
pop edi
pop esi
pop ebp
pop ebx
pop ecx
ret
}
}
}
void Start()
{
DWORD dwSize=0x180000;
DWORD dwAddress=0;
do
{
dwEhsvc = (DWORD)GetModuleHandleA("EHsvc.dll");
Sleep(10);
} while (!dwEhsvc);
Sleep(100);
printf("ehsvc: %x\n",dwEhsvc);
memcpy(FakeBaseAddr,(const void *)dwEhsvc,CRCSize);
dwEhsvcEnd=dwEhsvc+CRCSize;
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x8b\x41\x24\x56\x8b", "xxxxx");
hscrc_return_1=dwAddress+0x3D112+7;
hscrc_return_2=dwAddress+0x3125D5+6;
hscrc_return_3=dwAddress+0x203BF0+0xA13F;
hscrc_return_4=dwAddress+0x1A08D+6;
hscrc_return_5=dwEhsvc+0x3F57D+9;
Dhscrc_return_1=dwAddress-0x46E3E;
HSCRC_CHECK1 = hscrc_return_1 - 0x100;
HSCRC_CHECK2 = hscrc_return_1 + 0x100;
HSCRC_CHECK3 = hscrc_return_2 - 0x100;
HSCRC_CHECK4 = hscrc_return_2 + 0x100;
Hook(HSCRC_HOOK1,hscrc_return_1,7);
Hook(HSCRC_HOOK2,hscrc_return_2,6);
Hook(HSCRC_HOOK3,hscrc_return_3,0xA13F);
Hook(HSCRC_HOOK4,hscrc_return_4,6);
Hook(HSCRC_HOOK5,hscrc_return_5,9);
Hook(DHSCRC_HOOK1,Dhscrc_return_1,0);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x55\x8B\xec\x83\xec\x28\x53\x56\x57\x89\x4D", "xxxxxxxxxxx"); //CALLBACK1 OK
//printf("CALLBACK1 %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\xC3",1);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x83\xC4\x00\x85\xDB\x75\x15", "xx?xxxx"); //CALLBACK2 OK
//printf("CALLBACK2 %X\n",dwAddress);
MemoryEdit((void *)(dwAddress+5), (void *)"\x90\x90",2);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x83\xec\x08\x53\x55\x56\x57", "xxxxxxx"); //DETECTION OK
//printf("DETECTION %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\xc2\x04\x00",3);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x75\x40\x8b\x46\x00\x8b\x7f\x00", "xxxx?xx?"); //ASSEMBLY OK
//printf("ASSEMBLY %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\x90\x90",2); //9090 or eb40
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x85\xC0\x74\x0A\x83\x7D\xE0\x02", "xxxxxxxx"); //ANTICRASH OK
//printf("ANTICRASH %X\n",dwAddress);
MemoryEdit((void *)(dwAddress+2), (void *)"\x90\x90",2);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x8b\x87\x00\x00\x00\x00\x85\xc0\x0F", "xx????xxx");//NANOCHECK1
//printf("NANOCHECK1 %X\n",dwAddress);
MemoryEdit((void *)(dwAddress+8), (void *)"\x90\x90\x90\x90\x90\x90",6);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x8b\xb4\x24\x1c\x01\x00\x00\x85\xf6\x0f", "xxxxxxxxxx");//NANOCHECK2
//printf("NANOCHECK2 %X %x %x\n",dwAddress,*(BYTE *)(dwAddress+10),*(BYTE *)(dwAddress+11));
MemoryEdit((void *)(dwAddress+9), (void *)"\x90\x90\x90\x90\x90\x90",6);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x85\xc0\x74\x5b\xc6\x85", "xxxxxx");//NANOCHECK3
//printf("NANOCHECK3 %X\n",dwAddress);
MemoryEdit((void *)(dwAddress+2), (void *)"\x90\x90",2);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x55\x8b\xec\x81\xec\x00\x00\x00\x00\x56\x57\xb9", "xxxxx????xxx");//Hard
//printf("Hard %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\x31\xc0\xc3",3);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x81\xec\x00\x00\x00\x00\x53\x8b\x9c\x24\x00\x00\x00\x00\x85", "xx????xxxx????x");//Hard2
//printf("Hard2 %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\x31\xc0\xc3",3);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x81\xec\x00\x00\x00\x00\x56\x57\xb9\x00\x00\x00\x00\x33\xc0\x8d\x7c\x24\x00\xf3\xab", "xx????xxx????xxxxx?xx");//Hard3
//printf("Hard3 %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\x31\xc0\xc3",3);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x55\x8b\xec\x6a\xff\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x64\xa1\x00\x00\x00\x00\x50\x64\x89\x25\x00\x00\x00\x00\x81\xec\x00\x00\x00\x00\x53\x56\x57\x89\x65\xe8\xbe\x00\x00\x00\x00\x89\x75", "xxxxxx????x????xx????xxxx????xx????xxxxxxx????xx");//Hard4
//printf("Hard4 %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\x31\xc0\xc2\x18\x00",5);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x55\x8b\xec\x6a\xff\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x64\xa1\x00\x00\x00\x00\x50\x64\x89\x25\x00\x00\x00\x00\x83\xec\x00\x53\x56\x57\x89\x00\x00\x8b\x00\x00\x8b\xfa", "xxxxxx????x????xxxxxxxxxxxxxxxx?xxxx??x??xx");//Soft1
//printf("Soft1 %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\x31\xc0\xc3",3);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x81\xec\x00\x00\x00\x00\xa0\x00\x00\x00\x00\x53\x55\x56\x8b\xe9\x57\x88", "xx????x????xxxxxxx");//PROCESSSCANNER OK
printf("Process Scaner %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\x31\xc0\xc2\x04\x00",5);
dwAddress=FindPattern(dwEhsvc, dwSize, (PBYTE)"\x8b\x41\x24\x56\x8b", "xxxxx"); //MODULESCANNER OK
printf("MODULESCANNER %X\n",dwAddress);
MemoryEdit((void *)(dwAddress), (void *)"\x31\xc0\xc2\x04\x00",5);
MemoryEdit((void *)(dwEhsvc+0xDADAA), (void *)"\xb8\x00\x00\x00\x00",5);
}
BOOL APIENTRY DllMain( HMODULE hModul,DWORD ul_reason_for_ca,LPVOID lpReserve)
{
switch (ul_reason_for_ca)
{
case DLL_PROCESS_ATTACH:
AllocConsole();
if ( (FakeBaseAddr = VirtualAlloc(NULL, CRCSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL )
{
printf("fail to VirtualAlloc (), %d\n", GetLastError());
return -1;
}
freopen( "CON", "w", stdout ) ;
printf("FakeBaseAddr : %x\n",FakeBaseAddr);
CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)Start,NULL,NULL,NULL);
MessageBoxA(NULL,"HackShield Bypass 2015-06-12","By Empier",MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
소스안에는 CRC를 제외한 프로세스스캐너 모듈스캐너를 변조하는 코드도 있으나
글의 취지는 CRC Bypass 설명에 초점을 맞췄습니다. 아래는 해당 코드가 적용된 게임의 실행화면입니다.
시연 동영상:
'리버싱' 카테고리의 다른 글
안드로이드 dex, Native 를 동시 디버깅 하는 방법 (1) | 2020.05.04 |
---|---|
[PE+Reversing] 프리서버 레드문 개발 (2) | 2019.12.09 |
안드로이드 리퍼블릭 분석 (1) | 2019.09.19 |
카카오 멜론 DRM 해제하기 ( .dcf -> .mp3 ) (40) | 2019.06.22 |
IOS App 동적분석하기 with LLDB (Tested on ios 11.4.1) (0) | 2019.06.10 |
댓글