Be myself :: SEH를 이용한 안티디버깅

달력

42024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

1 개요

SEH(Structured Exception Handler)는 윈도우에서 제공하는 예외처리 매커니즘이다. 이 SEH는 안티 디버깅 기법으로도 사용되는데 SEH가 무엇인지, 어떻게 안티 디버깅으로 사용되는지, 우회법은 무엇인지 등을 알아보자.

2 SEH(프로그래밍으로)

예외 처리의 과정을 알아보자. 우선 프로그램에서 예외가 발생할 경우, OS는 프로세스에게 예외처리를 맡긴다. 하지만, 프로그램이 적절한 예외처리를 하지 못할 경우 OS에서 오류처리를 한답시고 프로그램을 종료시켜 버린다. 이것이 일반적인 예외처리 메커니즘인데 디버깅을 하는 경우, 예외처리 루틴을 디버기(디버그를 수행하는 프로그램)에 맡겨버린다. 이러한 상황을 한번 보자.

다음과 같이 예외처리를 하는 프로그램을 작성하였다.

#include<Windows.h>
#include<stdio.h>
int main(void)
{
	int seh_flag = 0;
	int a = 1;
	int b = 0;
	int result = 0;
	__try{
		result = a / b;
	}

	__except(EXCEPTION_EXECUTE_HANDLER){
		seh_flag = 1;
		printf("normal processing \n");
	}

	if (seh_flag == 0)
	{
		printf("debuge detected \n");
		getchar();
	}
	getchar();
	return 0;
}

0으로 나누는 것은 EXCEPTION_INT_DIVIDE_BY_ZERO예외를 발생 시킨다. 이 것을 그냥 실행했을 때와 올리디버그상에서 실행했을 때를 보자.

<일반 실행> <디버그로 실행>

왜 이렇게 될까? 그것은 앞에서 설명 했듯이 예외 발생 시 디버기에서 예외처리를 담당하기 때문에 프로그램에서의 예외처리는 무시된다. 이는 올리디버거 상에서 확인할 수 있다.

IDIV 즉, 나누는 과정에서 break되고 과정을 살펴 보면, normal processing이라 뜨는 루틴이 생략되는 것을 알 수 있다.

그렇다면 이를 우회하는 방법은 이제 답이 나온 셈이다. 아예 예외가 발생하는 명령어 부분을 고쳐서 디버깅을 다시 하던지, 아님 올리디버거에 예외처리를 디버기(디버깅을 당하는 프로그램)에게 맡기는 옵션을 주면 정상 실행된다.

위 그림과 같이 설정하면 된다.

 

3 SEH(어셈블리로)

아깐, 디버기에서의 예외처리를 이용하여 안티디버깅을 했지만 이번엔, SEH chain에 직접 예외 핸들러를 등록해서 예외처리 루틴에서 안티디버깅을 수행한다.

먼저 SEH를 등록하는 과정을 보자.

처음 PUSH한 것이 예외를 처리하는 핸들러의 주소 값이다 FS:[0]는 TEB의 주소 값인데, NtTib.ExceptionList구조체 멤버의 주소값이기도 하다. TEB에 관한 내용은 다음에 설명하도록 하겠다. 즉 위에서 명령어가 수행되면, 전 FS:[0] (SE핸들러 멤버를 가진 구조체의 주소값 더 자세히는 EXCEPTION_REGISTRATION_RECORD 구조체의 첫 번째 멤버인데 자세한 내용은 구글을 통해 알아 보시길) 를 스택에 넣고 SEH를 등록하는 과정이다.

이후 프로그램 과정에서 다음과 같이 일부로 예외를 발생시킨다.

EAX의 주소값이 0인데 이 부분에 1이라는 값을 넣고 있다. 이제 예외가 발생했으므로 우리가 아까 등록한 예외 핸들러가 실행될 것이다.

어디서 많이 본 명령어 이지 않은가? 0x40105E~ 0x401064명령어를 보면 TEB의 30번째 멤버(PEB)에 접근해 PEB의 BeingDebugged멤버에 접근해 이 값이 1인지 아닌지 판별하는 IsDebuggerPresent() 함수의 내용과 같은 명령어를 수행한다.

여기서 ESI엔 예외가 발생하기 전 pContext값이 들어가게 된다. 예외처리 핸들러의 3번째 매개변수이다. Context엔 예외가 발생하기 전 레지스터나 세그먼트 들의 값이 저장되어 있다. Context는 각 스레드마다 내부적으로 가지고 있다. Context가 존재하는 이유는, 전에 OS kernel관련 포스팅에 게시되어 있는데 멀티스레딩 환경에서 필수적이기 때문이다.

아무튼, Context에서 B8만큼 떨어진 값에는 Eip의 값이 들어 있으므로, 이 값을 변경하면 예외 핸들러루틴이 마무리되면, 설정된 주소값으로 JMP하게 된다. 따라서, 디버깅 중이라면, 40106A로 점프하게 되는게 이 명령어를 보면 Eip를 401023으로 설정하고 있다. 이 값에 해당하는 영역으로 가보자.

예상 했듯이, 디버거가 발견되었다는 메시지박스가 출력된다.

우회방법은 위의 __try, __except방법과 같고 그 후 위의 IsDebuggerPresent() 함수의 우회법을 적용하면 된다.

'Reversing' 카테고리의 다른 글

PEB를 활용한 안티디버깅  (0) 2014.05.16
WinDbg, xp 연동  (0) 2014.05.16
TLS 콜백 안티디버깅  (0) 2014.05.15
인라인 패치  (0) 2014.05.11
Upack 분석중 정리  (0) 2014.05.10
Posted by flack3r
|