Anti Game hacking 프로그램의 구현.


작성자 : Dual5651 (dual@null2root.org) in Null@Root

소개

 이번 글에서는 그 시점에서 알게된 Anti GH 프로그램이 갖추어야할 기능이 무엇
이며, 그 기능의 구현에 필요한 코드와 그 기능의 존재하는 약점등에 대해서 다루어
보고자 합니다. 이 글의 구성은 다음과 같습니다.

1. Anti Game Hacking 프로그램에 필요한 기능들.
2. Anti Game Hacking Program 구현에 필요한 코드들.


1. Anti Game Hacking 프로그램에 필요한 기능들.

 Anti GH 프로그램은 어떤 기능들이 필요할까요?

1. (Binary /Data File) Packing / Encrypting

2. GH Program Detecting

3. AutoPlay Blocking

4. Message Hooking Blocking

5. Unauthorized memory access Blocking

6. Debugging Blocking

7. SpeedHack Blocking

8. Integrity Checking

9. System descriptor restoring

 첫번째로 필요한 기능으로써, 실행파일 및 데이터 파일의 압축 또는 암호화
들 수 있습니다. 이 부분에 대해선 다른 의견을 가지고 있는 분들도 많은거 같습니다.
이런 분들의 주장에 근거로는 패킹 및 인크라입팅에 의존하면, 프로그램 자체의
Secure Coding이 약해지고, 대부분 이러한 패킹 및 인크라입팅은 범용적으로
사용되는 상용프로그램일 가능성이 높음으로, 풀릴(뚫릴 - 그만큼 공략하는 사람도
많기 때문에) 가능성도 높으며, 이로 인한 결과는 상상이상으로 치명적일 것이기
떄문입니다. 그럼에도 불구하고, 최근 실행파일의 패킹을 하지 않은 프로그램을
더 찾기 힘들만큼 많이 사용되어 지고 있으며, 또 어느정도 첫번째  방어선으로써
기대되어 지는 기능을 하여주고 있습니다.

그렇다면 게임의 데이터 파일들에 대한 패킹 및 인크라입팅은 왜 필요한 것일까요?
최근 Anti GH 프로그램의 도입으로 Memory Hacking이 힘들어지자, 최근 공격자들은
게임의 데이터파일을 조작하는 방식의(고전적이라면 고전적인 방법의) 해킹을 다시
시도하기 시작하였습니다. 실제 2007년도 국내의 어떤 게임에서는 해당 방식에 의한
GH이 이루어지기도 하였습니다.

 두번째로 필요한 기능으로써, 게임해킹 프로그램의 감지를 들 수 있습니다.
최근 가장 많이 쓰이는 GH방식으로 Memory Hacking이 있습니다.
이러한 MH(Memory Hacking)은 공격자 입장에서 Generic Game Hacking Tool
(ex: CheatEngine,Tsearch and so on...)로 우선 대상 Game에 대한 분석을 하고
그 후 그 게임만을 대상으로 하는 게임 트레이너가 나오게 됩니다.
즉, 범용 게임 해킹툴의 완전한 차단은 특정 게임 대상 트레이너의 제작을 막는 방법
이기도 합니다. 물런 이는 반드시 그러한 것은 아니며, 거의 근접한 %의 역활을 할 수
있습니다. 감지하는 방법으로써는 첫번째로 패턴 매칭이 있습니다.
패턴 매칭이란 특정한 GH 프로그램에서 발견되는 문자열이나, 코드배열등을
Anti GH프로그램에서 모든 프로세스에서 찾아 보고, 있다면 GH 프로그램으로
간주하는 방식입니다. 두번째로 Finding Named Object기법이 있습니다.
예를들어,
 hFileMap = CreateFileMapping((HANDLE)INVALID_HANDLE_VALUE,NULL,
  PAGE_READWRITE,0,dwSize,"DUALMEM");

의 경우 처럼, GH 프로그램이 사용되는 시스템 전역에 공유되는 파일맵의 이름이라던지,
GH 프로그램의 창이름, 클레스 이름, 프로세스 이름, 로드되는 드라이버 이름, 뮤텍스,
레지스트리 키등등의 요소등을 검사해보는 방법입니다. 이러한 요소를 확인하는 방법은
sysinternals의 Process Explorer를 이용하여 해당 GH프로그램이 가지고 있는 핸들등
을 통해서 확인하여 볼 수 있습니다.


 세번째로 필요한 기능으로써, 자동 플레이 차단을 들 수 있습니다.
최근 메모리 해킹보다 더 큰 문제로 대두되고 있는 것이, 자동플레이 문제 입니다.
과거에는 '작업장'이라는 이름으로, 빈곤한 국가에서 아동 및 청소년의 노동력을
착취하여 지속적인 게임 플레이를 통한 아이템 획득하여, 이를 판매, 이익을 얻는
방식의 범죄가 존재한 반면, 최근에는 이러한 노동력 착취가 아닌, 자동으로
게임을 플레이 해주는 프로그램 및 하드웨어를 이용하여 자동적으로 플레이 하여,
아이템을 획득, 이를 판매하여, 이익을 얻는 방식으로 변모하였습니다.

최근 단순한 메크로 방식의 자동 플레이 보다 진화된 형태의 두가지 자동 플레이
방식이 등장하였는데,  첫번쨰로 게임 클라이언트와 서버와의 통신 프로토콜등을
분석하여 게임 클라이언트와 똑같은 기능을 하는 클라이언트를 만들어(UI는 없는)
해당 클라이언트에 AI를 붙이어 자동적으로 사냥하게 하는 것입니다.
이 방식으로 접근하게 될 경우, Anti GH프로그램은 올바른 역활을 할 수 없게 되며,
서버측의 입장에서도 올바른 클라이언트와 공격자에 의해 만들어진 오토 플레이용
클라이언트를 분간하는 것도 현재로선, 명확한 방법은 존재하지 않습니다.
또 이 방식의 공격은 2차적인 피해를 낮기도 하는데, 이런 오토 플레이로써 기능을
하다가, 후에는 '프리 서버'라는 이름으로 판매되어, 실제 게임의 플레이어 수를
줄이고, 저작권을 침해하는 범죄가 2차적으로 이어집니다.
두번쨰로 하드웨어 방식의 오토 플레이 입니다. 이는 실제 사용자에 의해 이뤄지는
올바른 입력과 분간하는 것이 힘듭니다. 이를 분간하기 위한 방법으로써, 행동
패턴 기반의 감지방법이나, WMI등을 이용, 하드웨어의 ID를 확인하여 오토 플레이용
하드웨어를 감지하는 방법등이 사용되어 지고 있습니다.

 네번쨰로 필요한 기능으로써, 메시지 후킹 차단을 들 수 있습니다.
공격자는 메시지 후킹을 사용자의 키입력등을 가로 챌 목적으로 사용합니다.
게임의 경우, 키 입력을 가로채어, 아이디와 패스워드등을 알아낸 후,
접속하여 아이템등을 빼가는 등의 범죄에 사용되어 집니다.
현재 존재하는 대부분의 Anti GH 프로그램등은 대부분 기본적으로 유저레벨단의
메시지 후킹을 차단하고 있으며, 이로 인해 최근에는 키로거들도 유저레벨에서
메시지 후킹을 하는 방식이 아닌, 커널레벨에서 필터 드라이버등으로써 키로거
기능을 수행하고 있습니다.

 다섯번쨰로 필요한 기능으로써, 허용되지 않은 메모리 접근차단을 들 수 있습니다.
매시각각 생겨나는 GH 프로그램에 대응한다는 것은 참 힘든일이며,
Private한 GH 프로그램에 대해선 대응하기가 힘들다는 이유에서 이 방법은 Anti GH에
큰 도움이 되는 방법입니다. 게임 및 보호 프로그램의 프로세스에 허용되지 않은
접근을 차단함으로써, Private한 GH 프로그램도 견제할 수 있게 됩니다.
이를 수행하기 위해서 유저레벨에서는 각 프로세스에 Dll을 삽입하여, API를 후킹하게
하고, 커널레벨에서는 NtOpenProcess(), NtRead/WriteVirtualMemory()등의 함수를
후킹하여 프로세스로의 접근을 차단합니다. 그러나 최근 GH 프로그램도 이런 후킹에
대응하기 위하여, 기존의 API를 호출하는 것이 아닌, Pesudo 코드를 사용함으로써,
위에서 말했던 API들로는 충분치 않아, 최근에는 KeAttachProcess() 같은 프로세스
메모리 전환 계열 함수도 후킹대상에 포함되고 있습니다.

 여섯번쨰로 필요한 기능으로써, 디버깅 차단을 들 수 있습니다.
게임을 디버거에게 내준다는 것은, 알몸을 보여주는 것과 같습니다.
그럼으로, 디버거는 어떻게든 차단해야 하는 존재입니다. 디버거를 감지하는
방법으로는 우선 앞에서 말했던 요소들을 혼합함으로써 이루어 질 수 있습니다.
디버거들이 사용하는 API(DebugActiveProcess)같은 프로세스들을 후킹하는
것도 중요하며, 유명한 범용 디버거가 있는지 정기적으로 확인하여, 이들의
창이름, 클레스 이름, 프로세스 이름등 Anti GH프로그램의 두번쨰 기능에서
거론한 요소들로 감지할 수 있으며, 디버거 역시 유저레벨 디버거의 경우
다섯번째 기능으로 차단되며, 커널레벨 디버거의 경우는 반드시 두번쨰 기능을
이용하여 차단되어져야 합니다. 유명한 커널레벨 디버거로써는 SoftIce와
syser가 있으며, 필수적인 차단리스트 입니다.

 일곱번째로 필요한 기능으로써, 스피드 핵 차단을 들 수 있습니다.
공격자 입장에서 생각하기에는 단순히 스피드 핵을 차단하는 이유가 공정성 때문
이라고 생각할지 모르겠지만, 실제적으로 스피드 핵을 차단하는 이유는 공정성
그 이상의 이유가 있습니다. 스피드 핵을 사용할 경우 특정한 기능을 수행하는
루틴의 반복시간에 Gap이 짧아 짐으로써, (Delay시간의 감소) 서버로 유입되는
패킷량 역시 증가되게 됩니다. 만약, 스피드를 정상적인 속도에 10배로 하였다면,
서버로 보내는 패킷량도 10배에 근접하게 된다고 보시면 됩니다. 즉 반드시
차단해야 합니다. 스피드 핵은 두종류가 있는데, 첫번쨰는 GetTickCount() ,
timeGetTime(),QueryPerformanceCounter()등의 시간관련 함수를 후킹하는
방식이 있습니다. 이는 프로세스로의 메모리 접근을 차단함으로써 해결할 수
있는 문제입니다. (사실 메모리 접근을 하지 않고 후킹을 할 수도 있습니다.
-CopyOnWrite 관련) 두번쨰는 시스템의 PIT자체를 줄임으로써, 시스템 시간
전체를 빠르게 돌아가게 하는 방법이 있습니다. 이는 시스템 내부적으론 감지가
힘들게 되며, 서버와의 연동체크를 통해서 감지하는 방법이 있습니다.

 여덟번째로 필요한 기능으로써, 무결성 검사를 들 수 있습니다.
무결성 검사를 하는 이유로는, 만약 게임프로세스 및 보호 프로세스가 패킹이
해제되거나, 변조된 상태에서 실행된다면, Anti GH은 정상적인 기능을 수행할 수
없게 됩니다. 무결성 검사는 보호 프로세스가 시작될떄에, 게임 파일 및 보호
프로세스 자체 파일들에 대하여 우선적으로 이루어져야 하며,
실행 중에도 코드섹션에 대하여 정기적으로 이루어져야 합니다.
이렇게 검사를 수행할 경우, 만약 다섯번쨰 기능이 우회되여, 게임의 메모리나,
게임의 데이터 파일등이 수정되었다고 하여도, 이를 감지할 수 있습니다.

 아홉번째로 필요한 기능으로써, 시스템 디스크립터 복구를 들 수 있습니다.
게임이 시작하기전 시스템의 중요한 디스크립터들(ex: ssdt, idt, and so on)을
복구 하여둠으로써, 꺠끗한 환경에서 게임이 돌아가도록 해야 합니다.
이 작업이 수행되어 지지 않으면, Anti GH 프로그램은 올바른 작동을 할 수 없을
수도 있습니다.


2. Anti Game Hacking Program 구현에 필요한 코드들.

 앞에서는 안티 게임 해킹 프로그램이 갖추어야할 기능들에 대해서 알아 보았습니다.
이번에는 이러한 기능들을 구현하기 위해서 알아야 할 점 및 코드에 대해서 다루어
보도록 하겠습니다.

첫번째로 실행파일 및 데이터 파일의 압축 또는 암호화의 경우 코드의 길이가
짧은편이 아님으로, 관련 링크로 대신하도록 하겠습니다.
http://dual5651.hacktizen.com/tt/entry/Make-your-owner-PE-Protector-Part-1-Your-
first-EXE-Protector

참고 적으로 최근의 게임 EXE 패킹은 더미다 패커가 인기를 끌고 있고,
드라이버의 경우 Code Virtualizer가 인기를 끌고 있습니다.

두번쨰로 게임해킹 프로그램의 감지의 코드는 다음과 같습니다.

프로세스 메모리 영역의 패턴 검사 :

BOOL PatternSearchFromAllProcesses()
{
 char StringDataBase[][30] = {"CheatEngine",
          "AutoPlay",
          "GameHack",
          "Memory Search",
          "TSearch"};
 char szProcess[MAX_PATH] = {0,};
 DWORD ProcessesID[1024];
 char WarningString[MAX_PATH] = "게임 해킹 프로그램 발견 되었습니다.\n게임이 종료 됩니다.\n";
 MEMORY_BASIC_INFORMATION struct_mbi;
 SYSTEM_INFO struct_si;
 HMODULE hMod;
 DWORD dbNeeded,dbNeeded2;
 HANDLE hProcess;
 LPVOID newbuf;
 LPVOID p;
    ULONG ret;
 int cnt;

 GetSystemInfo(&struct_si); //Getting Information to get max,min address

 printf("StartAdr : 0x%x, EndAdr : 0x%x\n",
   struct_si.lpMinimumApplicationAddress,
   struct_si.lpMaximumApplicationAddress);

 for(cnt=0; ;cnt++)    //Counting Pattern
  if(!StringDataBase[cnt][0])
   break;
 
 EnumProcesses(ProcessesID,sizeof(ProcessesID),&dbNeeded); //Get Processes IDs.
 
 for(int i = 0; i < dbNeeded / sizeof(DWORD); i++)
 {
  if((ProcessesID[i] == GetCurrentProcessId()) || ProcessesID[i] == 4) continue; //Do not scan by self
 
  hProcess = OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION,FALSE,ProcessesID[i]);
 
  if(hProcess)
  {
   EnumProcessModules(hProcess,&hMod,sizeof(hMod),&dbNeeded2);
   GetModuleBaseName(hProcess,hMod,szProcess,sizeof(szProcess)); //Get Module Name
   p = struct_si.lpMinimumApplicationAddress;
   
   do
   {
      VirtualQueryEx(hProcess,p,&struct_mbi, sizeof(struct_mbi));
      newbuf = malloc(struct_mbi.RegionSize);
      //Sometimes regionSize makes me crazy!!!
      if(newbuf)
      {
       ReadProcessMemory(hProcess,p,newbuf,struct_mbi.RegionSize,&ret);
       if(ret)
       {
        for(DWORD i = (DWORD)newbuf; i <= (DWORD)newbuf+(DWORD)struct_mbi.RegionSize; i++)
        {
         for(int j = 0; j < cnt; j++)
         {
        if(!strnicmp((char *)StringDataBase[j],(char *)i,strlen((char *)StringDataBase[j])))
        {
         strcat(WarningString,szProcess);
         strcat(WarningString,"프로세스를 종료하신후 다시 실행하여 주세요.\n");
         MessageBox(NULL,WarningString,"해킹 프로그램 발견",MB_ICONSTOP);
         CloseHandle(hProcess);
         ExitProcess(-1);
        }
         }
        }
       }
      }
      free(newbuf);
      *((PDWORD)&p) += (DWORD)struct_mbi.RegionSize;
    }while(p < struct_si.lpMaximumApplicationAddress);
  }
      CloseHandle(hProcess);
 }
 return TRUE;
}


실제로 실행해보면 알 수 있지만, ReadProcessMemory()를 수행하는 것은 꾀 시간을
많이 소요하는 작업입니다. 이렇게 하는 것보다는 Dll을 모든 프로세스에 인젝션 시키신 후,
각 프로세스에 인젝션된 Dll에서 메모리 검사를 하게되면, 같은 프로세스 영역 내임으로,
ReadProcessMemory()도 필요 없으며, 분할처리가 가능해져, 그 속도 역시 빨라 집니다.
Dll을 인젝션 하는 방식의 코드는 별도로 코드를 첨부하도록 하겠습니다.

Finding Named Object기법의 경우는 창이름 이나 클레스 이름은 EnumWindow 함수를
돌면서 GetWindowText(), GetClassName()함수를 이용하여 구하여 비교하는 방식을
쓰면 됩니다. 코드는 다음과 같습니다.

char szWindowDataBase[][30] = {"CheatEngine",
         "AutoPlay",
         "GameHack",
         "Memory Search",
         "TSearch"};

char szClassDataBase[][30] = {"CheatEngine",
         "AutoPlay",
         "GameHack",
         "Memory Search",
         "TSearch"};

int cnt,cnt2;

BOOL CALLBACK EnumWinProc(HWND hwnd,LPARAM lparam)
{
   char szWindow[255];
   char szClass[255];
 
   if(GetParent(hwnd)) return TRUE;
   GetWindowText(hwnd,szWindow,255);
   GetClassName(hwnd,szClass,255);
  
   for(int i = 0; i < cnt; i++)
   {
   if(!strnicmp(szWindowDataBase[i],szWindow,strlen(szWindowDataBase[i])))
   {
  MessageBox(NULL,"게임 해킹 프로그램 발견 되었습니다.\n게임이 종료 됩니다.\n","해킹 프로그램 발견",MB_ICONSTOP);
  ExitProcess(-1);
   }
   }

   for(i = 0; i < cnt; i++)
   {
   if(!strnicmp(szClassDataBase[i],szClass,strlen(szClassDataBase[i])))
   {
  MessageBox(NULL,"게임 해킹 프로그램 발견 되었습니다.\n게임이 종료 됩니다.\n","해킹 프로그램 발견",MB_ICONSTOP);
  ExitProcess(-1);
   }
   }
   return TRUE;
}

BOOL FindBadWindowOrClass()
{
    for(cnt = 0; ;cnt++)    //Counting Pattern
      if(!szWindowDataBase[cnt][0])
           break;

    for(cnt2 = 0; ;cnt2++)    //Counting Pattern
      if(!szWindowDataBase[cnt2][0])
           break;

 EnumWindows(EnumWinProc,NULL);
 return TRUE;
}

프로세스 이름의 경우 EnumWindow 콜백에서 GetThreadProcessId() 함수를 한 후,
OpenProcess() 하여주고, GetModuleBaseName()함수를 이용하여 프로세스 이름을
구하여 확인시켜 주는 방식으로 진행하여 주면 됩니다. 이 코드는 패턴 검사의 코드를
조금 수정하면 됨으로 따로 올리진 않겠습니다. 그 나머지 요소는 GH 프로그램이
사용하고 있는 요소들을 어떻게 확인하는지 Process Explorer를 이용한 예제를 보여드
리 겠습니다.

사용자 삽입 이미지

먼저 위와 같이 Lower panel view를 Handles로 해줍니다.

사용자 삽입 이미지
TSearch에서 사용중인 핸들 목록을 볼 수 있는데요.
위 그림에서 보면, TSearch는 3가지의 독특한 Event를 만드는 것을 알 수 있습니다.
즉, 다음 3가지 중 하나를 선택하여 해당 이벤트가 존재하는지를 확인한다면,
TSearch의 실행여부를 확인할 수 있습니다. 코드는 다음과 같습니다.

BOOL CheckByEventName()
{
 char szEventDataBase[][30] = {
         "User stopped search",
         "Debugger Loaded",
         "TSearch.ServerLoaded"
        };
 HANDLE hEvent;

  for(int cnt = 0; ;cnt++)    //Counting Pattern
      if(!szEventDataBase[cnt][0])
           break;

  for(int i = 0; i < cnt; i++)
  {
 if(OpenEvent(EVENT_ALL_ACCESS,FALSE,szEventDataBase[i]))
 {
    MessageBox(NULL,"게임 해킹 프로그램 발견 되었습니다.\n게임이 종료 됩니다.\n","해킹 프로그램 발견",MB_ICONSTOP);
       ExitProcess(-1);
 }
  }

  return TRUE;
}

뮤텍스를 이용한 확인 방법의 코드는 다음과 같습니다.

BOOL CheckByMutexName()
{
 char szMutexDataBase[][30] = {
         "TSearch",
         "CheatEngine",
         "GameHack"
        };
 HANDLE hEvent;

  for(int cnt = 0; ;cnt++)    //Counting Pattern
      if(!szMutexDataBase[cnt][0])
           break;

  for(int i = 0; i < cnt; i++)
  {
 CreateMutex(NULL,FALSE,szMutexDataBase[i]);
 if(GetLastError() == ERROR_ALREADY_EXISTS)
 {
    MessageBox(NULL,"게임 해킹 프로그램 발견 되었습니다.\n게임이 종료 됩니다.\n","해킹 프로그램 발견",MB_ICONSTOP);
       ExitProcess(-1);
 }
  }

  return TRUE;
}

파일맵을 이용한 방법의 코드는 다음과 같습니다.

BOOL CheckByFileMapName()
{
 char szFileMapDataBase[][30] = {"DUALMEM",
         "TSearch",
         "CheatEngine",
         "GameHack"
        };
 HANDLE hFileMap;
 LPVOID pMapFile;

  for(int cnt = 0; ;cnt++)    //Counting Pattern
      if(!szFileMapDataBase[cnt][0])
           break;

  for(int i = 0; i < cnt; i++)
  {
 hFileMap = CreateFileMapping((HANDLE)INVALID_HANDLE_VALUE,NULL,
      PAGE_READWRITE,0,1,szFileMapDataBase[i]);
 if(GetLastError() == ERROR_ALREADY_EXISTS)
 {
    MessageBox(NULL,"게임 해킹 프로그램 발견 되었습니다.\n게임이 종료 됩니다.\n","해킹 프로그램 발견",MB_ICONSTOP);
       ExitProcess(-1);
 }
  }

  return TRUE;
}

레지스트리, 파일로 확인하는 방법은 어떤 레지스트리나 파일을 쓰는지는 Process
Explorer를 확인하는 것으로 동일하고, 확인은 레지스트리나 파일을 열 떄,
이미 존재하는지 여부만 확인해주면 됨으로 별도의 코드는 첨부하지 않겠습니다.
윈도우즈에 로드되어 있는 드라이버의 목록은 ntdll.dll의
ZwQuerySystemInformation() 를 이용하여 구할 수 있습니다.

참고적으로 위의 핸들목록을 출력하는 기능은 정덕영님의 저서 'Windows 구조와
원리 그리고 Codes'에 나온 코드인 ListHandles라는 코드도 똑같은 기능을 합니다.
코드는 다음과 같습니다.

#include "stdafx.h"
#include "windows.h"
#include <stdio.h>
#include "nativeAPI.h"

BOOL EnablePrivilege(PCSTR name)
{
    TOKEN_PRIVILEGES priv = {1, {0, 0, SE_PRIVILEGE_ENABLED}};
    LookupPrivilegeValue(0, name, &priv.Privileges[0].Luid);

    HANDLE hToken;
    OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);

    AdjustTokenPrivileges(hToken, FALSE, &priv, sizeof priv, 0, 0);
    BOOL rv = GetLastError() == ERROR_SUCCESS;

    CloseHandle(hToken);
    return rv;
}


int main(int argc, char* argv[])
{
    ULONG pid;
 HANDLE hProcess, hCurrentProcess;
 SYSTEM_HANDLE_INFORMATION  *aHandles;
 ULONG  nHandles, nCount;
 ULONG  *pULONG;
 
 //1. Parameter parsing
    if (argc == 1)
 {
  printf("HELP : ListHandles PID\n");
  return 0;
 }
 pid = strtoul(argv[1], 0, 0);

 //2. Get process handle
    EnablePrivilege(SE_DEBUG_NAME);
    hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid);
 hCurrentProcess = GetCurrentProcess();

 //3. Get Handle's Information
    nCount = 100;
    pULONG = (PULONG)malloc(nCount * sizeof(SYSTEM_HANDLE_INFORMATION) + sizeof(ULONG));
    while (ZwQuerySystemInformation(SystemHandleInformation, pULONG,
    nCount * sizeof(SYSTEM_HANDLE_INFORMATION)+ sizeof(ULONG)
    , 0) == STATUS_INFO_LENGTH_MISMATCH)
 {
  free(pULONG);
  nCount += 50;
  pULONG = (PULONG)malloc(nCount * sizeof(SYSTEM_HANDLE_INFORMATION) + sizeof(ULONG));
 }
 nHandles = *pULONG;
 aHandles = (PSYSTEM_HANDLE_INFORMATION)(pULONG + 1);
 
 //4. Print Handle's Information
 printf("Process ID : %x\n", pid);
    for (ULONG i = 0; i < nHandles; i++)
 {
        if (aHandles[i].ProcessId == pid)
  {
   HANDLE hObject;
   OBJECT_BASIC_INFORMATION obi;
   POBJECT_TYPE_INFORMATION pOti;
   POBJECT_NAME_INFORMATION pOni;
   ULONG nTypeName, nObjectName, n;

   if(DuplicateHandle(hProcess, (HANDLE)aHandles[i].Handle,
    hCurrentProcess, &hObject, 0, 0, DUPLICATE_SAME_ACCESS) == FALSE)
    continue;

            ZwQueryObject(hObject, ObjectBasicInformation, &obi, sizeof(obi), &n);
            printf("%p %04hx %3lx %3ld %4ld ",
                   aHandles[i].Object, aHandles[i].Handle, obi.Attributes,
                   obi.HandleCount - 1, obi.PointerCount - 2);
   //Object Type
            nTypeName = obi.TypeInformationLength + 2;
            pOti = (POBJECT_TYPE_INFORMATION)malloc(nTypeName);

            ZwQueryObject(hObject, ObjectTypeInformation, pOti, nTypeName, &nTypeName);
            printf("%-14.*ws ", pOti->Name.Length / 2, pOti->Name.Buffer);
   //Object Name
            nObjectName = obi.NameInformationLength == 0
                ? MAX_PATH * sizeof (WCHAR) : obi.NameInformationLength;

             pOni = (POBJECT_NAME_INFORMATION)malloc(nObjectName);
           
            if (NT_SUCCESS(ZwQueryObject(hObject, ObjectNameInformation, pOni,
       nObjectName, &nObjectName)))
                printf("%.*ws", pOni->Name.Length / 2, pOni->Name.Buffer);

            printf("\n");
   
   free(pOni); free(pOti); CloseHandle(hObject);
        }
    }
    free(aHandles);
    CloseHandle(hProcess);

    return 0;
}


세번쨰로 자동 플레이 차단은 에플리케이션 방식의 오토플레이라면 유저레벨에서
막는방법과 커널레벨에서 막는 방법으로 나뉠 수 있습니다. 사실 후킹하는 시점이
다를 뿐, 실제적으로 차단해야할 API들은 같습니다. 유저레벨에서는 모든 프로세스에
Dll을 인젝션 한 후, 해당 Dll에서 GetPixel(), PostMessageA(), PostMessageW(),
SendInput(), SendMessageA(), SendMessageW(), SetCursorPos(), keybd_event(),
mouse_event() 등의 함수를 차단하여 주어야 합니다.
커널레벨에서는 KeServiceDescriptorTableShadow에 있는 SendInput(),
NtUserQueryWindow(), NtUserBuildHwndList(), NtUserFindWindowEx(),
NtUserGetForegroundWindow(), GetDC(), GetWindowDC() 등의 함수를 후킹해서
처리해 주어야 합니다.  그래픽 관련 함수를 후킹하는 이유는 최근 오토플레이들은
단순한 메크로가 아닌, 픽셀 정보를 읽어온 후, 그에 interact하여 작동하는 방식을
많이 취하기 떄문입니다. 위의 함수들을 모두 후킹하여 처리하여 주면, 에플리케이션
방식의 메크로는 거의 차단된다고 보시면 됩니다. (커널레벨에서의 irp 발생 및
pesudo 코드 사용등 예외는 존재합니다.)

네번쨰로 메시지 후킹 차단은 커널단에서 비교적 강력하게 구현될 수 있는데,
메시지 후킹은 SetWindowHookEx()를 이용해서 이루어 지는데, 운영체제 내에서는
다음과 같은 구조체로 관리 되어 집니다.
typedef struct _HOOK {
ULONG hHook;
ULONG cLockObj;
PTHREADINFO pti; //THREADINFO of CurrentThread
ULONG rpdesk;
ULONG pSelf;
struct _HOOK *phkNext;
int iHook; //HOOK_TYPE
ULONG offPfn; //Proc
unsigned int flags; //Flags
int ihmod;
PTHREADINFO ptiHooked; //THREADINFO of TargetThread (if Flags=HF_GLOBAL,=0)
PDESKTOP rpdesk;
} HOOK, *PHOOK;

TEB의 Win32ThreadInfo 구조체의 pDeskInfo에 aphkStart라는 배열이
훅을 관리하는데, 즉 이 배열을 주기적으로 0으로 초기화 시킴으로써 설치된 훅을
제거할수도 있으며 메시지 후킹을 차단할수도 있습니다.

다섯번쨰로 필요한 기능으로써, 허용되지 않은 메모리 접근차단은 유저레벨에서는
각 프로세스에 Dll을 인젝션 한 후, NtOpenProcess(), NtProtectVirtualMemory(),
NtReadVirtualMemory(), NtWriteVirtualMemory(), ZwOpenProcess(), ZwProtect
VirtualMemory(), ZwReadVirtualMemory(), ZwWriteVirtualMemory(),
OpenProcess(), ReadProcessMemory(), VirtualProtect(), VirtualProtectEx(),
WriteProcessMemory(), GetWindowThreadProcessId()등의 API를 후킹하여
주어야 합니다. 유저레벨에서의 후킹코드는 앞에서 다뤘던 코드에 조금의 수정을
가하면 되는 것임으로, 생략하도록 하겠습니다. 커널레벨에서의 후킹코드들은
다음과 같습니다.

ZwOpenProcess Hook :

NTSTATUS NewZwOpenProcess(
    OUT PHANDLE ProcessHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes,
    IN PCLIENT_ID ClientId OPTIONAL
)
{
    NTSTATUS rc;
    NTSTATUS rc2;
    CHAR Caller_Process_Name[PROCNAMELEN];
    CHAR Target_Process_Name[PROCNAMELEN];
    PEPROCESS Process;
    char *nameptr;
    
    GetProcessName( Caller_Process_Name );  //Get Process Name

    rc = ((ZWOPENPROCESS)(OldZwOpenProcess)) (
   ProcessHandle,
   DesiredAccess,
   ObjectAttributes,
   ClientId OPTIONAL); 
 
 if(DesiredAccess == PROCESS_ALL_ACCESS)
 {

  if(NT_SUCCESS(rc))    
  {       
   
   rc2 = ObReferenceObjectByHandle(
      *ProcessHandle,
      PROCESS_ALL_ACCESS,
      NULL,
      KernelMode,
      (void *)&Process,
      NULL);
   if(NT_SUCCESS(rc2))
   {
    nameptr = (PCHAR)Process + gProcessNameOffset;
    strncpy(Target_Process_Name, nameptr, NT_PROCNAMELEN);
    Target_Process_Name[NT_PROCNAMELEN] = 0;
    if(!strncmp(Target_Process_Name,MYPROCESS,strlen(MYPROCESS)))
    {
     ObDereferenceObject(Process);
     ZwClose(ProcessHandle);
     rc = STATUS_INVALID_HANDLE;
     ProcessHandle = 0;
    }
   }
  }
 }
    return rc;
}

ZwOpenProcess() 함수를 후킹하고 있다가, 만약 핸들을 오픈하고자 하는 프로세스가,
보호하는 프로세스라면 핸들을 닫고 잘못된 핸들이라고 값을 리턴합니다.

ZwWriteVirtualMemory Hook :

NTSTATUS NTAPI NewZwWriteVirtualMemory(
 IN HANDLE hProcess,
 IN PVOID BaseAddress,
 IN PVOID Buffer,
 IN ULONG BytesToWrite,
 OUT PULONG BytesWritten
)
{
 NTSTATUS rc;
 NTSTATUS rc2;
                  CHAR Attack_Process_Name[PROCNAMELEN];
 CHAR Target_Process_Name[PROCNAMELEN];
 PEPROCESS Process;
 char *nameptr;

 GetProcessName( Caller_Process_Name );

 rc2 = ObReferenceObjectByHandle(
     hProcess,
     PROCESS_ALL_ACCESS,
     NULL,
     KernelMode,
     (void *)&Process,
     NULL);
 if(NT_SUCCESS(rc2))
 {
  nameptr = (PCHAR)Process + gProcessNameOffset;
  strncpy(Target_Process_Name, nameptr, NT_PROCNAMELEN);
  Target_Process_Name[NT_PROCNAMELEN] = 0;
  ObDefreferenceObject(Process);
  if(!strncmp(Target_Process_Name,MYPROCESS,strlen(MYPROCESS)))
  {
   return STATUS_UNSUCCESSFUL;
  }
 }

 rc = ((ZWWRITEVIRTUALMEMORY)(OldZwWriteVirtualMemory)) (
  hProcess,
  BaseAddress,
  Buffer,
  BytesToWrite,
  BytesWritten);
 return rc;
}


ZwReadVirtualMemory Hook :

NTSTATUS NTAPI NewZwReadVirtualMemory(
 IN HANDLE hProcess,
 IN PVOID BaseAddress,
 OUT PVOID Buffer,
 IN ULONG BytesToRead,
 OUT PULONG BytesRead
)
{
 NTSTATUS rc;
  NTSTATUS rc2;
                  CHAR Caller_Process_Name[PROCNAMELEN];
 CHAR Target_Process_Name[PROCNAMELEN];
 PEPROCESS Process;
 char *nameptr;

 GetProcessName( Caller_Process_Name ); 

 rc2 = ObReferenceObjectByHandle(
     hProcess,
     PROCESS_ALL_ACCESS,
     NULL,
     KernelMode,
     (void *)&Process,
     NULL);
 if(NT_SUCCESS(rc2))
 {
  nameptr = (PCHAR)Process + gProcessNameOffset;
  strncpy(Target_Process_Name, nameptr, NT_PROCNAMELEN);
  Target_Process_Name[NT_PROCNAMELEN] = 0;
  ObDefreferenceObject(Process);
  if(!strncmp(Target_Process_Name,MYPROCESS,strlen(MYPROCESS)))
  {
   {
    return STATUS_INVALID_HANDLE;
   }
  }
 }
 
 rc = ((ZWREADVIRTUALMEMORY)(OldZwReadVirtualMemory)) (
   hProcess,
   BaseAddress,
   Buffer,
   BytesToRead,
   BytesRead);
 return rc;
}

여섯번쨰로 디버깅 차단은 코드에 안티 디버깅 루틴을 삽입과 후킹을 사용합니다.
안티 디버깅 루틴을 먼저 다루어 보면,
첫번쨰로 IsDebuggerPresent() 함수를 이용한 방법이 있는데,
IsDebuggerPresent()함수는 kernel32.dll에 의해 export되어지는 함수로써,
해당 함수를 호출한 프로세스가 디버깅 당하는 중이면 TRUE(1)을,
아닐 경우는 FALSE(0)을 반환하는 함수이다. 즉 이 함수를 주기적으로 호출하여
줌으로써 디버깅 여부를 확인할 수 있다. 코드는 다음과 같습니다.

if(IsDebuggerPresent())
{
    OutputDebugString("Debugeed!!");
    //디버깅 당하는 중일떄의 어떠한 처리
}

두번쨰 방법으로 PEB 구조체의 BeingDebuggged 값을 직접 조작하는 방법이 있다.
IsDebuggerPresent()함수는 내부적으로, PEB의 BeingDebugged값을 읽어서 리턴
하여 주는 함수입니다. 즉 IsDebuggerPresent()함수를 호출하지 않고, 직접
PEB의 BeingDebugged의 값을 읽어와도 결과는 같다는 소리입니다.
다음은 PEB의 BeingDebugged의 값을 읽어오는 함수 입니다.

BOOL Pesudo_IsDebuggerPresent()
{
BOOL Retval = 0;
  __asm
  {
      push eax
      mov eax,dword ptr fs:{0x18]
      mov eax,dword ptr ds:[eax+0x30]
      movzx eax,byte ptr ds:[eax+0x2]
      mov Retval,eax
     pop eax
  }
  return Retval;
}

세번쨰 방법으로 CheckRemoteDebuggerPresent() 함수를 이용하는 방법이 있습니다.
이 함수는 함수를 호출하는 프로세스 자신외에도, 타 프로세스에도 사용이 가능합니다.
다음과 같이 사용합니다.

BOOL CheckDebugger(HANDLE hProcess)

{
     BOOL Retval = 0;
 
     CheckRemoteDebuggerPresent(hProcess,&Retval);
     return Retval;
}

* CheckRemoteDebuggerPresent()는 NTAPI의 ZwQueryInformationProcess()로
연결됩니다. ZwQueryInformationProcess()는 다음과 같은 호출 인자를 갖는 함수입니다.

NTSTATUS NTAPI ZwQueryInformationProcess(HANDLE ProcessHandle,
    PROCESSINFOCLASS ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength,
    PULONG ReturnLength);

첫번쨰는 질의 하고자 하는 대상 프로세스의 핸들.
두번쨰는 질의 하고자 하는 내용(디버깅 여부는 ProcessDebugPort)
세번쨰는 결과
네번쨰는 길이
다섯번쨰는 실반환 길이이다.
해당 함수를 직접 호출하는 것도 하나의 방법이 될 수 있을 것입니다.

네번쨰 방법으로 NtGlobalFlag의 값을 확인하는 방법이 있다.
디버거가 프로세스를 디버깅 할때는, 셋되어 지는 Flag들이 있는데, NtGlobalFlag는
그 중 하나이다. 코드는 다음과 같습니다.

BOOL CheckNtGlobalFlag()
{
  BOOL Retval = 0;

  __asm
  {
        push eax
        mov eax,dword ptr fs:[0x30]
         mov eax,0x68
         mov eax,dword ptr ds:[eax]
         cmp eax,0x70
        pop eax
        jne NotDebuged
         mov Retval,1
NotDebugged :
         nop
    }
    return Retval;
}

다섯번쨰 방법으로 Heap flags를 이용하는 방법이 있습니다. Heap의 상태가 디버그
되지 않고 있는 평소 상태와 다른지를 확인하는 것은 강력한 안티디버그 메소드가 될
수 있습니다. 예를들면, 힙 해더에 있는 ForceFlags(오프셋 0x10)은 디버거의 존재
여부를 확인하기 위해 쓰일 수 있습니다.대략적인 코드는 다음과 같습니다.

mov eax, fs:[30h]
mov eax, [eax+18h] ;process heap
mov eax, [eax+10h] ;heap flags
test eax, eax
jne @DebuggerDetected

여섯번쨰 방법으로 UnhandledExceptionFilter를 이용하는 방법이 있습니다.
SEH 핸들러의 주소로 디버거가 없을시 실행할 코드로 넣어주고,
고의적으로 예외를 발생시키어, 디버거가 있는지 없는지를 확인하여 주는
방법입니다. (디버거가 존재함으로, SEH 핸들러로 넘어 가지 않고, 다음 명령을
실행 시키게 되는점을 이용) 코드는 다음과 같습니다.

push @not_debugged
call SetUnhandledExceptionFilter
xor eax, eax
mov eax, dword [eax] ; trigger exception
;program terminated if debugged
;...
@not_debugged:
;process the exception
;continue the execution
;...

일곱번째로 NtSetInformationThread를 이용하는 방법이 있습니다. ThreadInformationClass 가 0x11 (ThreadHideFromDebugger 상수)로 지정되고
호출되면, 스레드는 디버거로부터 분리 될 것입니다. (ThreadHideFromDebugger는
ETHREAD의 필드중에 하나죠.) 코드는 다음과 같습니다.

Example:
push 0
push 0
push 11h ;ThreadHideFromDebugger
push -2
call NtSetInformationThread
;thread detached if debugged
;...

여덟번쨰로 kernel32!CloseHandle and NtClose 를 이용하는 방법이 있습니다.
프로세스가 디버그될 떄, ZwClose() 를 잘못된 핸들을 주고 호출하는 것은
STATUS_INVALID_HANDLE(0xC0000008) 예외를 발생 시킵니다.
디버깅 당하고 있다면, CloseHandle()를 호출하는 다음줄의 코드를 발생시킬 것이고,
디버깅 되고 있지 않다면, SEH핸들러가 호출될 것임을 이용한 방법입니다.
코드는 다음과 같습니다.

push offset @not_debugged
push dword fs:[0]
mov fs:[0], esp
push 1234h ;invalid handle
call CloseHandle
; if fall here, process is debugged
;...
@not_debugged:
;...

아홉번쨰로 OutputDebugStringA를 이용하는 간단한 방법도 있습니다.
OutputDebugString()함수가 성공적으로 수행되는지, 안되는지 (리턴값)을 확인하는
방법입니다. 코드는 다음과 같습니다.

xor eax, eax
push offset szHello
call OutputDebugStringA
cmp eax, 1
jne @DebuggerDetected
...

앞서 말한 방법들을 모두 TLS Callback으로써 등록하여 작동시킬수도 있습니다.
TLS Callback은 스텔스하게 안티 디버그 코드를 실행시킬 수 있는 방법으로 사용될
수 있는데, 단지 안티 디버그 코드를 프로그램에 넣어놓고, 헥스 에디터등을 이용하여
PE해더의 Thread Local Storage entry (PE optional header에서 10번째 디렉토리 엔트리
에 있는) 의 값을 코드의 주소로 바꾸어줌으로써, 안티 디버그 코드가 프로그램의
엔트리 포인트가 실행되기전에, 실행 되도록 만들어 줄 수 있습니다.

열번째 방법으로 커널레벨에서 확인하는 방법으로써,  보호하고자 하는 프로세스의
EPROCESS에 debug port의 값을 확인하는 방법이 있습니다. debug port의 값은
디버깅 되고 있지 않을떄는 0이며, 디버깅 될때에는 0이 아닙니다. 이 점을 이용해서
디버깅 여부를 확인하고 싶은 프로세스의 EPROCESS 스트럭쳐의 debug port값을
확인하는 것은 아주 강력한 안티 디버그 방법으로서 사용될 수 있습니다.
이 밖에도 훨씬 많은 안티 디버깅 방법들이 존재하며,
나머지 목록및 더 자세한 설명은 다음페이지를 참조하시기 바랍니다.
http://dual5651.hacktizen.com/tt/entry/Windows-Anti-Debug-Reference

이렇게 많은 안티 디버깅 루틴이 존재하지만, 안티 디버깅 루틴만으로 충분한 것은
아닙니다. 위에서 말했듯이, 후킹 역시 중요한 부분을 차지 합니다.
DebugActiveProcess() 같은 함수를 필수적으로 후킹해 주어서 디버거가 붙는
자체를 차단해 주어야 합니다. 또 게임을 바로 실행시키는 방식이 아닌,
로더를 거치는 방식을 사용하여, CreateProcess()에 DEBUG_ONLY_THIS_PROCESS
같은 옵션을 가지고 임으로 사용되는 것을 막아주어야 합니다.

여덟번쨰로 필요한 기능인 무결성 검사의 코드는 다음과 같습니다.

#include "stdafx.h"
#include <windows.h>

int main(int argc, char* argv[])
{
 int a;
 DWORD checksum = 0xE1E4CDE2;
 __asm
 {
  pushad
  pushfd

  mov esi,offset StartAddressOfCheck
  mov ecx,offset EndAddressOfCheck
  sub ecx,offset StartAddressOfCheck
  xor eax,eax
  xor ebx,ebx

  Check_Loop:
    mov ebx, [esi]
    add eax,ebx
    rol eax,1
    inc esi
    loop Check_Loop
 
    cmp eax,checksum
    jne EndAddressOfCheck //코드 조작 감지
    popfd
    popad
 }
StartAddressOfCheck:
 a = 5;
 if(a > 10)
 {
  MessageBox(NULL,"You beat this program!!","Congratulation",64);
  return 0;
 }
 else
 {
  MessageBox(NULL,"a 변수의 값이 10보다 작습니다.","ashole!",MB_ICONWARNING);
  return 0;
 }
EndAddressOfCheck:
 __asm
 {
  popfd
  popad
 }
 MessageBox(NULL,"You can`t beat this program sxxker!!","Cracker!!",MB_ICONWARNING);
 return 0;
}

StartAddressOfCheck 로 부터 EndAddressOfCheck 까지의 명령어들의 합산값을
미리 계산하여 두고, 프로그램을 실행할 떄에, 그값이 기존의 값과 같은지 여부를
확인하여 변조되었는지를 확인하는 방법입니다. 이 확인하는 루틴을 별도의 스레드를
만들어 와일을 돌리거나, 타이머를 이용하는 방법으로, 코드의 지속적인 감시도 가능합니다.
단, 명령코드의 합산은 현재 코드가 존재하는 주소에 의해서도 영향을 받기 떄문에,
컴파일 모드가 디버그 인지, 릴리즈 인지, StartAddressOfCheck앞에 새로운 코드나
문자열등 StartAddressOfCheck의 주소에 영향을 줄 수 있는 변수가 있으면, 새롭게
checksum 값을 계산하여 상수값으로 주어야 합니다. 다소 귀찮은 면이 있지만,
프로그램을 안전하게 보호하는데 한 역활을 할 수 있습니다. 위의 코드는 프로세스에
대한 검사이지만, 파일에 대해서도 쉽게 적용할 수 있을 것입니다. (ReadFile())
또, 조금만 생각해보면, 게임에 무결성 체크 코드를 넣지 않아도, Anti GH 프로그램에서
ReadProcessMemory()함수를 이용하여 체크할수도 있을 것입니다.

아홉번쨰로 필요한 기능인 시스템 디스크립터 복구이 의미하는 것은 Rootkit에
의해 변조되어 있을, 디스크립터들(ex: SSDT, IDT, and so on..)을 의미 합니다.
SSDT 나 IDT의 변조여부는 어떻게 확인하는가? 원리는 간단합니다.
SSDT나 IDT는 기본적으로 ntoskrnl.exe의 메모리 영역에 속함으로,
각 모듈들의 시작주소와 끝주소들을 구해놓고, 만약 백터나 디스패쳐의 주소가
ntoskrnl.exe의 시작주소와 끝주소 사이에 있지 않다면, 이는 변조된 것으로
판단하면 됩니다. 코드는 다음과 같습니다.

PMODULE_LIST GetListOfModules(PNTSTATUS pns)

{

    ULONG ul_NeededSize;

    ULONG *pul_ModuleListAddress = NULL;

    NTSTATUS     ns;

    PMODULE_LIST pml = NULL;

    // Call it the first time to determine the size required

    // to store the information.

    ZwQuerySystemInformation(SystemModuleInformation,

                             &ul_NeededSize,

                             0,

                             &ul_NeededSize);

    pul_ModuleListAddress = (ULONG *) ExAllocatePool(PagedPool, ul_NeededSize);

    if (!pul_ModuleListAddress) // ExAllocatePool failed.

    {

        if (pns != NULL)

          *pns = STATUS_INSUFFICIENT_RESOURCES;

        return (PMODULE_LIST) pul_ModuleListAddress;

    }

    ns = ZwQuerySystemInformation(SystemModuleInformation,

                                  pul_ModuleListAddress,

                                  ul_NeededSize,

                                  0);

    if (ns != STATUS_SUCCESS)// ZwQuerySystemInformation failed.

    {

        // Free allocated paged kernel memory.

        ExFreePool((PVOID) pul_ModuleListAddress);

        if (pns != NULL)

           *pns = ns;

        return NULL;

    }

    pml = (PMODULE_LIST) pul_ModuleListAddress;

    if (pns != NULL)

       *pns = ns;

    return pml;

}

..................

   g_pml = GetListOfModules(&ns);
   if(!g_pml)
   {
    DbgPrint("Get Failed");
    return;
   }
    for(count = 0; count < g_pml->d_Modules; count++)
 {
  if(!_stricmp("ntoskrnl.exe",g_pml->a_Modules[count].a_bPath+g_pml->a_Modules[count].w_NameOffset))
  {
   g_NTOSKRNL.Base = (ULONG)g_pml->a_Modules[count].pBase;
   g_NTOSKRNL.End = ((ULONG)g_pml->a_Modules[count].pBase + g_pml->a_Modules[count].d_Size);
  }
 }

위의 코드로 ntoskrnl.exe의 시작주소와 끝주소를 알 수 있으며, SSDT와 IDT를
끝번호까지 돌면서, ntoskrnl.exe에 속하는지만 확인하여 주면 됩니다.
만약, 이런식으로 루프를 돌다가 변조된 것이 발견되면 어떻게 복구해야할까요?
그것에 대해서는
http://dual5651.hacktizen.com/tt/entry/SSDT-후킹-무력화에-대한-연구
의 SSDT Restore에 대해서 읽어보시고, 코드를 참조하시면 됩니다.

목차에는 없지만 최근 많이 사용되고 있는 프로세스 숨기기에 대해서도 간단히
다루어 보겠습니다. 첫번째로 사용할 수 있는 방법으로서 커널단에서
ZwQuerySystemInformation() 이라는 함수를 후킹하는 방법이 있습니다.
코드는 다음과 같습니다.

NTSTATUS NTAPI NewZwQuerySystemInformation(
            IN ULONG SystemInformationClass,
   IN PVOID SystemInformation,
   IN ULONG SystemInformationLength,
   OUT PULONG ReturnLength
)
{
 NTSTATUS rc;
 CHAR Attack_Process_Name[PROCNAMELEN];
 
 GetProcessName( Attack_Process_Name );

 rc = ((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemInformation)) (
   SystemInformationClass,
   SystemInformation,
   SystemInformationLength,
   ReturnLength );

 if( NT_SUCCESS( rc ) && strncmp(Attack_Process_Name,ProcessName,NT_PROCNAMELEN-1))
 {

  if(5 == SystemInformationClass)
  {
   struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;
   struct _SYSTEM_PROCESSES *prev = NULL;

   while(curr)
   {    
    ANSI_STRING process_name;
    ANSI_STRING ANSI_Enemy_Name;
    UNICODE_STRING Enemy_Name;
    RtlUnicodeStringToAnsiString( &process_name, &(curr->ProcessName), TRUE);
    if( (255 > process_name.Length) && (0 < process_name.Length) )
    {
     if(0 == strncmp( process_name.Buffer, ProcessName, NT_PROCNAMELEN-1))
     {
 
      //DbgPrint("[Alarm] ProcessScan Detected\n");
      //DbgPrint("Called by %s\n",Attack_Process_Name);
       if(prev)
       {
        if(curr->NextEntryDelta)
        {
         prev->NextEntryDelta += curr->NextEntryDelta;
        }
        else
        {
         prev->NextEntryDelta = 0;
        }
       }
       else
       {
        if(curr->NextEntryDelta)
        {
         (char *)SystemInformation += curr->NextEntryDelta;
        }
        else
        {
         SystemInformation = NULL;
        }
       }
     }
    }
    RtlFreeAnsiString(&process_name);
    prev = curr;
    if(curr->NextEntryDelta) ((char *)curr += curr->NextEntryDelta);
    else
    {
     curr = NULL;
    }
   }
  }
 }
 return rc;
}

NtUserQueryWindow(), NtUserBuildHwndList(), NtUserFindWindowEx(),
NtUserGetForegroundWindow() 라는 함수들은 창의 핸들과 관련이 있는 함수들입니다.
창의 핸들을 획득하는 것을 차단하기 위해 위의 함수들을 후킹하는 코드 입니다.

UINT_PTR NewNtUserQueryWindow(IN ULONG WindowHandle,IN ULONG TypeInformation)
{
    ULONG WindowHandleProcessID;
    CHAR Attack_Process_Name[PROCNAMELEN];
    CHAR Target_Process_Name[PROCNAMELEN];
    PEPROCESS Process;
    char *nameptr;

        GetProcessName(Attack_Process_Name);
 if(strncmp(Attack_Process_Name,ProcessName,NT_PROCNAMELEN-1))
 {
  WindowHandleProcessID = ((NTUSERQUERYWINDOW)(WINDOWSERVICEIDX(483)))(WindowHandle,0);
  if(PsLookupProcessByProcessId((HANDLE)WindowHandleProcessID,&Process) == STATUS_SUCCESS)
        {
   ObDereferenceObject(Process);
                 nameptr = (PCHAR)Process + gProcessNameOffset;
   strncpy(Target_Process_Name, nameptr, NT_PROCNAMELEN);
   Target_Process_Name[NT_PROCNAMELEN] = 0;
   if(!strncmp(Target_Process_Name,ProcessName,NT_PROCNAMELEN-1) || WindowHandleProcessID == SaruenProcessID)
   {
    return 0;
   }
  }
    }  
 return OldNtUserQueryWindow(WindowHandle,TypeInformation);
}

NTSTATUS NewNtUserBuildHwndList(IN HDESK hdesk, IN HWND hwndNext, IN ULONG fEnumChildren, IN DWORD idThread, IN UINT cHwndMax, OUT HWND *phwndFirst, OUT ULONG* pcHwndNeeded)
{
 NTSTATUS result;
    CHAR Attack_Process_Name[PROCNAMELEN];
 CHAR Target_Process_Name[PROCNAMELEN];
 ULONG ProcessID;
 PEPROCESS Process;
 char *nameptr;
 ULONG i = 0,j;
 
    GetProcessName(Attack_Process_Name);
 if(strncmp(Attack_Process_Name,ProcessName,NT_PROCNAMELEN-1))
 {
  if(fEnumChildren == 1)
  {
   ProcessID = OldNtUserQueryWindow((ULONG)hwndNext,0);
   if(PsLookupProcessByProcessId((HANDLE)ProcessID,&Process) == STATUS_SUCCESS)
            {
    ObDereferenceObject(Process);
                   nameptr = (PCHAR)Process + gProcessNameOffset;
     strncpy(Target_Process_Name, nameptr, NT_PROCNAMELEN);
    Target_Process_Name[NT_PROCNAMELEN] = 0;
    if(!strncmp(Target_Process_Name,ProcessName,NT_PROCNAMELEN-1) || ProcessID == SaruenProcessID)
    {
     return STATUS_UNSUCCESSFUL;
    }
   }
          }
  result=((NTUSERBUILDHWNDLIST)(WINDOWSERVICEIDX(312)))(hdesk,hwndNext,fEnumChildren,idThread,cHwndMax,phwndFirst,pcHwndNeeded);
  if (result == STATUS_SUCCESS)
  {
   while (i<*pcHwndNeeded)
   {
    ProcessID=OldNtUserQueryWindow((ULONG)phwndFirst[i],0);
    if(PsLookupProcessByProcessId((HANDLE)ProcessID,&Process) == STATUS_SUCCESS)
              {
                    nameptr = (PCHAR)Process + gProcessNameOffset;
      strncpy(Target_Process_Name, nameptr, NT_PROCNAMELEN);
     Target_Process_Name[NT_PROCNAMELEN] = 0;
     ObDereferenceObject(Process);
     if(!strncmp(Target_Process_Name,ProcessName,NT_PROCNAMELEN))
     {
       for (j=i; j<(*pcHwndNeeded)-1; j++)    
         phwndFirst[j]=phwndFirst[j+1];
       phwndFirst[*pcHwndNeeded-1]=0;
       (*pcHwndNeeded)--;
       continue;
     }
    }
    i++;
   }
  }
  return result;
 }
 return OldNtUserBuildHwndList(hdesk,hwndNext,fEnumChildren,idThread,cHwndMax,phwndFirst,pcHwndNeeded);
}

ULONG NewNtUserFindWindowEx(IN HWND hwndParent, IN HWND hwndChild, IN PUNICODE_STRING pstrClassName OPTIONAL, IN PUNICODE_STRING pstrWindowName OPTIONAL, IN DWORD dwType)
{
 ULONG result;
 ULONG ProcessID;
        PEPROCESS Process;
        char *nameptr;
        CHAR Attack_Process_Name[PROCNAMELEN];
        CHAR Target_Process_Name[PROCNAMELEN];

        GetProcessName(Attack_Process_Name);
        if(!strncmp(Attack_Process_Name,ProcessName,NT_PROCNAMELEN-1))
  {
   result=OldNtUserFindWindowEx(hwndParent,hwndChild,pstrClassName,pstrWindowName,dwType);
   return result;
  }
  result = ((NTUSERFINDWINDOWEX)(WINDOWSERVICEIDX(378)))(hwndParent,hwndChild,pstrClassName,pstrWindowName,dwType);
  if(strncmp(Attack_Process_Name,ProcessName,NT_PROCNAMELEN-1))
        {
           ProcessID = OldNtUserQueryWindow(result,0);
           if(PsLookupProcessByProcessId((HANDLE)ProcessID,&Process) == STATUS_SUCCESS)
           {
                 nameptr = (PCHAR)Process + gProcessNameOffset;
   strncpy(Target_Process_Name, nameptr, NT_PROCNAMELEN);
  Target_Process_Name[NT_PROCNAMELEN] = 0;
  if(!strncmp(Target_Process_Name,ProcessName,NT_PROCNAMELEN-1) || ProcessID == SaruenProcessID)
  {
   result=0;
  }
  ObDereferenceObject(Process);
           }
           
        }
 return result;
}

ULONG NewNtUserGetForegroundWindow(VOID)
{
 ULONG result;
 ULONG ProcessID;
        PEPROCESS Process;
        char *nameptr;
        CHAR Attack_Process_Name[PROCNAMELEN];
        CHAR Target_Process_Name[PROCNAMELEN];

        GetProcessName(Attack_Process_Name);
  if(!strncmp(Attack_Process_Name,ProcessName,NT_PROCNAMELEN-1))
  {
   result=OldNtUserGetForegroundWindow();
   return result;
  }
  result = ((NTUSERGETFOREGROUNDWINDOW)(WINDOWSERVICEIDX(404)))();
        if(strncmp(Attack_Process_Name,ProcessName,NT_PROCNAMELEN-1))
        {
           ProcessID = OldNtUserQueryWindow(result,0);
           if(PsLookupProcessByProcessId((HANDLE)ProcessID,&Process) == STATUS_SUCCESS)
           {
                 nameptr = (PCHAR)Process + gProcessNameOffset;
   strncpy(Target_Process_Name, nameptr, NT_PROCNAMELEN);
  Target_Process_Name[NT_PROCNAMELEN] = 0;
  if(!strncmp(Target_Process_Name,ProcessName,NT_PROCNAMELEN-1) || ProcessID == SaruenProcessID)
  {
   result=LastForegroundWindow;
  }
  else
  {
   LastForegroundWindow=result;
  }
  ObDereferenceObject(Process);
           }
           
        }
 return result;
}

두번쨰로 사용할 수 있는게 DKOM입니다. DKOM은 예외경우가 많이 발생할 수 있음으로,
필드 프로그램에서 사용할 떄에는, 신중에 신중을 고려하여야 할 부분입니다.

첫번째로 가장 많이 알려진 DKOM적 방법인 EPROCESS LINK를 끊는 방법입니다.
먼저 숨기고자 하는 프로세스 이름으로 EPROCESS를 찾는 함수 입니다.

PEPROCESS
FindProcessByName(char *Name)
{
 PLIST_ENTRY start_plist,plist_hTable = NULL;
 ULONG *d_pid;
 PEPROCESS eproc;
 char *nameptr;
 CHAR Process_Name[NT_PROCNAMELEN];
 NTSTATUS rc;

 plist_hTable = (PLIST_ENTRY)((*(ULONG*)
  ((ULONG)PsInitialSystemProcess + 0xC4)) + 0x1C);
 
 start_plist = plist_hTable;


 do
 {
  d_pid = (ULONG*)(((ULONG)plist_hTable + 0x8)
      - 0x1C);
  rc = PsLookupProcessByProcessId((HANDLE)*d_pid,&eproc);
  if(eproc && NT_SUCCESS(rc))
  {
   nameptr = (PCHAR)eproc + gProcessNameOffset;
   strncpy(Process_Name, nameptr, NT_PROCNAMELEN);
   Process_Name[NT_PROCNAMELEN] = 0;
   ObDereferenceObject(eproc);
   if(!strncmp(Process_Name,Name,NT_PROCNAMELEN-1))
   {
    return eproc;
   }
  }
 // DbgPrint("%s\n",Process_Name);
  plist_hTable = plist_hTable->Flink;  
 }while(start_plist != plist_hTable);

 return 0;
}

그후에는 다음과 같은 코드로 숨킬 수 있습니다.

PEPROCESS MyProcess;
PLIST_ENTRY plist_active_procs;

MyProcess = FindProcessByName(ProcessName);

    if(MyProcess)
    {
     plist_active_procs = (LIST_ENTRY *)((DWORD)MyProcess + 0x88);
     *((DWORD *)plist_active_procs->Blink) = (DWORD)plist_active_procs->Flink;
     *((DWORD *)plist_active_procs->Flink+1) = (DWORD)plist_active_procs->Blink;
     plist_active_procs->Flink = (LIST_ENTRY *) &(plist_active_procs->Flink);
     plist_active_procs->Blink = (LIST_ENTRY *) &(plist_active_procs->Flink);
    }

위의 코드에 다음과 같은 한줄을 붙여넣음으로서, 보호하고자 하는 프로세스의
PID를 0으로 만들어 접근이 불가능하게 할 수도 있습니다.

*((DWORD*)((DWORD)MyProcess+0x84)) = 0;

csrss.exe 에서 핸들을 지우는 코드 입니다.

 PEPROCESS gpeproc_csrss;

  gpeproc_csrss = (PEPROCESS)FindProcessByName("csrss.exe");
    if(!gpeproc_csrss) gpeproc_csrss = (PEPROCESS)FindProcessByName
                                                                               ("CSRSS.EXE");


if(gpeproc_csrss) EraseHandle((PEPROCESS)gpeproc_csrss, (PVOID)MyProcess);

void EraseHandle(PEPROCESS eproc, PVOID tarHandle)
{
 PTABLE_ENTRY   orig_tableEntry, p_tableEntry, *pp_tableEntry, **ppp_tableEntry;
 int a, b, c;
 int i_numHandles, i_hperPage, i_numTables;
 int i_handle;

 //DbgPrint("Hiding %x from %s process handle table.\n", tarHandle, (DWORD)eproc+gul_ProcessNameOffset);
 i_numHandles = *(int*)((*(PDWORD)((DWORD) eproc + 0xC4)) + 0x3C);
 orig_tableEntry = (PTABLE_ENTRY)*(PDWORD)((*(PDWORD)((DWORD) eproc + 0xC4)));
 i_numTables = ((DWORD)orig_tableEntry & 3);

 i_hperPage = PAGE_SIZE/sizeof(TABLE_ENTRY);
 
 if (i_numTables == 0)
 {
  //DbgPrint("Found a single level handle table.\n");
  p_tableEntry = (PTABLE_ENTRY)((DWORD)orig_tableEntry & 0xfffffff8);
  for (a = 0; a < i_hperPage; a++)
  {
   if (((p_tableEntry[a].object ^ 0x80000000) & 0xfffffff8) == ((DWORD)tarHandle - 0x18))
   {
    //DbgPrint("Handle = %x Object Header %x Security %x\n", a*4, ((p_tableEntry[a].object | 0x80000000) & 0xfffffff8), p_tableEntry[a].security);
    p_tableEntry[a].object = 0;
    p_tableEntry[a].security = 0;
   }
  }
 }
 else if (i_numTables == 1)
 {
  //DbgPrint("Found a two level handle table.\n");
  pp_tableEntry = (PPTABLE_ENTRY)((DWORD)orig_tableEntry & 0xfffffff8);
  for (a = 0; a < i_hperPage; a++)
  {
   if (pp_tableEntry[a] == NULL)
    break;

   for (b = 0; b < i_hperPage; b++)
   {
    if (((pp_tableEntry[a][b].object ^ 0x80000000) & 0xfffffff8) == ((DWORD)tarHandle - 0x18))
    {
     //DbgPrint("Handle = %x Object Header %x Security %x\n", ((a*512)+b)*4, ((pp_tableEntry[a][b].object | 0x80000000) & 0xfffffff8), pp_tableEntry[a][b].security);
     pp_tableEntry[a][b].object = 0;
     pp_tableEntry[a][b].security = 0;
    }
   }
   
  }
 }
 else if (i_numTables == 2)
 {
  //DbgPrint("Found a three level handle table.\n");
  ppp_tableEntry = (PPPTABLE_ENTRY)((DWORD)orig_tableEntry & 0xfffffff8);
  for (a = 0; a < i_hperPage; a++)
  {
   if (ppp_tableEntry[a] == NULL)
    break;

   for (b = 0; b < i_hperPage; b++)
   {
    if (ppp_tableEntry[a][b] == NULL)
     break;

    for (c = 0; c < i_hperPage; c++)
    {
     if (((ppp_tableEntry[a][b][c].object ^ 0x80000000) & 0xfffffff8) == ((DWORD)tarHandle - 0x18))
     {
      //DbgPrint("Handle = %x Object Header %x Security %x\n", ((a*512)+(b*256)+c)*4, ((ppp_tableEntry[a][b][c].object | 0x80000000) & 0xfffffff8), ppp_tableEntry[a][b][c].security);
      ppp_tableEntry[a][b][c].object = 0;
      ppp_tableEntry[a][b][c].security = 0;
     }
    }
   
   }
  }
 }
}

PspCidTable에서 오브젝트를 지우는 함수 입니다.

먼저 다음과 같이 PspCidTable을 지우는 함수를 먼저 호출 합니다.

DWORD gcid_table;

gcid_table = GetPspCidTable();

DWORD
GetPspCidTable()
{
    PVOID pPspCidTable = NULL;
    ULONG i;
    UNICODE_STRING  usPsLookup;
    PUCHAR Buff;
   
    RtlInitUnicodeString( &usPsLookup, L"PsLookupProcessByProcessId" );
    Buff = MmGetSystemRoutineAddress( &usPsLookup );

    if( Buff != NULL )
    {   
        for( i = 0; i < 0x40; i++, Buff++ )
        {
            if( *(PUSHORT)(Buff) == 0x35ff && *((PUCHAR)Buff+6) == 0xe8 )
            {
                pPspCidTable = (PVOID)(*(PULONG)(Buff+2));
                break;
            }
        }
    }

    return (DWORD)pPspCidTable ? *(DWORD*)pPspCidTable : (DWORD)NULL;
}

 EraseObjectFromPspCidTable(gcid_table, (PVOID)MyProcess, 0,*((DWORD*)((DWORD)MyProcess+0x84)), 0 );

void EraseObjectFromPspCidTable(DWORD handle_table, PVOID tarHandle, enum ObjectType obj_type, DWORD pid, DWORD tid)
{
 PTABLE_ENTRY   orig_tableEntry, p_tableEntry, *pp_tableEntry, **ppp_tableEntry;
 int a, b, c;
 int i_numHandles, i_hperPage, i_numTables;
 int i_handle;

 i_numHandles = *(int*)(handle_table + 0x3c);
 orig_tableEntry = (PTABLE_ENTRY)*(PDWORD)(handle_table + 0);
 i_numTables = ((DWORD)orig_tableEntry & 3);

 i_hperPage = PAGE_SIZE/sizeof(TABLE_ENTRY);
 
 if (i_numTables == 0)
 {
 
//   DbgPrint("Found a single level handle table.\n");
  p_tableEntry = (PTABLE_ENTRY)((DWORD)orig_tableEntry & 0xfffffff8);
  for (a = 0; a < i_hperPage; a++)
  {
   if (((p_tableEntry[a].object | 0x80000000) & 0xfffffff8) == ((DWORD)tarHandle))
   {
//     DbgPrint("[%d]\n",a);
//     DbgPrint("Handle = %x Object %x Security %x\n", a*4, ((p_tableEntry[a].object | 0x80000000) & 0xfffffff8), p_tableEntry[a].security);
   
//     add_index(&g_PspCidTableList, SINGLE_LEVEL,obj_type,(DWORD)tarHandle,pid,tid, a,0, 0, p_tableEntry[a].object, p_tableEntry[a].security);
   
    p_tableEntry[a].object = 0;
    p_tableEntry[a].security = ((PHANDLE_TABLE)handle_table)->FirstFree;
    ((PHANDLE_TABLE)handle_table)->FirstFree = (ULONG)( (tid!=0) ? tid : pid );
   }
  }
 }
 else if (i_numTables == 1)
 {
//   DbgPrint("Found a two level handle table.\n");
  pp_tableEntry = (PPTABLE_ENTRY)((DWORD)orig_tableEntry & 0xfffffff8);
 
  for (a = 0; a < i_hperPage; a++)
  {
   if (pp_tableEntry[a] == NULL)
    break;

   for (b = 0; b < i_hperPage; b++)
   {
    //DbgPrint("Comparing %x to %x\n", ((pp_tableEntry[a][b].object | 0x80000000) & 0xfffffff8), tarHandle);
    if (((pp_tableEntry[a][b].object | 0x80000000) & 0xfffffff8) == ((DWORD)tarHandle))
    {
//      DbgPrint("[%d][%d]\n",a,b);
//      DbgPrint("Handle = %x Object %x Security %x\n", ((a*512)+b)*4, ((pp_tableEntry[a][b].object | 0x80000000) & 0xfffffff8), pp_tableEntry[a][b].security);
//      add_index(&g_PspCidTableList, DOUBLE_LEVEL,obj_type,(DWORD)tarHandle, pid,tid,  a,b, 0, pp_tableEntry[a][b].object, pp_tableEntry[a][b].security);

     pp_tableEntry[a][b].object = 0;
     pp_tableEntry[a][b].security = ((PHANDLE_TABLE)handle_table)->FirstFree;
     ((PHANDLE_TABLE)handle_table)->FirstFree = (ULONG)( (tid!=0) ? tid : pid );
    }
   }
   
  }
 }
 else if (i_numTables == 2)
 {
//   DbgPrint("Found a three level handle table.\n");
  ppp_tableEntry = (PPPTABLE_ENTRY)((DWORD)orig_tableEntry & 0xfffffff8);
  for (a = 0; a < i_hperPage; a++)
  {
   if (ppp_tableEntry[a] == NULL)
    break;

   for (b = 0; b < i_hperPage; b++)
   {
    if (ppp_tableEntry[a][b] == NULL)
     break;

    for (c = 0; c < i_hperPage; c++)
    {
     if (((ppp_tableEntry[a][b][c].object | 0x80000000) & 0xfffffff8) == ((DWORD)tarHandle))
     {
//       DbgPrint("Handle = %x Object %x Security %x\n", ((a*512)+(b*256)+c)*4, ((ppp_tableEntry[a][b][c].object | 0x80000000) & 0xfffffff8), ppp_tableEntry[a][b][c].security);
//       add_index(&g_PspCidTableList, TRIPLE_LEVEL,obj_type, (DWORD)tarHandle, pid,tid,  a,b, c, ppp_tableEntry[a][b][c].object, ppp_tableEntry[a][b][c].security);

      ppp_tableEntry[a][b][c].object = 0;
      ppp_tableEntry[a][b][c].security = ((PHANDLE_TABLE)handle_table)->FirstFree;
      ((PHANDLE_TABLE)handle_table)->FirstFree = (ULONG)( (tid!=0) ? tid : pid );
     }
    }
   
   }
  }
 }
}

HandleListEntry 해제하는 코드 입니다.

UnHookHandleListEntry((PEPROCESS)MyProcess);

void UnHookHandleListEntry(PEPROCESS eproc)
{
 PLIST_ENTRY plist_hTable = NULL;
 plist_hTable = (PLIST_ENTRY)((*(PDWORD)((DWORD) eproc + 0xc4)) +  0x1c);

 //DbgPrint("Unhooking the handle table of Process: %s\n", (DWORD)eproc+gul_ProcessNameOffset);
 *((DWORD *)plist_hTable->Blink) = (DWORD) plist_hTable->Flink;
 *((DWORD *)plist_hTable->Flink+1) = (DWORD) plist_hTable->Blink;

 //plist_hTable->Flink = (LIST_ENTRY *) &(plist_hTable->Flink); // Change the current LIST_ENTRY
 //plist_hTable->Blink = (LIST_ENTRY *) &(plist_hTable->Flink); // so we don't point to crap

}

프로세스에 속해 있는 스레드를 숨기는 코드 입니다.

if(gpeproc_csrss) HideThreadsInTargetProcess((PEPROCESS)MyProcess, gpeproc_csrss);


void HideThreadsInTargetProcess(PEPROCESS eproc, PEPROCESS target_eproc)
{
 PETHREAD start, walk;
 DWORD check1, check2;

 if (eproc == NULL)
  return;

 check1 = *(DWORD *)((DWORD)eproc + 0x50);
 check2 = ((DWORD)eproc + 0x50);
 // If check1 points back to the EPROCESS, there are no threads in the process.
 // It must be exiting.
 if (check1 == check2)
  return;

 start = *(PETHREAD *)((DWORD)eproc + 0x50);
 start = (PETHREAD)((DWORD)start - 0x1b0);
 walk = start;
 do
 {
  EraseHandle(target_eproc, walk);
  walk = *(PETHREAD *)((DWORD)walk + 0x1b0);
  walk = (PETHREAD)((DWORD)walk - 0x1b0);
 }while (walk != start);
}


 


지금 소개한 프로세스 숨기는 코드들만 사용하여도, 충분히 강력한 효과를 내실 수 있
을 것입니다. 하지만 꼭 명심하시기 바랍니다. DKOM을 사용할떄는 충분한 필드 테스트가
선행되어져야 한다는 것을...

여담이지만, 사실 이 글의 제목은 'Anti Game hacking 프로그램이 갖추어야할 기능들과
약점들.' 이었는데요. 글을 쓰다보니 -_-;; 필요한 기능과 코드만 다루어도 글의 길이가
꾀 길더군요. 그래서 두편으로 나누기로 결정을 하였습니다. 그럼 다음편에서는
Anti GH 프로그램의 약점과 그 대안에 대해서 알아보도록 하겠습니다.
이 글에서 잘못된 부분은 dual@null2root.org 로 알려주시거나, 이 글에 리플을 달아
주셔도 감사하겠습니다. 그럼 이만 (__)...

프로세스에서 메모리 패턴 검사 :

윈도우,클레스 목록에서 검사 :

이벤트,뮤텍스,파일맵 이름 검사 :

CRC 검사 :

참고 문헌 (?)
- 추후 추가 예정.

==============================================================================================================

출처 : 데브피아  : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNO=20&no=7806&page=2

'C/C++언어 > 후킹' 카테고리의 다른 글

Hooking 을 사용하는 프로그램 내의 구현  (0) 2007.11.11
Global Hooking in Win32  (0) 2007.11.11
[본문스크랩] 메세지 후킹  (0) 2007.09.06
[본문스크랩] Knowledge Base API  (0) 2007.09.06
API Hooking Revealed  (0) 2007.09.06

일단 이것땜에 숙제, 턴프로젝트, 할일등 모두 내 팽겨쳐 졌다.. orz

유니코드는 이 아래에서 긁어온 자료와 같이 여러 종류가 있다고 한다.

하지만 지금 중요한건 저 글자를 파일로 저장한뒤 온전하게 불러오는데 목적이 있다.

예제는 한줄경우 어떻게 처리가 되었지만, 몇백줄 넘어가는 자료를 저장하면 그 나름대로 테크닉이
필요하다.

데브 피아 등 여러곳을 돌아다니가 결국 잘못된곳을 찾아냈다..

다음은 J-Notebook 프로그램의 파일 저장 소스 일부분이다.

if(dlg.DoModal() == IDOK)
 {
  CFile file;
  CFileException e;
  if(!file.Open(dlg.GetFileName(), CFile::modeWrite | CFile::modeCreate, &e))
  {
   e.ReportError();
   return;
  }
  // 파일을 쓴다. 음... file.Write가 \n 이 자동으로 붙고, \r\n하면 한줄씩 내려준다.
  // 유니코드 시작
  WORD  a;
  a = 0xFEFF;
                                   <- 16진수 변환 프로그램을 보니, 제대로 읽히는 파일은 FFFE로 시작한다.
                                       하지만 FEFF로 넣었다.. 왜냐? 윈도우는 리틀 앤디안이기 떄문이라;;;
  file.Write((void*)&a, sizeof(a));
                                // 맨 처음부분에 넣어준다. 왜인지 모르겠지만 아무래도 유니코드 식별인자 인거 같다;;

  // 유니코드에서 한줄내림은 코드는 0x0a인듯 하다..
  a = 0x0A;
  JWord note;
  CString strWord;
 
  int i=0;
  for(; i < pDoc->m_szNoteWord.size()-1; i++)
  {
   note = pDoc->m_szNoteWord[i];
   strWord.Format(L"%s;%s;%s;%d\r",note.word, note.yomigana, note.mean, note.count);
   // 글자를 쓰고,
   file.Write(strWord, strWord.GetLength()* sizeof(wchar_t));
   // 이 글자의  유니코드식별자? 를 추가해준다.. 파일흐름상 개행라인인거 같다.
   file.Write((void*)&a, sizeof(a));

  }
  // 마지막은 개행할 필요가 없으므로 따로 써준다.
  note = pDoc->m_szNoteWord[i];
  strWord.Format(L"%s;%s;%s;%d",note.word, note.yomigana, note.mean, note.count);
  file.Write(strWord, strWord.GetLength()* sizeof(wchar_t));
 }

이짓을 해주면. CFile::typeText | CFile::modeRead 로 문제없이 읽어온다.

하지만... 유니코드... 이거 난해해서.. 괜한 지뢰밭 들어간건 아닌지 걱정된다.

유니코드 정보 모음 | 기타 기술정보
전체공개 0 / 2007.05.20 12:04


UTF의 개념
UTF는 16bit 유니코드 문자들을 7비트 또는 8비트 문자로 변환하기 위한 방법이다.
UTF-8은 유니코드를 8비트 문자로 변경하는 것입니다.
유니코드란 - 각각의 다른 나라를 시스템의 호환성 및 확장성에 문제를 일으키는 관계로
이를 하나의 문자인 유니코드로 통합시켜 표현하는 방법중에 한가지입니다.


UTF-8의 동작 방법
DNS를 찾을 경우 클라이언트에서 utf-8로 인코딩 되어 서버의 ip주소를 찾아 갑니다.
그러나 특정한 사이트인 경우 utf-8을 지원하지 못할 수 도 있고 서버에 접속을 못할 수 도 있습니다.

유니코드 용어의 이해

유니코드 관련 문서를 읽다보면 가장 많이 마주치는 용어들이
UCS2, UCS4, UTF8, UTF16, UTF32 등과 같은 단어들입니다.

기본언어판, BMP (Basic Mulitilingual Plane)
유니코드의 첫 65,536개의 코드를 의미합니다.

언어판, Plane (256x256 즉 65,536 개씩의 코드 묶음)
유니코드에서는 현재 17개의 언어판을 사용할 수 있습니다.
모두 그룹 00에 포함됩니다.

언어판 그룹, Group (256개씩의 언어판을 묶어 하나의 그룹)
유니코드의 17개 언어판은 모두 Group 00에 있습니다.
유니코드는 17개의 언어판에 한정되어 정의됩니다.
반면 ISO 표준(UCS-4)에서는 모두 128개의 언어판 그룹이 정의될 수 있습니다.
1 Plane = 65,536 code points
1 Group = 256 planes = 256x65,536 = 16,777,216 code points
UCS-4 = 128 groups = 128x16,777,216 = 2,147,483,648 code points

인코딩, Encoding (문자집합을 표현하는 방식)
유니코드는 코드체계 또는 문자집합을 명명하는 것이며 이를 표현하기 위해서는
UTF-8, UTF-16, UTF-32 등과 같은 인코딩이 필요합니다.

UCS-2: Universal Character Set 2(octets)
좀더 정확하게는 Universal Multipe-Octet Coded Character Set 2입니다.
ISO/IEC 10646의 용어로 BMP의 65,536 코드를 정의하며, 2바이트로 표현됩니다.
1개의 언어판, 즉 BMP만이 이에 해당합니다.
UCS-2는 인코딩 방법이 아니며 문자코드 자체입니다.
인코딩으로 봐도 무방하겠군요. 여기서 octet이라는 용어를 사용했는데 이 용어는
ISO쪽에서 사용하는 용어로, 유니코드 진영에서 사용하는 바이트와 같은 뜻입니다

UCS-4: Universal Character Set 4(octets)
ISO/IEC 10646의 용어로 4바이트로 표현됩니다.
모두 128개의 언어판 그룹, 즉 128*256 언어판 = 32,768 언어판을 정의합니다.
이는 대략 231 = 2,147,483,648개의 코드에 해당합니다.
UCS-4는 인코딩 방법이 아니며 문자코드 자체입니다.

UTF-8: UCS Transformation Format, 8-bit form
Unicode 표준의 인코딩 방식중의 하나입니다.
표준에서는 17개 언어판의 문자만을 표현할 수 있으나 기술적으로는
UCS-4 전영역의 문자를 표현할 수 있습니다.
문자에 따라 1 ~ 4(또는 6) 바이트로 표현됩니다.

UTF-16: UCS Transformation Format, 16-bit form
유니코드 3.0에서는 16을 16비트로 해석한 것이 아니라,
그룹 00의 16개 언어판이라고 써 놓았군요.
UTF-32의 32가 32비트를 지칭하므로 통일성을 위해 16비트로 이해하시는 게 좋습니다.
16비트로 표현한다는 점에서는 UCS-2와 흡사하지만 대행문자영역(Surrogates)을
이용하여 16개의 보충 언어판 코드를 표현할 수 있는 인코딩입니다.
대행문자영역 2개로 16개의 보충 언어판을 표현할 수 있습니다.
UCS-2에서는 65536개의 코드만을 정의할 수 있으나 UTF-16에서는
1백만여자를 더 표현할 수 있습니다.

UTF-32: UCS Transformation Format, 32-bit form
32비트 즉 4바이트로 각 문자를 표현합니다.
이점에서 UCS-4와 동일하지만 17개의 언어판만을 정의한다는 점에서는
UCS-4의 부분집합으로 간주하면 됩니다. UCS-4와 동일하나
0x00000000 ~ 0x0010FFFF 범위만을 문자코드로 간주한다고 이해하시면 됩니다.

출처 : http://cafe.naver.com/crazysystem/189


-------------------------------------------------------------------------


데브피아 VC QA 게시판에서 발췌.


1) "한글abc"가 유니코드로 써있다면 한글과 영어 구분은 어떻게 해요


    --> 영어와 한글은 code range가 다른 영역에 위치하므로 영역검사로 충분히 구분가능합니다.


2) 유니코드빌드로 하지 않고도 유니코드를 노트패드처럼 유니코드 txt문서로 저장할 수 있나요? 그런 함수는 없는것 같아요.


    --> 가능합니다. 저장할 data를 mbstowcs, MultiByteToWideChar를 통해 멀티바이트 데이타에서 unicode 데이타로 저장합니다.


3) 유니코드빌드로 해도 유니코드 txt문서로 저장할수없다고 앞에서 본거같은데 사실인가요?

    --> 유니코드는 크게 2가지로 보면 됩니다. (사실은 더많지만)

                UCS2 : 이것은 정말로 모든 문자로 2바이트로 표현되서 프로그램 내부의 데이타표현형으로 씁니다.

                            (WCHAR가 이경우죠)


                UTF8 : 이것은 영문은 1바이트 그외의 문자는 2~6(?)바이트의 가변바이트로 저장하게 됩니다.

                            영문권얘들이 자기네 기존 영문데이타와 호환시키기위해 만들었는데 파일저장등에 사용합니다.


            그러므로 저장시에 위의 포맷중 어느것인 확인해서 저장해야겠지요.    UTF8저장경우에는 내부의 WCHAR를

                다시 UTF8 encoding으로 변환해서 저장해야합니다.

                       

4) 유니코드로 해도 데이타베이스에 저장하려면 또 그 데이타베이스가 쓰는 유니코드로 바꿔야한다는데 사실인가요?


        아마 위의 UCS2와 UTF8등의 선택이 있겠지요.. 그러나 ODBC등을 사용한다면 encoding변환에 문제가 없지 않을까요?


5) 유니코드로 해도 윈도우 OS에 쓰는 유니코드와 맞게 고쳐야 유니코드 txt로 저장할수 있는건가요?

        UCS2라 하더라도 OS에 따라 bigendian format으로 little endian format으로 저장하는 것에 차이가 좀있습니다.


6) 유니코드도 종류가 많아서 오히려 쓰기 불편할거 같은생각도 드는데 괜한걱정인가요?

        어차피 encoding 변환 함수를 사용한다면 유니코드encoding변환끼리는 큰 문제가 되지 않을 겁니다.

        encoding변환함수를 짜는 사람이 머리아픈것이지...


        제가 보기에는 파일로 저장형식으로는 UTF8을 일반적으로 많이 쓰이는 것 같습니다.



----------------------------

이정신

http://redhotfrog.mytripod.co.kr




----------------------------------------------------------------------------------------------



아래 데브피아에서 QnA 발췌

자주올라오는 질문들에 대한 응답을 한번 모아보았습니다.

 보충하고 싶은 내용은 메일을 요청바랍니다.


/////////////////////////////////////////////////////////////////////////////

UNICODE PRIVATE FAQ 0.1 (2004.08.03) by redfrog@jitco.net


----------------------------------------------------------------------------

A000 : character set, code set, encoding, codepage가 뭡니까?

A001 : MBCS, SBCS, DBCS가 뭡니까?

A002 : i18n, l10n이 뭡니까?

A003 : 유니코드가 뭡니까?

A004 : 유니코드 UCS2에서 UTF8로 변환은 어떻게 하나요?

A005 : windows 9x와 windows NT계열 OS의 차이.

A006 : C/C++의 표준 문자열정의.

A007 : MFC의 TCHAR.

A008 : "한글abc"가 유니코드로 써있다면 한글과 영어 구분은 어떻게 해요.

A009 : 중국어는 유니코드에서 어떻게 다루나요?

A010 : 유니코드와 파일

A011 : db에 저장하려면 또 그 dbms가 쓰는 유니코드로 바꿔야한다는데 사실인가요?

A012 : database에서는 어떻게 다루나요?

A013 : 참조할 만한 자료들.

----------------------------------------------------------------------------


이것을 저는 UTF8로, 즉 8비트로 변환을 하고자 하는데 어떻게 하면 될까요?


============================================================================

A000 : character set, code set, character encoding, codepage가 뭡니까?

----------------------------------------------------------------------------

character set이란 문자의 집합입니다. 단 이때 각문자에는 숫자코드가 부여됩니다.

그렇지만 코드숫자가 컴퓨터상에서 어떻게 표현되는 가는 정해지지 않은 상태라고

보면 됩니다.


encoding이란 character set에 좀더 제약이 강해서 컴퓨터상에서 어떻게 표현 되는가까지를

정해진 상태의 문자의 집합입니다. 같은 그림이라도 압축방법에 따라 gif, png,

bmp등등의 파일형식이 있듯이 code set과 encoding의 차이를 이해할 수도 있을

겁니다. 실제 예를 들면 완성형한글인 KSC5601 codeset은 UNIX에서는 euc-kr이란

encoding으로 표현되고 DOS에서는 codeset 949란 encoding으로 표현됩니다.


오래전에는 character set과 character encoding은 같은 말이었습니다.

그러나 언젠가 부터 시스템의 종류도 많아지고 다국어시스템의 지원등등 여러여건들에

의해 character set에서 부터 character encoding이 분리된 것이죠.


code set이란 말은 어쩔 때는 character set의 의미로 어쩔 때는 encoding의

의미로 사용됩니다. 그렇다 보니 문맥을 보고서 적당히 해석해서 사용을 해야 합니다.


codepage는 IBM에서 사용하던(?) 말로 encoding과 같은 것으로 보면됩니다.

MICROSOFT에서 DOS를 만들때 IBM과 같이 만들었기 때문에 MICROSOFT에서는

codepage라는 말을 많이 사용합니다.


============================================================================

A001 : MBCS, SBCS, DBCS가 뭡니까?

----------------------------------------------------------------------------

MBCS이란 Multibyte code Set으로 하나의 문자를 표현하는 code가 문자에 따라

한 바이트로도 여러 개의 바이트로도 표현되는 encoding을 의미합니다.

완성형 한글의 경우 영문은 1바이트로 한글/한자는 두 바이트로 표현됩니다.


SBCS란 Singlebyte code Set으로 하나의 문자를 표현하는 code가 항상 한 바이트로

표현될 수 있는 encoding을 의미합니다.


DBCS는 Double Byte code Set으로 하나의 문자를 표현하는 code가 한 바이트나

두 바이트인 encoding을 의미합니다.


windows환경에서는 SBCS와 DBCS가 MBCS의 특수한 경우로 처리됩니다.



============================================================================

A002 : l10n, i18n이 뭡니까?

----------------------------------------------------------------------------

l10n은 localization(지역화)의 약칭입니다. 개발자들이 긴 낱말이 싫어서 약어로

사용하는 말입니다. 소프트웨어가 localization되어있다는 말은 소프트웨어를

사용하는 사용자를 위해 한 언어에 맞추어 개발이 되어있다는 겁니다. 그래서

이 경우에는 한번에 다중언어를 사용할 수 없습니다.


i18n은 internationalization(국제화)의 약칭입니다. software가

internationalization되었다는 말을 들을려면 여러언어를 예를 들면 한국어든

중국어든 동시에 입력해서 사용할 수 있어야 합니다.


multiligual system이란 말과 i18n system은 동일한 말입니다.



============================================================================

A003 : 유니코드가 뭡니까?

----------------------------------------------------------------------------

영문권에서 i18n을 위해서 만든 character set입니다.

유니코드가 나오기 이전에는 i18n system을 만들기가 어려웠습니다. 왜냐하면

여러언어를 포함하는 하나의 단일 character set이 없었기 때문입니다. 그래서

예전에는 여러 character set을 포함하는 가상의 character set을 각 소프트웨어회사마다

내부적으로 만들어서 사용하였습니다. 그러다보니 호환이 잘 안되겠지요.

그래서 유니코드 콘소시엄을 결성하여 모두가 동의하는 문자셋을 만들게 되었습니다.

이것이 바로 유니코드입니다. 유니코드에서 정의하는 character set은 UCS2와

UCS4가 있습니다. 우리가 보통 일반 프로그램을 개발할 때는 UCS2를 기반으로

만들게 됩니다. UCS4는 산스크리트어나 옛 이집트 고어와 같은 것까지 포함하는

것으로 알고 있습니다. 그러므로 보통 유니코드라고 말할 때는 UCS2를 지칭합니다.


UCS2/UCS4는 character set이면서 encoding으로도 존재합니다. 이 encoding의

특징은 UCS2경우에는 영문을 포함한 모든 문자가 두 바이트로 표현되고 UCS4경우에는

네 바이트로 표현됩니다. 이렇게 고정된 길이의 encoding을 쓰면 장점은

문자열내의 특정 문자를 index로 쉽게 접근할 수 있다는 것입니다. MBCS처럼

문자마다 길이가 다른 경우에는 n번째 문자를 접근하려면 문자열의 처음부터

검색을 해야 한다는 점을 생각한다면 문자열처리에 잇점이 있겠지요.


그러나 UCS2 encoding에 장점만 있는 것은 아닙니다. 문제는 기존의 ASCII기반으로

된 모든 소프트웨어와 데이타베이스를 UCS2로 업그레이드해야만 UCS2와 호환된다는

겁니다. Y2K보다 더 황당한 비용이 발생하겠지요. 그래서 이러한 단점을

보완하기 위한 encoding이 UTF7, UTF8입니다. 이 encoding들의 특징은 기존 MBCS처럼

한문자가 1바이트에서 여러바이트를 가질 수 있습니다. 이 경우 encoding의 디자인이

기존 ascii파일은 utf8 encoding을 하더라도 차이가 없도록 되어 있습니다. 즉 ascii파일은

그냥 utf8 encoding이 되어있다고 가정해도 상관없는 겁니다.


그래서 실제적으로 프로그램이 유니코드를 지원한다고 하면 내부적으로는 UCS2/UCS4 encoding을

사용하고 파일/데이타베이스 같은 외부자원에 대해서는 UTF7/UTF8과 같은 encoding을

사용합니다. 즉 혼용해서 사용하는 겁니다.


============================================================================

A004 : 유니코드 UCS2에서 UTF8로 변환은 어떻게 하나요?

----------------------------------------------------------------------------


WINDOWS API에서 보면 WideCharToMultiByte라는 함수가 있습니다.

이 함수의 인터페이스는 다음과 같습니다.


    int WideCharToMultiByte(

      UINT CodePage,            // code page

      DWORD dwFlags,            // performance and mapping flags

      LPCWSTR lpWideCharStr,    // wide-character string

      int cchWideChar,          // number of chars in string

      LPSTR lpMultiByteStr,     // buffer for new string

      int cbMultiByte,          // size of buffer

      LPCSTR lpDefaultChar,     // default for unmappable chars

      LPBOOL lpUsedDefaultChar  // set when default char used

    );


이 함수의 첫번째 매개변수 CodePage에 CP_UTF8를 할당하여 사용합니다.



============================================================================

A005 : windows 9x와 windows NT계열 OS의 차이.

============================================================================


windows 9x (windows 95,98,me)계열은 windows api를 표준적으로 ANSI 버젼을 지원합니다.

즉 unicode를 windows api에서 직접지원하지 않고 mbcs만을 직접지원합니다.

이 때 ANSI는 MBCS라고 생각해도 무방합니다.



windows NT (windows NT, windows 2000, windows XP등등)계열은 windows api를

ANSI버젼과 UNICODE버젼을 모두 지원합니다. ANSI버젼의 API를 쓰는 경우에는

OS가 api를 다시 UNICODE버젼으로 변환합니다. 그러므로 UNICODE로 버젼으로

만들어진 프로그램은 windows NT계열에서 약간의 속도향상을 가져올 수 있습니다.

그러나 windows 9x에서 동작하지 않는 단점이 있습니다. 그래서 microsoft에서는

windows 9x계열에서 UNICODE버젼으로 개발된 소프트웨어를 손쉽게 동작하게 하기

위해서 windows 9x용 unicode를 지원 dll을 제공합니다. 컴파일시 이 dll을 이용하면

unicode버젼으로 개발되었어도 windows 9x계열에서 동작가능합니다.



============================================================================

A006 : C/C++의 표준 문자열정의.

============================================================================


"안녕"이라고 literal을 표현하면 이것은 MBCS(언어규격 표준 용어로는 mcs)입니다.

L"안녕"이라고 literal을 표현하면 이것은 UCS encoding (언어규격 표준 용어로는 wcs)입니다.

어떤 encoding을 따르는 것인가는 compiler와 OS에 따라 다릅니다.

예를 등어 windows에서 visual studio 6.0 한글버젼을 사용하는 경우 MBCS로 컴파일시

codepage 949를 따르게 됩니다.


char데이타표현은 MBCS의 문자를 표현하는데 사용합니다. char는 사실 1바이트로

고정되어 있으므로 MBCS의 문자는 한 char또는 두 char로 표현됩니다.


wchar_t데이타표현은 UCS의 문자를 표현하는데 사용합니다. 많은 경우 2바이트이지만

시스템에 따라 4바이트거나 8바이트일 수도 있습니다. 이러한 UCS문자를

언어규격 표준 용어에서는 wide character라고 합니다.



============================================================================

A007 : MFC의 TCHAR.

============================================================================

MFC는 소스를 수정할 필요없이 mbcs 와 unicode 간의 compile switch을 손쉽게해주는

많은 macro를 제공합니다. strcpy같은 형태가 아니라 _tcscpy와 같은 방식으로 말이죠..

또한 strcpy를 와 같이 mbcs에서 먹는 함수를 바로 써버리면 아무리 UNICODE switch로

compile해보아보았자 MBCS로 컴파일되게 됩니다. 되도록이면 TCHAR계열 마크로를

쓰는 습관을 들이는 것이 중요합니다.


MBCS모드에서는 TCHAR는 char로 그리고 _tcscpy는 strcpy로 변환됩니다. _T("문자열") ---> "문자열"


UNICODE모드에서는 TCHAR가 wchar_t로 그리고 _tcscpy는 wcscpy로 변환됩니다. _T("문자열") ---> L"문자열"


============================================================================

A008 : "한글abc"가 유니코드로 써있다면 한글과 영어 구분은 어떻게 해요

============================================================================


영어와 한글은 code range가 다른 영역에 위치하므로 영역검사로 충분히 구분가능합니다.

자세한 것은 www.unicode.org에 있는 문자 테이블을 참조하십시요..


참고로 MBCS로 컴파일된 경우 완성형을 쓰기 때문에 영문검사시 if (ch < 0x80)

해도 무리가 없습니다.




============================================================================

A009 : 중국어는 유니코드에서 어떻게 다루나요?

============================================================================

중국어는 크게 두가지가 있습니다. 간체 (simplified)와 번체(traditional)이

그 것입니다. character set자체가 다르지요.


번체는 대만에서 표준으로 정립한 문제체계로 예전부터 내려온 기존한문을

그대로 씁니다. 그래서 이름이 traditional이라고 불리우는 것이지요..


간체는 중국본토에서 표준으로 정립한 문제체계로 공산화되면서 많은 한문문자가

약자로 표기되기 때문에 그내용도 약자한문을 담고 있습니다.

그래서 이름이 simplified이라고 불리우는 것이지요.


GB라고 앞에 붙은 encoding이나 character set은 간체를 의미합니다. GB-2312가

처음에 나와서 GBK의 서브셋으로 보면되고 보통은 이정도만 구현해도 많이 작동을

하지만, windows의 지원하는 중국어 간체는 GBK(GB-18030)이라고 보시면 됩니다. 몇

글자는 약간 변경되었지만요. 그리고 나온 것이 GB2K로 알고 있습니다. Unicode는 GB-

18030과 호환이라 보시면될 겁니다. 그외는 저도 기억이 잘나지 않네요.


홍콩이나 싱카폴등등에서 만든 문자셋도 있지만 요즘은 거의 쓰이지 않고 점차

간체가 힘을 쓰는 것 같습니다. 국력에 비례하네요..


============================================================================

A010 : 유니코드와 파일

============================================================================

유니코드 switch로 compile을 처음 해보는 경우 마추치게 되는 문제의 하나가

왜 파일에 저장시 유니코드로 저장되지 않는가 하는 점이다. 앞에서

말했듯이 유니코드라고 하더라도 다양한 encoding이 있으며 유니코드 switch는

API 함수와 자료들을 위한 ucs2 encoding만을 지원한다는 점이다. 즉 파일에

저장한다던지 utf encoding등으로의 변환등은 여전히 전적으로 개발자의 몫으로

남는다는 점이다.


파일로 저장시에는 많은 경우에는 UTF8 encoding을 사용하게되는데 이때는

WideCharToMultiByte API를 사용해서 변환하면 된다.


UTF계열이 아니라 UCS를 바로 저장하는 경우에는 조심해야 하는 것이

byte order이다. 이것은 CPU에 따라 다르게 되어있는데 intel계열은 little

endian이라 는 불리우는 바이트 순서로, 그 외 mac이나 workstation계열은

많은 경우 big endian으로 불리우는 바이트 순서를 가진다. 그러므로

CPU와 OS에  따라 bigendian format으로 little endian format으로 저장하는 것에

차이가 있다.


============================================================================

A011 : db에 저장하려면 또 그 dbms가 쓰는 유니코드로 바꿔야한다는데 사실인가요?

============================================================================

아마 UCS2와 UTF8등의 선택이 있겠지요.. 그러나 ODBC등을 사용한다면

encoding변환에 문제가 없지 않을까요?


============================================================================

A012 : database에서는 어떻게 다루나요?

============================================================================

어떤 분이 database에 대해 문의를 하셨는데 저도 줏어 들은 것 밖에 없고

실제 경험이 없기 때문에 확실한 답변은 안되어도 전에 응답하였던 내용을

올려 봅니다.



질문 :

    UTF8로 세팅된 DB에서 일본어문자를 ODBC를 통해 VC++어플리케이션에서

    가져옵니다.(rsData.GetFieldValue) 그런데 이 UTF8문자가 어플리케이션에서

    가져오면서 유니코드로 저절로 바뀌어서 들어옵니다. [(예)DB에 저장된

    문자열("????"  (12바이트)를 어플리케이션에서 CString으로 가져와서

    GetLength()한값은 8바이트로 찍힙니다.)] 이걸 UTF8로 유지시키려면 어떻게

    해야하나요? (일본어문자하나가 3바이트로 인식되게) 또 전체 어플리케이션의

    코드셋을 UTF8로 바꾸려면 어떻게 해야하나요?


응답 :

    ------------------------------------------------------------------------

    UTF8로 세팅된 DB에서 일본어문자를 ODBC를 통해 VC++어플리케이션에서

    가져옵니다.(rsData.GetFieldValue) 그런데 이 UTF8문자가 어플리케이션에서

    가져오면서 유니코드로 저절로 바뀌어서 들어옵니다.

    ------------------------------------------------------------------------


        ODBC가 UCS2로 자동변환하는 것 같습니다.


    ------------------------------------------------------------------------

    이걸 UTF8로 유지시키려면 어떻게 해야하나요? (일본어문자하나가 3바이트로

    인식되게)

    ------------------------------------------------------------------------

        그럴 필요가 정있다면 다시 UCS2를 UTF8문자열로 바꾸는 WideCharToMultiByte

        함수를 사용하세요. 단 이때 대상 인코딩을 CP_UTF8로 지정하십시요..



    ------------------------------------------------------------------------

    또 전체 어플리케이션의 코드셋을 UTF8로 바꾸려면 어떻게 해야하나요?

    ------------------------------------------------------------------------

        한글과 같은 Multibyte set과 UCS2만을 지원하신다고 보시면 됩니다.

        이론적으로는 UTF8를 기준으로 프로그램을 만들 수는 있지만 의미는 없습니다.

        MFC나 모든 관련 API들이 유니코드지원할 때 UCS2 기준으로 만들어져 있기 때문입니다.

        그러므로 필요할 때만 UTF8로 변환하여 사용하십시요.

       

       


질문 :

    우선 저희 솔루션은 한국어용으로 개발된것인데요 이번에 프로젝트에서 중국어와

    연계가 된 부분이 있어서 일부분을 수정작업하고 있습니다 중국어 문자열을

    추출하여 오라클에 저장하는 프로그램입니다. 그런데 별짓을 다해도

    ???????????????????? 이런식으로 깨져버리는군요 디비는 UTF-8부터해서 한국어

    확장완성형까지 다 바꿔봤지만 결국은 디비 문제가 아닌것을 알았습니다. 디비에

    입력할때 중국어가 깨지는것 같은데?


응답 :

    db의 encoding이 UTF8로 일단 맞춘것으로 가정하죠.


    ????????????????????으로 깨졌다는 것은 데이타가 UTF8형식으로 되어 있지

    않다는 것을 의미합니다. 즉 application이 db에 데이타를 넣을 때 utf8형식으로

    입력을 하지 않았다는 것이죠..


    제가 보기에는 application이 한글지원으로 만들어졌을 때 unicode가 아닌

    multibyte형식으로 compile되어 있는 것같습니다.


    다국어버젼 특히 단일 프로그램으로 다국어를 지원하는 경우에는 unicode형식을

    지원하도록 application을 수정하여야 합니다.


============================================================================

A013 : 참조할 만한 자료들.

============================================================================

1. MSDN

    당연히 윈도우즈에서 개발하려면 MSDN이 없으면 안되겠지요..

   

2. www.unicode.org

    유니코드 콘소시엄 공식 사이트입니다.

   

3. UTF계열 encoding

    자세한 정보가 http://www.elfdata.com/plugin/storage.html 에 잘 나와있네요..

   

4. 전문가가 되려면 다음 책은 필수 입니다.

    그러나 이것은 전문가를 위한 책입니다. 먼저 기본 지식을 습득한 후에 보세요.

   

    Title       : CJKV Information Processing (Chinese, Japanese, Korean &

                    Vietnamese Computing)

    Author      : Ken Lunde

    Publisher   : O'Reilly Media, Inc.

    ISBN        : 1-56592-224-7





-------------------------------------------------------------------------


아래 unnicows dll 링크관련 정보


Here is what MS says:

1. Add the following two files to your project:
   UnicoWS.dll -- the Microsoft Layer for Unicode DLL
   UnicoWS.lib -- the LIB file to which you link

Note that the Microsoft Layer for Unicode does not automatically load from
the $(WINDOWS) or $(WINSYS) directories. Thus, do not put UnicoWS.dll there
unless you are running from a system process that is located there. Instead,
keep the UnicoWS.dll in your application directory and call LoadLibrary
yourself to ensure that you load the correct .dll.

*** I did it: now unicows.dll is in my application directory
*** I receive no more the "unicows.dll not found" error but...
*** ... now I have an error on Kernel32.DLL  :(

Add the following to the link options for your application (note that these
libraries are not separated by commas because that is how you add them to
the link list):
First, add the following:
/nod:kernel32.lib /nod:advapi32.lib /nod:user32.lib /nod:gdi32.lib
/nod:shell32.lib
/nod:comdlg32.lib /nod:version.lib /nod:mpr.lib /nod:rasapi32.lib
/nod:winmm.lib
/nod:winspool.lib /nod:vfw32.lib /nod:secur32.lib /nod:oleacc.lib
/nod:oledlg.lib
/nod:sensapi.lib.

*** Not done: which are already added by wxWidget? Which are not? Help
required!

2. Then add UnicoWS.lib.

Finally, add the libraries that the Microsoft Layer for Unicode uses
explicitly:

kernel32.lib advapi32.lib user32.lib gdi32.lib shell32.lib comdlg32.lib
version.lib mpr.lib rasapi32.lib winmm.lib winspool.lib vfw32.lib
secur32.lib
oleacc.lib oledlg.lib sensapi.lib.

In this step, omit any libraries listed after kernel32.lib whose APIs are
not used in your application. However, if your application uses another
component, such as MFC, ATL, or CRT, be sure to include any libraries on
which the component depends.

*** Same as before. What sould I add and what should I not?

3. Compile your application.

If you are using side-by-side assemblies, you must define
MICROSOFT_LAYER_FOR_UNICODE as 1.

*** I assume this is done by wxWidget when I set MSLU to 1.

When you follow these steps, the Microsoft Layer for Unicode loads itself by
calling LoadLibrary. However, if you want to control the loading of the
UnicoWS.lib you must perform the following additional steps. (These steps
are also necessary if you are using side-by-side assemblies.)

*** Is this the case of wxWidget?

To control loading MSLU or use side-by-side assemblies
Add the following code to your application:
#ifdef _cplusplus
extern "C" {
#endif
extern FARPROC _PfnLoadUnicows = (FARPROC) &LoadUnicowsProc;
#ifdef _cplusplus
}
#endif

*** I assume this is done by wxWidget? Is it?

Define the LoadUnicowsProc function. This function is a user-defined
callback function that takes no parameters. The loader calls it when needed
to load the Microsoft Layer for Unicode. Note that LoadUnicowsProc is only
called on Windows 95/98/Me. Also, LoadUnicowsProc is called before the
DllMain PROCESS_ATTACH call and, for an .exe, before WinMain.

*** I assume this is done by wxWidget? Is it?

HMODULE LoadUnicowsProc(void);
The following is a typical implementation of LoadUnicowsProc.
HMODULE LoadUnicowsProc(void)
{
    return(LoadLibraryA("unicows.dll"));
}

Note that you must call LoadLibraryA and all other Ansi APIs explicitly.
This is because compiling as Unicode defines APIs like LoadLibrary as
LoadLibraryW. For more information, see Conventions for Function Prototypes.

*** I assume this is done by wxWidget? Is it?

If you load the Unicows.lib in this manner, you must never call any of the
APIs that the Microsoft Layer for Unicode itself wraps. Doing so leads to a
stack overflow because your callback calls the loader which calls your
callback, and so forth.

*** ????

Thank you for any help... in advance ;-)

DdJ


리스트뷰 컨트롤의 추가, 삭제, 수정, 검색 등

아주 기초적인 사용법에 대해 적어보고자 합니다.




이런식으로 전체적인 레이아웃을 잡고 시작한다고 봐야겠죠?

그리고 중요한 컨트롤의 컨트롤의 ID 의 값을 각각 아래와 같이 정하고 시작하겠습니다.




각각의 컨트롤에 대해서는 WM_COMMAND 메시지를 다루면 되겠죠.



추가



추가의 경우에는 지난번에 했던 방식 그대로

ListView_InsertItem 과 ListView_SetItemText 매크로를 이용하면 됩니다.


   li.mask=LVIF_TEXT;
   li.state=0;
   li.stateMask=0;
   
   idx=ListView_GetItemCount(hList);

   GetDlgItemText(hWnd,IDC_NAME,szName,255);
   GetDlgItemText(hWnd,IDC_SEX,szSex,255);
   GetDlgItemText(hWnd,IDC_ADDR,szAddr,255);


   li.iItem=idx;
   li.iSubItem=0;
   li.pszText=szName;


   ListView_InsertItem(hList,&li);
   ListView_SetItemText(hList,idx,1,szSex);
   ListView_SetItemText(hList,idx,2,szAddr);



그나마 지난번과 달라진건 컨트롤의 값을 읽어오는 GetDlgItemText 와

리스트 뷰의 아이템들의 수를 읽는 ListView_GetItemCount 가 추가된 것 밖에 없겠군요 ^^


int ListView_GetItemCount(HWND hWnd)


이게 원형입니다. sizeof 와 비슷하다고 보면 되겠군요.



삭제


삭제를 하기 위해서 사용되는 매크로는 아래와 같습니다.


int ListView_GetNextItem(HWND hWnd, int iStart, UINT flags);

BOOL ListView_DeleteItem(HWND hWnd, int iItem)


ListView_GetNextItem 의 첫번째 인수는 당연히 리스트의 핸들이고,

두번째는 어디서부터 찾을 것인가를 정하는 것입니다. -1 의 경우는

전체에서 찾는 것이고 주어지는 값에 따라 그 인덱스부터 차례로 찾아가는 것이죠.


리턴값이 int 형인데 이건 찾은 인덱스 값을 리턴합니다 ^^ 몇 번째 존재하는지를 리턴하죠.

세번째 인수는 Flags 값을 지정하는 것인데 LVNI_ALL | LVNI_SELECTED 를 보통 지정합니다.

왜 그런가 싶으면 LVNI_ALL 이나 LVNI_SELECTED 를 하나씩 삭제하여 컴파일해보면 알겠죠?


idx=ListView_GetNextItem(hList, -1, LVNI_ALL | LVNI_SELECTED);


이 경우는 만약 찾았을 경우에는 idx 의 값에는 해당 인덱스가 들어가겠지만,

찾기 실패하였을 경우에는 idx 에 -1 값이 들어갑니다.



   idx=ListView_GetNextItem(hList,-1,LVNI_ALL|LVNI_SELECTED);
   if(idx==-1)
        MessageBox(hWnd,"삭제할 아이템을 선택하세요","Error",MB_OK);
   else
   {
        while(idx!=-1)
        {
             ListView_DeleteItem(hList,idx);
             idx=ListView_GetNextItem(hList,idx-1,LVNI_ALL|LVNI_SELECTED);
        }
   }



여기서 중요한 것은 while 문의 ListView_GetNextItem 에 idx-1 이 들어간다는 것이죠.

선택된 아이템들을 하나씩 찾아서 지워주는 것이기 때문에 idx 보다 -1 된 것을 넣어야

차례로 지워나갑니다. idx  를 그대로 넣어준다면 무슨 일이 일어나는지는 직접 해보시면 압니다.


선택된 아이템들이 전부 삭제가 되지 않겠죠;



수정


수정은 추가와 비슷하여 상당히 쉽습니다. 다만 ListView_InsertItem 매크로 대신

ListView_SetItem 을 통하여 재설정을 해줘야 합니다. 아주 조금 추가와 다를 뿐,

기본 방식은 같습니다.



   idx=ListView_GetNextItem(hList,-1,LVNI_ALL|LVNI_SELECTED);

   if(idx==-1)
        MessageBox(hWnd,"수정할 아이템을 선택하세요","Error",MB_OK);
   else
   {
        GetDlgItemText(hWnd,IDC_NAME,szName,255);
        GetDlgItemText(hWnd,IDC_SEX,szSex,255);
        GetDlgItemText(hWnd,IDC_ADDR,szAddr,255);


        li.iItem=idx;
        li.iSubItem=0;
   
        ListView_SetItem(hList,&li);
        ListView_SetItemText(hList,idx,0,szName);
        ListView_SetItemText(hList,idx,1,szSex);
        ListView_SetItemText(hList,idx,2,szAddr);
   }



보시면 아시겠지만 일단 선택된 것을 찾아서 idx 에 그 인덱스 값을 넣어줍니다.

만약 선택되지 않았다면 Error 메시지박스가 뜨겠죠. 이제 LVITEM 구조체인

li 의 iItem 에 idx 값을 지정하고 iSubItem 멤버에도 첫번째 컬럼을 의미하는 0을 지정한 뒤


ListView_SetItem 으로 li 를 hList 에 설정합니다.

일단 내가 몇 번째 리스트 뷰 아이템을 설정한다고 알려주는 것이죠.


그런 뒤 ListView_SetItemText 매크로를 통하여 수정해주면 됩니다 ^^



검색



다행히 검색을 할 때도 편리하게(?) 매크로가 존재합니다.


int ListView_FindItem(HWND hWnd, int iStart, const LPLVFINDINFO plvfi);


여기서 중요한건 LVFINFO 구조체가 들어가는데요, 이건 검색의 조건으로


typedef struct tagLVFINDINFO {
    UINT flags;
    LPCTSTR psz;
    LPARAM lParam;
    POINT pt;
    UINT vkDirection;
} LVFINDINFO, *LPFINDINFO;


flags 멤버로 중요한건


LVFI_STRING    psz 멤버가 지정하는 문자열과 일치하는 텍스트를 가진 항목을 검색

LVFIN_WRAP    검색 대상이 발견되지 않을 경우 처음부터 다시 검색


이 두 개 정도로만 봐도 가장 기초적인 곳에서는 사용할 수 있습니다.

그리고 vkDirection 이라는 특이한 멤버가 존재하는데요,

이건 VK_LEFT , VK_DOWN, VK_HOME 등 가상 키를 넣어주는겁니다.


대게 VK_DOWN 을 설정하는 편이라는군요.



   LVFINDINFO fi;

   GetDlgItemText(hWnd,IDC_NAME,szName,255);


   fi.flags=LVFI_STRING;
   fi.psz=szName;
   fi.vkDirection=VK_DOWN;

   idx=ListView_FindItem(hList,-1,&fi);


   if(idx==-1)
        MessageBox(hWnd,"검색된 아이템이 없습니다","Error",MB_OK);
   else
   {
        ListView_SetItemState(hList,-1,0,LVIS_FOCUSED|LVIS_SELECTED);
        ListView_SetItemState(hList,idx,LVIS_FOCUSED|LVIS_SELECTED,0x000F);
        ListView_EnsureVisible(hList,idx,FALSE);
   }



이렇게 IDC_NAME 의 값을 읽어와 psz 에 설정한 후 ListView_FindItem 매크로에서 찾아주면 되네요.

ListView_FindItem 에서 -1 은 가장 처음에서 찾는, 전체에서 찾는다는 의미죠. 같은 문자열로 찾기를

계속 이어주려면 idx-1 값으로 루프를 돌려주면서 찾으면 되겠군요 ^^


찾았을 경우 ListView_SetItemState 로 상태를 설정해줘야하는데요.


ListView_SetItemState(hList,-1, 0,LVIS_FOCUSED|LVIS_SELECTED);

ListView_SetItemState(hList,idx,LVIS_FOCUSED|LVIS_SELECTED,0x000F);


이 두개가 왜 존재할까요? -1은 전체를 의미하고, 0은 상태를 없앤다고 보면 되죠.

일단 리스트 뷰에 어떤 항목들이 선택되어있거나 한다면 리플래시를 시켜 없앱니다.


그게 바로 ListView_SetItemState(hList,-1, 0,LVIS_FOCUSED|LVIS_SELECTED) 입니다.

그리고 나서 찾은 idx 번째의 리스트 뷰의 아이템을 선택해야하는데요, 그게 바로 두 번째인


ListView_SetItemState(hList,idx,LVIS_FOCUSED|LVIS_SELECTED,0x000F);


입니다. 마지막 인수가 16진수로 되어있지만 이것도 역시 아래와 같이 바꿀 수 있습니다.


ListView_SetItemState(hList,idx,LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);


여기까지가 가장 기초적인(?) 방법이었네요.

사실상 개인적으로 시간이 많이 먹었던 부분이예요.


좀더 자세한건 소스 첨부 했으니 참고하시면 되구요 ^^;

다음엔 WM_NOTIFY 에 해당되는 리스트 뷰의 메시지 관리를 적어보겠습니다.

출처 : http://cafe.naver.com/seen/131

1) GetAsyncKeyState() 함수와 2) GetKeyState()는 사용방법이 다른데


1)은 비동기(Asynchronism)으로 처리 한다.

  : 호출된 시점에서 키 상태를 조사하여, 메시지 큐를 거치지 않고 바로 리턴을

    해주므로 키 입력을 바로 처리해 줄 수 가 있다.

2)는 호출된 시점에서 메시지 큐를 거치며, 메시지 발생 후의 상태를 리턴하게 되므로, 키보드 메시지

   처리 루틴내에서 사용해야 한다.


바로바로 키 입력을 처리해야하는 경우는 GetAsyncKeyState()를 사용하는 것이 바람직하다.


1) GetAsyncKeyState() 사용법


GetAsyncKeyState(해당키) 는 키가 눌려진 시점에 0x8000 값을 리턴해주고

키가 눌려 있었다면 0x0001 값을 리턴한다.


0x8000 은 현재 키가 눌려진 상태를 말하고

0x0001 은 키가 눌려있었음을 말해준다.


따라서 GetAsyncKeyState() & 0x8000 을 하면 키가 방금 눌렸을 경우만 1이 된다.

          GetAsyncKeyState() & 0x0001 을 하면 키가 눌려있었는지를 판단할 수 있다.


간단 예제


 while(1) {
  while(GetAsyncKeyState(VK_UP) & 0x0001) {
   au.vol_up();
  }
  while(GetAsyncKeyState(VK_DOWN) & 0x0001) {
   au.vol_down();
  }
  if(GetAsyncKeyState(VK_LEFT) & 0x0001) break;
 }


윗키, 아래키를 눌렀을때는 볼륨을 올리고 내리고

왼쪽키는 while문을 중단 시키는 역할을 한다.


 2) GetKeyState() 사용법

   : GetKeyState()는 해당키가 눌린 상태일때는 음수값을 리턴, 아닐 경우는

      해당키가 눌리지 않았음을 나타낸다.


if(GetKeyState(VK_CONTROL) < 0) // 눌려있는 상태이면... 이란 뜻으로 쓰인다

출처 : http://blog.naver.com/mklife/150020920925

+ Recent posts