반응형

윤근용 happyme9@nhncorp.com | 필자는 시스템 프로그래머로 시스템에 대한 다양한 분야에 관심이 많다. 특히 보안 문제에 대한 관심이 높아 다년간 보안 업무에 종사하고 있다. 바이러스 보안 업체를 거쳐 현재는 NHN에서 주로 보안 관련 프로젝트를 수행하고 있다.


루트킷은 자신을 감추기 위해서 다양한 기술을 이용한다. 가장 대표적인 것이 후킹을 이용하는 방법이다. 주로 SSDT 후킹과 같은 커널 레벨에서의 후킹이 많이 사용되는데 이를 위해서는 루트킷이 커널 드라이버 형태로 작성돼야 한다. 루트킷은 스스로를 더욱 교묘하고 은밀하게 숨기기 위해 커널 패치와 같은 로우 레벨의 조작을 수행하는 경우가 많으며, 심지어는 BIOS와 같은 펌웨어를 이용하기도 한다. 가상화 기술을 이용하는 루트킷 기술은 이제 더 이상 새로운 것이 아니며, 이미 새로운 위협으로 예상되어 왔다. 하지만 현재 대부분의 보안 제품으로는 탐지하기 힘들다면 것이 문제라고 할 수 있다. 즉 가상화 기술을 이용해서 자신의 VM(Virtual Machine) 안에서 독자적으로 루트킷이 실행된다면 현재의 보안제품으로는 탐지가 불가능하다. 따라서 악성 코드가 가상화 기술을 이용하는 경우에는 문제가 더욱 심각하다.

가상화의 위협

루트킷 탐지가 어려운 이유 중 하나는, 루트킷 탐지 소프트웨어와 동일한 레벨의 권한을 가지고 수행된다는 것이다. 대부분의 루트킷 탐지 소프트웨어는 커널 레벨에서 동작하지만 루트킷 또한 커널 레벨에서 동작하는 커널 드라이버 형태로 많이 작성된다. 따라서 서로 동일한 능력을 가지고 대치되기 때문에 탐지 및 무력화가 쉽지 않은 것이 현실이다. 따라서 어느 한쪽이 더 높은 권한을 가진다면 승패는 한쪽으로 기울 것이다. 커널 레벨보다 한 층 더 높은 권한을 부여 받을 수 있는 방법이 바로 가상화다. 가상화로 커널 레벨보다 높은 권한을 갖는 루트킷은 커널 레벨에서 동작하는 루트킷 탐지 소프트웨어를 무력화 시킬 수 있다(<그림 1> 참조). 이와 반대로 루트킷 탐지 소프트웨어가 커널 레벨보다 더 높은 권한을 갖는다면 커널 레벨에서 동작하는 루트킷은 부처님 손바닥 안의 손오공 신세가 돼 버릴 것이다(<그림 2> 참조).

가상화

가상화(Virtualization)라는 용어는 다양한 분야에서 사용된다. 컴퓨터 시스템에서의 가상화는 시스템의 유효한 리소스(하드웨어, 운영체제, 데이터 등)를 보다 효율적으로 사용하기 위해서 연구되어 왔다. 그 중 하나가 운영체제 가상화인데, 이는 하드웨어 리소스가 부족했던 6~70년대 VMM(Virtual Machine Monitor)이라는 개념으로 출발했다. VMM은 하드웨어와 운영체제 사이에 위치해서 하드웨어에 대한 리소스(CPU, Memory, Physical Disk)를 가상화한다. VMM이 운영체제에게 제공하는 인터페이스는 하드웨어가 운영체제에게 제공하는 인터페이스와 동일하기 때문에 운영체제 입장에서는 VMM의 존재 여부에 영향을 받지 않고(존재 자체를 모르고) 동작할 수 있으며, VMM에 의해서 동시에 여러 운영체제가 동작할 수 있다.

하이퍼바이저

하이퍼바이저(Hypervisor)란 하나의 시스템에서 동시에 여러 개의 운영체제를 사용할 수 있게 해주는 가상화 플랫폼을 말한다. 가장 대표적이고 쉬운 예로 VM웨어를 들 수 있다. 하이퍼바이저는 다음과 같이 크게 두 가지 형태로 구분된다. 물론 아래의 두 형태가 복합된 형태 또한 가능하다. Type 1에서는 VMM (Virtual Machine Monitor)이 Host 운영체제 안에서 동작한다.

Type 2에서는 VMM(Virtual Machine Monitor)이 하드웨어와 게스트 운영체제(Guest OS) 사이에 존재한다.

다음은 Type 1형태로 구현된 VM웨어의 내부 구성요소이다.

- VMApp : 유저 레벨 애플리케이션(Host OS의 App로써 동작)
- VMDriver : 호스트 시스템을 위한 디바이스 드라이버(Host OS의 커널에서 동작, 호스트 운영체제와 게스트 운영체제 간의 실행 context switching을 담당한다.)
- VMM : VMDriver가 로드되면서 생성되는 virtual machine monitor

소프트웨어 가상화

IA-32에서 운영체제는 Ring 0, 애플리케이션은 Ring 3 권한으로 동작한다. 하지만 가상화 환경에서는, 하드웨어와 직접 연결되어 가상화를 수행하는 VMM이 Ring 0 권한으로 실행돼야 한다. 이때, 운영체제도 동일하게 Ring 0 권한으로 실행되면 운영체제가 VMM 코드를 제어할 수 있을 뿐만 아니라 VMM에 의한 가상화 자체가 불가능해 진다. 따라서 운영체제는 VMM보다 낮은 권한을 부여한다. 이러한 작업을 Ring Deprivileging이라고 하는데, Ring Deprivileging은 다음과 같은 두 가지 형태로 수행된다.

♦ VMM : Ring 0
   Guest OS : Ring 1
    App : Ring 3

♦ VMM : Ring 0
   Guest OS : Ring 3
   App : Ring 3

이렇게 운영체제의 실행 권한이 하향 조정됨으로써 야기되는 문제점 때문에 VMM의 역할 비중이 커졌다. 즉 운영체제가 하드웨어에 접근하는 작업이나 특정한 시스템 콜과 같이 Ring 0 권한이 요구되는 작업을 모두 모니터링해서 그에 맞는 작업을 수행해야 한다. 동시에 여러 개의 운영체제가 동작 중이라면 이들에 대한 Context Switching을 VMM이 담당해야 한다. 실행권한 변경에 따른 문제점을 극복하기 위해서 Paravirtualization, Binary Translation 등의 방법들이 이용됐다.
하지만 Paravirtualization은 운영체제의 커널을 수정해서 특정한 VMM에서 가상화가 가능하도록 하는 것인데, 이는 리눅스와 같은 오픈소스 운영체제에서나 가능한 일이며 일반적으로 적용할 수 있는 방법이 아니다. 또한 Binary Translation은 상대적인 성능상의 부하가 문제가 된다. 이런 소프트웨어 가상화에 의한 문제점을 해결해 주는 것이 CPU 레벨의 가상화라고 할 수 있다. 즉 가상화를 구현하기 위해서 사용된 Paravirtualization, Binary Translation과 같은 작업을 CPU 레벨에서 지원하는 것이다.

하드웨어 가상화

인텔, AMD의 CPU 레벨에서 제공하는 하이퍼바이저를 하드웨어 가상화라고 할 수 있다. 인텔이나 AMD에서 CPU 레벨의 가상화를 지원하기 전까지는 대부분 소프트웨어 가상화에 의존해왔다. 하지만, IA-32 자체가 가상화에 맞지 않는 구조를 가지기 때문에, (애초에 하나의 운영체제만 동작하도록 설계됨) 소프트웨어 가상화 구현의 어려움  뿐만 아니라 그에 따른 부가적인 문제점을 갖게 된다.

인텔

- VT(Virtual Technology, 코드명: Vanderpool) 간단히 IVT라고 한다.
- IA-32 기반 CPU에서의 IVT를 VT-x라고 하며 IA-64(itanium)기반 CPU에서의 IVT를 VT-i라고 한다.
- 제공 CPU: 일부 펜티엄 4, Xeon, 코어듀오, 코어 2 듀오 프로세서(IVT기능을 지원하는 정확한 인텔 CPU 목록은 http://process orfinder.intel.com에서 조회 할 수 있다.)

AMD

- AMD-V(AMD Virtualization, 코드명: Pacifica)
- 제공 CPU: (64비트 x86 아키텍처) AMD 애슬론 64, 애슬론 64×2, 튜리온 64?2, 옵테론, 페놈

VT-x

이제 인텔 CPU에서 제공하는 VT-x에 대해서 간단히 살펴보자. VT-x에서는 VMX(Virtual Machine Extension)라는 실행 모드를 지원함으로써 가상화를 가능케 한다. VMX는 두 가지 모드로 나뉘는데, VMX root operation(fully privileged Ring 0) 모드와 VMX non-root operation(less privileged Ring 0) 모드이다. 여기서 VMM은 VMX root operation 모드로 동작하고 VM은 VMX non-root operation 모드로 동작한다. VMX root operation 모드는 몇 가지를 제외하고는 일반적인 CPU 동작 모드와 거의 동일하다. VM non-root operation 모드에서의 CPU 작업은 가상화를 지원하기 위해서 제약되거나 변경된다. 더 정확히 말하면 root operation 모드로 동작하는 VMM은 Ring 0 보다 권한이 높고, non-root operation 모드로 동작하는 게스트 운영체제는 Ring 0, App는 Ring 3 권한으로 동작한다. VMX의 동작 모드 변경은 다음 두 가지 경우가 있다.

♦  VM entry: VMM이 VMX non-root operation 모드로 VMX not-root operation 모드로 동작하는 VM의 코드(운영체제, 애플리케이션)가 실행된다.
♦  VM exit: VMM이 VMX root operation 모드로 VMX root operation 모드로 동작하는 VMM으로 CPU 제어권이 반환된다.

VMS operation의 시작과 종료는 VMXON, VMXOFF 명령에 의해서 수행된다. 특정한 명령이나 이벤트(예외 발생, I/O 디바이스 접근, 특정 레지스터 접근 등)가 발생하면 VM exit에 의해서 VMX root operation 모드가 된다. VMCS(Virtual Machine Control Structure)의해서 VMX의 모드 변경과 VMX non-root operation이 관리된다(AMD-V의 경우는 VMCB (Virtual Machine Control Block)).

<그림 5>에서 보는 바와 같이 루트킷 디텍터 등의 보안 프로그램은 어디까지나 루트킷 VM에 의해서 로드되고 컨트롤 되는 하나의 게스트 운영체제 상의 커널 모듈이거나 일반적인 애플리케이션일 뿐이다. 따라서 이런 구조에서의 루트킷 탐지는 거의 불가능하다(100 퍼센트 불가능한 것은 아님).

가상화 기술을 이용하는 루트킷

이제 앞에서 언급한 가상화 기술을 이용하는 루트킷에 대해서 알아보자.

SubVirt

SubVirt는 마이크로소프트와 미시간 대에서 가상화 기술을 이용한 루트킷이 가능한지 여부를 증명하기 위해서 만든 것이며, 기존의 가상화 소프트웨어(VM웨어, 버추얼-PC)를 이용한 것이므로 진정한 의미의 가상화를 이용한 루트킷이라고 보기 힘들다. SubVirt는 호스트 운영체제로 리눅스를 이용하며 시스템의 Boot Sequence를 수정해서 원래의 운영체제를 로드한다.

Vitriol

Dino Dai Zovi에 의해서 개발된 VM 루트킷으로써 MaxOS X 운영체제에서 동작하며 인텔 VT-x(인텔 코어듀오/솔로)기술을 이용했다(2006년 10월 마이크로소프트의 BlueHat 컨퍼런스에서 발표되었다).

BluePill

Vista x64(AMD 64)에서 AMD-V를 이용한 VM 루트킷. BIOS 수정이나 SubVirt처럼 Boot Sequence를 변경시키는 작업을 수행하지 않으므로 재부팅 없이 언제든지 설치가 가능하며 어떤 입출력에 대해서도 가상화를 않기 때문에 성능상의 부하가 거의 없다.

현재 그리고 미래

루트킷과 악성코드를 구분하는 게 모호하긴 하지만 엄밀히 말하면 같지는 않다. 루트킷과 악성코드의 결합, 정확히 말하면 악성 코드가 루트킷 기술을 사용하면(이미 이런 악성코드가 많은 것이 현실이다.) 더욱 강력한 공격력과 전파력을 보유한다. 그러면 탐지나 방어, 치료(제거)와 같은 후속 조치가 더 어려워질 것이다. 사실, MPack과 같은 공격 툴킷이 거래되고 있는 블랙마켓이 존재하는 것이 현실이다. 심지어 악의적인 행위 자체도 거래가 된다. 이를 통한 잠재적인 피해 규모는 산정하기 힘들 정도다. 예전에는 악성 코드를 여러 형태로 분류했지만 이미 오래 전부터 대부분의 악성코드가 복합적인 성격을 띄기 시작했다. 서로간의 사용 기술을 복합적으로 사용하고 있을 뿐만 아니라 목적 자체도 복합적인 것들이 많아졌다.

CPU에서 제공하는 가상화 기능을 이용하는 것은 단지 루트킷의 은닉 기술 중 하나일 뿐이다. 가상화 이외에도 하드디스크의 사용하지 않는 섹터를 이용해서 은닉하는 기술, ATAPI 스펙을 이용한 은닉과 같이 탐지가 불가능한 경우가 상당히 많다. 가장 무서운 것은 알려지지 않은 루트킷, 좀더 정확히 말하면 알려지지 않은 기술을 사용한 루트킷은 존재여부 조차도 모르고 있다. 더 큰 문제는 기존의 보안 프로그램이 교묘한 방법을 사용하지 않고 전통적인 기술을 사용하는 루트킷도 만족스럽게 탐지하지 못한다는 점이다. 탐지해도 안전하게 제거하는 기술은 아직 미흡한 실정이다. 루트킷 탐지를 위한 기술 영역뿐만 아니라 루트킷을 안전하고 완벽하게 제거하는 기술 영역도 지속적인 연구가 필요하다. 현재까지 안정적으로 루트킷을 탐지할 수 있는 증명된 기술은 없다. 루트킷이 사용하는 기술과 보안 소프트웨어가 사용하는 기술이 크게 다르지 않기 때문에 누가 먼저 새로운 기술을 이용하느냐에 따라서 그 승패가 좌우된다. 마이크로소프트에서 얼마 전에 코모쿠(komoku)라고 하는 안티 루트킷 솔루션 업체를 인수했다는 소식은 시사하는 바가 크다고 할 수 있다.

반응형

신영진 pop@jiniya.net, http://www.jiniya.net|웰비아닷컴에서 보안 프로그래머로 일하고 있다. 시스템 프로그래밍에 관심이 많으며 다수의 PC 보안 프로그램 개발에 참여했다. 현재 데브피아 Visual C++ 섹션 시삽과 Microsoft Visual C++ MVP로 활동하고 있다. C와 C++, 프로그래밍에 관한 이야기를 좋아한다.


SDT 후킹

SDT(Service Descriptor Table) 후킹은 시스템 서비스 테이블의 값을 조작하는 API 후킹의 한 방법이다. 유저모드 API 후킹의 경우 프로세스간 컨텍스트가 다르기 때문에 시스템 전역 후킹을 하기 위해서는 일일이 모든 프로세스에 관련 코드를 주입(injection) 시켜야 한다는 불편한 점이 있었다. 이러한 문제를 해결한 한 가지 방법이 SDT 후킹이다. SDT 후킹은 커널모드에서 한 번만 후킹을 하면 모든 프로세스에 적용되기 때문에 별도로 컨텍스트 관리를 할 필요가 없기 때문이다.

SDT 후킹의 방법을 이해하기 위해서는 우선 윈도우의 API 호출이 어떻게 커널로 전달되는지를 알아야 한다. 간단하게 windbg를 통해서 TerminateProcess 호출이 커널로 전달되는 과정을 살펴보도록 하자. 응용 프로그램에서 호출한 Terminate Process는 ntdll.dll의 NtTerminateProcess로 이어진다. <리스트 1>에 windbg를 통해서 디스어셈블한 NtTerminateProcess 함수가 나와있다. eax에 0x101를 넣고, 0x7ffe03000에 저장된 번지를 호출하는 것을 알 수 있다.

<리스트 2>에 나와있는 것처럼 0x7ffe0300는 edx에 현재 스택 포인터를 저장하고 sysenter 명령어를 호출하는 코드임을 알 수 있다. sysenter 명령어는 유저모드에서 커널모드로 진입하기 위한 명령어로 MSRs에서 실행할 커널 함수 주소를 가져와서 그 번지로 이동시키는 역할을 한다. rdmsr 명령어를 통해서 sysenter 명령어가 수행됐을 때 실행되는 함수를 찾아보면 KiFastCall Entry라는 것을 알 수 있다. KiFastCallEntry는 eax에 저장된 서비스 번호에 해당하는 함수를 호출하는 역할을 한다.

여기까지 과정을 요약하면 TerminateProcess 호출은 ntdll. dll의NtTerminateProcess로 이어지고, NtTerminate Process는 eax에 TerminateProcess API의 서비스 함수 번호인 0x101을 넣고 커널모드에서 KiFastCallEntry를 호출한다. KiFast CallEntry는 SDT를 참조해서 eax에 저장된 서비스 번호에 해당하는 함수를 호출한다. 이제 실제로 SDT에 대해서 살펴보도록 하자. SDT는 총 네 개의 서비스 테이블로 구성된다. 각 테이블은 <리스트 3>에 나와있는 SDE 구조체로 이루어져 있다. SDE 테이블의 각 필드별 설명은 <표 1>에 나와 있다. SDT의 첫 번째 테이블은 윈도우의 네이티브(native) API 함수를 저장하고 있는 ntoskrnl 테이블이다. 두 번째 테이블은 GUI 함수들을 저장하고 있는 win32k 테이블이다. 세 번째, 네 번째 테이블은 추후 사용하기 위해서 예약된 테이블이다.

윈도우 커널에는 기본적으로 두 종류의 SDT가 포함되어 있다. KeServiceDescriptorTable은 일반적인 스레드에 사용되는 테이블로 ntoskrnl 테이블만 사용하고, 나머지 테이블은 모두 사용하지 않는다. KeServiceDescriptorTableShadow는 GUI 스레드에 사용되는 테이블로 ntoskrnl과 win32k 테이블을 사용한다. <그림 1>은 이러한 구조를 도식화한 것이다.

windbg를 통해서 실제 커널의 SDT를 살펴보도록 하자. <리스트 4>에 커널의 SDT 구조체를 덤프한 내용이 나와있다. 살펴보면 ntoskrnl 테이블의 base 주소는 0x84e46a8, counter는 0, limit은 0x11c, arg 필드 값은 0x80514eb8인 것을 알 수 있다. 함수 테이블을 덤프한 것을 보면 첫 번째 함수의 시작 주소는 0x80581302인 것을 알 수 있고, 인자 크기는 0x18 바이트란 것을 알 수 있다. 한 가지 주의해서 볼 사실은 GUI 함수 테이블과 GUI 인자 테이블은 커널 내부가 아닌 win32k.sys 내부에 존재한다는 것이다. <리스트 4>에서도 이 두 테이블의 주소만 확연히 틀린 것을 확인할 수 있다.

이제 SDT에서 TerminateProcess API의 서비스 함수를 찾아보자. <리스트 1>을 보면 eax에 0x101을 집어넣고 있으므로 TerminateProcess API의 서비스 함수 번호는 0x101이란 것을 알 수 있다. 함수 테이블은 4바이트 기준이고, 인자 테이블은 한 바이트 기준이란 점을 생각해서 해당 내용을 덤프 해보면 <리스트 5>와 같다. TerminateProcess의 서비스 함수 주소는 0x80586740이고, 인자는 0x08 바이트 크기인 것을 알 수 있다. 해당 주소를 디스어셈블하면 커널 내부의 NtTerminateProcess 함수가 출력된다.

이제 끝으로 SDT 후킹을 하는 방법을 생각해보자. SDT 후킹이라 함수 테이블의 번지를 바꾸는 작업을 하는 것을 말한다. <리스트 5>에서 0x101번에 해당하는 서비스 함수 주소는 0x80586740이다. 이 값을 우리가 만든 임의의 함수 주소로 변경한다면, 응용 프로그램에서 TerminateProcess를 호출할 때 원본 서비스 함수가 아닌 우리가 새롭게 작성한 함수가 호출된다.

SDT 복구

루트킷은 API 호출 결과를 조작하기 위해서, 보안 프로그램은 시스템 상황을 모니터링하기 위해서 SDT 후킹을 광범위하게 사용한다. 이런 경쟁 조건에서는 먼저 실행되어서 SDT를 후킹한 쪽에 우선권이 주어진다. 예를 들어 루트킷이 NtQuerySystem Information 함수를 후킹한 상태를 생각해보자. 이후 실행되는 보안 프로그램에서 호출하는 NtQuerySystemInformation의 결과값은 루트킷에 의해서 조작된 값이기 때문에 신뢰할 수 없게 된다. 따라서 보안 프로그램은 자신이 동작하는 환경이 안전한지를 점검하는 것이 최우선 과제라 할 수 있다.

이러한 문제를 해결하기 위해서 나온 것이 SDT 복구 기술이다(http://www.security.org.sg/code/sdtrestore.html). 이 기술의 원리는 간단하다. 현재 시스템의 SDT와 파일로 존재하는 커널의 SDT를 비교해서 같은지 다른지를 비교하는 것이다. SDT 후킹을 하더라도 변경되는 것은 현재 메모리의 내용일 뿐 파일의 내용은 원본 그대로 이기 때문이다. 이 방법의 전체적인 실행 순서는 다음과 같다.

♦ 현재 로드된 커널의 이미지 이름과 로드 주소를 알아낸다.
♦ 로드된 커널의 KeServiceDescriptorTable의 base 주소를 알아낸다.
♦ 동일한 이름의 커널을 LoadLibraryEx를 통해서 메모리에 로드시킨다.
♦ 로드된 이미지에서 base와 동일한 오프셋에 있는 테이블 내용을 비교한다.

<리스트 6>은 이를 간단한 의사 코드로 만들어본 것이다. 핵심적인 부분은 함수 테이블 주소의 오프셋을 사용해서 Load LibraryEx로 로드한 커널에서 해당 부분을 찾아내는 것이다. 현재 로드된 커널의 이미지 이름과 베이스 주소는 NtQuery SystemInformation 함수를 사용해서 구할 수 있다.

SDT 재배치

참 아이러니 한 사실은 보안 제품에서 루트킷을 잡기 위해서 개발된 SDT 복구 기술이 역으로 루트킷에서 사용한다는 것이다. 보안 제품도 시스템 모니터링을 하기 위해서 SDT 후킹을 사용하는데 루트킷이 보안 제품을 무력화하기 위해서 SDT 복구를 사용하는 것이다. 이러한 루트킷에 대항하기 위해서 만들어진 기술이 SDT를 재배치하는 방법이다. 이 방법은 앞서 살펴본 SDT 복구가 로드된 커널의 KeServiceDescriptorTable[0].base에서 찾는다는 사실을 이용한다. base 필드의 값이 커널 이미지 외부에 있다면 로드한 커널 파일에서는 그 부분을 찾을 수 없다. 메모리를 할당한 다음 해당 영역으로 함수 테이블을 복사하는 것이 핵심이다. 그리고 커널의 base 필드 값을 할당된 메모리로 연결해주면 된다. 이렇게 변경된 경우의 테이블 구조가 <그림 2>에 나와 있다.

SDT를 찾는 또 다른 방법

SDT 재배치 방법이 나오고 얼마지 않아 루트킷 진영에서는 오리지널 커널 이미지 파일로부터 SDT의 주소를 알아내는 새로운 방법이 나왔다(http://www.rootkit.com/newsread.php? newsid=176). 이 방법은 커널의 초기화 코드에서 base 주소를 찾아내는 방법을 사용한다. KeServiceDescriptorTable은 커널의 KiInitSystem이란 함수에서 <리스트 7>과 갈은 형태로 초기화된다. base 주소에는 KiServiceTable이란 고정적인 값이 할당되기 때문에 이 부분을 통해서 base 주소의 기본 값을 찾아낼 수 있다.

이 방법의 전체적인 실행 순서는 다음과 같다. 글만 보고는 의미를 이해하기가 쉽지 않기 때문에 <그림 3>에 나와 있는 명령어 구조와 재배치 오프셋의 관계를 보면서 의미를 파악하도록 하자. <리스트 8>에는 재배치 정보에서 SDT 주소를 찾아내는 과정에 대한 의사코드가 나와있다.

♦ 현재 로드된 커널의 이미지 이름을 알아낸다.
♦ 동일한 이름의 커널을 LoadLibraryEx를 통해서 메모리에 로드한다.
♦ GetProcAddress를 통해서 KeServiceDescriptorTable 주소를 구한다.
♦ 모든 재배치 정보를 조사해서 재배치하는 곳의 참조 주소가 KeSer viceDescriptorTable인 것을 찾는다.
♦ 찾아낸 부분의 명령어 코드 형태가 mov [mem32], imm32 형태 인지를 조사한다.
♦ 5단계까지 일치했다면 재배치 주소+4에 있는 값이 KeService DescriptorTable[0].base의 초기 값이 된다.

전용 SDT

SDT를 둘러싼 이러한 싸움이 한창일 때 SDT 후킹에 대한 원론적인 의문을 품은 사람들이 있었다. 그 의문은 다름 아닌 SDT 후킹이 과연 시스템 전역 후킹인가에 대한 것이었다. 그들은 좀 더 세밀하게 커널을 분석했고, SDT 후킹이 시스템 전역이 아니라는 결론에 도달하기에 이르렀다(http://zap.pe.kr/index. php?page=pages/researches/winternals_kr.php). <리스트 9>에 나와있는 것처럼 커널 스레드 구조체에는 ServiceTable이란 필드가 있다. 이 필드의 역할은 해당 스레드의 서비스 테이블을 가리키는 역할을 한다. 앞서 살펴본 것과 같이 일반적인 경우에 모든 스레드는 <그림 4>에 나타난 것처럼 KeService Descri ptorTable이나 KeServiceDescriptorTableShadow 중에 하나의 서비스 테이블을 가리키기 때문에 SDT 후킹이 시스템 전역인 것처럼 보였던 것이다.

전용 SDT란 <그림 5>에 나타난 것처럼 특정 스레드의 Service Table을 수정해서 전혀 새로운 서비스 테이블과 연결시키는 방법이다. 앞서 살펴보았던 방법을 통해서 커널 파일에서 원본 SDT를 추출한 다음 그 정보를 기반으로 새로운 서비스 테이블을 만들면 SDT 후킹이 이루어진 상태라 하더라도 해당 스레드는 SDT 후킹으로부터 자유로울 수 있다.

인라인 후킹 vs 트램펄린

SDT를 새롭게 만들어서 스레드에 연결시키는 방법이 나오면서 더 이상 SDT 후킹은 의미가 없어졌다. 보안 개발자들은 좀 더 하위레벨의 코드를 직접 후킹하는 방법을 선택했다. 인라인(inline) 후킹으로 불리는 이 방법은 커널 함수 도입부를 후킹된 함수로 점프하는 코드로 패칭하는 것을 말한다. <리스트 10>과 같은 NtTerminateProcess 함수 본문을 <리스트 11>과 같이 바꾸는 것이다.

후킹에 관심이 있는 개발자라면 대부분 알고 있듯이 이러한 인라인 후킹은 트램펄린 함수를 사용해서 손쉽게 무력화 시킬 수 있다. 트램펄린 함수란 원본 함수와 도입부를 동일하게 구현하고 점프 코드 다음으로 바로 이동시키는 것을 말한다. <리스트 12>에는 이러한 트램펄린 함수의 한 예가 나와있다.

보안 개발자들의 다음 선택은 단순하게 트램펄린 함수에 무력화 되지 않도록 여러 함수를 동시에 인라인 후킹하는 방법이었다. 다중 인라인 후킹으로 불리는 이 방법은 후킹을 하고자 하는 함수에서 사용하는 다른 함수도 같이 인라인 후킹을 하는 것이다. NtTerminateProcess 함수는 내부적으로 PspTerminate ThreadByPointer, ObfDereferenceObject, ObClearProcess HandleTable와 같은 함수를 사용한다. 이들 함수를 동시에 후킹하면 앞서 살펴본 TL_NtTerminateProcess를 호출하더라도 TerminateProcess 함수 본문에서 호출하는 다름 함수에서 걸리기 때문에 단순 트램펄린 함수로는 우회하기가 쉽지 않다. <그림 6>은 해커가 이러한 상황을 공격하기 위해서 각각에 대한 트램펄린 함수를 만들어둔 화면을 보여주고 있다. <그림 6>에 나와 있듯이 이렇게 각각의 트램펄린을 만든다고 하더라도 CALL로 연결된 2번 선 때문에 인라인 후킹을 무력화 할 수 없다.

물론 그렇다고 이러한 다중 인라인 후킹을 무력화할 수 있는 방법이 전혀 없는 것은 아니다. 트램펄린 대신 함수를 통째로 복사해서 사용한다면 다중 인라인 후킹도 무력화할 수 있다. <그림 7>은 이런 방식으로 함수를 통째로 복사해서 다중 인라인 후킹을 무력화하는 방법이 나와있다. NtTerminateProcess를 원본 운영체제 코드를 복사해서 그대로 만들되 CALL 하는 부분만 Psp TerminateThreadByPointer가 아닌 TrampolinePsp Termina teThreadByPointer를 호출하도록 만드는 것이다. 인라인 후킹된 함수가 많다면 굉장히 번거로운 작업이 되겠지만 결국은 시간과 노력만 투자한다면 어떤 종류의 인라인 후킹이든 우회할 수는 있다.

물론 복잡하게 트램펄린을 만들지 않고 JMP 부분만 원본 코드로 덮어 쓴다면 쉽게 후킹을 우회할 수 있다. 하지만 이렇게 값을 덮어 쓰는 방법은 SDT 후킹과 마찬가지로 경쟁 조건을 유발 시킨다. 서로 JMP와 원본 코드를 끊임없이 덮어쓴다면 그 결과는 예측할 수 없다. 또한 일부 보안 프로그램에서는 자신의 후킹 코드가 손상되는 경우에는 시스템을 중단시키기도 하기 때문에 값을 덮어쓰는 방법은 신중하게 결정해야 한다.

후킹을 넘어서

점프 코드 지뢰밭을 일일이 제거하면서 나가는 것도 재미는 있지만 속도는 더디게 마련이다. 지뢰를 굳이 제거하지 않고 그 위를 날아간다면 그 또한 좋은 방법일 것이다. 앞서 우리가 원본 SDT를 커널 파일에서 찾은 것처럼 커널 파일에 있는 원본 코드를 사용한다면 지뢰밭을 뚫지 않고 날아가는 것도 가능하다.

<그림 8>에는 이러한 아이디어가 나와있다. 커널 파일 자체도 PE 포맷이라는 점과 재배치 섹션이 존재한다는 점을 생각한다면 커널을 로딩하는 것이 불가능한 일은 아니라는 것을 알 수 있다. 커널 모드 코드에서 메모리를 할당하고 그곳에 커널을 올린다음 재배치를 수행하면 <그림 8>의 왼쪽과 같은 구조가 된다. 커널 코드가 두 벌이라는 것과 함께 각각의 커널은 자신의 자료 구조를 가질 것이다. 하지만 이렇게 되서는 곤란하다. 왜냐하면 자료 구조는 전부 기존 커널의 것을 이용해야 하기 때문이다. 약간의 트릭을 써서 커널을 할당된 메모리에 올린 다음 재배치를 기존 커널의 주소에 로딩된 것처럼 수행한다면 <그림 8> 오른쪽 그림과 같은 구조가 된다. 커널의 코드는 두 벌이 되지만 참조하는 자료 구조는 하나가 된다. 이 상황에서 새로 로딩한 커널의 NtTer minateProcess를 호출한다면 트램펄린 함수를 하나도 만들지 않고 모든 인라인 후킹을 우회할 수 있다.

반응형

강병탁 window31@empal.com, www.window31.com|바이너리 취약점 분석 업무를 하고 있다. 안티 크래킹/안티 디버깅 엔진 개발을 다년간 해왔으며 시스템 프로그래밍과 리버스 엔지니어링에 관심이 많다. 악성 코드나 해킹툴이 내부에 담고 있는 천재적인 알고리즘에 감탄하며 오늘도 IDA를 돌린다.


루트킷은 반드시 커널 레벨에서만 가동돼야 한다는 고정관념이 있다. 유저 레벨에서 아무리 숨겨 보았자 커널에서 쉽게 발견할 수 있다는 점에서 기능상의 한계를 많이 지적하는 편이다. 하지만 루트킷을 단순히 ‘최고의 은닉’ 기술에만 국한시켜서는 안 된다. 은닉이라는 기본 테마가 ‘무엇으로부터의 은닉’ 이라는 구체적 명제에 목매이지 않는다면, 유저 레벨 루트킷도 훌륭한 투명인간이 될 수 있다. 예를 들어 유저 레벨의 루트킷은 IceSword 같은 대부분의 루트킷 디텍터에 쉽게 발견되는 편이다. 하지만 그 같은 커널 드라이버를 이용하는 루트킷 디텍터를 사용할 수 없는 경우, 그리고 루트킷을 사용할 목적지가 드라이버와 무관한 장소일 경우라면 유저 레벨 루트킷도 충분한 가치를 발산할 수 있다. 더욱이 루트킷 하면 드라이버라고 생각하는데, 유저 레벨 루트킷은 드라이버를 설치하지 않고 숨겼다는 점에서 어떤 면에서는 더욱 훌륭하다고 볼 수 있다.

그리고 유저 레벨 루트킷은 “절대로 발견할 수 없도록 숨긴다”라는 목적보다는 그 기술이나 구현론 자체에 더욱 중점을 둬야 한다고 본다. 유저 레벨 루트킷을 구현하려면 API Hooking이나 각종 시스템 지식들이 다양하게 요구된다. “절대 발견할 수 없도록 숨긴다”에 너무 치중하지만 않는다면, 유저 레벨 루트킷을 연구하면서 얻을 수 있는 시스템 개발적 지식은 아주 많으리라 생각된다. 또한 유저 레벨의 루트킷은 굳이 루트킷 구현이 아닌 다른 여러 곳에서도 이용될 수 있는 알고리즘도 있기 때문에 얻을 수 있는 것은 더욱 많다고 생각된다. 따라서 루트킷을 커널에서만 구현해야 한다는 관념에 너무 얽매일 필요는 없으며, 유저 레벨에서 구현할 수 있는 루트킷은 어떤 것이 있고, 구조는 어떠한지 살펴볼 필요가 있다.
 
API Hooking

유저 레벨 루트킷으로써의 가장 기본임과 동시에 모든 부분이 되기도 하는 기법이 바로 API Hooking이다. Ring3에서는 시스템에 전역적인 영향을 끼치는 커널 구조체를 컨트롤 할 수 없으므로 은닉을 위해서는 기본적으로 후킹 기법을 필수적으로 사용한다. 그리고 타겟이 되는 API는 사용자가 공격자의 코드나 바이너리를 발견하지 못하도록 모듈을 찾는 관련 함수가 직접적인 대상이 된다. 예를 들면 파일을 찾을 때 FindNext File() 이나 프로세스를 찾을 때의 Process32Next() 같은 API가 대표적인 경우이다. 따라서 유저 레벨 루트킷은 이와 같은 API를 후킹하는 작업을 필수적으로 진행하므로 먼저 API Hooking에 대해서 살펴볼 필요가 있다. API Hooking에는 기본적으로 두 가지 기법으로 나누어진다. 첫째는 IAT Hook, 둘째는 Inline Hook(Overwrite Hook)이다. 여기서 사실 IAT Hook은 루트킷을 구현하는데 있어서 실전에서는 그다지 많이 쓰이지 않는 편이다. 왜냐하면 요즘 필드에 릴리즈되는 바이너리들은 암호화를 위해 대부분 프로텍팅·패킹이 돼서 배포되고 있는데, 프로텍터나 패커들은 API Call의 정보를 숨기거나 다른 보안상의 이유로 IAT를 망가뜨리거나 여러 갈래 꼬아놓은 자체 IAT를 사용하고 있다. 그래서 이 경우는 IAT가 잘 구해지지 않기 때문에 해당 테이블을 후킹 할 수 없고, 당연히 내가 원하는 은닉 기술을 활성화 할 수 없다. 따라서 IAT와는 무관하게 궁극적으로 DLL의 엔트리 포인트로 이동하는 부분을 노린, Inline Hook 방법을 많이 사용한다.

API Hooking을 위한 사전 준비 지식

유저 레벨에서 API Hooking을 위해서는 반드시 해당 프로세스에 DLL을 주입해야 한다(물론 DLL없이 빈 번지를 할당하여 코드를 삽입하고 리모트 스레드를 돌리는 방법도 있지만, 그 방법 역시 지금부터 설명할 세 가지 방법 중, 3번에 해당하는 것이기 때문에 별도 설명은 생략한다). DLL을 주입하는 방법에는 세 가지 방법이 있다. 첫째는 메시지 후킹, 둘째는 AppInit_DLLs 이용, 셋째는 CreateRemoteThread 이다. 이중 첫째는 윈도우가 없는 프로세스는 DLL을 집어넣을 수 없고, 둘째의 AppInit_ DLLs은 다른 프로그램이 사용하는 경우도 있으니 루트킷 구현이 목적인 API Hooking 방법으로는 적절한 방법이 아니다. 따라서 모든 프로세스의 DLL을 집어넣기 위한 방법으로는 셋째인 CreateRemoteThread가 가장 적합하다. 이 기법은 대부분 OpenProcess -> VirtualAllocEx -> WriteProcess Memory -> CreateRemoteThread 의 패턴을 사용한다. API Hooking에 이용되는 가장 표준적인 방법이기도 하다.

다음은 어느 API를 후킹해야 루트킷이 되는지 기능별로 살펴볼 필요가 있다. 목적은 자신을 발견할 수 없게 하는 법이 우선이다. 따라서 모듈이나 프로그램을 발견하기 위해서는 파일을 찾거나 메모리를 뒤지는 방법을 생각할 수 있는데 루트킷은 그것을 저지해야 하는 기능이 반드시 포함돼야 한다. 즉 모듈을 찾을 때에는 프로세스 검색, 파일 검색 더불어 서비스 리스트 검색까지 세 가지 방법이 있으며 루트킷은 이 세 가지 검색 방법을 모두 무력화 시켜야 한다.

프로세스 숨김

프로세스 리스트에서 공격자가 만든 바이너리가 보이지 않아야 하는 것이 첫 번째 과제이다. 프로세스를 나열하는 방법은 대표적으로 툴헬프32와 PSAPI가 있다. 따라서 루트킷은 유저 레벨에서 프로세스 리스트를 링크에서 제외시키기 위하여, Pro cess32Next()와 OpenProcess()를 후킹해야 한다. Open Process()만 후킹하면, 프로세스의 핸들만 얻을 수 없다 뿐이지, 툴헬프로는 PID와 프로세스 이름까지는 구해지기 때문에 유저 레벨에선 반드시 Process32Next()까지 처리해야 한다. 자신의 핸들이나 PID를 구해 놓고, OpenProcess()나 Process32Next() 후킹 함수에서 자신을 찾으려 할 때, 자신의 정보를 숨기는 방법이다. 만약 DLL을 가진 경우는 Module32Next()도 같은 방법으로 처리해 준다.

서비스 숨김

바이러스나 백도어를 찾을 때 서비스에 등록됐는지 살펴보는 경우가 많다. 그 때 루트킷이 서비스에 등록이 되어 있는 모습이 쉽게 발견되면 안 되므로, 서비스 리스트를 뽑을 때 그 목록에서 제거할 필요가 있다. 서비스 리스트는 EnumServices StatusEx 이라는 API를 사용한다. 루트킷은 이 API를 후킹하여 자신의 링크를 끊는다.

파일 숨김

탐색기나 내 컴퓨터 등에서 파일 리스트가 보이지 않아야 한다. 원리는 프로세스 리스트 때와 비슷하며 타겟이 되는 API는 FindNextFile 이다. 유저 레벨 루트킷은 이 API도 놓치면 안 된다. 이렇게 유저 레벨 루트킷을 구현하기 위한 세 가지 처리 부분을 살펴보았다. 프로세스, 서비스, 파일 리스트에서 자신을 숨긴다면, 별다른 루트킷 디텍터의 도움을 받지 않는 이상 해당 모듈을 찾기는 결코 쉽지 않다. 커널 드라이버를 설치할 필요도 없고, EXE와 DLL만으로 루트킷을 구현할 수 있다. 이것이 유저 레벨의 루트킷이다.

유저 레벨에서 루트킷 검출 방법

그렇다면 이번엔 유저 레벨에서 루트킷을 찾는 방법에 대해 알아보자. 물론 유저 레벨에서의 검출 방법은 커널 레벨에 비해 그 활용성이 지극히 제한적이고, 기법도 다양하지 않은 편이다. 그러나 방법이 전혀 쓸모없는 것도 아니다. 또한 이런 방법이 반드시 루트킷을 찾을 때만 사용되는 기술은 아니라는 점에서 다른 여러 시스템 프로그래밍에 활용할 수 있는 부분도 있다. 그렇다면 유저 레벨에서의 검출 방법에는 어떤 것이 있는지 살펴보자.

csrss.exe 의 스레드 리스트 이용

윈도우 시스템 프로세스 중 하나인 csrss.exe를 활용하면 유저 레벨에서도 루트킷을 감지할 수 있다. csrss.exe는 Client Server Run-time SubSystem의 약자로 윈도우 시스템을 가동시키는 핵심 프로세스이다. 이 프로세스는 윈도우에서 실행되는 모든 프로세스의 오브젝트를 관장하고 있다. 따라서 csrss.exe의 스레드 리스트를 구하면 루트킷 프로세스까지 검출이 가능하다.

후킹 원복화

후킹 되어 있는 API를 직접 원복화 시켜 버리는 방법이다. 물론 툴로 제공되는 경우도 있고 직접 VirtualProtect()와 Write ProcessMemory()를 이용하여 구현할 수도 있다. 지금은 디버거를 직접 붙여서 강제로 원복화를 시켜 보도록 하겠다. <화면 4>를 보자. 현재 FindNextFile이 h.dll 로 후킹 된 상태이다.

이 상태에서 이 코드를 원복화 시켜버리자. FindNextFile의 엔트리 코드는 mov edi, edi 로 시작하며 그 이후의 코드까지 포함하여 후킹 된 5바이트를 OpCode로 표현하면 0x8B, 0xFF, 0x55, 0x8B, 0xEC이다. 이 코드로 엔트리를 덮어써 버리자. <화면 5>가 그 내용이다.

그 후킹된 코드는 사라지고 오리지날 상태의 엔트리 포인트가 된다(<화면 6> 참조). 이제 FindNextFile의 후킹은 제거했다. 따라서 무결 상태의 FindNextFile을 사용할 수 있을 것이며 루트킷의 DLL을 거치치 않게 되므로 파일 검색이 가능하다. 이런 식으로 API Hooking을 원복화 시켜서 무력화 할 수 있다.

PID 연속 대입 방법

아무리 루트킷이 프로세스를 숨겨도 PID는 당연히 있기 마련이다. 그리고 PID는 0xC번부터 0xFFFF번까지 존재한다(4번 PID는 시스템이 사용한다). 따라서 PID를 4씩 늘려가며 연속해서 프로세스의 핸들을 오픈해 본다면, 현재 프로세스 리스트엔 없지만 PID가 있는 경우가 나타날 수 있다. 이 경우는 100% 루트킷의 프로세스이다. PID Brute Force 라는 기법이며, 단점은 루트킷이 프로세스의 핸들조차 얻지 못하도록 처리해 놓았을 때는 이 방법으로 검출이 불가능하다는 점이다.

유저 레벨 루트킷은 커널 하층부에 또 하나의 시스템이 더 들어올 수 있다는 약점이 있으므로 한계가 있고, 또 모든 프로세스를 통제해야 한다는 점에서 커널 루트킷보다 오히려 더 까다로울 수도 있다. 하지만 유저 레벨 루트킷은 반드시 루트킷 구현의 목적이 아닌 시스템 프로그래밍의 진수를 보여주는 여러 가지 기법을 사용한다. DLL Injection과 API Hooking, 그리고 각종 오브젝트의 활용적인 부분까지 유저 레벨 쪽의 Windows Internals에 대한 지식을 많이 함축하고 있다. 따라서 유저 레벨의 루트킷은 그 기능의 강력함의 여부를 떠나 기술 그 자체, 구현 알고리즘 자체에 우선순위를 두어 평가하는 것도 좋지 않을까 생각한다.

참고자료

1. Rootkit - http://www.rootkit.com

2. Anti-Rootkit - http://antirootkit.com/

3. Rootkits : Subverting the Windows Kernel - by Greg Hoglund, Jamie Butler

반응형

권용휘 rodream@gmail.com|필자는 악성코드 제거 프로그램인 ‘울타리’를 제작/배포하고 있다. 그 이외에도 여러 프리웨어를 http://rodream.net을 통해 배포하고 있다. 가끔씩 코드프로젝트나 루트킷에 글을 기고한다. 악성코드로부터 시스템을 지키기 위해 오늘도 컴퓨터를 켠다.

루트킷은 이미 오래전부터 존재했다. 유명세를 탄 무렵에는 이미 기술이 대부분 공개된 상황이었다. 기술 자체는 새롭거나 특별할 것이 없지만, 언제부터인가 ‘보안의 핵심’ 으로 대두됐다. 과연 왜 갑자기 유명세를 탔을까? 루트킷이 유명해진 이유는 앞에서 언급했듯이 큰 기업의 스캔들로 인해서 루트킷에 관심이 없던 해커와 크래커 그리고 프로그래머에게 존재가 알려졌기 때문이다. 정체가 알려지자 많은 크래커가 루트킷을 사용했다. 악의적인 일을 좀 더 효과적으로 그리고 완전히 성취하기 위해 루트킷의 소스코드나 프로그램을 활용하기 시작했다. 결국 기존의 바이러스가 좀 더 고급적인 보안 기법을 써서 시스템을 파괴했고 백신 업체들은 이를 방어하기 위해 루트킷에 대한 대응책을 마련할 수밖에 없었다.

루트킷의 정확한 의미

이제 루트킷이 정확히 무엇을 의미하는지 알아보자. 말 그대로 루트킷은 루트(Root) 권한을 쉽게 얻게 해주는 킷(Kit)이다. 루트킷을 사용하면, 보안 허점을 이용해 원하는 일을 처리하도록 도와준다. 파일이나 레지스트리를 숨기는 것이 루트킷이 하는 대표적인 일이다. 이는 악성 프로그램이나 바이러스를 시스템에서 제거하려는 시도를 무력화시키기 위한 것이다.

이와 같이 루트킷이 실제로 하는 일은 자신을 은폐하거나 삭제할 수 없도록 하는 게 대부분이다. 일반적으로 자신을 은폐하기 위해서 루트킷은 운영체제나 시스템을 변화시킨다. 우리가 쓰는 운영체제는 크게 두 개의 계층(유저 모드와 커널 모드)으로 이루어져 있다. 커널 모드에는 실제로 운영체제의 핵심적인 작업을 수행하는 코드가 동작하며, 유저 모드는 커널 모드의 코드를 사용하기 위해 API를 사용한다. 예를 들면, 파일을 지우기 위해 프로그램에서 DeleteFile API를 사용하면, 유저 모드에서는 파일을 직접 지우는 기능이 없으므로 커널 모드의 파일 지우는 코드가 동작하도록 요청한다.

또한, 백신 프로그램 등이 바이러스가 동작중인지를 찾아내기 위한 방법으로 어떤 프로그램이 실행중인지 알아내는 방법도 이와 동일한 과정을 거친다. 즉 유저 모드에서는 실제로 작업을 하는 게 아니라, 단지 커널 모드로의 이관을 도와준다. 루트킷을 써서 은폐하는 프로그램은 바로 이렇게 커널 모드로 이관하는 길목에서 자신을 은폐하기 위한 일을 한다. 은폐하는 일 자체가 점점 고도화되고 복잡하기 때문에 루트킷을 이러한 기술의 일련으로 오해할 수 도 있다. 하지만, 루트킷이라고 부르는 것은 기술 자체보다는 기술을 사용해 기존 시스템의 정책(Policy)에 역행할 수 있는 것을 의미한다.

앞에서 언급하였던 유저 모드에서 커널 모드로 이관되는 과정을 변형 시키는 것을 가리켜 후킹(Hooking)이라고 한다. 하지만, 후킹을 사용했다고 해서 모두 루트킷이라고 명명하면 안 된다. 생각해보면 이는 매우 당연한 것인데, 현재 많이 사용하는 DRM 프로그램이 포함한 실시간 암·복호화도 후킹을 사용해 구현되는 경우가 대부분이지만, 아무도 이를 루트킷이라고 말하지는 않는다. 최근 소니 USB에서 새로운 루트킷이 검출됐다는 보도가 나왔다.

기사에서는 루트킷을 판별하는 데 기술적인 측면보다 파일과 프로그램을 은닉하는 것이 악용될 수 있다는 점에 집중하고 있다. 그렇다면, 모두에게 인정받은 프로그램이 정상적인 목적으로 이러한 기능을 사용하는 경우에는 어떻게 될까? 답은 이런 경우에도 루트킷이라는 칭호를 달 수 밖에 없다는 것이다.

필자가 직접 확인했던 모 언론사의 툴바 프로그램은 다른 프로그램이나 사용자에 의해 임의로 지워지지 않도록 프로그램을 숨기고 삭제하지 못하도록 해 놨다. 분명 루트킷이라고 판별할 수 있다. 이는 프로그램을 사용해 다른 프로그램도 함께 숨겨지거나 삭제할 수 없도록 할 수 있기 때문이다. 설명을 덧붙이면, 이 프로그램이 만약 C:\Program Files\??Toolbar 라는 폴더 아래에 있는 모든 파일을 숨기고 아래에 있는 프로그램을 사용자로부터 은닉시킨다면 악성코드를 만드는 사람의 입장에서는 자신의 프로그램의 실행 파일을 자동으로 C:\Program Files\??Toolbar 폴더 아래에 자동으로 복사하도록 설정할 수 있다. 이렇게 된 경우에, 사용자가 ??Toolbar를 설치하면 자기 자신을 보호하기 위한 프로그램이 악성코드를 숨겨주는 부가 효과(Side Effect)가 발생한다. 이러한 부가 효과는 시스템에 매우 치명적이며 악영향을 미친다. 지워지지 않는 악성코드가 악성코드 제작자의 의지가 아닌 상태에서 만들어진 것이다. 만약 ??Toolbar가 매우 유명하고 대부분의 사람들이 사용한다면 모든 악성코드 제작자들은 이 폴더로 자신의 프로그램이 설치되도록 유도하게 될 것이고, 결국 ??Toolbar는 악성코드를 보호하는 프로그램이 되어 악성코드의 숙주가 된다.

루트킷의 종류

루트킷은 범용적으로 쓰이기 때문에 종류도 다양하다. 여기서 언급하는 루트킷의 종류는 필자가 개인적으로 나눈 것으로 절대적인 것이 아니라는 것을 먼저 밝힌다. 우선 루트킷의 전체를 볼 수 있도록, 어떤 루트킷이 있는지 알아보자. 루트킷이 동작하는 환경에 따라서 나누면, 운영체제 별로 루트킷이 존재한다. 윈도우에서 동작하는 루트킷만 살펴보면, 유저 모드 루트킷과 커널 모드 루트킷 그리고 그에 속하지 않는 루트킷(윈도우가 구동되기 전에 동작하는 루트킷)이 있다. 유저 모드와 커널 모드가 따로 있는 것은, 유저 모드에서 할 수 없는 일을 커널 모드에서 할 수 있는 경우가 많기 때문이다.

우리가 가장 많이 사용하는 CPU 인 인텔 호환 CPU는 명령어를 실행할 수 있는 권한을 가지고 있는데, ring0 부터 시작해서 ring1, ring2, ring3 까지, 4개의 ring 권한을 가진다. 윈도우의 경우 유저 모드에서는 ring 권한 중 가장 낮은 ring3 권한을 가지므로 CPU에 시스템 큰 영향을 줄만한 명령어를 실행할 수 없다. 하지만, 커널 모드에서는 ring0를 사용하므로 이를 위해 커널 모드 루트킷이 필요한 것이다. 또한, 생소하게 생각될 수도 있지만, 커널 모드도 유저 모드도 아닌, 루트킷이 있다. 이는 운영체제가 로드되기 전, 하드 디스크의 MBR(Master Boot Record)을 참조하게 되는데, MBR을 수정해 자신이 먼저 실행되도록 시스템을 수정하는 루트킷이다. 또한, 하드디스크가 아닌 비디오카드에 기생하는 루트킷도 존재한다.

이와 같이 다른 하드웨어에 존재하는 저장장치에 기생할 경우에 얻는 이점은 일반적으로 바이러스에 감염됐다고 판단해 하드디스크를 포맷하거나 운영체제를 재설치하는 경우가 많은데, 이러한 루트킷은 포맷과 재설치에도 불구하고 살아남는다는 특징이 있다.

루트킷은 누가 만드는가?

대체 루트킷은 누가 만드는 것일까? 실질적으로 많은 루트킷이 공개되고 배포되는 루트킷 닷컴(http://rootkit.com)에 가면 루트킷을 공개하는 사람들이 자신의 신원을 밝히고 자신의 결과물(RootKit)을 공개한다.

루트킷 자체는 ‘악성’이라기보다는 시스템 보안의 허점을 공개하는 하나의 도구이기 때문에, 그 자체가 나쁜 일은 아니기 때문이다. 물론 이를 악용하면 큰 문제를 일으킬 수 있겠지만, 아직까지 국제적인 제재는 없는 상황이다. 중국의 경우 실제로 크래킹 방법을 잡지에 기고하고 판매하는 경우도 있다. 국내 사정은 아직 먼 이야기지만. 몇 년 전 『해킹, 파괴의 광학』이라는 책이 매우 성황리에 팔렸던 적이 있다. 국내에서는 아직 이 책에 공개된 수준까지만 허용된다. 그렇다면 루트킷을 만들어서 공개하는 이들은 누구일까? 대부분 보안 프로그래머이다. 이 얘기가 다소 혼란스러울 수도 있을 것 같다. 어떻게 시스템을 파괴하기 위한 도구를 보안 프로그래머가 만들 수 있을까 하는 의문에 봉착한다. 이는 방패를 만들기 위해서는 창에 대해 잘 알아야 하고, 창을 잘 아는 사람만이 효과적인 방패를 만들 수 있는 이치와 같다. 하지만, 여기서 필자가 한 가지 명확하고 싶은 것은 루트킷을 만드는 사람과 악성코드를 만드는 사람을 확실하게 구분해야 한다는 것이다. 대부분의 악성코드 제작자는 시스템 자체에 대한 이해도가 높지 않다. 그들은 단지 루트킷을 사용하는 것뿐이다. 이러한 사람들을 스크립트 키디(Script-x Kiddie)라고 부른다. 이들은 시스템에 대한 탐구 의지보다는 과시욕이나 시스템을 파괴하려는 생각만 앞선 사람들이다.

루트킷 기술들

루트킷에서 나오는 기술은 무척 다양하다. 기술의 수준도 높은 반면에, 누구나 알만한 내용도 존재한다. 하지만 “아... 이렇게 역으로 생각할 수 도 있구나”라는 식의 역발상적인 기술이 많다. 필자 개인적으로는 한가한 시간에 여러 프로그래머나 해커의 사이트를 돌아다니면서 그들이 최근에 해온 일들을 훑어보는 습관이 있다. 루트킷에 대한 내용을 읽다 보면, 좀 더 창의적이고 기발한 생각을 떠올리도록 도와주는 경우가 있다.

가장 잘 알려진 기술인 후킹도 기술 자체는 이미 오래전에 알려졌지만(리눅스 등의 운영체제에서는 이렇게 후킹을 하는 것이 운영체제의 일부가 되있기도 하다), 루트킷을 잡아내고 복구(Restore)하는 기술이 발전함에 따라서 좀 더 지능적으로 진화해 왔다. 예를 들면, 백신 프로그램이 루트킷을 탐지해내려면 루트킷 프로그램의 연속된 특정 어셈블리 코드를 찾는 경우가 많다. 특히 <리스트 1>의 코드는 커널 모드에서 Write Protection 기능을 제거하기 위해 CR0 비트를 조작하는 코드이다. 하지만, 루트킷에 많이 쓰이면서부터, 백신 프로그램은 이 코드를 기준으로 루트킷을 탐지한다.

이러한 문제를 해결하기 위해, 안티-루트킷(Anti-RootKit) 프로그램이 특정 실행 코드(op code: 기계어)가 연속적으로 나오는 경우에만 검출해낼 수 있다는 허점을 노려, INC EAX, DEC EAX와 같이 의미 없는 코드로 코드의 흐름을 섞어 놓는 간단한 방법으로 백신의 검출망을 피해간 것이 바로 <리스트 2>의 코드이다. 이러한 의미 없는 코드는 매우 많다. <리스트 2>와 같이 어떤 일을 한 후에 다시 그 일을 되돌리는 명령을 사용하는 것도 있으며 JUMP 명령어를 사용해 이리저리 코드의 흐름을 어지럽히는 방법도 있다. 또한, 어셈블리에서는 아무것도 하지 않는 명령어로 NOP라는 명령어가 있는데, 이를 사용하는 경우도 있다. 이러한 것을 ‘Code Randomization 기법’이라고 하는데, 최근 루트킷에서 흔히 발견하는 간단한 트릭이다. 하지만, 최근에는 실행 코드 자체만 고민하던 해커들이 실행 코드보다는 코드가 사용하는 데이터에 집중하면서 DKOM (Direct Kernel Object Manipulation)이라는 기법이 새롭게 생겨났다. 이 기법은 실행 코드가 사용하는 데이터(프로세스 목록을 저장하기 위한 링크드 리스트 등)를 변경하여 자신을 감추는 방법 등으로 활용되고 있다. DKOM을 사용해 프로세스를 숨기는 방법을 간단히 설명하면 다음과 같다. 먼저, 윈도우에서는 Process 목록을 링크드 리스트를 사용하여 관리하게 되는데, 이 링크드 리스트의 각 항목들을 가지고 연결 고리를 조작하는 방법이다.

<그림 2>와 <그림 3>을 보면 <그림 2>에 있던 세 개의 프로세스가 연결된 상태에서 <그림 3>처럼 하나의 연결을 끊게 되면, 두 개의 프로세스만이 존재하는 것처럼 보인다. 이것이 바로 DKOM 기법을 사용하여 Process 목록을 숨기는 원리이다. DKOM은 매우 혁신적인 아이디어라고 할 수 있다. 왜냐하면, 이전의 루트킷이 실행 코드를 바꾸는데 전념했고, 그렇기 때문에 루트킷을 탐지해내는 방법으로 실행 코드를 확인하는 방법을 많이 사용했다. 루트킷이 아무리 똑똑해도, 언젠가는 메모리 어딘가에 자신을 실행해 달라는 흔적(보통 jmp 명령어나 자신의 코드가 있는 메모리 주소가 된다)을 남긴다. 하지만, DKOM 기법을 사용하면, 데이터 조작 여부를 판단하기가 매우 애매해진다.  이렇게 숨긴 Process를 알아채기 위한 방법은 Process 목록에서 자신을 지워버려도 언젠가 악성코드가 실행되려면 윈도우가 실행 코드 스케쥴을 하는 기반인 스레드(Thread) 목록에서는 지우지 않는 다는 것에 착안하여 검출해내는 방식을 사용한다.

필자는 2007년 10월, RootKit.com에 네이티브 애플리케이션이 루트킷으로 사용될 수 있다는 내용을 기고한 적이 있다. 비스타에서 기존 윈도우와 가장 많이 바뀐 내용이 UAC에 관련된 내용이며, 이것이 네이티브 애플리케이션을 통해 무력화 될 수 있다는 내용이었다. 최근에는 이런 식으로, 새롭게 생긴 정책의 논리적인 구멍(Hole)을 공격하는 경우가 늘고 있다. UAC에 관련된 유사한 예로, 윈도우 비스타의 정식 버전이 출시되기 전인 RC 버전에서는 윈도우의 Swap 기능을 역이용해, Swap out 된 가상 메모리의 내용을 조작하여 다시 그 코드가 Swap In 되어 실행 될 때 시스템의 권한을 가로채는 기발한 방법이 나오기도 했다. 물론 이 문제는 정식 버전이 나오기 전에 이미 수정됐다.

루트킷이 시사하는 것

루트킷이 시사하는 것은 이미 대부분의 사람들이 시스템의 허점을 이용할 준비가 돼 있다는 것이며, 이는 곧 우리가 불안한 디지털 세상을 살고 있다는 말이기도 하다. 더 이상, 크래킹은 많은 지식을 가진 고급 크래커들만의 몫이 아니다. 하지만, 대부분의 사람들이 운영체제가 잘못 설계됐기 때문이라고 오해한다. 물론 운영체제 자체의 문제도 있겠지만 새롭게 보안을 강화시킨 비스타에도 여전히 루트킷은 존재하며, 많은 사람들이 안전하다고 생각하는 유닉스와 리눅스조차도 수많은 루트킷이 존재한다.

필자는 이제 기술적인 문제에서 떠나야 한다고 생각한다. 보안 위협이 사회현상으로 부각되면서 여러 가지 법안이 제정됐지만, 충분한 생각과 고려 없이 제정된 법들은 루트킷의 악용을 규제하기 보다는 안티-루트킷 프로그램에 ‘인증’을 받으면 루트킷 기능을 이용할 수 있다는 새로운 ‘권력’ 을 생산해내기에 이르렀다. 세계적으로 많은 안티-루트킷(백신) 업체들이 있지만, 이들의 방식에도 문제가 많다.

특히, 루트킷을 진단하는 방법으로 실제 코드를 분석하는 방식을 사용하는데, 이에 대한 기준도 업체마다 달라 ‘오진’이 빈번하게 발생한다. ‘오진’의 대부분은 기술적이지 못한 사용자들에게 마치 바이러스인 것처럼 겁을 줘서 ‘오진’ 당한 업체에게는 명예 실추와 영업 손실을 끼친다. 이러한 구조로 인해 백신 소프트웨어를 만드는 업체들이 마치 프로그램 인증서를 발급하는 것과 같은 양상이 됐다. 지금은 오래된 이야기 이지만, ActiveX로 인한 악성코드 및 바이러스가 기승을 부리면서부터, ActiveX에는 인증서라는 것이 필수요소로 작용하게 되었다. 하지만, 인증서를 악성코드 제작자들이 자유롭게 취득하지 못하도록 가격이 책정돼 버렸다. 이로 인해 ActiveX를 사용해 웹 페이지에서 프로그램을 서비스하려면 인증서를 꼭 구입해야 한다. 인증서 가격은 가장 저렴한 방법으로 구매해도 1년에 25만원에 육박하는 적지 않은 가격이다. 이것은 과연 누구를 위한 것인가? 인증서를 구매한다고 해서 악성코드나 바이러스가 박멸되는 것일까? 전혀 그렇지 않다. 오히려 지금에 와서는 합법적으로 인증서를 구매해서 악성코드를 만드는 것이 당연시 되어 버렸다. 이러한 방식은 마치 벼룩을 잡기위해 초가삼간을 다 태우는 격이며, 앞으로 생길 새로운 보안 위협에 대해서는 이러한 새로운 권력을 창출해 내지는 말아야 한다고 생각한다.

반응형

Cracking & Reverse Engineering

크랙과 리버스 엔지니어링에 관한 자료를 제공합니다. 주로 제가 크랙한 프로그램에 대한 자료를 제공합니다. 하지만 제 실력이 그리 좋지 않은 관계로 하이레벨 자료를 기대하지 않는게 좋습니다. ^^; 직업(?)이 아니라 취미로 하는것이기에... 좀 전문적이지 않습니다. 가끔씩 머리가 아플때마다 머리식히기 위해서 하는데 상당히 재미있습니다. 여러분도 한번 해보세요. ^^

Fry (Translate ALZIP to ZIP) - 2004년 2월 8일

어제 웃대에서 휴식을 취하고 있다가 안타까운 글을 읽었습니다. 모 개발자와 모 업체가 알집 파일을 푸는 알고리즘을 갖고 서로 싸우는 내용이었는데, 개인적으로 보기 안좋더군요. 한국사람들끼리 원수처럼 싸우는 것이 참... 그렇게 헐뜯고 싸울만큼 가치있는 기술도 아닌데 말이죠. 리버스 엔지니어링에 조금이라도 경험이 있는 분이라면 손쉽게 분석하실수 있습니다. 아래는 간단한 예제 코드 입니다. 알집이 zip 알고리즘을 그대로 사용하기 때문에, 헤더만 조금 고쳐주시면 zip 파일로 손쉽게 변환이 가능하죠. 아래 예제에서는 한개의 파일만 압축되어 있는 알집 파일을 zip파일로 변환시키는 모습입니다. 코드를 조금 수정 하시면, 여러개의 파일이 압축되어 있는 알집 파일도 변환 가능하죠. 뭐 다 아시겠지만.. ^^

  • fry.zip - 소스 파일 입니다. 콘솔에서 테스트 하다가 mfc로 옮기다 보니 소스가 좀 지저분 합니다. ^^;

Tinker Bell for WinMine 2000 Pro & XP Pro - 2003년 7월 1일

지뢰찾기 맵핵 입니다. 시스템 전역 후킹 라이브러리를 만들다가, 어떻게 하다보니 만들게 되었습니다. 황당하게도 프로그램 이름은 팅커벨로 정했습니다. 만들고 있던 라이브러리 이름이 후크선장 이어서 무의식적으로 프로젝트 이름을 팅커벨로 입력해 버렸거든요. 후킹 라이브러리에서 사용하는 부분들을 그대로 팅커벨(MFC)로 옮기다 보니 소스의 변수 선언부가 약간 어지럽습니다. 혹시라도 소스 보실분은 이점 이해해 주세요. 2003년 7월 1일 업데이트 이후로 윈도우즈 2000 프로페셔널 버젼과 함께 윈도우즈 XP 프로페셔널 버젼의 지뢰찾기도 맵핵이 됩니다. 나머지는 리버싱을 안해봐서 잘 모르겠네요. 선언부만 변경해주시면 다른 버젼도 적용 가능합니다. ^^

  • winmine.zip - Windows 2000 Professional에 들어있는 지뢰찾기 실행파일 입니다.
  • tinkerbell.zip [screenshot 1st 2nd] - 팅커벨 소스 파일입니다. Visual C++ 7.0이 필요합니다. 서비스팩 깔기 귀찮아서 이제부터 6.0은 안쓰기로 했습니다. ^^

'WinMine for Windows 2000 Professional' Cracking - 2003년 4월 25일

이번 크랙은 정말 얼떨결에 하게 됐습니다. 어떻게 된거나면, dcinside란 곳에서 놀고있는데 한분이 자신이 지뢰찾기 게임을 중급자 모드로 해서 30초만에 클리어 했다는 글을 올리신 것에서 부터 시작됐죠. 이후로 다른분들이 많이 도전하셨지만 50초가 최고였습니다. 저도 지뢰찾기는 꽤 한다고 생각했는데, 쉽지가 않더군요. 하지만, 뭐 모로가든 서울만 가면 된다는 말이 있잖습니까. 게임이 어려우면 내게 쉽도록 바꾸면 되죠. ^^ 이번 크랙은 간단했습니다. 약 100byte 정도의 코드만 추가해 주시면 되요. 한번 해보세요.

  • Original WinMine - Windows 2000 Professional에 들어있는 지뢰찾기 실행파일 입니다. 공짜에요. (맞죠?)
  • Cracked WinMine [screenshot 1st 2nd 3rd] - 크랙된 지뢰찾기 실행파일 입니다. 아래 두번째 사진이 크랙된 모습이에요. 실행하시면 지뢰가 보입니다. O_O;;;;

'ReverseMe#1 by SantMat' Reverse Engineering - 2002년 12월 9일

ReverseMes.de에 있는 문제 입니다. 처음 해보는 리버싱이지만 스토리를 갖고 있어서 상당히 재밌게 했습니다. ^^ 인류의 종말(멸망?)을 구한 영웅이 된 기분이랄까요? ㅎㅎ. 크랙보다는 훨씬 재미있네요. 이거 하면서 가장 힘들었던 점은 기계어 코드를 직접 입력하는것.. --; OpGen이라는 명령어 생성기를 구했는데 2000에서는 안돌아 가더군요. (미치는줄 알았음.) 그래서 생각보다 시간이 많이 걸려 버렸습니다. 계속 구해보다가 못구하면 하나 만들어서 써야 겠네요. 쩝. 그리고 리버스한 프로그램의 암호는 'fuckingUSA'로 설정했습니다. 미국이 싫어요! >.< ReverseMes에 리버스한 프로그램이랑 튜토리얼을 올릴려고 했는데 'fuckingUSA'암호로 올렸다가는 매장당할것 같아서 못올리겠네영 ㅋㅋ.. 그렇다고 암호는 바꾸기 싫고. -_- 암튼 잼있으니 여러분도 한번 해보세요. ^^

'CrackMe B1' Cracking - 난이도 : 4/10, 2002년 12월 3일

Le4rN TO Cr4cK에 있는 난이도 4의 문제 입니다. 아래의 문제들과는 달리 패치를 하는게 아니라 암호 key를 찾는 것 입니다. 크랙해 보니까 확실히 패치하는 것 보다는 어렵더군요. 디어셈 해보시면 아시겠지만 key에 따른 function table이 메모리에 있어서 그걸 참조 합니다. 더이상 말하면 재미 없겠죠? ^^; 한번 풀어보세요. 아마 연습장과 계산기가 필요하실 겁니다. ^^

  • CrackMe B1 - CrackMe B1 프로그램 입니다.
  • 튜토리얼 문서 [screenshot] - 아직 공개하지 않습니다. 'Le4rN TO Cr4cK'에서 출제중인 문제입니다. 따라서 스크린샷만 공개합니다.

'CrackMe 8' and 'CrackMe 8.1' Cracking - 난이도 : 1/10, 2/10, 2002년 12월 3일

Le4rN TO Cr4cK에 있는 난이도 1과 2의 문제 입니다. 프로그램을 실행하면 'Unregistered'라고 나오면서 등록하라고 그러는데 이걸 크랙해서 'Registered'라고 나오도록 바꾸면 됩니다. 이건 제가 가장 처음에 도전한 문제입니다. 이 두개는 똑같은 방법으로 풀었습니다. 그래서 이렇게 같이 적습니다. 출제자는 뭔가다르게 바꾼것 같은데 뭘 의도한지는 잘 모르겠네요. --; 쉬운 난이도인만큼 실제로 머리쓸일은 없을 겁니다.

  • CrackMe 8, CrackMe 8.1 - CrackMe 8과 CrackMe 8.1 프로그램 입니다.
  • 튜토리얼 문서 [screenshot] - 아직 공개하지 않습니다. 'Le4rN TO Cr4cK'에서 출제중인 문제입니다. 따라서 스크린샷만 공개합니다.

유용한 문서

홈페이지 링크

    $ 국내 홈페이지 $
  • Le4rN TO Cr4cK - 국내 크래킹 관련 포럼. 많은 해커들이 자신이 크래킹한 프로그램에 대한 튜토리얼 문서를 제공.
  • oPEN rEVERSE fORUMS - 리버스 엔지니어링 포럼.
  • dive2code님의 홈페이지 - 크래킹에 필요한 여러 문서를 공개.
  • x3chun님의 홈페이지 - 크래킹과 리버스 엔지니어링에 관한 문서와 프로젝트를 진행.

    $ 국외 홈페이지 $
  • CrackMes.de - 전세계 크래커들이 모여있는 곳. 난이도별로 수많은 크래킹 문제들 출제.
  • ReverseMes.de - 리버스 엔지니어링 사이트.
  • Crack Store - 폴란드의 크래킹 커뮤니티. 크래킹에 필요한 툴, 튜토리얼 문서, 툴사용 문서, 소스등을 제공.
  • Programmer Tools - 컴파일, 디버깅, 패킹 관련 툴을 제공.
  • Stenri님의 홈페이지 - 소프트아이스 화면을 캡쳐하는 IceExt라는 플러그인을 개발.
  • CoDe_InSiDe's Manual UnPacking Page - Packer와 Encrypter에 관한 문서 제공.
반응형
RE를 공부한 사람이라면 누구나 알만한 그룹 ARTeam 이 쓴 문서다. part1부터 part10 까지 있으며, RE를 이제 막 시작하려는 사람에게 좋은 문서다. 앞으로 하나하나 번역해서 올릴 생각이다.
- forc1

--------------------------------------------

Gabri3l Tutorial #1

Beginner Tutorial: Serial fishing
번역 : forc1 (http://forc1.net)

The Target:
WorldTV 7.1
http://www.netfor2.com/WorldTV.html
The Tools:
OllyDbg 1.09d, PEiD 0.92, W32dasm, HexWorkshop 4.1
The Protection:
Serial Protection
Other Information:
이 문서는 초보 크래커에게 올리로 serial fishing을 소개하기 위한문서다. 과정을 하나하나 보여줄 것이다. 새로운 타겟에 어떻게 접근할지 더 좋은 아이디어를 얻는데 도와줄것이다.

Intro:

필요한 툴은 인터넷에서 모두 구할 수 있다.
http://home.t-online.de/home/Ollydbg/odbg109d.zip
http://peid.has.it/
http://protools.cjb.net/

일단 툴을 설치하고 크랙할 준비를 한다. 타겟이 있는 폴더를 연다. 이 경우 c:\program files\worldtv\ 이고 만약의 경우를 대비해 백업을 해둬라. 나는 worldtv2.exe 로 백업했고 이 문서에선 이것을 참조할것이다.

패커나 프로택터를 확인하기 위해 PEiD를 사용한다. PEiD를 열고 worldtv2.exe를 이곳에 드래그한다. 그 결과는 "Microsoft Visual C++ 5.0". 놀랍게도 타겟은 패킹이나 프로택팅 되지 않았다. 이것은 크랙하기 더욱 쉽도록 한다.


Body:

우리가 파일을 언팩할 필요가 없을거란걸 알고 있는것은 우리가 프로텍션 scheme의 검사를 조금
하게 한다. 이제 worldtv를 실행한다. 등록되지 않았다는걸 알려주는 nag 스크린으로 바로 간다.
이것은 프로그램이 로드되기 전에 등록키(키 파일이나 레지스트리 키)를 체크한다는걸 우리에게
알려주기 때문에 중요하다. 임의의값을 넣어본다. "Invalid Registration Code" 라는 메시지박스가
떳다. 이것을 적어둔다. 만약 전에 다른 문서를 읽어봤다면 능숙히 얻을 수 있으리라 본다.
 확인을 누르고 프로그램을 종료한다.

Finding the Bytes:
일 단 쉬운 방법으로 해볼것이다. invalid 등록 박스로 리턴하는 jump를 패치할 것이다. w32dasm을
실행하고 WordTV.exe를 연다. 오리지날 WorldTV.exe 파일을 열고 있기때문에 작업은 위에서 만든
만든 복사본에서 할 수 있다. 디스어셈블이 끝난 후, 메시지박스에서 문자열을 찾을 것이다.
w32dasm 의 위쪽에 있는 String References 버튼을 클릭한다.
파일에서 찾을 수 있는 모든 문자열의 리스트가 새로운 창에 뜬다. "Invalid Registration Code"를
찾을 때까지 스크롤을 아래로 내리고 그것을 더블클릭한다. 아마도 아래의 라인에 있을것이다.



test eax, eax와 그 밑의 jne 0041B54C 코드를 보라.



아무 시리얼이나 넣어도 프로그림에 등록되도록 하기 위해 jne(jump if not equal)을 jmp(jump)로
바꾸길 원한다. 이것을 하기 위해 WorldTv.exe에서 jne 명령이 위치한곳을 찾을 필요가 있다.
그 정보는 w32dasm 창의 아랫쪽에서 찾을 수 있다. 아마 다음과 같을것이다.

Line:52558 Pg 657 and 658 of 1734 Code Data @:0041B521 @Offset 0001A915h in File
 WorldTV.exe

0001A915h의 오프셋 값에 관심이 있다.
 hexadecimal 값을 의미하는 마지막에있는 h를 빼고 이 숫자를 적어둔다.

Patching the Bytes:
HexWorkshop으로 WorldTV2.exe를 연다. 다이얼로그 박스로 이동하기 위해 CTRL+G를 누른다.
 Edit 아래에서도 찾을 수 있다. 아까 적어논 0001A915를 입력한다.
 










Hex 옵션과 Beginning of File 옵션을 체크한다
. 다 됐으면 Go를 누른다.

아마도 jne 0041B54C에 위치할 것이다. 7
5
는 명령어 JNE의 오피코드고 74는 명령어 JE의 오피코드라는걸 알꺼라 믿는다.
 이 경우 bad serial로 점프하는거보단 프로그램이 어떤 serial로 점프하길 원한다.
 75를 JMP 명령어인 EB로 바꿀것이다.
 


[ Before ]

[ After ]

WorldTV2.exe를 저장한다. 백업하고 싶거든 뭐라 물었을때 YES를 선택한다.
 이제 패치된 WorldTV2.exe를 실행해본다.

시리얼을 물으면 아무값이나 넣는다. 나는 1234567을 넣을것이다.
Validate Registration을 누르면.. 성공!
Registration Code Accepted. 여기서 끝? 아니다 :P

WorldTV2.exe를 닫았다가 다시 연다. 여전히 시리얼을 묻는다.
사용할 때마다 항상 시리얼을 넣어야 할 것이다. 매우 귀찮다.
그래서 우리는 진짜 시리얼을 찾을것이다.

Finding a Serial:
시작하기 전에, 이 프로그램에 대해 알고 있는 것을 재검토 해보자.
1. 시작할 때 시리얼을 체크한다.
2. 가짜 시리얼로 등록하면 다음에 시작할 때 등록되지 않는다.
이 것은 프로그램이 완전히 로드되기 전에 제대로된 시리얼을 체크한다는걸 의미한다.
시리얼이 어디에 저장되어 있는지 찾아야 한다. 보통 두군데에 저장이 된다. 레지스트리와 파일.
레지스트리를 체크하면서 시작할 것이다. WorldTV2.exe를 실행하고 시리얼로 1234567을 넣는다.
 시리얼을 확인하고 WorldTV를 닫는다.

시작-실행에 가서 "regedit"를 입력한다. regedit이 뜨고 두 개의 창이 보인다.
왼쪽창의 HKEY_CURRENT_USER 앞에 [+]를 클릭하면 또 다른 리스트가 열릴 것이다.
같은 방법으로 Software-WorldTV 까지 간다.
아하! 오른쪽창에 우리가 아까 입력한 1234567이 저장된 RegCode라는 키가 있다.
이제 WorldTV는 로드되기 전에 레지스트리에서 시리얼을 체크한다는걸 알게되었다.

올리를 실행할 것이다. 1.10은 브포설정할 때 가끔 충돌이 일어나서 나는 1.09d 버전을 쓸 것이다.
원본 WorldTV.exe를 연다.


아마 위에 있는 그림과 비슷하게 나올 것이다. Run 키 를 누르기 전에 브포를 걸어야 한다.
Code 창에서 우클릭-Search For-All Intermodular Calls를 선택한다.
 그러면 Calls 창이 뜰것이다. Destination으로 정렬한다.
RegQueryValueExA를 찾을 때까지 스크롤을 내린다.
그것을 선택하고 우클릭-Set breakpoint on every call to RegQueryValueExA을 선택한다..


이제 Run 버튼을 누른다. 처음에
FF15 0C304400 CALL DWORD PTR DS:[<&ADVAPI32.RegQueryValueExA>]에 서 멈출 것이다.
오른쪽에 있는 레지스터 윈도우를 보면 EDI에서 아스키값 "Recordings"을 볼 수 있다.
이것은 우리가 찾던 레지스트리 키가 아니므로 다시 실행을 누른다.
여기에 도착하기까지 24번의 실행을 눌러야 할 것이다.


EAX 값이 아스키 "RegCode" 라는걸 주의하라. 한번 더 실행을 누르면 여기에 위치한다.


그리고 ECX는 아스키 값 "RegCode"가 있다.
WorldTV가 등록 코드를 찾았기 때문에 점점 가까워 지고 있다는걸 알고있다.
를 통해 코드를 스텝할 것이고 레지스터에 집중할 것이다.
몇 스텝후 ESI 가 가짜 시리얼 1234567값을, EDI에
00000000-00000000-00000000-00000000
가지고 있는걸 찾았다. 이것은 흥미롭지만 000... 이 등록 코드인지는 의심스럽다.
좀 더 스텝 하면, EDI 가  
-00000000-00000000-00000000로 줄어든걸 볼 수 있다.
이것은 아직 우리에게 시리얼을 주지 않는다. 계속 나아가다 보면 EDI가 "
C:\Program Files\WorldTV\Scheduler.txt.tmp"로 바뀐다. 여기 도착한 포인트 다음에 곧 찾을것이다.


EAX, EBX, EDX가 0이 된걸 주목하라. 또한 EDI안에 하나의 아스키 값이 들어간걸 볼 수 있다.
 시리얼 코드를 볼곳이 바로 여기다. 스탭을 통해 조금 더 진행하면 루프안에 있다는걸 발견한다.
 시리얼이 만들어지는걸 볼 수 있고 다음의 주소에서 볼 수 있다.
MOV EDI,WorldTV.004C8950.
코드 라인을 통해 step 하기보단 MOV EDI,WorldTV.004C8950에 브포를 걸고 시리얼을 볼 것이다.
 라인을 선택하고 브포 설정을 위해 F2를 누른다.
이제 Run 버튼을 누르면 잠시 후 완성된 시리얼을 볼 수 있다.


이젠에 EDI가 시리얼이 8문자 4개 세트이거나 8문자 3개 세트였다는걸 유의하라.
3개의 전체 세트를 유심히 보면 빠진게 있다.
오직 2문자만 남았을 때 실행 버튼을 누는걸 멈추고 코드를 step 하라.
REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
라인을 지날 때 마지막 2문자도 볼수있다.


이 숫자들을 적어라. 또 다른 추가된 문자 세트가 있는지 확인하기 위해 Run을 다시 누른다.
없군.. Run을 다시 누르는 것은 분리된 시리얼이 계산되는 또 다른 루프를 돌게 할것이다.
(무엇을 위해? 프로그램에 등록하지 않을 것이기 때문에 확실친 않다)

이제 올리를 닫는다. 원본 WorldTV.exe를 열고 적어논 시리얼로 등록해본다.
Registration Code Accpeted, 진짜 시리얼로 성공적으로 WorldTV를 등록했다.
 패치 없이 말이다.

 


Conclusion:

나는 이 프로그램을 순수하게 올리를 이용해 시리얼을 찾는걸 설명하기 위해 사용했다. 만약 이 프로그램을 사용하려거든 구입하기바란다.

이 문서를 작성하는데 도움을준 사람들 모두에게 감사한다. 배울 수 있는 좋은 장소를 제공해준 Exetools, Woodmann, Arteam 에게 감사한다.

질문이나 할말이나 수정할것이 있으면 email 달라. Gabri3l2003[at]yahoo.com


출처 : http://forc1.net/

+ Recent posts