본문 바로가기

Research/Reverse

Anti Reverse Engineering - BreakPoint







리버스 엔지니어가 사용하는 BP 의 종류에는 보통 세가지 - 하드웨어 / 메모리 / INT 3h - 가 있다.

동적 분석에 있어 리버서에게 필수적인 BP 는 실행 중인 코드 어디든 설치할 수 있으며 또한 실행을 멈출 수 있다.

BP를 적절히 활용함으로써 리버서는 API 를 분석할 수 있고 또한 원하는 메시지를 훨씬 쉽게 찾을 수 있다.

BP는 리버싱 과정에서 문자열을 검색하는 것 만큼이나 가장 많이 활용되며, MessageBox / VirtualAlloc, CreateDialog 와 같은 중요도가 높은 API 함수를 체크하는 데 활용할 수 있다. 





INT 3

BP를 가장 흔히 표현하는 방법이며 IA-32 명령어에서 0xCC opcode로 표현하고 있다.

0xCC 를 0xCD 0x03 으로도 표현 가능한데 이 방법은 트러블이 발생할 수 있다.

아래의 소스를 통해 비교적 간단한 방법으로 이 BP 를 탐지할 수 있는데 오탐(false positive)이 발생할 수 있다.




#include <stdio.h>

#include <process.h>

#include <Windows.h>


unsigned __stdcall checkBP(void* );

int main()

{

HANDLE pThread;

unsigned int threadID;


pThread  = (HANDLE) _beginthreadex(NULL, 0, &checkBP, NULL, 0 , &threadID);



while(1)

{

printf("BP test...\n");

Sleep(1000);

}


}



unsigned __stdcall checkBP(void* )

{

int k = 0;

unsigned char *pTmp = (unsigned char*)main; 

while(1)

{

for (size_t i = 0; i < 0x10; i++)

{

if(pTmp[i] == 0xCC)

{

printf("BP detected at %d\n", i);

Sleep(1000);

}

}


}



뭐 눈에 너무 잘 띄니까 이렇게도 할 수 있다

if(pTmp[i] == 0xCC) ==> if(0x99 == (tmpchar ^ 0x55) )



의문점::

둘 다 while 문을 돌고 있는 상태에서 main 의 while 내부에 bp 를 걸면 탐지를 못 한다.

왜 그럴까



메모리 브레이크포인트

디버거는 GUARD PAGE 를 특정 지점에 설치하는 방식으로 메모리 BP를 형성한다.

그리고 이 BP 는 해당 메모리 페이지에 최초 1회 접근에 한해 동작한다. 

예를 들면 PAGE_GUARD 가 설정되어 있는 특정 메모리의 페이지에 접근 했을 때에, 프로그램에 의해 컨트롤될 수 있는 STATUS_GUARD_PAGE_VIOLATION 예외가 발생한다. 메모리 BP 가 발생하는 정확히 순간을 체크할 수는 없지만, 디버거가 메모리 BP 를 생성하는 방식을 이용해서 현재 프로세스가 디버깅 중인지 아닌지를 판단할 수 있다.

 

먼저 동적 버퍼를 할당하고 버퍼에 RET 를 쓴다고 하자. 그리고 guard page 를 체크해 주고, 잠재적 리턴 어드레스를 스택에 push 한다. 우리가 체크한 페이지로 점프를 해 들어오게 되는데, 올리와 같은 디버거 아래에서 돌아가고 있다면 우리는 RET 명령어를 건드릴 것이고 페이지로 점프하기 전에 스택에 push 해 두었던 주소로 리턴될 것이다.  만약 그렇지 않았다면 STATUS_GUARD_PAGE_VIOLATION 예외가 발생될 것이며, 올리에 의해 디버깅 상태가 아니었음을 알 수 있다.

 

#include <Windows.h>
#include <stdio.h>


int main()
{
 VOID* pAllocation=NULL;
 char* pMem=NULL;
 DWORD OldProtect;

 SYSTEM_INFO sysinfo;
 GetSystemInfo(&sysinfo);

 pAllocation = VirtualAlloc(NULL, sysinfo.dwPageSize,
  MEM_COMMIT | MEM_RESERVE,
  PAGE_EXECUTE_READWRITE);

 if(pAllocation ==NULL)
  printf("VirtualAlloc failed\n");

 pMem = (char*) pAllocation;
 *pMem = 0xC3;

 if(VirtualProtect(pAllocation, sysinfo.dwPageSize,
  PAGE_EXECUTE_READWRITE | PAGE_GUARD,
  &OldProtect) == 0)
 {
  printf("Error Code : %d", GetLastError());
  printf("VirtualProtect failed\n");
  return false;
 }
 __try
 {
  __asm
  {
   mov eax, pAllocation
   push debugged
   jmp eax
  }
 }
 __except(EXCEPTION_EXECUTE_HANDLER)
 {
  printf("exception occured and no debugger was detected\n");
  VirtualFree(pAllocation, NULL, MEM_RESERVE);
  return false;
 }
 __asm{debugged:}
 printf("debugger detected\n");
 return false;

}

 

 

하드웨어 BP

 

하드웨어 브레이크포인트는 인텔 프로세서 아키텍처에서 지원하는 기술이며, Dr0 - Dr7 이라고 이름 붙어있는 레지스터에 의해 컨트롤된다. Dr0 부터 Dr3 는 실제 BP를 동작하도록 하는 32비트의 레지스터이며, Dr4/5 는 다른 레지스터를 디버깅하기 위해서 예약되어 있는 레지스터이고, Dr6 과 7은 브레이크포인트의 행동을 컨트롤하기 위해 사용되는 레지스터이다. Dr6 과 7 레지스터가 어떻게 BP 의 행위에 영향을 미치는지 설명한 문서는 굉장히 많지만, 흥미가 있는 사람이라면 IA32 매뉴얼 Volume 3B (System programming guide for an in-depth explanation of how the registers work) 는 반드시 읽어보아야 한다.

 

하드웨어 BP를 탐지하고 제거하기 위해 GetThreadContext 와 SetThreadContext 함수를 활용할 수 있다. 그게 아니면 SEH 를 사용할 수도 있다.

 

 

 

 

#include <Windows.h>
#include <process.h>
#include <stdio.h>


unsigned __stdcall hwbpdetect(void*);

int main()
{

 HANDLE pThread=NULL;
 unsigned int threadID;

 pThread = (HANDLE) _beginthreadex(NULL, 0, &hwbpdetect, NULL, 0, &threadID);

 while(1)
 {
  printf("main thread\n");
  Sleep(1000);
 
 }
}


unsigned __stdcall hwbpdetect(void* arg)
{
 unsigned int Numbp;
 CONTEXT ctx;

 while(1)
 {
  Numbp = 0;
  ZeroMemory(&ctx, sizeof(CONTEXT));
  ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
 
  HANDLE hThread = GetCurrentThread();
  if(GetThreadContext(hThread, &ctx)==0){
   printf("GetThreadContext Error\n");
  }

  if(ctx.Dr0 != 0) Numbp++;
  if(ctx.Dr1 != 0) Numbp++;
  if(ctx.Dr2 != 0) Numbp++;
  if(ctx.Dr3 != 0) Numbp++;

  printf("hwbpdetect thread  - numbp : %d\n", Numbp);
  Sleep(300);
 
 }

}

 

디버그 레지스터를 조작할 수 있는 SEH 는 안티 리버싱 프로그램에서 흔히 볼 수 있다. 간단한 ASM 으로 만들 수 있다.

 

 

 

 

 

; One quick note about this little prelude; in Visual Studio 2008
; release builds are compiled with the /SAFESEH flag which helps
; prevent exploitation of SEH by shellcode and the likes.

VS 08 릴리즈 빌드 컴파일 시에는 SEH에서 쉘코드 등에 의해 발생할 수 있는 익스플로잇을 방지하기 위해 SAFESEH 라는 플래그가 세팅되어 나간다.


; What this little snippet does is add our SEH Handler to a
; special table containing a list of "safe" exceptions handlers , which
; if we didn't in release builds our handler would never be called,
; this problem plauged me for a long time, and im considering writing
; a short article on it

이런 몇몇 정보들은 SEH 핸들러가 안전한 예외 처리 핸들러를 가진 테이블을 추가한다는 것을 의미한다. 릴리즈 빌드에서 우리가 만든 핸들러가 호출되지 않는다면 이런 문제들이 있을 수 있음을 참고하라.

 

 

 

 

타이밍 어택


RDTSC


Timing Consuming