본문 바로가기
리버싱

[HackShield] SDK Version 5.7.14.555 Bypass

by dladbru 2019. 12. 2.

 (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을 넣어 다시 그부분에 동적 후킹을 걸어 해결하였고 문제없이 작동하였습니다.  (이해가 안된다면 코드를 보시라)

 

 

 

Ehsvc.dll  의 크기만큼 메모리 할당
Ehsvc.dll  메모리 복사

 

일단은 CRC부분이 해쉬화 되는 터라 간단히 점프문을 바꿔서는 해결 되지는 않으니 코드를 수정하기전에 메모리를 할당후 EHsvc.dll의 덤프해 FakeBaseAddr이란 메모리를 만들었습니다. 

 

 

CRC연산을 하는 함수를 후킹한 코드

 

CRC검사를 하는 부분에 후킹을 하여 비교하는 주소가 Ehsvc의 영역이라면 Ehsvc.dll의 ImageBase를 빼고 FakeBaseAddr의 ImageBase를 더 해서 원본으로 계산하도록 수정.  

 

 

 

후킹코드 삽입된 부분 확인 ㅋㅋ

 

제대로 내 DLL코드로 넘어온다..!

 

 

해치웠나..? 이제 CRC는 문제없는것인가?

 

 

동적 할당해 연산하는 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 설명에 초점을 맞췄습니다. 아래는 해당 코드가 적용된 게임의 실행화면입니다.

 

 

시연 동영상:

 

반응형

댓글