반응형

제가 2004년에 썼던 글입니다.


DLKM 방식의 후킹 모듈 보호 메커니즘

요 약

운영체제의 시스템 서비스 테이블 또는 인터럽트 및 시스템 콜을 후킹하여 제어하는 보안 모듈이나 기타 커널 서비스들의 취약성이 많이 나타나고 있는 가운데 이를 방어하기 위한 기술들이 필요하게 되었다. 특히나 보안 후킹 모듈의 경우는 후킹 모듈의 취약성이 바로 보안 취약성으로 나타날 수 있기 때문에 더욱더 중요한 이슈가 되고 있다. 이에 본 논문에서는 DLKM 방식의 후킹 모듈을 보호하는 기술에 대하여 연구하기로 한다.


I. 서론

 

최근의 IT 정보보호기술 중 보안 침해 사고의 최종 목적인 시스템의 데이터에 대한 보호 기술인 Secure OS가 핵심 기술로써 많은 연구가 진행 중으로 있다.

이 Secure OS는 시스템에 가해지는 오퍼레이션이 작동하기 전에 접근 제어를 할 수 있는 시스템으로, 상황에 따라서 해당 오퍼레이션을 허용 또는 차단할 수 있는 기능을 가지고 있다. 즉 시스템 접근이 발생하는 게이트 부분을 후킹이나 다른 방법을 통하여 사전에 접근 판단 작업을 처리하는 것으로 시작되는 것이다.

오퍼레이션이 발생하기 전에 이를 제어하기 위한 방법으로는 커널 모듈로써 동적으로 작동할 수 있고, 시스템 콜 진입 부분을 후킹하는 DLKM(dynamic loadable kernel module) 방식이 있고, 직접 커널 코드를 수정하여 제어를 하는 Kernel-Modify 방식이 있다.

LKM (Loadble Kernel Module)의 정의는 실행중인 커널에 동적으로 로딩 또는 언로딩이 가능한 커널 프로그램으로 보통은 디바이스 드라이버를 로딩하는데 사용되며, 새로운 디바이스를 시스템에 장착시 커널을 새로 컴파일하고 재부팅해야 하는 불편함을 없애기 위한 목적으로 사용된다.

커널 후킹 방식 중 Kernel-Modify 방식은 커널 소스를 직접 수정하고 컴파일해야 하므로 OS에 의존적인 부분이 상당히 있으며, 시스템의 패치나 기타 환경 변화에 영향을 받는다는 단점이 있다. 반면 DLKM 방식은 기존 커널의 변경 없이 동적으로 작동할 수 있는 장점은 있지만, 후킹 포인트를 해킹당할 위험성을 내포하고 있다.

이에 본 논문에서는 DLKM 방식의 후킹 모듈의 단점인 후킹 우회 공격에 대비하기 위한 보호 메커니즘에 대하여 Windows 운영체제를 예시로 들어 상세히 설명하기로 한다.

본 논문은 2장에서 시스템 콜이 처리되는 과정과 DLKM 방식의 후킹이 어떤 식으로 구성되는지를 기술하고, 3장에서는 후킹 모듈의 우회 공격 방법에 대해 알아본다. 4장에서 이러한 공격들을 막는 방어 메커니즘을 알아보고, 마지막으로 5장에서 본 논문의 결론에 대하여 기술한다.

II. 시스템 콜 처리 과정 및 후킹

DLKM 방식의 후킹 모듈을 공격하기 위해서는 먼저 API (Application Program Interface)가 어떻게 처리되는지 살펴보기로 하고, 어떻게 후킹을 하는지에 대한 원리를 이해해야 하므로 그 과정을 살펴보기로 한다.

 

1. 시스템 콜 처리 과정

어플리케이션에서 하나의 시스템 콜이 발생하면 다음과 같은 과정이 수행된다.

•사용자 메모리에 적재된 ntdll.dll 모듈에서 user mode의 native api (Zw*) 주소를 찾는다.

ntdll ! Zw* 계열의 user mode native api가 실행된다.

Zw* api에서는 해당 시스템 콜의 서비스 인덱스 번호를 레지스터에 설정하고, 인터럽트 0x2E 를 발생시킴으로써 커널 모드로 진입한다.


 

0: kd> u ntdll!ZwCreateFile

ntdll!NtCreateFile:

77f8f9ba b820000000 mov eax,0x20

77f8f9bf 8d542404 lea edx,[esp+0x4]

77f8f9c3 cd2e int 2e

77f8f9c5 c22c00 ret 0x2c


•IDT (interrupt descriptor table)에서 인터럽트 번호 0x2E의 핸들러인 KiSystemService 모듈이 수행된다.


 

0: kd> r idtr

idtr=80036400

 

typedef struct _IDTGATE {

unsigned short off1;

unsigned short sel;

unsigned char none, flags;

unsigned short off2;

} IDTGATE, *PIDTGATE;

 

IDTGATE 레코드 하나의 노드 사이즈는 8 바이트로 구성된다. 따라서 0x2e 인덱스의 옵셋은 0x2e * 8 = 0x170

그러므로, 인터럽트 0x2e의 IDTGATE 주소는 0x80036570이 된다.

 

0: kd> db 80036570

80036570 c0 62 08 00 00 ee 46 80-9c 99

이 데이터를 IDTGATE 구조체에 맞추면, off1=62c0, off2=8046 이 되고, 실제 주소를 계산하면 (off2 || off1) = 0x804662c0

0: kd> ln 804662c0

(804662c0) nt!KiSystemService | (8046639b) nt!KiServiceExit

Exact matches:

0: kd> u kisystemservice

nt!KiSystemService:

804662c0 6a00 push 0x0

804662c2 55 push ebp

...

즉, KiSystemService의 주소와 일치함을 알 수 있다.


KiSystemService에서는 SDT (Service Descriptor Table)에서 SST (System Service Table)의 주소를 참조하고, 해당 서비스 번호를 인덱스로 하여 실제 kernel native api 주소를 찾는다.


 

0: kd> dd KeServiceDescriptorTable

80483560 80474f00 00000000 000000f8 804752e4

80483570 00000000 00000000 00000000 00000000

80483580 bd126840 00000000 00000007 bd126860

80483590 00000000 00000000 00000000 00000000

0: kd> ln 80474f00

(80474f00) nt!KiServiceTable | (804752e0) nt!KiServiceLimit

Exact matches:

SDT에서 SST주소를 찾았다.

ZwCreateFile의 서비스 번호 0x20의 주소를 계산해 내면, 옵셋이 0x20*4 = 0x80이 되고 주소는 0x80474f80이 된다.

0: kd> dd 80474f80

80474f80 804a91a0 804a8bb8 804e67a0

0: kd> ln 804a91a0

(804a91a0) nt!NtCreateFile | (804a91d2) nt!NtCreateNamedPipeFile

Exact matches:

nt!NtCreateFile = <no type information>

0: kd> u nt!ntcreatefile

nt!NtCreateFile:

804a91a0 55 push ebp

804a91a1 8bec mov ebp,esp

804a91a3 33c0 xor eax,eax

804a91a5 50 push eax

804a91a6 50 push eax

NtCreateFile의 주소와 일치하였다.

•ntoskrnl 내부의 native api가 수행된다.


그림 1은 Windows 2000에서 시스템 콜 처리 과정을 도식화 한 것으로 OpenFile이라는 API가 발생되었을 경우 내부적으로 ZwCreateFile system call이 수행되는 예를 든 것이다.

1. 시스템 콜 후킹 메커니즘

DLKM 방식의 일반적인 시스템 콜 후킹 메커니즘은 SST라는 System Service Table을 변경하여 시스템 콜의 주소를 바꿔주는 방법을 많이 이용한다. Windows 시스템의 SST가 바로 UNIX나 LINUX 시스템의 시스템 콜 엔트리 테이블에 해당된다.


그림 2는 DLKM 방식의 시스템 콜 후킹 메커니즘을 도식화한 것이다. 그림에서 DLKM 후킹 모듈은 hooker 모듈이 해당되며, hooker 모듈 로딩시, SST의 시스템 콜 주소 레코드를 변경하면 가능하다. 여기서는 NtCreateFile 시스템콜이 발생하는 경우 hooker 모듈의 New_NtCreateFile 이 작동되게 된다.

I. LDKM 후킹 모듈 우회 공격 기술

앞에서 설명한 LDKM 방식의 정상적인 후킹 모듈에는 큰 취약성이 있다. 후킹을 우회하는 공격포인트가 여러군데 보이기 때문이다. 시스템 콜의 처리 과정에서 참조되는 메모리는 모두 공격 포인트가 될 수 있기 때문이다. 즉, IDT, SDT, SST 메모리를 누군가 접근하여 변경시키게 되면 시스템 콜의 우회를 가능하게 할 수 있다.

1. IDT (Interrupt Descriptor Table) 공격

인터럽트가 처리되는 핸들러가 등록된 IDT (Interrupt descriptr toable)에 시스템 콜 인터럽트인 0x2E 인터럽트 핸들러나, 0x01인 디버그 핸들러 등 여러 가지 인터럽트에 대한 주소를 변경함으로써 시스템 콜 처리 과정을 변경시킬 수 있다.


그림 3은 IDT에서 시스템 콜 인터럽트 핸들러를 가상으로 변경해 본 그림이다. KiSystemService 모듈에서 처리하는 과정이 핸들러 주소의 변경에 따라 정상적인 시스템 콜 처리를 충분히 방해할 수도 있으며, 복잡하지만 원래의 시스템 콜 주소를 이용하여 후킹을 무력화 시킬 수 있는 공격이 이론상 충분히 가능하다.

1. SDT (Service Descriptor Table) 공격

KiSystemService에서 SDT를 참조하여 SST 주소를 구하는데, 이 과정에서 SDT를 변경하여 SST를 바꿔칠 수 있다.

 

그림 4는 DLKM 후킹 모듈이 기존의 SST를 변경시켜 후킹을 했다 하더라도, SDT에 등록된 SST의 주소를 미리 백업 받아둔 backuped SST의 테이블의 주소로 변경함으로써 후킹을 무력화 시킬 수 있음을 보여준다.

 

1. SST (System Service Table) 접근 공격

Windows 시스템에서의 SST는 Unix나 Linux시스템에서의 시스템 콜 엔트리 테이블과 마찬가지로 실제 native api의 주소가 기록되어 있다. LDKM 후킹 모듈은 일반적으로 이 테이블을 변경하여 작동하게 된다. 여기서는 정상적인 DLKM 후킹 모듈이 SST를 후킹하였음에도 이 후에 다른 커널 모듈에 의하여 재 후킹이 된 경우를 의미한다.


그림 5는 이미 hooker에 의해 시스템 콜이 후킹된 상태에서 다른 커널 모듈에 의해 SST를 원상 복구 시킬 경우, 후킹을 우회할 수 있는 공격을 설명하고 있다.

이 경우는 여러 가지 DLKM 후킹 모듈을 한 시스템에서 작동시키는 경우 모듈 간 충돌이 발생되는 원인이기도 하다.

1. Physical Memory 접근 공격

위에서 설명한 세가지 공격 방식은 모두 커널 모듈을 작동시켜 시스템 콜 처리시 참조되는 메모리를 커널 모드에서 커널 메모리를 수정함으로써 작동한다.

Physical Memory 접근 방식은 위의 세 가지 공격을 커널 모드로 가지 않고도 수행하게끔 도와주는 메커니즘이다. 즉 실제 공격방법은 위의 세 가지 방법으로 수행하는 것이다.

일반적으로 커널 메모리의 수정은 유저 모드에서 불가능하도록 되어 있다. 그러나, 운영체제에서 제공하는 physical memory 디바이스를 통한 수정은 가능하다.


 

 

1. \device\physicalmemory 디바이스를 오픈

(NtOpenSection)

2. 물리적 메모리를 프로세스의 가상 메모리에 매핑한다.

(NtMapViewOfSection)

3. 커널 메모리를 수정한다.

 

 

1. Symbolic link & Physical Memory 접근 공격

이 공격 방법은 4와 동일하지만 약간 진화된 형태로 \device\physicalmemory 디바이스에 접근하는 오퍼레이션을 막은 경우에 가능한 방법이다.

즉, \device\physicalmemory에 심볼링 링크를 생성한 디바이스를 새로 만들어 심볼릭 링크로 디바이스에 접근하는 방식이다.


 

 

1. \device\physicalmemory 디바이스에 symbolic link로 연결할 새로운 디바이스를 만든다.

(IoCreateSymbolicLink)

2. symbolic link로 생성된 디바이스를 오픈

(NtOpenSection)

3. 물리적 메모리를 프로세스의 가상 메모리에 매핑한다.

(NtMapViewOfSection)

4. 커널 메모리를 수정한다.

 


 

I. DLKM 후킹 모듈 우회 공격에 방어 메커니즘

앞에서 설명한 5가지 형태의 DLKM 후킹 모듈 공격 방법은 실제 구현 가능하며, 이미 나와 있는 공격 툴들도 존재한다.

본 장에서는 어떻게 DLKM 후킹 모듈에 대한 공격을 대비해야 하는지에 대한 보호 메커니즘을 설명하였다.

1. 커널 모듈 로딩 제어

인가된 커널 모듈만 로딩 할 수 있도록 제어한다.

위에서 설명한 IDT, SDT, SST 공격 방법은 커널 모드인 Ring 0 모드에서 수행되므로, Ring 0에 진입하기 전인 커널 모듈 로딩 시에 허용된 커널 모듈만 드라이버 로딩을 허용하고, 그 외에는 드라이버 로딩을 불가능하게 접근 제어를 한다.

이렇게 함으로써 불법적인 드라이버들이 커널 모듈로 작동할 수 없게 되어 Ring 0 모드로의 진입을 차단할 수 있어, 커널 모드에서의 위의 세가지 공격을 방어할 수 있다.

2. Physical Memory 디바이스 접근 제어

직접적으로 Ring 0에 진입하지 않고도 공격하는 형태인 4, 5방법의 경우는 인가된 프로세스만 physical memory 디바이스에 접근할 수 있도록 제어한다.

\device\physicalmemory 디바이스에 접근하는 오퍼레이션을 허용된 프로세스만 접근을 허용하고 그 외의 프로세스는 접근을 거부하여 공격 위협을 차단할 수 있다.

그러나, 공격 방식 5번의 경우처럼 디바이스명을 심볼릭 링크로 접근할 수 있으므로 링크인 경우 대상 디바이스명을 추적하여 physicalmemory 디바이스에 접근하는지도 검사하여 같은 과정을 수행함으로써 제어한다.

3. 주기적인 커널 메모리 무결성 복구

인가된 프로세스 나 커널 모듈에서의 공격에 대한 방어나 기타 예기치 못한 공격이 발생할 수 있으므로, 이에 대비하기 위해 커널 쓰레드를 이용하여 주기적인 IDT, SDT, SST의 무결성 검사를 수행한다.

최초 DLKM 후킹 모듈이 작동한 이후의 IDT, SDT, SST의 메모리를 미리 백업해 두고, 주기적으로 각 메모리 영역의 무결성이 훼손되었는지 점검하고, 무결성이 깨진 경우 백업 받은 메모리를 덮어쓰는 것으로 원상 복구시킨다.

물론, 실시간 방어 기술이 아닌 주기적인 검사이기 때문에 완벽한 대응 방법은 아니다. 그러나 예기치 못한 상황에서 후킹이 우회 되는 경우, 후킹 기능을 복원할 수 있기 때문에 어느 정도 효과를 기대할 수 있을 것이다.

II. 결 론

이제까지 DLKM 후킹 모듈의 구조 및 후킹 모듈의 공격 형태와 이를 보호하기 위한 메커니즘을 살펴보았다.

DLKM 후킹 모듈을 접근 제어 목적으로 사용하는 보안 소프트웨어는 앞에서 제시한 DLKM 후킹 공격을 통해 접근 제어가 우회되는 취약성에 대비해야 그 목적을 정상적으로 수행할 수 있다.

따라서 본 페이퍼에서 제시한 커널 모듈 로딩 접근 제어, 메모리 디바이스 접근 제어, 커널 메모리 무결성 검사 이 3가지 보호 메커니즘을 활용한다면 보다 안전한 DLKM 후킹 모듈로써 기능을 다 할 것이라 기대한다.


참 고 문 헌

[1]Secure OS기반의 지능형 다단계 정보보호시스템, 정보처리 학회, 2003

[2]전산망정보보호 - 접근통제기술, 한국정보보호센터, 1996.12.

[3]Advanced Windows 2000 Rootkits Detection Jan K.Rutkowski, 2003 July

[4]Detecting Windows Server Compromises with Patchfinder 2, Joanna Rutkowska

[5]Avoiding Windows Rootkit Detection, Edgar Barbosa, 2004 Feb

[6]Undocumented Windows 2000 Secrets, SVEN B. SCHREIBER

[7] 커널 루트킷(Root) 탐지 방법 , 아주대 정보통신 대학원, 2001.10



 







'Security' 카테고리의 다른 글

openssl 커맨드 / 암호화  (0) 2018.03.02
버퍼오버런(Buffer-Overrun)의 실체와 분석  (0) 2015.06.06
해시(Hash)  (0) 2015.06.02

+ Recent posts