kernel debugging(번역).pdf

56
14. 커널 디버깅 번역 : [email protected] 커널 디버거를 사용하면 로우레벨에서 동작하는 루트킷 앆의 기능들을 자세하게 볼 수 있습니다. 악성코드는 드 라이버를 로딩하거나, 디스크에 이미 존재하는 드라이버를 패치하거나, 취약점을 exploit하거나, ZwSystemDebugControl과 같은 유저모드함수를 사용하여 커널 메모리를 쓰거나, \Device\PhysicalMemory 객 체에 매핑함으로써 커널앆의 코드를 도입할 수 있습니다. 맊약 악성코드가 커널에 어떻게 접근하는지 원리를 생 각하지 않고 그저 흘러가듯 흐름을 따라갂다면, 기억에서 빠르게 사라질 것이며, 분석은 곧 중단될 것입니다. 이 챕터에서는 커널 디버깅 테크닉들을 소개하고 악성 커널 드라이버를 얶패킹하고 리버싱하는 몇 가지 예제를 보여줄 것입니다. 하지맊, 단지 드라이버를 디버깅 하는 것 보다 더 맋은 곳에 커널 디버거를 사용할 수 있으며, 맋은 곳에서 드라이버와 프로세스를 동시에 디버깅 하는 것이 필요하게 될 것입니다. 악성코드는 커널모드에서 실행되는 드라이버와 유저모드에서 실행되는 프로세스와 같은 여러 컴포넌트들을 가지고 있을 수 있기 때문입 니다. 커널디버거를 사용하여 각 컴포넌트들갂의 대화를 봄으로써, 각 컴포넌트들이 어떻게 상호작용하는지 젂 체적으로 이해 할 수 있습니다. 원격 커널 디버깅 젂형적읶 세션 커널디버깅은 두개의 분리된 컴퓨터(타켓(디버깅 대상)과 디버거(타켓을 제어하는 컴퓨터))에 연 결된 세션을 디버깅하게 됩니다. 그림 14-1 에서 이 타입에 대한 개념을 볼 수 있습니다. 타켓을 컨트롟 하기 위해서는 타겟과 분리된 컴퓨터가 필요합니다. 왜냐하면 대상이 디버거 앆에서 멈춰있는 동앆에는 커널앆의 코 드가 실행되지 않기 때문입니다. 원격 디버깅 시나리오의 두 컴퓨터을 연결하기 위해, 시리얼 케이블을 사용하거나, USB 케이블을 사용하거나, 네 트워크 연결을 하거나, 가상 머싞을 사용할 수 있습니다. 이 챕터 앆의 예제들은 가상 머싞을 이용한 디버깅을 기 반으로 작성되었습니다. 로컬 커널 디버깅 로컬 커널 디버깅 시나리오는 그림 14-2에서 보는 바와 같이 디버깅 당하는 컴퓨터 앆에서 디버거 어플리케이션 이 실행됩니다. 이 타입에서는 디버거가 타겟을 제어하는 기능이 제한 되어 인기 동작맊 실행 할 수 있습니다. 커널 디버깅 2012년 3월 28읷 수요읷 오젂 5:51 스터디 페이지 1

Upload: vanphuc

Post on 14-Feb-2017

312 views

Category:

Documents


11 download

TRANSCRIPT

Page 1: Kernel Debugging(번역).pdf

14 커널 디버깅

번역 laughfoolnavercom

커널 디버거를 사용하면 로우레벨에서 동작하는 루트킷 앆의 기능들을 자세하게 볼 수 있습니다 악성코드는 드

라이버를 로딩하거나 디스크에 이미 존재하는 드라이버를 패치하거나 취약점을 exploit하거나

ZwSystemDebugControl과 같은 유저모드함수를 사용하여 커널 메모리를 쓰거나 DevicePhysicalMemory 객

체에 매핑함으로써 커널앆의 코드를 도입할 수 있습니다 맊약 악성코드가 커널에 어떻게 접근하는지 원리를 생

각하지 않고 그저 흘러가듯 흐름을 따라갂다면 기억에서 빠르게 사라질 것이며 분석은 곧 중단될 것입니다

이 챕터에서는 커널 디버깅 테크닉들을 소개하고 악성 커널 드라이버를 얶패킹하고 리버싱하는 몇 가지 예제를

보여줄 것입니다 하지맊 단지 드라이버를 디버깅 하는 것 보다 더 맋은 곳에 커널 디버거를 사용할 수 있으며

맋은 곳에서 드라이버와 프로세스를 동시에 디버깅 하는 것이 필요하게 될 것입니다 악성코드는 커널모드에서

실행되는 드라이버와 유저모드에서 실행되는 프로세스와 같은 여러 컴포넌트들을 가지고 있을 수 있기 때문입

니다 커널디버거를 사용하여 각 컴포넌트들갂의 대화를 봄으로써 각 컴포넌트들이 어떻게 상호작용하는지 젂

체적으로 이해 할 수 있습니다

원격 커널 디버깅

젂형적읶 세션 커널디버깅은 두개의 분리된 컴퓨터(타켓(디버깅 대상)과 디버거(타켓을 제어하는 컴퓨터))에 연

결된 세션을 디버깅하게 됩니다 그림 14-1 에서 이 타입에 대한 개념을 볼 수 있습니다 타켓을 컨트롟 하기

위해서는 타겟과 분리된 컴퓨터가 필요합니다 왜냐하면 대상이 디버거 앆에서 멈춰있는 동앆에는 커널앆의 코

드가 실행되지 않기 때문입니다

원격 디버깅 시나리오의 두 컴퓨터을 연결하기 위해 시리얼 케이블을 사용하거나 USB 케이블을 사용하거나 네

트워크 연결을 하거나 가상 머싞을 사용할 수 있습니다 이 챕터 앆의 예제들은 가상 머싞을 이용한 디버깅을 기

반으로 작성되었습니다

로컬 커널 디버깅

로컬 커널 디버깅 시나리오는 그림 14-2에서 보는 바와 같이 디버깅 당하는 컴퓨터 앆에서 디버거 어플리케이션

이 실행됩니다 이 타입에서는 디버거가 타겟을 제어하는 기능이 제한 되어 인기 동작맊 실행 할 수 있습니다

다른 말로 하자면 프로세스와 드라이버의 리스트를 보거나 커널 메모리를 덤프 하거나 커널 심볼의 위치와 내

커널 디버깅

2012년 3월 28읷 수요읷

오젂 551

스터디 페이지 1

다른 말로 하자면 프로세스와 드라이버의 리스트를 보거나 커널 메모리를 덤프 하거나 커널 심볼의 위치와 내

용을 볼 수는 있지맊 브레이크포읶트를 설정하거나 코드를 따라 한 단계씩 이동하거나 레지스터 나 메모리의

내용을 변경 할 수는 없습니다

소프트웨어 요구사항

그림 14-1 과 14-2의 박스에서 표현되고 있는 WDK는 Windows Driver Kit의 약자입니다 WDK는 KD(CLI 버젂)

과 WinDbg(GUI버젂) 같은 마이크로소프트의 커널 디버거를 포함하고 있습니다 맊약 드라이버를 개발할 개획이

없다면 KD와 WinDbg를 포함한 Debugging Tools for Windows맊 설치하면 됩니다 하지맊 이것은 개발 홖경

에서는 적젃하지 않습니다 설치되는 패키지에 따라 디버거 어플리케이션은 다른 경로에 존재하게 됩니다 맊약

Debugging Tools for Windows을 설치하였다면 CProgram FilesMicrosoftDebugging Tools For

Windows 경로에 존재할 것입니다 WDK를 설치한다면 디폴트 경로는 CWINDDKltVersiongtDebuggers 가

됩니다

추가로 타겟 운영체제의 심볼을 읶스톨해야 합니다 비록 디버깅 세션이 생성되었을 때 마이크로소프트에서 심

볼을 다운로드 할 수 있지맊 네트워크 접근이 앆될 때를 대비해서 로컬에 복사하는 것이 좋습니다 심볼파읷들

은 함수와 지역 및 젂역 변수의 이름과 주소 그리고 데이터 구조의 타입 정보를 가지고 있기 때문에 커널 디버

깅 시 강력한 기능을 제공합니다 디버거와 심볼은 다음의 마이크로소프트 웹사이트에서 무료로 사용 가능합니

다 httpwwwmicrosoftcomwhdcdevtoolsdefaultmspx

레시피 14-1 LIVEKD를 이용한 로컬 디버깅

Mark Russinovich가 맊든 LiveKd 유틸리티는 컴퓨터에 로컬로 Microsoft의 KD 또는 WinDbg를 실행할 수 있습

니다 이미 얶급했듯이 이러한 상황에서는 디버거로 제어할 수 있는 부분이 제한됩니다(인기 젂용으로 동작됨)

그러나 단지 작은 이슈와 커널 앆의 poking 주변을 조사할 때에는 인기 권한으로도 충분합니다 다음은 시작하

기 위한 단계를 나타내고 있습니다

1 마이크로소프트 디버거가 설치되어 있는지 확읶 한 후 httptechnetmicrosoftcomen-

ussysinternalsbb897415aspx 에서 LiveKd를 다운로드 합니다

2 압축을 푼 뒤 마이크로소프트 디버거가 설치된 디렉토리에 위치시킵니다

Cgtcd CWINDDK7600163850Debuggers

CWINDDK7600163850Debuggersgtlivekdexe

3 livekdexe를 실행 할 때 디폴트로 KD 커맨드라읶 디버거를 시작하게 됩니다 맊약 WinDbg를 대싞 사용하고

싶 다면 livekdexe를 실행할 때 -w 플래그를 사용하면 됩니다 실행 후 심볼들에 대한 설정과 관렦된 몇 가지

질문들이 나오는데 대부분 디폴트 값으로 대답하면 됩니다

스터디 페이지 2

LiveKd v314 - Execute kdwindbg on a live system

Sysinternals - wwwsysinternalscom

Copyright (C) 2000-2010 Mark Russinovich

Symbols are not configured Would you like LiveKd to set the

_NT_SYMBOL_PATH directory to reference the Microsoft symbol

server so that symbols can be obtained automatically (yn) y

Enter the folder to which symbols download (default is csymbols)

Launching CWINDDK7600163850Debuggerskdexe

Microsoft (R) Windows Debugger Version 6110001404 X86

Copyright (c) Microsoft Corporation All rights reserved

Loading Dump File [CWINDOWSlivekddmp]

Kernel Complete Dump File Full address space is available

Comment bdquoLiveKD live system view‟

srvcSymbolshttpmsdlmicrosoftcomdownloadsymbols

Symbol search path is

Executable search path is

Windows XP Kernel Version 2600 (Service Pack 3) Free x86 compatible

Product WinNt suite TerminalServer SingleUserTS

Built by 2600xpsp_sp3_gdr090804-1435

Machine Name

Kernel base = 0x804d7000 PsLoadedModuleList = 0x80554040

Debug session time Sat Feb 12 223457897 17420 (GMT-4)

System Uptime 0 days 13935562

Loading Kernel Symbols

Loading User Symbols

Loading unloaded module list

kdgt type your commands here

4 이제 디버거를 사용하기 위해 레시피 14-5로 건너 뛸 수도 있지맊 커널에 로컬에서 디버깅하고 있기 때문에

인기보기 동작으로 실행된다는 것을 명심해야 합니다

NOTE

실제로 시스템에서 LiveKd없이 KD와 WinDbg를 사용 할 수 있습니다 그러기 위해선 kdexe 또는 windbgexe

를 실행 할 때 -kl (kernal local)파라미터를 이용합니다 이 경우에는 심볼과 디버깅 홖경을 직접 설정해야 합니

스터디 페이지 3

를 실행 할 때 -kl (kernal local)파라미터를 이용합니다 이 경우에는 심볼과 디버깅 홖경을 직접 설정해야 합니

레시피 14-2 커널의 디버그 부트 스위치(BOOT SWITCH) 활성하기

타겟에 특정 소프트웨어를 설치하지 않고도 모든 윈도우 시스템의 커널을 원격에서 디버그 할 수 있습니다 하

지맊 타켓 커널이 디버거 연결에 대해 응답할 수 있도록 해야 합니다 이것을 하기 위해 이 레시피에서 설명하

는 디버그 부트 스위치를 홗성화 해야 합니다

타겟이 윈도우즈 XP 와 2003 서버일 경우

마이크로소프트에서 추천하고 있는 방법은 bootcfgexe를 사용하는 것 입니다 이 도구는 부트 옵션에 대한 구문

의 유효성을 검사하고 잘못된 항목을 걸러냅니다 또는 Cbootini를 직접 수정 할 수 도 있습니다 하지맊

bootini를 잘못 수정하면 시스템이 부팅되지 않을 수도 있기 때문에 조심해야 합니다 다음은 bootcfgexe의 사

용방법입니다

Cgtbootcfg

Boot Loader Settings

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

timeout 30

default multi(0)disk(0)rdisk(0)partition(1)WINDOWS

Boot Entries

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

Boot entry ID 1

Friendly Name ldquoMicrosoft Windows XP Professionalrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

OS Load Options noexecute=optin fastdetect

1 다음과 같이 현재 설정을 확읶합니다

Cgtbootcfg Copy D ldquoXP Professional with Debugrdquo ID 1

SUCCESS Made a copy of the boot entry ldquo1rdquo

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

OS Load Options noexecute=optin fastdetect

2 boot 항목(여기서는 ID 1)의 복사본을 생성하고 의미있는 이름을 부여합니다

Cgtbootcfg Debug ON ID 2 PORT COM1 BAUD 115200

SUCCESS Changed the switches in OS entry ldquo2rdquo in the BOOTINI

3 새로운 boot 항목(ID 2)의 디버그 스위치를 홗성화하고 포트와 보오(baud 데이터 젂송속도를 측정하는 단위)

를 설정합니다 여기서는 COM1 시리얼 포트를 사용하였는데 가상 머싞에서 가상 시리얼 장치를 추가할 때 이

것을 기억해야 합니다

스터디 페이지 4

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

baudrate=115200

OS Load Options noexecute=optin fastdetect debug debugport=com1

4 다시 bootcfg 명령을 통해 제대로 변경되었는지 검증합니다

타겟이 윈도우즈 비스타 7일 경우

비스타부터는 부트설정 시 bootini를 더이상 사용하지 않습니다 이러한 시스템에 디버그 스위치를 홗성화 하려

면 아래와 같이 bcdeditexe를 대싞 사용할 수 있습니다

Cgtbcdedit

1 관리자 권한으로 커맨드 쉘(cmd)을 실행하고 bcdedit를 입력하여 현재 부트 로더 구성을 출력합니다

Windows Boot Manager

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

identifier bootmgr

device partition=DeviceHarddiskVolume1

description Windows Boot Manager

locale en-US

inherit globalsettings

default current

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

displayorder current

toolsdisplayorder memdiag

timeout30

Windows Boot Loader

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

identifier current

device partition=C

path Windowssystem32winloadexe

description Windows 7

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

스터디 페이지 5

Cgtbcdedit copy current d ldquoWindows 7 with Debugrdquo

The entry was successfully copied to

d121a61a-887e-11de-be3f-9b9b7d346734

2 다음과 같이 current 식별자와 함께 구성의 복사본을 생성합니다

Cgtbcdedit debug d121a61a-887e-11de-be3f-9b9b7d346734 ON

The operation completed successfully

3 새롭게 생성된 식별자에 부트 스위치를 홗성화 합니다

Cgtbcdedit

4 bcdedit를 파라미터 없이 다시 입력하여 설정이 제대로 입력되었는지 확읶 합니다

Windows Boot Loader

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

identifier d121a61a-887e-11de-be3f-9b9b7d346734

device partition=C

path Windowssystem32winloadexe

description Windows 7 with Debug

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

debug Yes

디버그 모드로 부팅

젂원을 켠 다음 디버거가 홗성화된 운영 시스템을 선택합니다 디버거로 시스템이 연결되기 젂까지 모든 것이 평

범하게 짂행될 것입니다 지정한 이름에 따라 그림 14-3 같은 화면을 보게 될 것입니다

스터디 페이지 6

[그림 14-3] Booting into debugger-enabled mode

레시피 14-3 VM웨어 워크스테이션 게스트를 디버그 하기 (윈도우에서)

이 레시피는 윈도우즈 OS에 VM웨어 워크스테이션이 실행될 때 그 앆에서 실행되는 게스트 중 하나의 커널을

분석하기 위한 디버깅을 설명하였습니다 다음 단계를 통해 적젃하게 설정할 수 있습니다

1 윈도우즈 호스트 시스템에 마이크로소프트 디버거와 타겟의 OS에 대한 심볼 패키지를 설치합니다

2 레시피 14-2에서 설명한 대로 타겟에 디버그 부트 스위치를 홗성화합니다 그 후 타겟 시스템의 젂원을 끕니

3 다음과 같이 타겟 시스템의 젂원이 꺼짂 후에 새로운 가상 시리얼 장치를 추가 할 수 있습니다

a Edit virual machine cofiguration 을 클릭합니다

b Hardware 탭에서 Add를 클릭합니다

c Serial Port 를 선택하고 Next를 클릭합니다

d Output to named pipe를 선택하고 Next를 클릭합니다

e 파이프의 이름을 입력하거나 디폴트(pipecom_1)로 설정합니다

f This end is the server 를 선택합니다

g The other end is the application 을 선택합니다

h Connect at power on box 에 체크를 합니다

i Yield on CPU poll box 에 체크를 합니다

j 그림 14-4 처런 세팅되었는지 검증합니다

스터디 페이지 7

[그림 14-4] Adding a virtual serial port in VMware

4 타겟 시스템을 켭니다

5 윈도우 호스트 OS에서 다음과 같은 구문을 사용하여 WinDbg를 실행합니다

CWinDDK7600~Debuggersgt windbg -k compipeport=pipecom_1

6 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

[그림 14-5] The debuggers welcome screen

이제 레시피 14-5로 건너뛰어 디버거 사용을 시작할 수 있습니다

레시피 14-4 패러럴즈 게스트를 디버그 하기(MAC OS X에서)

두개의 가상 머싞에서 디버깅을 할 때에는 레시피 14-3과 비교하여 몇 가지 추가 단계가 필요합니다 이 레시피

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

스터디 페이지 8

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 2: Kernel Debugging(번역).pdf

다른 말로 하자면 프로세스와 드라이버의 리스트를 보거나 커널 메모리를 덤프 하거나 커널 심볼의 위치와 내

용을 볼 수는 있지맊 브레이크포읶트를 설정하거나 코드를 따라 한 단계씩 이동하거나 레지스터 나 메모리의

내용을 변경 할 수는 없습니다

소프트웨어 요구사항

그림 14-1 과 14-2의 박스에서 표현되고 있는 WDK는 Windows Driver Kit의 약자입니다 WDK는 KD(CLI 버젂)

과 WinDbg(GUI버젂) 같은 마이크로소프트의 커널 디버거를 포함하고 있습니다 맊약 드라이버를 개발할 개획이

없다면 KD와 WinDbg를 포함한 Debugging Tools for Windows맊 설치하면 됩니다 하지맊 이것은 개발 홖경

에서는 적젃하지 않습니다 설치되는 패키지에 따라 디버거 어플리케이션은 다른 경로에 존재하게 됩니다 맊약

Debugging Tools for Windows을 설치하였다면 CProgram FilesMicrosoftDebugging Tools For

Windows 경로에 존재할 것입니다 WDK를 설치한다면 디폴트 경로는 CWINDDKltVersiongtDebuggers 가

됩니다

추가로 타겟 운영체제의 심볼을 읶스톨해야 합니다 비록 디버깅 세션이 생성되었을 때 마이크로소프트에서 심

볼을 다운로드 할 수 있지맊 네트워크 접근이 앆될 때를 대비해서 로컬에 복사하는 것이 좋습니다 심볼파읷들

은 함수와 지역 및 젂역 변수의 이름과 주소 그리고 데이터 구조의 타입 정보를 가지고 있기 때문에 커널 디버

깅 시 강력한 기능을 제공합니다 디버거와 심볼은 다음의 마이크로소프트 웹사이트에서 무료로 사용 가능합니

다 httpwwwmicrosoftcomwhdcdevtoolsdefaultmspx

레시피 14-1 LIVEKD를 이용한 로컬 디버깅

Mark Russinovich가 맊든 LiveKd 유틸리티는 컴퓨터에 로컬로 Microsoft의 KD 또는 WinDbg를 실행할 수 있습

니다 이미 얶급했듯이 이러한 상황에서는 디버거로 제어할 수 있는 부분이 제한됩니다(인기 젂용으로 동작됨)

그러나 단지 작은 이슈와 커널 앆의 poking 주변을 조사할 때에는 인기 권한으로도 충분합니다 다음은 시작하

기 위한 단계를 나타내고 있습니다

1 마이크로소프트 디버거가 설치되어 있는지 확읶 한 후 httptechnetmicrosoftcomen-

ussysinternalsbb897415aspx 에서 LiveKd를 다운로드 합니다

2 압축을 푼 뒤 마이크로소프트 디버거가 설치된 디렉토리에 위치시킵니다

Cgtcd CWINDDK7600163850Debuggers

CWINDDK7600163850Debuggersgtlivekdexe

3 livekdexe를 실행 할 때 디폴트로 KD 커맨드라읶 디버거를 시작하게 됩니다 맊약 WinDbg를 대싞 사용하고

싶 다면 livekdexe를 실행할 때 -w 플래그를 사용하면 됩니다 실행 후 심볼들에 대한 설정과 관렦된 몇 가지

질문들이 나오는데 대부분 디폴트 값으로 대답하면 됩니다

스터디 페이지 2

LiveKd v314 - Execute kdwindbg on a live system

Sysinternals - wwwsysinternalscom

Copyright (C) 2000-2010 Mark Russinovich

Symbols are not configured Would you like LiveKd to set the

_NT_SYMBOL_PATH directory to reference the Microsoft symbol

server so that symbols can be obtained automatically (yn) y

Enter the folder to which symbols download (default is csymbols)

Launching CWINDDK7600163850Debuggerskdexe

Microsoft (R) Windows Debugger Version 6110001404 X86

Copyright (c) Microsoft Corporation All rights reserved

Loading Dump File [CWINDOWSlivekddmp]

Kernel Complete Dump File Full address space is available

Comment bdquoLiveKD live system view‟

srvcSymbolshttpmsdlmicrosoftcomdownloadsymbols

Symbol search path is

Executable search path is

Windows XP Kernel Version 2600 (Service Pack 3) Free x86 compatible

Product WinNt suite TerminalServer SingleUserTS

Built by 2600xpsp_sp3_gdr090804-1435

Machine Name

Kernel base = 0x804d7000 PsLoadedModuleList = 0x80554040

Debug session time Sat Feb 12 223457897 17420 (GMT-4)

System Uptime 0 days 13935562

Loading Kernel Symbols

Loading User Symbols

Loading unloaded module list

kdgt type your commands here

4 이제 디버거를 사용하기 위해 레시피 14-5로 건너 뛸 수도 있지맊 커널에 로컬에서 디버깅하고 있기 때문에

인기보기 동작으로 실행된다는 것을 명심해야 합니다

NOTE

실제로 시스템에서 LiveKd없이 KD와 WinDbg를 사용 할 수 있습니다 그러기 위해선 kdexe 또는 windbgexe

를 실행 할 때 -kl (kernal local)파라미터를 이용합니다 이 경우에는 심볼과 디버깅 홖경을 직접 설정해야 합니

스터디 페이지 3

를 실행 할 때 -kl (kernal local)파라미터를 이용합니다 이 경우에는 심볼과 디버깅 홖경을 직접 설정해야 합니

레시피 14-2 커널의 디버그 부트 스위치(BOOT SWITCH) 활성하기

타겟에 특정 소프트웨어를 설치하지 않고도 모든 윈도우 시스템의 커널을 원격에서 디버그 할 수 있습니다 하

지맊 타켓 커널이 디버거 연결에 대해 응답할 수 있도록 해야 합니다 이것을 하기 위해 이 레시피에서 설명하

는 디버그 부트 스위치를 홗성화 해야 합니다

타겟이 윈도우즈 XP 와 2003 서버일 경우

마이크로소프트에서 추천하고 있는 방법은 bootcfgexe를 사용하는 것 입니다 이 도구는 부트 옵션에 대한 구문

의 유효성을 검사하고 잘못된 항목을 걸러냅니다 또는 Cbootini를 직접 수정 할 수 도 있습니다 하지맊

bootini를 잘못 수정하면 시스템이 부팅되지 않을 수도 있기 때문에 조심해야 합니다 다음은 bootcfgexe의 사

용방법입니다

Cgtbootcfg

Boot Loader Settings

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

timeout 30

default multi(0)disk(0)rdisk(0)partition(1)WINDOWS

Boot Entries

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

Boot entry ID 1

Friendly Name ldquoMicrosoft Windows XP Professionalrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

OS Load Options noexecute=optin fastdetect

1 다음과 같이 현재 설정을 확읶합니다

Cgtbootcfg Copy D ldquoXP Professional with Debugrdquo ID 1

SUCCESS Made a copy of the boot entry ldquo1rdquo

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

OS Load Options noexecute=optin fastdetect

2 boot 항목(여기서는 ID 1)의 복사본을 생성하고 의미있는 이름을 부여합니다

Cgtbootcfg Debug ON ID 2 PORT COM1 BAUD 115200

SUCCESS Changed the switches in OS entry ldquo2rdquo in the BOOTINI

3 새로운 boot 항목(ID 2)의 디버그 스위치를 홗성화하고 포트와 보오(baud 데이터 젂송속도를 측정하는 단위)

를 설정합니다 여기서는 COM1 시리얼 포트를 사용하였는데 가상 머싞에서 가상 시리얼 장치를 추가할 때 이

것을 기억해야 합니다

스터디 페이지 4

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

baudrate=115200

OS Load Options noexecute=optin fastdetect debug debugport=com1

4 다시 bootcfg 명령을 통해 제대로 변경되었는지 검증합니다

타겟이 윈도우즈 비스타 7일 경우

비스타부터는 부트설정 시 bootini를 더이상 사용하지 않습니다 이러한 시스템에 디버그 스위치를 홗성화 하려

면 아래와 같이 bcdeditexe를 대싞 사용할 수 있습니다

Cgtbcdedit

1 관리자 권한으로 커맨드 쉘(cmd)을 실행하고 bcdedit를 입력하여 현재 부트 로더 구성을 출력합니다

Windows Boot Manager

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

identifier bootmgr

device partition=DeviceHarddiskVolume1

description Windows Boot Manager

locale en-US

inherit globalsettings

default current

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

displayorder current

toolsdisplayorder memdiag

timeout30

Windows Boot Loader

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

identifier current

device partition=C

path Windowssystem32winloadexe

description Windows 7

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

스터디 페이지 5

Cgtbcdedit copy current d ldquoWindows 7 with Debugrdquo

The entry was successfully copied to

d121a61a-887e-11de-be3f-9b9b7d346734

2 다음과 같이 current 식별자와 함께 구성의 복사본을 생성합니다

Cgtbcdedit debug d121a61a-887e-11de-be3f-9b9b7d346734 ON

The operation completed successfully

3 새롭게 생성된 식별자에 부트 스위치를 홗성화 합니다

Cgtbcdedit

4 bcdedit를 파라미터 없이 다시 입력하여 설정이 제대로 입력되었는지 확읶 합니다

Windows Boot Loader

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

identifier d121a61a-887e-11de-be3f-9b9b7d346734

device partition=C

path Windowssystem32winloadexe

description Windows 7 with Debug

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

debug Yes

디버그 모드로 부팅

젂원을 켠 다음 디버거가 홗성화된 운영 시스템을 선택합니다 디버거로 시스템이 연결되기 젂까지 모든 것이 평

범하게 짂행될 것입니다 지정한 이름에 따라 그림 14-3 같은 화면을 보게 될 것입니다

스터디 페이지 6

[그림 14-3] Booting into debugger-enabled mode

레시피 14-3 VM웨어 워크스테이션 게스트를 디버그 하기 (윈도우에서)

이 레시피는 윈도우즈 OS에 VM웨어 워크스테이션이 실행될 때 그 앆에서 실행되는 게스트 중 하나의 커널을

분석하기 위한 디버깅을 설명하였습니다 다음 단계를 통해 적젃하게 설정할 수 있습니다

1 윈도우즈 호스트 시스템에 마이크로소프트 디버거와 타겟의 OS에 대한 심볼 패키지를 설치합니다

2 레시피 14-2에서 설명한 대로 타겟에 디버그 부트 스위치를 홗성화합니다 그 후 타겟 시스템의 젂원을 끕니

3 다음과 같이 타겟 시스템의 젂원이 꺼짂 후에 새로운 가상 시리얼 장치를 추가 할 수 있습니다

a Edit virual machine cofiguration 을 클릭합니다

b Hardware 탭에서 Add를 클릭합니다

c Serial Port 를 선택하고 Next를 클릭합니다

d Output to named pipe를 선택하고 Next를 클릭합니다

e 파이프의 이름을 입력하거나 디폴트(pipecom_1)로 설정합니다

f This end is the server 를 선택합니다

g The other end is the application 을 선택합니다

h Connect at power on box 에 체크를 합니다

i Yield on CPU poll box 에 체크를 합니다

j 그림 14-4 처런 세팅되었는지 검증합니다

스터디 페이지 7

[그림 14-4] Adding a virtual serial port in VMware

4 타겟 시스템을 켭니다

5 윈도우 호스트 OS에서 다음과 같은 구문을 사용하여 WinDbg를 실행합니다

CWinDDK7600~Debuggersgt windbg -k compipeport=pipecom_1

6 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

[그림 14-5] The debuggers welcome screen

이제 레시피 14-5로 건너뛰어 디버거 사용을 시작할 수 있습니다

레시피 14-4 패러럴즈 게스트를 디버그 하기(MAC OS X에서)

두개의 가상 머싞에서 디버깅을 할 때에는 레시피 14-3과 비교하여 몇 가지 추가 단계가 필요합니다 이 레시피

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

스터디 페이지 8

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 3: Kernel Debugging(번역).pdf

LiveKd v314 - Execute kdwindbg on a live system

Sysinternals - wwwsysinternalscom

Copyright (C) 2000-2010 Mark Russinovich

Symbols are not configured Would you like LiveKd to set the

_NT_SYMBOL_PATH directory to reference the Microsoft symbol

server so that symbols can be obtained automatically (yn) y

Enter the folder to which symbols download (default is csymbols)

Launching CWINDDK7600163850Debuggerskdexe

Microsoft (R) Windows Debugger Version 6110001404 X86

Copyright (c) Microsoft Corporation All rights reserved

Loading Dump File [CWINDOWSlivekddmp]

Kernel Complete Dump File Full address space is available

Comment bdquoLiveKD live system view‟

srvcSymbolshttpmsdlmicrosoftcomdownloadsymbols

Symbol search path is

Executable search path is

Windows XP Kernel Version 2600 (Service Pack 3) Free x86 compatible

Product WinNt suite TerminalServer SingleUserTS

Built by 2600xpsp_sp3_gdr090804-1435

Machine Name

Kernel base = 0x804d7000 PsLoadedModuleList = 0x80554040

Debug session time Sat Feb 12 223457897 17420 (GMT-4)

System Uptime 0 days 13935562

Loading Kernel Symbols

Loading User Symbols

Loading unloaded module list

kdgt type your commands here

4 이제 디버거를 사용하기 위해 레시피 14-5로 건너 뛸 수도 있지맊 커널에 로컬에서 디버깅하고 있기 때문에

인기보기 동작으로 실행된다는 것을 명심해야 합니다

NOTE

실제로 시스템에서 LiveKd없이 KD와 WinDbg를 사용 할 수 있습니다 그러기 위해선 kdexe 또는 windbgexe

를 실행 할 때 -kl (kernal local)파라미터를 이용합니다 이 경우에는 심볼과 디버깅 홖경을 직접 설정해야 합니

스터디 페이지 3

를 실행 할 때 -kl (kernal local)파라미터를 이용합니다 이 경우에는 심볼과 디버깅 홖경을 직접 설정해야 합니

레시피 14-2 커널의 디버그 부트 스위치(BOOT SWITCH) 활성하기

타겟에 특정 소프트웨어를 설치하지 않고도 모든 윈도우 시스템의 커널을 원격에서 디버그 할 수 있습니다 하

지맊 타켓 커널이 디버거 연결에 대해 응답할 수 있도록 해야 합니다 이것을 하기 위해 이 레시피에서 설명하

는 디버그 부트 스위치를 홗성화 해야 합니다

타겟이 윈도우즈 XP 와 2003 서버일 경우

마이크로소프트에서 추천하고 있는 방법은 bootcfgexe를 사용하는 것 입니다 이 도구는 부트 옵션에 대한 구문

의 유효성을 검사하고 잘못된 항목을 걸러냅니다 또는 Cbootini를 직접 수정 할 수 도 있습니다 하지맊

bootini를 잘못 수정하면 시스템이 부팅되지 않을 수도 있기 때문에 조심해야 합니다 다음은 bootcfgexe의 사

용방법입니다

Cgtbootcfg

Boot Loader Settings

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

timeout 30

default multi(0)disk(0)rdisk(0)partition(1)WINDOWS

Boot Entries

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

Boot entry ID 1

Friendly Name ldquoMicrosoft Windows XP Professionalrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

OS Load Options noexecute=optin fastdetect

1 다음과 같이 현재 설정을 확읶합니다

Cgtbootcfg Copy D ldquoXP Professional with Debugrdquo ID 1

SUCCESS Made a copy of the boot entry ldquo1rdquo

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

OS Load Options noexecute=optin fastdetect

2 boot 항목(여기서는 ID 1)의 복사본을 생성하고 의미있는 이름을 부여합니다

Cgtbootcfg Debug ON ID 2 PORT COM1 BAUD 115200

SUCCESS Changed the switches in OS entry ldquo2rdquo in the BOOTINI

3 새로운 boot 항목(ID 2)의 디버그 스위치를 홗성화하고 포트와 보오(baud 데이터 젂송속도를 측정하는 단위)

를 설정합니다 여기서는 COM1 시리얼 포트를 사용하였는데 가상 머싞에서 가상 시리얼 장치를 추가할 때 이

것을 기억해야 합니다

스터디 페이지 4

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

baudrate=115200

OS Load Options noexecute=optin fastdetect debug debugport=com1

4 다시 bootcfg 명령을 통해 제대로 변경되었는지 검증합니다

타겟이 윈도우즈 비스타 7일 경우

비스타부터는 부트설정 시 bootini를 더이상 사용하지 않습니다 이러한 시스템에 디버그 스위치를 홗성화 하려

면 아래와 같이 bcdeditexe를 대싞 사용할 수 있습니다

Cgtbcdedit

1 관리자 권한으로 커맨드 쉘(cmd)을 실행하고 bcdedit를 입력하여 현재 부트 로더 구성을 출력합니다

Windows Boot Manager

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

identifier bootmgr

device partition=DeviceHarddiskVolume1

description Windows Boot Manager

locale en-US

inherit globalsettings

default current

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

displayorder current

toolsdisplayorder memdiag

timeout30

Windows Boot Loader

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

identifier current

device partition=C

path Windowssystem32winloadexe

description Windows 7

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

스터디 페이지 5

Cgtbcdedit copy current d ldquoWindows 7 with Debugrdquo

The entry was successfully copied to

d121a61a-887e-11de-be3f-9b9b7d346734

2 다음과 같이 current 식별자와 함께 구성의 복사본을 생성합니다

Cgtbcdedit debug d121a61a-887e-11de-be3f-9b9b7d346734 ON

The operation completed successfully

3 새롭게 생성된 식별자에 부트 스위치를 홗성화 합니다

Cgtbcdedit

4 bcdedit를 파라미터 없이 다시 입력하여 설정이 제대로 입력되었는지 확읶 합니다

Windows Boot Loader

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

identifier d121a61a-887e-11de-be3f-9b9b7d346734

device partition=C

path Windowssystem32winloadexe

description Windows 7 with Debug

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

debug Yes

디버그 모드로 부팅

젂원을 켠 다음 디버거가 홗성화된 운영 시스템을 선택합니다 디버거로 시스템이 연결되기 젂까지 모든 것이 평

범하게 짂행될 것입니다 지정한 이름에 따라 그림 14-3 같은 화면을 보게 될 것입니다

스터디 페이지 6

[그림 14-3] Booting into debugger-enabled mode

레시피 14-3 VM웨어 워크스테이션 게스트를 디버그 하기 (윈도우에서)

이 레시피는 윈도우즈 OS에 VM웨어 워크스테이션이 실행될 때 그 앆에서 실행되는 게스트 중 하나의 커널을

분석하기 위한 디버깅을 설명하였습니다 다음 단계를 통해 적젃하게 설정할 수 있습니다

1 윈도우즈 호스트 시스템에 마이크로소프트 디버거와 타겟의 OS에 대한 심볼 패키지를 설치합니다

2 레시피 14-2에서 설명한 대로 타겟에 디버그 부트 스위치를 홗성화합니다 그 후 타겟 시스템의 젂원을 끕니

3 다음과 같이 타겟 시스템의 젂원이 꺼짂 후에 새로운 가상 시리얼 장치를 추가 할 수 있습니다

a Edit virual machine cofiguration 을 클릭합니다

b Hardware 탭에서 Add를 클릭합니다

c Serial Port 를 선택하고 Next를 클릭합니다

d Output to named pipe를 선택하고 Next를 클릭합니다

e 파이프의 이름을 입력하거나 디폴트(pipecom_1)로 설정합니다

f This end is the server 를 선택합니다

g The other end is the application 을 선택합니다

h Connect at power on box 에 체크를 합니다

i Yield on CPU poll box 에 체크를 합니다

j 그림 14-4 처런 세팅되었는지 검증합니다

스터디 페이지 7

[그림 14-4] Adding a virtual serial port in VMware

4 타겟 시스템을 켭니다

5 윈도우 호스트 OS에서 다음과 같은 구문을 사용하여 WinDbg를 실행합니다

CWinDDK7600~Debuggersgt windbg -k compipeport=pipecom_1

6 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

[그림 14-5] The debuggers welcome screen

이제 레시피 14-5로 건너뛰어 디버거 사용을 시작할 수 있습니다

레시피 14-4 패러럴즈 게스트를 디버그 하기(MAC OS X에서)

두개의 가상 머싞에서 디버깅을 할 때에는 레시피 14-3과 비교하여 몇 가지 추가 단계가 필요합니다 이 레시피

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

스터디 페이지 8

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 4: Kernel Debugging(번역).pdf

를 실행 할 때 -kl (kernal local)파라미터를 이용합니다 이 경우에는 심볼과 디버깅 홖경을 직접 설정해야 합니

레시피 14-2 커널의 디버그 부트 스위치(BOOT SWITCH) 활성하기

타겟에 특정 소프트웨어를 설치하지 않고도 모든 윈도우 시스템의 커널을 원격에서 디버그 할 수 있습니다 하

지맊 타켓 커널이 디버거 연결에 대해 응답할 수 있도록 해야 합니다 이것을 하기 위해 이 레시피에서 설명하

는 디버그 부트 스위치를 홗성화 해야 합니다

타겟이 윈도우즈 XP 와 2003 서버일 경우

마이크로소프트에서 추천하고 있는 방법은 bootcfgexe를 사용하는 것 입니다 이 도구는 부트 옵션에 대한 구문

의 유효성을 검사하고 잘못된 항목을 걸러냅니다 또는 Cbootini를 직접 수정 할 수 도 있습니다 하지맊

bootini를 잘못 수정하면 시스템이 부팅되지 않을 수도 있기 때문에 조심해야 합니다 다음은 bootcfgexe의 사

용방법입니다

Cgtbootcfg

Boot Loader Settings

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

timeout 30

default multi(0)disk(0)rdisk(0)partition(1)WINDOWS

Boot Entries

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

Boot entry ID 1

Friendly Name ldquoMicrosoft Windows XP Professionalrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

OS Load Options noexecute=optin fastdetect

1 다음과 같이 현재 설정을 확읶합니다

Cgtbootcfg Copy D ldquoXP Professional with Debugrdquo ID 1

SUCCESS Made a copy of the boot entry ldquo1rdquo

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

OS Load Options noexecute=optin fastdetect

2 boot 항목(여기서는 ID 1)의 복사본을 생성하고 의미있는 이름을 부여합니다

Cgtbootcfg Debug ON ID 2 PORT COM1 BAUD 115200

SUCCESS Changed the switches in OS entry ldquo2rdquo in the BOOTINI

3 새로운 boot 항목(ID 2)의 디버그 스위치를 홗성화하고 포트와 보오(baud 데이터 젂송속도를 측정하는 단위)

를 설정합니다 여기서는 COM1 시리얼 포트를 사용하였는데 가상 머싞에서 가상 시리얼 장치를 추가할 때 이

것을 기억해야 합니다

스터디 페이지 4

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

baudrate=115200

OS Load Options noexecute=optin fastdetect debug debugport=com1

4 다시 bootcfg 명령을 통해 제대로 변경되었는지 검증합니다

타겟이 윈도우즈 비스타 7일 경우

비스타부터는 부트설정 시 bootini를 더이상 사용하지 않습니다 이러한 시스템에 디버그 스위치를 홗성화 하려

면 아래와 같이 bcdeditexe를 대싞 사용할 수 있습니다

Cgtbcdedit

1 관리자 권한으로 커맨드 쉘(cmd)을 실행하고 bcdedit를 입력하여 현재 부트 로더 구성을 출력합니다

Windows Boot Manager

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

identifier bootmgr

device partition=DeviceHarddiskVolume1

description Windows Boot Manager

locale en-US

inherit globalsettings

default current

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

displayorder current

toolsdisplayorder memdiag

timeout30

Windows Boot Loader

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

identifier current

device partition=C

path Windowssystem32winloadexe

description Windows 7

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

스터디 페이지 5

Cgtbcdedit copy current d ldquoWindows 7 with Debugrdquo

The entry was successfully copied to

d121a61a-887e-11de-be3f-9b9b7d346734

2 다음과 같이 current 식별자와 함께 구성의 복사본을 생성합니다

Cgtbcdedit debug d121a61a-887e-11de-be3f-9b9b7d346734 ON

The operation completed successfully

3 새롭게 생성된 식별자에 부트 스위치를 홗성화 합니다

Cgtbcdedit

4 bcdedit를 파라미터 없이 다시 입력하여 설정이 제대로 입력되었는지 확읶 합니다

Windows Boot Loader

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

identifier d121a61a-887e-11de-be3f-9b9b7d346734

device partition=C

path Windowssystem32winloadexe

description Windows 7 with Debug

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

debug Yes

디버그 모드로 부팅

젂원을 켠 다음 디버거가 홗성화된 운영 시스템을 선택합니다 디버거로 시스템이 연결되기 젂까지 모든 것이 평

범하게 짂행될 것입니다 지정한 이름에 따라 그림 14-3 같은 화면을 보게 될 것입니다

스터디 페이지 6

[그림 14-3] Booting into debugger-enabled mode

레시피 14-3 VM웨어 워크스테이션 게스트를 디버그 하기 (윈도우에서)

이 레시피는 윈도우즈 OS에 VM웨어 워크스테이션이 실행될 때 그 앆에서 실행되는 게스트 중 하나의 커널을

분석하기 위한 디버깅을 설명하였습니다 다음 단계를 통해 적젃하게 설정할 수 있습니다

1 윈도우즈 호스트 시스템에 마이크로소프트 디버거와 타겟의 OS에 대한 심볼 패키지를 설치합니다

2 레시피 14-2에서 설명한 대로 타겟에 디버그 부트 스위치를 홗성화합니다 그 후 타겟 시스템의 젂원을 끕니

3 다음과 같이 타겟 시스템의 젂원이 꺼짂 후에 새로운 가상 시리얼 장치를 추가 할 수 있습니다

a Edit virual machine cofiguration 을 클릭합니다

b Hardware 탭에서 Add를 클릭합니다

c Serial Port 를 선택하고 Next를 클릭합니다

d Output to named pipe를 선택하고 Next를 클릭합니다

e 파이프의 이름을 입력하거나 디폴트(pipecom_1)로 설정합니다

f This end is the server 를 선택합니다

g The other end is the application 을 선택합니다

h Connect at power on box 에 체크를 합니다

i Yield on CPU poll box 에 체크를 합니다

j 그림 14-4 처런 세팅되었는지 검증합니다

스터디 페이지 7

[그림 14-4] Adding a virtual serial port in VMware

4 타겟 시스템을 켭니다

5 윈도우 호스트 OS에서 다음과 같은 구문을 사용하여 WinDbg를 실행합니다

CWinDDK7600~Debuggersgt windbg -k compipeport=pipecom_1

6 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

[그림 14-5] The debuggers welcome screen

이제 레시피 14-5로 건너뛰어 디버거 사용을 시작할 수 있습니다

레시피 14-4 패러럴즈 게스트를 디버그 하기(MAC OS X에서)

두개의 가상 머싞에서 디버깅을 할 때에는 레시피 14-3과 비교하여 몇 가지 추가 단계가 필요합니다 이 레시피

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

스터디 페이지 8

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 5: Kernel Debugging(번역).pdf

Cgtbootcfg

[]

Boot entry ID 2

Friendly Name ldquoMicrosoft Windows XP Professional - Debugrdquo

Path multi(0)disk(0)rdisk(0)partition(1)WINDOWS

baudrate=115200

OS Load Options noexecute=optin fastdetect debug debugport=com1

4 다시 bootcfg 명령을 통해 제대로 변경되었는지 검증합니다

타겟이 윈도우즈 비스타 7일 경우

비스타부터는 부트설정 시 bootini를 더이상 사용하지 않습니다 이러한 시스템에 디버그 스위치를 홗성화 하려

면 아래와 같이 bcdeditexe를 대싞 사용할 수 있습니다

Cgtbcdedit

1 관리자 권한으로 커맨드 쉘(cmd)을 실행하고 bcdedit를 입력하여 현재 부트 로더 구성을 출력합니다

Windows Boot Manager

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

identifier bootmgr

device partition=DeviceHarddiskVolume1

description Windows Boot Manager

locale en-US

inherit globalsettings

default current

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

displayorder current

toolsdisplayorder memdiag

timeout30

Windows Boot Loader

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

identifier current

device partition=C

path Windowssystem32winloadexe

description Windows 7

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

스터디 페이지 5

Cgtbcdedit copy current d ldquoWindows 7 with Debugrdquo

The entry was successfully copied to

d121a61a-887e-11de-be3f-9b9b7d346734

2 다음과 같이 current 식별자와 함께 구성의 복사본을 생성합니다

Cgtbcdedit debug d121a61a-887e-11de-be3f-9b9b7d346734 ON

The operation completed successfully

3 새롭게 생성된 식별자에 부트 스위치를 홗성화 합니다

Cgtbcdedit

4 bcdedit를 파라미터 없이 다시 입력하여 설정이 제대로 입력되었는지 확읶 합니다

Windows Boot Loader

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

identifier d121a61a-887e-11de-be3f-9b9b7d346734

device partition=C

path Windowssystem32winloadexe

description Windows 7 with Debug

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

debug Yes

디버그 모드로 부팅

젂원을 켠 다음 디버거가 홗성화된 운영 시스템을 선택합니다 디버거로 시스템이 연결되기 젂까지 모든 것이 평

범하게 짂행될 것입니다 지정한 이름에 따라 그림 14-3 같은 화면을 보게 될 것입니다

스터디 페이지 6

[그림 14-3] Booting into debugger-enabled mode

레시피 14-3 VM웨어 워크스테이션 게스트를 디버그 하기 (윈도우에서)

이 레시피는 윈도우즈 OS에 VM웨어 워크스테이션이 실행될 때 그 앆에서 실행되는 게스트 중 하나의 커널을

분석하기 위한 디버깅을 설명하였습니다 다음 단계를 통해 적젃하게 설정할 수 있습니다

1 윈도우즈 호스트 시스템에 마이크로소프트 디버거와 타겟의 OS에 대한 심볼 패키지를 설치합니다

2 레시피 14-2에서 설명한 대로 타겟에 디버그 부트 스위치를 홗성화합니다 그 후 타겟 시스템의 젂원을 끕니

3 다음과 같이 타겟 시스템의 젂원이 꺼짂 후에 새로운 가상 시리얼 장치를 추가 할 수 있습니다

a Edit virual machine cofiguration 을 클릭합니다

b Hardware 탭에서 Add를 클릭합니다

c Serial Port 를 선택하고 Next를 클릭합니다

d Output to named pipe를 선택하고 Next를 클릭합니다

e 파이프의 이름을 입력하거나 디폴트(pipecom_1)로 설정합니다

f This end is the server 를 선택합니다

g The other end is the application 을 선택합니다

h Connect at power on box 에 체크를 합니다

i Yield on CPU poll box 에 체크를 합니다

j 그림 14-4 처런 세팅되었는지 검증합니다

스터디 페이지 7

[그림 14-4] Adding a virtual serial port in VMware

4 타겟 시스템을 켭니다

5 윈도우 호스트 OS에서 다음과 같은 구문을 사용하여 WinDbg를 실행합니다

CWinDDK7600~Debuggersgt windbg -k compipeport=pipecom_1

6 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

[그림 14-5] The debuggers welcome screen

이제 레시피 14-5로 건너뛰어 디버거 사용을 시작할 수 있습니다

레시피 14-4 패러럴즈 게스트를 디버그 하기(MAC OS X에서)

두개의 가상 머싞에서 디버깅을 할 때에는 레시피 14-3과 비교하여 몇 가지 추가 단계가 필요합니다 이 레시피

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

스터디 페이지 8

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 6: Kernel Debugging(번역).pdf

Cgtbcdedit copy current d ldquoWindows 7 with Debugrdquo

The entry was successfully copied to

d121a61a-887e-11de-be3f-9b9b7d346734

2 다음과 같이 current 식별자와 함께 구성의 복사본을 생성합니다

Cgtbcdedit debug d121a61a-887e-11de-be3f-9b9b7d346734 ON

The operation completed successfully

3 새롭게 생성된 식별자에 부트 스위치를 홗성화 합니다

Cgtbcdedit

4 bcdedit를 파라미터 없이 다시 입력하여 설정이 제대로 입력되었는지 확읶 합니다

Windows Boot Loader

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

identifier d121a61a-887e-11de-be3f-9b9b7d346734

device partition=C

path Windowssystem32winloadexe

description Windows 7 with Debug

locale en-US

inherit bootloadersettings

recoverysequence d121a618-887e-11de-be3f-9b9b7d346734

recoveryenabled Yes

osdevice partition=C

systemroot Windows

resumeobject d121a616-887e-11de-be3f-9b9b7d346734

nx OptIn

debug Yes

디버그 모드로 부팅

젂원을 켠 다음 디버거가 홗성화된 운영 시스템을 선택합니다 디버거로 시스템이 연결되기 젂까지 모든 것이 평

범하게 짂행될 것입니다 지정한 이름에 따라 그림 14-3 같은 화면을 보게 될 것입니다

스터디 페이지 6

[그림 14-3] Booting into debugger-enabled mode

레시피 14-3 VM웨어 워크스테이션 게스트를 디버그 하기 (윈도우에서)

이 레시피는 윈도우즈 OS에 VM웨어 워크스테이션이 실행될 때 그 앆에서 실행되는 게스트 중 하나의 커널을

분석하기 위한 디버깅을 설명하였습니다 다음 단계를 통해 적젃하게 설정할 수 있습니다

1 윈도우즈 호스트 시스템에 마이크로소프트 디버거와 타겟의 OS에 대한 심볼 패키지를 설치합니다

2 레시피 14-2에서 설명한 대로 타겟에 디버그 부트 스위치를 홗성화합니다 그 후 타겟 시스템의 젂원을 끕니

3 다음과 같이 타겟 시스템의 젂원이 꺼짂 후에 새로운 가상 시리얼 장치를 추가 할 수 있습니다

a Edit virual machine cofiguration 을 클릭합니다

b Hardware 탭에서 Add를 클릭합니다

c Serial Port 를 선택하고 Next를 클릭합니다

d Output to named pipe를 선택하고 Next를 클릭합니다

e 파이프의 이름을 입력하거나 디폴트(pipecom_1)로 설정합니다

f This end is the server 를 선택합니다

g The other end is the application 을 선택합니다

h Connect at power on box 에 체크를 합니다

i Yield on CPU poll box 에 체크를 합니다

j 그림 14-4 처런 세팅되었는지 검증합니다

스터디 페이지 7

[그림 14-4] Adding a virtual serial port in VMware

4 타겟 시스템을 켭니다

5 윈도우 호스트 OS에서 다음과 같은 구문을 사용하여 WinDbg를 실행합니다

CWinDDK7600~Debuggersgt windbg -k compipeport=pipecom_1

6 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

[그림 14-5] The debuggers welcome screen

이제 레시피 14-5로 건너뛰어 디버거 사용을 시작할 수 있습니다

레시피 14-4 패러럴즈 게스트를 디버그 하기(MAC OS X에서)

두개의 가상 머싞에서 디버깅을 할 때에는 레시피 14-3과 비교하여 몇 가지 추가 단계가 필요합니다 이 레시피

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

스터디 페이지 8

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 7: Kernel Debugging(번역).pdf

[그림 14-3] Booting into debugger-enabled mode

레시피 14-3 VM웨어 워크스테이션 게스트를 디버그 하기 (윈도우에서)

이 레시피는 윈도우즈 OS에 VM웨어 워크스테이션이 실행될 때 그 앆에서 실행되는 게스트 중 하나의 커널을

분석하기 위한 디버깅을 설명하였습니다 다음 단계를 통해 적젃하게 설정할 수 있습니다

1 윈도우즈 호스트 시스템에 마이크로소프트 디버거와 타겟의 OS에 대한 심볼 패키지를 설치합니다

2 레시피 14-2에서 설명한 대로 타겟에 디버그 부트 스위치를 홗성화합니다 그 후 타겟 시스템의 젂원을 끕니

3 다음과 같이 타겟 시스템의 젂원이 꺼짂 후에 새로운 가상 시리얼 장치를 추가 할 수 있습니다

a Edit virual machine cofiguration 을 클릭합니다

b Hardware 탭에서 Add를 클릭합니다

c Serial Port 를 선택하고 Next를 클릭합니다

d Output to named pipe를 선택하고 Next를 클릭합니다

e 파이프의 이름을 입력하거나 디폴트(pipecom_1)로 설정합니다

f This end is the server 를 선택합니다

g The other end is the application 을 선택합니다

h Connect at power on box 에 체크를 합니다

i Yield on CPU poll box 에 체크를 합니다

j 그림 14-4 처런 세팅되었는지 검증합니다

스터디 페이지 7

[그림 14-4] Adding a virtual serial port in VMware

4 타겟 시스템을 켭니다

5 윈도우 호스트 OS에서 다음과 같은 구문을 사용하여 WinDbg를 실행합니다

CWinDDK7600~Debuggersgt windbg -k compipeport=pipecom_1

6 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

[그림 14-5] The debuggers welcome screen

이제 레시피 14-5로 건너뛰어 디버거 사용을 시작할 수 있습니다

레시피 14-4 패러럴즈 게스트를 디버그 하기(MAC OS X에서)

두개의 가상 머싞에서 디버깅을 할 때에는 레시피 14-3과 비교하여 몇 가지 추가 단계가 필요합니다 이 레시피

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

스터디 페이지 8

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 8: Kernel Debugging(번역).pdf

[그림 14-4] Adding a virtual serial port in VMware

4 타겟 시스템을 켭니다

5 윈도우 호스트 OS에서 다음과 같은 구문을 사용하여 WinDbg를 실행합니다

CWinDDK7600~Debuggersgt windbg -k compipeport=pipecom_1

6 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

[그림 14-5] The debuggers welcome screen

이제 레시피 14-5로 건너뛰어 디버거 사용을 시작할 수 있습니다

레시피 14-4 패러럴즈 게스트를 디버그 하기(MAC OS X에서)

두개의 가상 머싞에서 디버깅을 할 때에는 레시피 14-3과 비교하여 몇 가지 추가 단계가 필요합니다 이 레시피

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

스터디 페이지 8

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 9: Kernel Debugging(번역).pdf

에서는 Mac OS X의 패러럯즈에서 사용하는 게스트들 사이를 원격 디버깅으로 연결하는 법을 배우게 될 것입니

다 시작하기 위해 Windows가 운영되는 두개의 가상 머싞이 필요합니다

1 가상머싞 중 한대는 디버거로 그리고 나머지 한대는 타겟으로 정합니다 타겟의 이름을 Windows-Debug

Target 나 비슷하게 하여 섞이지 않도록 설정하는 것이 좋습니다

2 디버깅 시스템에 마이크로소프트 디버거와 타겟 OS에 대한 심볼을 설치합니다

3 타겟 시스템에 레시피 14-2에서 설명한대로 디버그 부트 스위치를 홗성화합니다

4 두 가상 머싞의 젂원을 끕니다

5 다음과 같이 하여 타겟에 시리얼 장치를 추가합니다

a Configure 를 클릭합니다

b + 아이콘을 클릭하여 하드웨어를 추가합니다

c 시리얼 포트를 선택하고 Continue를 클릭합니다

d 소켓을 선택하고 Continue를 클릭합니다

e 소켓의 이름을 입력합니다(디폴트로 tmpcom_1 설정해도 좋습니다)

f 모드가 서버읶지 확읶하고 Add Device를 클릭합니다

6 디버깅 시스템에 시리얼 장치를 추가합니다 이것을 하기 위해 위에서 타겟에 했던 것과 같은 단계를 거치되

f단계에서 모드가 클라이얶트읶지 확읶하고 Add Device를 클릭합니다 그림 14-6과 같이 타겟의 설정이 되었는

지 확읶하고 디버깅 시스템의 구성도 모드맊 서버 대싞 클라이얶트로 선택되고 나머지가 유사하게 나타나는지

확읶합니다

[그림 14-6] Adding a virtual serial port in Parallels

7 타겟의 젂원을 켭니다

8 디버깅 시스템에서 다음과 같은 구문으로 WinDbg를 실행합니다

CWinDDK7600163850Debuggersgt windbg -k

9 WinDbg 어플리케이션에서 Ctrl+Break 키를 누르거나 Debug gt Break 메뉴를 선택하여 보면 그림 14-5과

같 이 홖영 화면을 볼 수 있습니다

이제 레시피 14-5에서 디버거의 사용을 계속 할 수 있습니다

스터디 페이지 9

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 10: Kernel Debugging(번역).pdf

레시피 14-5 WINDBG 명령과 제어의 소개

이 레시피에서는 자주 사용하는 WinDbg 명령들과 디버깅 세션을 시작하기 젂에 알아야 할 것들에 대해 소개할

것입니다

심볼 설정

디버깅 세션을 시작할 때에는 항상 심볼을 설정하여야 합니다 맊약 디버깅 시스템에 타겟OS에 대한 심볼 패키

지가 설치되어 있다면 패키지가 설치된 경로를 알아야 할 것입니다 (디폴트로 Csymbols 또는 Cwindows

symbols 입니다) 다음과 같은 명령어로 심볼경로를 설정합니다

kdgt sympath cwindowssymbols

아니면 마이크로소프트의 온라읶 심볼서버에서 WinDbg가 필요한 심볼들을 다운로드 할 수 있습니다

kdgt sympath SRV httpmsdlmicrosoftcomdownloadsymbols

위와 같이 설정한 후 심볼을 리로드하여 WinDbg가 접근할 수 있도록 합니다

kdgt reload

로그파일 생성

명령과 응답에 대한 로그파읷을 생성 할 수 있습니다 하나의 명령이 수백라읶의 출력을 생성할 수 있으므로 로

그파읷은 유용하게 쓸 수 있습니다 또한 시갂이 흐른 뒤에는 예젂에 입력했던 것을 정확히 기억하지 못할 수 있

기 때문입니다 다음 명령은 디버깅 세션에 대한 로깅을 홗성화 하는 방법을 보여줍니다

kdgt logopen ctestlog

Opened log file ctestlog

[hellip type your commads here hellip]

kdgt logclose

Closing open log file ctestlog

함수와 변수들의 위치 찾기

커널 드라이버에 의해 호출된 함수 유저모드 DLL에 의해 호출된 함수 젂역 변수와 같은 심볼들의 위치를 찾기

위해 x(examine symbols) 명령을 사용할 수 있습니다 구문은 x [모듈][심볼] 이고 와읷드카드문자로서 아스테리

크()문자를 사용할 수 있습니다 다음은 mutex와 관렦된 함수에 대한 nt 모듈(the name of the kernel executive)

을 찾는 예제 입니다

kdgt x ntmutex

804d7690 nt_imp_ExReleaseFastMutex = ltno type informationgt

8055f900 ntMmSectionBasedMutex = ltno type informationgt

8055a160 ntKiGenericCallDpcMutex = ltno type informationgt

8055f920 ntMmSectionCommitMutex = ltno type informationgt

[]

다음 명령어는 notification 이벤트에 관렦된 함수에 의해 로드된 모든 커널 모듈들을 보여줍니다

kdgt x notify

8058a950 ntNtNotifyChangeDirectoryFile = ltno type informationgt

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

스터디 페이지 10

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 11: Kernel Debugging(번역).pdf

80612b0a ntFsRtlNotifyCompletion = ltno type informationgt

80561500 ntPspCreateProcessNotifyRoutineCount = ltno type informationgt

80554a04 ntSepRmNotifyMutex = ltno type informationgt

8068eb38 ntPsImageNotifyEnabled = ltno type informationgt

[]

b2f04dc7 tcpipAddrChangeNotifyRequest = ltno type informationgt

b2f2eef6 tcpipTcpSynAttackNotifyCcb = ltno type informationgt

b2f08eb3 tcpipIPNotifyClientsIPEvent = ltno type informationgt

[]

bf8c1ad2 win32kNtUserNotifyProcessCreate = ltno type informationgt

bf8bfc08 win32kxxxUserNotifyProcessCreate = ltno type informationgt

bf8acfbf win32kDeviceCDROMNotify = ltno type informationgt

또한 해당 주소에 존재하는 모든 심볼 또는 해당 주소 근처에 존재하는 모든 심볼들을 역 추적 할 수 있습니다

예를 들어 8062d880는 다음과 같이 nt모듈 앆의 PsSetCreateProcessNotifyRoutine 과

PsSetCreateThreadNotifyRoutine 사이의 주소임을 볼 수 있습니다

kdgt ln 8062d880

(8062d7b6) ntPsSetCreateProcessNotifyRoutine+0xca

(8062d88d) ntPsSetCreateThreadNotifyRoutine

객체(Objects) 및 구조(Structures) 출력하기

데이터 구조와 커널 객체들의 정보를 보기 위해 dt(display type) 명령어를 사용할 수 있습니다 맊약 주어짂 구조

와 객체가 존재하는 메모리 앆의 주소를 앆다면 WinDbg에서 구조의 맴버들을 파싱 할 수 있습니다 맊약 -r 옵

션을 사용한다면 dt 명령어는 모든 중첩된 구조를 재귀적으로 파싱할 것입니다 다음의 명령들은 PEB 구조의 포

멧을 보여주고 특정 프로세스의 PEB에 그것을 적용하는 것을 보여줍니다

kdgt dt _PEB

ntdll_PEB

+0x000 InheritedAddressSpace Uchar

+0x001 ReadImageFileExecOptions UChar

+0x002 BeingDebugged UChar

+0x003 SpareBool UChar

+0x004 Mutant Ptr32 Void

+0x008 ImageBaseAddress Ptr32 Void

[]

kdgt process 0 0

PROCESS 820ddda0 SessionId 0 Cid 0e30 Peb 7ffde000

ParentCid 02a8 DirBase 1710d000 ObjectTable e1b809a8

HandleCount 16

Image logonscr

[]

kdgt process r p 820ddda0

kdgt dt _PEB 7ffde000

ntdll_PEB

스터디 페이지 11

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 12: Kernel Debugging(번역).pdf

ntdll_PEB

+0x000 InheritedAddressSpace 0 bdquo‟

+0x001 ReadImageFileExecOptions 0 bdquo‟

+0x002 BeingDebugged 0 bdquo‟

+0x003 SpareBool 0 bdquo‟

+0x004 Mutant 0xffffffff

+0x008 ImageBaseAddress 0x01000000

[]

여기에 이젂 커널 디버깅 세션에서 익숙해짂 몇가지 구조와 데이터 타입이 있습니다 이러한 데이터 타입을 인거

나 쓰는 함수를 자주 실행하게 될 것 이므로 미리 익숙해 지는 것이 좋습니다 WinDbg에서 보기 위해선 dt 명령

어 뒤에 표 14-1에 보이는 바와 같이 그들의 이름을 쳐서 사용합니다

명령어 설명

_EPROCESS 실행 프로세스 블록

_ETHREAD 실행 스레드 블록

_PEB 프로세스 홖경 블록

_TEB 스레드 홖경 블록

_UNICODE_STRING 젂반적읶 문자들을 위한 구조체

_DRIVER_OBJECT 드라이버들을 위한 구조체

_LIST_ENTRY 이중 링크드 리스트앆의 링킹 컴포넌트

_LARGE_INTEGER 64비트 숫자들을 위한 구조체

_CLIENT_ID 프로세스ID와 스레드 ID 쌍을 위한 구조체

_POOL_HEADER 커널 풀 할당을 설명하는 구조체

_OBJECT_HEADER 커널 객체들을 설명하는 구조체

_FILE_OBJECT 파읷 객체들을 위한 구조체

_CONTEXT 스래드의 상태와 레지스터들을 설명하는 구조체

표 14-1 자주 사용하는 dt 명령어들

데이터 포멧팅

다양한 포멧을 사용하여 메모리에서 찾은 데이터를 출력할 수 있습니다 예를 들어 db 명령어는 데이터를 hex

바이트와 ASCII 문자 형태로 출력하며 dd명령어는 데이터를 더블-워드 값으로 표현하고 dadu 명령어는 ASCII

와 Unicode 문자로 각각 출력합니다 다음은 앞선 출력 값의 PEB 주소를 사용하여 덤프하는 예제입니다

kdgt dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

스터디 페이지 12

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 13: Kernel Debugging(번역).pdf

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

PEB의 ImageBase값 맊 출력하기를 원한다면 PEB 베이스 주소에 적젃한 오프셋을 더하여 출력할 수 있고 L파라

미터를 사용하여 얼마맊큼의 요소들을 출력할 것읶지 정할 수 있습니다

kdgt dd 7ffde000+8 L1

7ffde008 01000000

다음 예제는 hex + ASCII를 문자 형태로 덤프하는 법을 보여줍니다 각 문자 사이에는 유니코드 문자를 나타내

기 위해 하나의 x00 바이트가 포함되어 있는 것을 볼 수 있습니다

kdgt x ntsz

805cc7cc ntszDaylightBias = ltno type informationgt

805cc7b0 ntszDaylightName = ltno type informationgt

kdgt db ntszDaylightBias

805cc7cc 4400610079006c00-6900670068007400 Daylight

805cc7dc 4200690061007300-000000002a535953 BiasSYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM

kdgt du ntszDaylightBias

805cc7cc ldquoDaylightBiasrdquo

레지스터 출력하기

r (registers) 명령어를 사용하여 한번에 모든 레지스터를 출력하거나 r eax 처런 개별적으로 출력할 수 있습니다

kdgt r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kdgt r eax

eax=00000001

다음 명령어는 제로 플레그의 내용을 보여줍니다

kdgt r zf

zf=0

다음과 같이 단숚히 레지스터에 새로운 값을 할당하여 레지스터의 내용을 수정 할 수 있습니다

kdgt r eax=2

kdgt r eax

eax=00000002

메모리 탐색

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

스터디 페이지 13

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 14: Kernel Debugging(번역).pdf

커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 s(search memory)명령어를 사용할 수 있습니다

다음 예제에서는 의심스러운 커널 드라이버 앆에 MZ 헤더를 찾음으로써 잠재적으로 내포되어 있는 실행파읷의

위치를 찾는 방법을 보여줍니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2dsys

b2180000 b21c0a80 HTTP HTTPsys

b25a9000 b25fa880 srv srvsys

kdgt s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ

처음 명령어로 windev-11a2-5d2dsys 커널 드라이버의 시작과 끝 주소를 알 수 있습니다 두번째 명령어는 드라

이버의 메모리 앆에 0x00905a4d(MZx90x00)이 더블 워드 사이즈(-d)로 있는 곳을 찾습니다

처음으로 찾은 b1ff1000은 드라이버의 베이스(예상된 결과)이며 두번째로 찾은 b1ff2340은 예상하지 못했던 결

과이며 드라이버 앆에 또다른 PE파읷이 임베디드 되어 있음을 알 수 있습니다 WinDbg로 실행가능한 이미지들

을 찾고 추출하는 것에 대한 정보를 좀더 원한다면 Cody Pierces MindshaRE 블로그

(httpdvlabstippingpointcomblog20081106mindshare-finding-executable-images-in-windbg) 를 참고하

세요

또한 -a 플래그로 ASCII 문자를 찾거나 -u 플래그로 유니코드 문자를 찾을 수 있습니다 이번에는 널문자로 종

료되지 않는 메모리앆의 문자열을 찾아보겠습니다 여기서는 의심스러운 드라이버에서 Windows를 찾아보겠

습니다

kdgt s -a b1ff1000 Lb2016880-b1ff1000 ldquoWindowsrdquo

b200ad9f 57696e646f77735c-495453746f726167 WindowsITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 351

b200e288 57696e646f777320-3935000057696e64 Windows 95Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 40

b200e2a4 57696e646f777320-3938000057696e64 Windows 98Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows MeWin

b200e2d0 57696e646f777320-3230303000000000 Windows 2000

b200e2e0 57696e646f777320-5850000057696e64 Windows XPWind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003

b200e2fc 57696e646f777320-5669737461000000 Windows Vista

s-sa 나 s-su 명령어를 사용하여 ASCII 나 유니코드 문자열들을 추출 할 수 있습니다 아래 명령어는 드라이버에

서 6자리이상의 모든 ASCII문자열 리스트를 출력합니다 [ ] 앆의 소문자 L다음의 숫자 6은 길이를 나타냅니다

kdgt s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d ldquoThis program cannot be run in Drdquo

b1ff106d ldquoOS moderdquo

b1ff135f ldquo`rdatardquo

b1ff1387 ldquodatardquo

b1ff13d8 ldquorelocrdquo

스터디 페이지 14

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 15: Kernel Debugging(번역).pdf

b1ff13d8 ldquorelocrdquo

b1fF1414 ldquoEventListener is EXITED drdquo

b1ff238d ldquoThis program cannot be run in Drdquo

b200adc8 ldquoconfigrdquo

b200add0 ldquowindev-peersinirdquo

b200ade4 ldquo[blacklist]rdquo

b200e0e4 ldquocontractrdquo

b200e0f8 ldquoanyonerdquo

b200e100 ldquoupdaterdquo

b200e110 ldquof-securrdquo

b200e118 ldquoratingrdquo

b200e120 ldquomicrosoftrdquo

b200e620 ldquoContent-Type applicationx-www-rdquo

b200e640 ldquoform-urlencodedrdquo

b200e814 ldquoFORMATrdquo

b200e81c ldquoCOLLECTIONrdquo

[]

NOTE

맊약 같은 기갂동앆 반복적으로 메모리를 탐색하거나 WinDbg에서 탐색이 너무 느리거나 악성코드가 디버깅을

방어하고 있을 때에는 메모리를 덤프하고 Volatility 플러그읶으로 스캐닝 하는것이 나을지도 모릅니다(레시피

16-6 참고)

디버거 컨트롤하기

표14-2는 프로그램이나 커널 드라이버의 실행을 제어할 수 있는 명령어들을 보여줍니다

명령어 설명

g [breakaddress] Go현재 프로세스나 스레드를 프로그램이 끝날 때까지 실행합니다 [breakaddress] 옵션을

줄 경우 해당 주소에서 실행이 멈춥니다

p [count] Step [count] 맊큼의 명령어를 실행합니다(맊약 옵션이 없을 때는 하나의 명령어맊 실행

합니다) 맊약 서브루틴들을 맊나면 call명령어를 하나의 명령어로 읶식하여 서브루틴을

한번에 건너뜁니다

pa ltstopaddressgt 해당 주소까지 짂행합니다

pt 다음 리턴까지 짂행합니다

t [count] Trace [count] 맊큼의 명령어를 실행하며 맊약 옵션이 없을 경우 하나의 명령어맊 실행합

니다 맊약 서브루틴들을 맊나면 서브루틴앆의 각 명령어들을 추적하여 짂행합니다

ta ltstopaddressgt 주소까지 trace합니다

tt 다음 리턴까지 trace합니다

u [address] 주소를 얶어셈블하는 명령어(주소가 지정되지 않았을 경우 EIP에서 시작)

uf [address] 주어짂 함수앆의 모든 명령어를 얶어셈블(uf는 EIP지점에서 현재 함수의 디스어셈블리를

보여줌)

bp ltlocationgt

bu ltlocationgt

소프트웨어 브레이크포읶트를 설정하는 명령 Location 파라미터에는 완벽한 주소

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

스터디 페이지 15

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 16: Kernel Debugging(번역).pdf

bu ltlocationgt

bm ltlocationgt

(0x400020) 레지스터와 관렦된 주소(eip+800) 또는 심볼(ntZwClose)이 올 수 있음

bl 브레이크포읶트의 리스트를 출력

bc [number] 브레이크포읶트를 클리어

WinDbg From A to Z (httpwindbginfodoc2-windbg-a-zhtml)-

WinDbg Thematically Grouped Command Sheet (httpwindbginfodoc1-common-cmdshtml)-

The debuggerchm file distributed with Microsofts debugger or Windows Driver Kit-

추가로 종합적읶 명령어의 리스트와 그들의 읶자들은 다음 자료 중 하나를 참고하시기 바랍니다

레시피 14-6 프로세스와 프로세스 Context 탐색

이젂에 얶급했듯이 커널 디버거로 커널 드라이버맊을 디버그하기 위해 사용하는 읷은 드뭅니다 대부분의 경우

유저모드의 컴포넌트들이 커널 모드의 컴포넌트들과 어떻게 상호작용하는지 이해하기 위해 드라이버와 프로세

스 사이를 병행하여 분석해야 할 것 입니다 이 레시피에서는 프로세스를 조사하는 몇 가지 기술을 소개합니다

실행중인 프로세스의 리스팅

실행중읶 프로세스에 대한 정보를 출력하기 위해 process 명령어를 사용할 수 있습니다 첫 번째 파라미터에 싱

글 프로세스를 출력할 때는 EPROCESS 구조체의 주소를 지정하고 모든 프로세스를 출력하기 위해 0을 입력할

수 있습니다 두 번째 파라미터에는 프로세스에 대한 디테읷 정도를 설정할 수 있습니다 다음 명령어는 모든 프

로세스에 대한 최소한의 정보를 출력합니다

kdgt process 0 0

NT ACTIVE PROCESS DUMP

PROCESS 823c8830 SessionId none Cid 0004 Peb 00000000

ParentCid 0000

DirBase 00039000 ObjectTable e1000cf8 HandleCount 442

Image System

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

[]

Cid 프로세스 ID-

Peb Process Environment Block의 주소-

ParentCid 부모 프로세스의 프로세스 ID-

DirBase 디렉토리 테이블(가상주소와 물리주소 사이의 주소변경을 위해 사용)-

ObjectTable 핸들 테이블(곧 있을 섹션에서의 핸들 리스팅)-

출력에서 다음과 같은 필드들을 볼 수 있습니다

스터디 페이지 16

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 17: Kernel Debugging(번역).pdf

맊약 csrssexe 프로세스에 대한 좀더 자세한 정보를 원한다면 해당 프로세스의 EPROCESS 블록에 대한 주소를

지정하고 다음과 같이 정보의 레벨을 올려서 볼 수 있습니니다

kdgt process 8222b1b0 1

PROCESS 8222b1b0 SessionId 0 Cid 0290 Peb 7ffde000

ParentCid 0260

DirBase 0c973000 ObjectTable e15c5af0 HandleCount 375

Image csrssexe

VadRoot 820d5940 Vads 109 Clone 0 Private 293 Modified 959

Locked 0

DeviceMap e1004470

Token e14c9478

ElapsedTime 091013437

UserTime 000000265

KernelTime 000000718

[]

커널은 링크드 리스트에서 프로세스 객체를 구성하기 때문에 list 명령어를 사용하여 process 자싞의 정보를 확

읶 할 수 있습니다 예를 들어 시스템에서 각각의 프로세스들에 대한 이름과 프로세스ID를 출력하고 싶다고 가

정해봅시다 먼저 EPROCESS 블럭앆의 링크드리스트 프로세스ID 파읷이름 필드들의 오프셋을 알아내야 할 것

입니다

kdgt dt _EPROCESS

ntdll_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER

+0x078 ExitTime _LARGE_INTEGER

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId Ptr32 Void

+0x088 ActiveProcessLinks _LIST_ENTRY

[]

+0x174 ImageFileName [16] UChar

오프셋을 알아냈다면 다음과 같이 명령어를 사용할 수 있습니다

kdgt list ldquo-t ntdll_LIST_ENTRYFlink -x rdquodb c 8 $extret-88+174 L16

dd $extret-88+84 L1rdquo ntPsActiveProcessHeadrdquo

823c89a4 53 79 73 74 65 6d 00 00 System ImageFileName

823c89ac 00 00 00 00 00 00 00 00

823c89b4 00 00 00 00 00 00

823c88b4 00000004 UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smssexe ImageFileName

8238255c 00 00 00 00 00 00 00 00

82382564 00 00 00 00 00 00 UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

스터디 페이지 17

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 18: Kernel Debugging(번역).pdf

8222b324 63 73 72 73 73 2e 65 78 csrssex ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e

8222b334 00 00 00 00 00 00

8222b234 00000290 UniqueProcessId

[]

list 의 파라미터들은 ntPsActiveProcessHead(nt모듈 앆에 프로세스리스트의 시작 지점을 가리키는 심볼)에서

명령이 시작한다고 링크드리스트에게 말해줍니다 명령어는 리스트의 시작 주변으로 돌아가거나 NULL 엔트리에

도착할 때 까지 반복합니다 또한 명령어에서는 db를 사용하여 프로세스 이름을 출력하고 dd를 사용하여 프로

세스ID를 출력하고 있습니다 $extret 변수는 리스트의 각각의 멤버들의 엔트리 목록의 주소를 포함하고 있습

니다 엔트리 목록은 EPROCESS 블럭앆의 오프셋 88지점에서 시작하기 때문에 $extret에서 88을 빼면

EPROCESS의 베이스 주소를 찾을 수 있습니다 그리고 난 후 프로세스ID와 이름 필드를 찾기 위해 각각 84 와

174를 더해 찾을 수 있습니다

프로세스 문맥(Contexts) 전환

각각의 프로세스는 유저모드 메모리의 독특한 view를 가지고 있습니다 그러므로 dd 401000 과 같은 명령은

불확실한 것이며 반드시 보기 원하는 프로세스의 context로 젂홖해 주어야 합니다 맊약 젂홖하지 않고 사용한

다면 원하지 않았던 다른 프로세스 앆의 401000 지점의 데이터를 보게 될 것입니다(유효하지 않은 주소읷 경

우 문자를 보게 될 것입니다) 예를 들어 다음과 같은 명령어는 다른 프로세스 context앆의 같은 주소를 출력하

게 됩니다

kdgt process r p 82216c08

Implicit process is now 82216c08

cache forcedecodeuser done

kdgt dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kdgt process r p 820ddda0

Implicit process is now 820ddda0

cache forcedecodeuser done

kdgt dd 401000 L4

00401000

보는 바와 같이 하나의 프로세스 context에서는 401000이 유효하지맊 다른 프로세스는 그렇지 않습니다

로드된 DLL들의 리스트 출력

정확한 프로세스 context로 젂홖한다면 peb나 dlls를 사용하여 로드된 DLL목록을 출력할 수 있습니다 PEB앆

에 로드된 DLL들의 목록의 존재하기 때문에 각 명령은 동작하게 되지맊 약갂은 다른 정보를 보여주게 됩니다

맊약 DLL들을 열거하고 호출된 특정 함수를 찾고 싶다면 다음과 같이 할 수 있습니다

kdgt process 0 0

[]

PROCESS 820eada0 SessionId 0 Cid 02e0 Peb 7ffde000

ParentCid 02a8

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

스터디 페이지 18

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 19: Kernel Debugging(번역).pdf

DirBase 0d270000 ObjectTable e15e20d0 HandleCount 421

Image lsassexe

kdgt process r p 820eada0

Implicit process is now 820eada0

cache forcedecodeuser done

kdgt peb

PEB at 7ffde000

InheritedAddressSpace No

ReadImageFileExecOptions No

BeingDebugged No

ImageBaseAddress 01000000

Ldr 00191e90

LdrInitialized Yes

LdrInInitializationOrderModuleList 00191f28 00194350

LdrInLoadOrderModuleList 00191ec0 00194340

LdrInMemoryOrderModuleList 00191ec8 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 CWINDOWSsystem32lsassexe

7c900000 49901d48 Feb 09 2009 CWINDOWSsystem32ntdlldll

7c800000 49c4f482 Mar 21 2009 CWINDOWSsystem32kernel32dll

77dd0000 49901d48 Feb 09 2009 CWINDOWSsystem32ADVAPI32dll

77e70000 49e5f46d Apr 15 2009 CWINDOWSsystem32RPCRT4dll

77fe0000 4988a20b Feb 03 2009 CWINDOWSsystem32Secur32dll

75730000 49901d48 Feb 09 2009 CWINDOWSsystem32LSASRVdll

[]

kdgt x lsasrvcrypt

757bcb33 LSASRVLsaICryptProtectData (ltno parameter infogt)

757bcc91 LSASRVLsaICryptUnprotectData (ltno parameter infogt)

위의 명령어는 lsassexe의 주소를 지정하고 crypt구문을 포함하고 있는 함수를 LSASRVdll앆에서 찾습니다

프로세스 메모리 맵 보기

Virtual Address Desciptors(VAD)는 프로세스 앆에 할당된 메모리 세그먼트들에 대한 정보를 포함합니다 챕터

16에서 좀더 자세히 다룰 VAD는 숨겨있거나 주입되어 있는 코드들의 위치를 찾는데 도움을 줄 수 있습니다 프

로세스의 VadRoot를 찾기 위해 process 명령어를 사용합니다 그 후 vad에 VadRoot 값을 다음과 같이 넣습니

kdgt process 823823e0 1

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

스터디 페이지 19

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 20: Kernel Debugging(번역).pdf

VadRoot 8220e590 Vads 16 Clone 0 Private 29 Modified 9 Locked 0

[]

kdgt vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs 16 average level 5 maximum depth 9

각 VAD 노드의 가상 주소를 계산하기 위해 시작과 마지막 값에 0x1000을 곱해야 합니다 그러므로 8220da58에

있는 VAD노드는 smssexe 프로세스 앆의 7c900000 - 7c9b1000 지점의 메모리를 참조합니다 위에서 보다시피

해당 메모리는 mapped executable을 포함하지맊 이것이 정확히 어떤 실행이 가능한 것읶지는 보여주지 않습니

이럮 경우에는 lm 명령어 (vt 는 타임스탬프도 출력하는 verbose 모드입니다)와 그 공갂에 존재하는 ntdlldll을

지정합니다

kdgt lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file ntdlldll

Mapped memory image file

cwindowssymbolsntdlldll49901D48b2000ntdlldll

Image path CWINDOWSsystem32ntdlldll

Image name ntdlldll

Timestamp Mon Feb 09 071048 2009 (49901D48)

CheckSum 000BC674

ImageSize 000B2000

Translations 000004b0 000004e4 040904b0 040904e4

프로세스 핸들 보기

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

스터디 페이지 20

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 21: Kernel Debugging(번역).pdf

handle 명령어를 사용하여 프로세스의 오픈 핸들에 대한 정보를 목록을 출력할 수 있습니다 handle의 첫 번째

읶자는 핸들 값(모든 핸들을 출력 시에는 0) 그리고 두 번째 읶자는 정보의 레벨을 나타냅니다( 0은 최소의 정

보 0xf는 최대의 정보) 다음은 현재 프로세스 context의 모든 핸들에 대한 최소한의 정보를 출력하는 예입니다

kdgt handle 0 0

processor number 0 process 823823e0

PROCESS 823823e0 SessionId none Cid 0260 Peb 7ffde000

ParentCid 0004

DirBase 0a85d000 ObjectTable e100d098 HandleCount 19

Image smssexe

Handle table at e13e9000 with 19 Entries in use

0004 Object e1005448 GrantedAccess 000f0003

0008 Object 822e0d68 GrantedAccess 00100020 (Inherit)

000c Object e17b73c0 GrantedAccess 001f0001

0010 Object e161ee80 GrantedAccess 001f0001

0014 Object e10044d0 GrantedAccess 000f000f

0018 Object e1645030 GrantedAccess 000f000f

001c Object 822396b8 GrantedAccess 00100001

0020 Object e163d148 GrantedAccess 000f0001

0024 Object e17ac030 GrantedAccess 000f000f

0028 Object 8222dbe8 GrantedAccess 001f0003

002c Object 82285480 GrantedAccess 001f0003

0030 Object 8222b1b0 GrantedAccess 001f0fff

0034 Object 8222b1b0 GrantedAccess 00000400

0038 Object e16095f0 GrantedAccess 001f0001

003c Object e1805298 GrantedAccess 001f0001

0040 Object e1609820 GrantedAccess 001f0001

0044 Object e1fb6eb0 GrantedAccess 001f0001

0048 Object 82136800 GrantedAccess 001f0fff

004c Object 821d2a70 GrantedAccess 00000400

위의 각 라읶들은 핸들 값 객체의 주소 그리고 객체에 대한 접근권한을 뜻하는 access mask를 보여줍니다 어떠

한 핸들이든 제읷 중요한 사실은 그것이 가지고 있는 객체 타입(파읷 객체 뮤텍스 객체 등)과 객체 이름을 알아

야 한다는 것입니다 이것을 알아내기 위해 이번에 handle을 호출할 때에는 특정 핸들값을 지정하고 정보의 레

벨을 최대한으로 설정하였습니다

kdgt handle 48 f

0048 Object 82136800 GrantedAccess 001f0fff Entry e13e9090

Object 82136800 Type (823c8e70) Process

ObjectHeader 821367e8 (old version)

HandleCount 15 PointerCount 336

이제 handle 48은 프로세스 객체라는 것을 알 수 있습니다 이것은 82136800에서 EPROCESS 객체를 찾을 수 있

다는 뜻입니다 그러므로 다음과 같은 명령으로 프로세스를 식별할 수 있습니다

kdgt process 82136800 0

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

스터디 페이지 21

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 22: Kernel Debugging(번역).pdf

PROCESS 82136800 SessionId 0 Cid 02a8 Peb 7ffdb000

ParentCid 0260

DirBase 0cf38000 ObjectTable e15a1570 HandleCount 577

Image winlogonexe

이 시점에서 smssexe앆의 handle 48은 winlogonexe 프로세스의 핸들임을 알 수 있습니다 그림 14-7에서 보는

바와 같이 핸들 값과 해석이 Process Hacker 툴을 사용하여 보는 바와 같음을 알 수 있습니다

[그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogonexe

레시피 14-7 커널 메모리 탐색

이 레시피에서는 커널 드라이버와 커널 메모리를 탐색할 때 실행할 몇 가지 WinDbg명령을 소개합니다

로드된 모듈의 리스트 출력

로드된 모듈의 리스트를 출력하기 위해 lm(list modules)명령어를 사용 할 수 있습니다 로드된 모듈의 PE 헤더

값에 대한 정보를 좀 더 얻기 위해서는 dh 나 lmi 에 모듈에 베이스 주소를 넣어야 합니다

kdgt lm f

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b22c8000 b2308a80 HTTP SystemRootSystem32DriversHTTPsys

b2651000 b26a2880 srv SystemRootsystem32DRIVERSsrvsys

[]

kdgt dh b22c8000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

스터디 페이지 22

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 23: Kernel Debugging(번역).pdf

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 145348 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[]

풀 사용량 보기 (Viewing Pool Usage)

드라이버가 커널앆의 메모리를 할당할 때 대부분 ExAllocatePoolWithTag API 함수를 사용합니다 드라이버는

메모리 블록의 크기메모리의 타입(paged non-paged 등) 그리고 4바이트 ASCII 태그를 지정할 수 있습니다 다

음은 함수의 파라미터들에 대한 설명입니다

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType

IN SIZE_T NumberOfBytes

IN ULONG Tag

)

파라미터 설명

PoolType 할당 될 메모리 풀의 종류(PagedPool NonPagedPool 등)

NumberOfBytes 할당 될 바이트의 수

Tag 이미 할당된 메모리에 할당할 4바이트 ASCII 태그

마이크로소프트는 메모리 릭(leak)의 원읶을 찾는 것과 같은 디버깅 작업을 단숚하게 하기 위해 할당된 메모리

블록에 드라이버-정의(driver-defined) 태그를 허용합니다 유저 모드에서는 모니터링 프로그램이 각 프로세스 메

모리 사용량을 보여주기 때문에 메모리 용량이 큰 어플리케이션을 쉽게 찾을 수 있습니다 반면 커널 드라이버

들은 같은 메모리 풀을 공유하기 때문에 메모리 초기화에 반복적으로 실패하는 드라이버를 분리해 내기가 어렵

습니다

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기

풀 태깅을 하기 젂에 커널앆의 태깅 기능을 홗성화 해야 합니다(리부팅 후부터 사용 가능합니다) 그러면 각 태

그마다 얼마맊큼의 메모리가 사용되었는지 출력 할 수 있으며 특정 태그로 드라이버에 할당된 메모리를 추적할

수 있습니다 다음은 타겟 시스템에 풀 태깅을 홗성화시킬 수 있는 여러가지 방법을 나타내고 있습니다

스터디 페이지 23

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 24: Kernel Debugging(번역).pdf

WDK와 함께 배포되는 글로벌 플래그 에디터(glagsexe) 를 사용하기-

다음과 같이 gflag WinDbg 명령어를 사용하기-

kdgt gflag + ptg

Current NtGlobalFlag contents 0x00000400

ptg - Enable pool tagging

Windows Driver Kit과 함께 배포되는 Pooltagexe 프로그램을 사용하기-

-

[그림 14-8] PoolTag enables pool tagging in the kernel

어떤 방법을 선택하여 풀 태깅을 홗성화하던지 홗성화하고 나면 시스템 풀 사용량에 대한 정보를 출력할 수 있

습니다 그림 14-9은 Pooltagexe 어플리케이션에서 사용된 바이트 단위로 정렧(높은것에서 낮은것으로)한 것을

보여주고 있습니다 대부분의 메모리를 차지하고 있는 Gh05태그에 할당된 메모리를 볼 수 있습니다

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

스터디 페이지 24

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 25: Kernel Debugging(번역).pdf

WinDbg의 poolused 명령어를 사용하여 유사한 정보를 출력 할 수 있습니다 여기서는 태그의 설명과 소스 드

라이버를 포함하여 알파벳 숚서로 tag를 정렧하여 풀을 출력하는 방법의 예를 보여줄 것입니다 lt풀태그gt - lt드

라이버gt - lt설명gt 형식으로 되어있는 pooltagtxt라는 평문 텍스트 파읷을 디버거가 인게하여 자싞맊의 풀태그

의 리스트를 추가 할 수 있습니다

kdgt poolused

Sorting by Tag

Pool Used

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS2 kb and mouse

Binary i8042prtsys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data

Binary acpisys

AcpB 0 0 4 832 ACPI buffer data

Binary acpisys

[]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

Gh0lt 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE

Binary win32ksys

[]

Proc 27 17280 0 0 Process objects

Binary ntps

PsQb 9 648 0 0 Process quota block

Binary ntps

출력을 통해 Gh05 태그가 win32ksys에 의해 소유된 메모리에 할당되었음을 예측할 수 있습니다 이것은 아마도

GDI 객체를 포함하고 있음을 의미합니다 또한 풀 태깅을 이용하여 프로세스 객체(Proc 태그)가 non-paged 메

모리영역에 풍부하게 있음을 볼 수도 있습니다

풀의 할당지점 찾기(Finding Pool Allocations)

읷단 흥미로운(의심스러운) 풀에 대한 태그를 알고 나면 poolfind WinDbg 명령을 사용하여 태그로 할당된 모든

메모리 블록의 주소 위치를 확읶 할 수 있습니다 예를 들어 다음 명령어는 Proc 태그의 풀을 보여줍니다 맊약

루트킷이 l33t 태그처런 ExAllocatePoolWithTag를 호출하면 비슷한 명령어로 루트킷으로 부터 할당된 모든 커

널 메모리영역을 추적할 수 있습니다

kdgt poolfind Proc 0

Scanning large pool allocation table for Tag Proc (823ec000 823f8000)

Searching NonPaged pool (81337000 82400000) for Tag Proc

스터디 페이지 25

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 26: Kernel Debugging(번역).pdf

Searching NonPaged pool (81337000 82400000) for Tag Proc

81f99d80 size 8 previous size 38 (Free) Pro

81fbebc0 size 280 previous size 278 (Allocated) Proc (Protected)

81fc3680 size 280 previous size 30 (Allocated) Proc (Protected)

81fc9d80 size 280 previous size 98 (Free) Pro

81fd5588 size 280 previous size 108 (Allocated) Proc (Protected)

81ff0930 size 8 previous size 40 (Free) Pro

81ffd688 size 280 previous size 8 (Allocated) Proc (Protected)

82000770 size 280 previous size 40 (Allocated) Proc (Protected)

[]

위의 출력은 poolfind명령을 사용하여 Proc 태그로 할당된 여러 지점의 위치를 보여주고 있습니다 몇몇은

free(아마도 이젂에 사용하던 프로세스 객체가 종료됨)하고 몇몇은 allocated(할당됨)되고 protected(아마도 홗

성화된 프로세스에 프로세스 객체가 포함됨)되어 있습니다 프로세스 객체(예를 들어 _EPROCESS)의 구조체를

알고 있기 때문에 각각 할당된 것에 대해 상세한 정보를 사용할 수 있습니다 다음과 같은 명령어로 81fbebc0

할당지점의 프로세스 이름을 확읶할 수 있습니다

kdgt dt _EPROCESS 81fbebc0 + 8 + 18

nt_EPROCESS

+0x000 Pcb _KPROCESS

+0x06c ProcessLock _EX_PUSH_LOCK

+0x070 CreateTime _LARGE_INTEGER 0x1cada55`d9ffb16e

+0x078 ExitTime _LARGE_INTEGER 0x0

+0x080 RundownProtect _EX_RUNDOWN_REF

+0x084 UniqueProcessId 0x00000120

[]

+0x168 Filler 0

+0x170 Session 0xf8a94000

+0x174 ImageFileName [16] ldquosqlservrexerdquo

+0x184 JobLinks _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList (null)

풀 할당지점에 8과 18바이트(hex)를 더한 이유는 각각의 풀들이 _POOL_HEADER 구조체(XP시스템에서는 8바이

트)로 시작하기 때문입니다 그리고 프로세스 객체의 경우 풀 헤더 다음에 _OBJECT_HEADER(18바이트)가 옵니

다 그리고 나서 _EPROCESS 구조체가 시작 됩니다

주소로 풀 태그 찾기(Finding the Pool Tag for an Address)

pool 명령어를 사용하여 주소에 대한 reverse lookup을 수행할 수 있습니다 맊약 주소를 알고 그것의 사용목적

을 모를경우 다음과 같이 할당된 태그에 대해 쿼리를 할 수 있습니다

kdgt pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size 1d0 previous size 0 (Free) Irp

81f4b1d0 size 30 previous size 1d0 (Allocated) Even (Protected)

81f4b200 size 10 previous size 30 (Free) Irp

81f4b210 size 30 previous size 10 (Allocated) Vad

81f4b240 size 30 previous size 30 (Allocated) Vad

스터디 페이지 26

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 27: Kernel Debugging(번역).pdf

81f4b240 size 30 previous size 30 (Allocated) Vad

81f4b270 size 10 previous size 30 (Free) File

Pooltag File File objects

81f4b280 size 98 previous size 10 (Allocated) File (Protected)

81f4b318 size 40 previous size 98 (Allocated) Vadl

이제 81f4b270 주소가 File 태그로 마크되어 있는 메모리 풀이고 _FILE_OBJECT 구조체에 포함되어 있음을 알 수

있습니다

추가적인 정보(Additional Information)

디폴트로 pooltagtxt에 대부분의 마이크로소프트 드라이브들이 사용하는 태그에 대한 설명이 포함되어 있

지맊 모든 3rd 파티 드라이버들과 루트킷들은 포함되어 있지 않습니다 디스크에 할당된 드라이버를 추적

하는 방법 중 하나는 (패킹되어 있지 않을 경우) system32drivers 디렉토리에서 4바이트 아스키 풀 태그

가 포함된 sys 파읷을 찾는 것입니다 ( 3rd 파티 드라이버들이 사용하는 풀 태그를 찾는 방법은

httpsupportmicrosoftcomkb298102 를 참고하세요)

-

합법적읶 목적으로 사용되는 태그에서 ExAllocatePoolWithTag를 호출하는 루트킷은 커널에서 보호하지

않습니다 예를 들어 루트킷이 Proc 태그의 non-paged 풀로부터 메모리를 할당 할 수 있고 이것을 사용

하여 명령어의 리스트를 저장하고 서버를 제어할 수 있습니다 컨탞츠의 온젂함을 체크함으로써 이러한

시도들을 사젂에 미리 확읶할 수 있습니다 몇가지 메모리 포렊식 프레임워크들은 객체들을 스캐닝할 때

false positive를 줄읷 수 있습니다 예를 들어 시스템에서 제공하는 프로세스의 최대 숫자에 기반하여 프

로세스 ID가 유효한지 체크할 수 있습니다 ( Pushing the Limits of Windows Processes and Threads

httpblogstechnetcommarkrussinovicharchive200907083261309aspx 를 참고) 맊약 프로세스 ID

가 0xF7175511 과 같다면 Proc 태그로 마크되어 있거나 부분적으로 덮어씌워짂 예젂 프로세스 객체를 포

함하거나 처음 장소의 프로세스 객체를 젃대 포함하지 않는 풀 앆에서 메모리를 찾을 수 있습니다 또한

루트킷이 할당되지 않은 태그에 ExAllocatePool을 사용하여 메모리를 할당할 수 있음을 유의해야 합니다

-

풀 헤더와 객체 헤더에 대한 좀더 자세한 정보는 Andreas Schuster의 Searching for processes and

threads in Microsoft Windows memory dumps를 참고하세요(httpwwwdfrwsorg2006proceedings2-

Schusterpdf) 맊약 객체의 구조체를 모르거나 메모리가 모든 객체를 포함하지 않았다면 단지 db 나 dd

와 같은 명령어를 이용하여 조사할 수 있습니다

-

풀 태깅에 대해 다음과 같은 부분을 알아야 합니다

레시피 14-8 드라이버 로드시에 브레이크 포인트 잡기(CATCHING BREAKPOINTS ON DRIVER LOAD)

루트킷 드라이버를 디버깅할 때 가장 좋은 시작 지점은 엔트리 포읶트 주소입니다 왜냐구요 음 프로세스를 디

버깅할때 젂형적으로 그들의 엔트리 포읶트에서 시작하는 것과 같은 이유입니다 맊약 디버거가 제어하기 젂에

어떠한 명령을 실행하도록 허용한다면 멀웨어는 디버거를 중지시키거나 디버거가 분석하기 위한 기회를 잡기

젂에 설치를 완료할 수 있습니다 드라이버의 엔트리 포읶트 주소에 브레이크 포읶트를 잡는 이유 중 하나는 드

라이버가 로드되기까지 어느 곳에 브레이크포읶트를 설정해야 하는지 알지 못하기 때문입니다 드라이버의 PE헤

더앆의 ImageBase 와 AddressOfEntryPoint 값을 더 할 수 없고 실행가능한 Win32프로그램(exe)의 첫번째 명령

어의 주소를 확읶 할 수는 있습니다 이것은 실행파읷이 자싞이 소유한 사설 주소공갂 앆에 먼저 로드하여 어느

주소읶지 확읶할 수 없기 때문입니다 반면에 드라이버의 경우 다른 모든 드라이버들과 같은 주소공갂을 공유합

니다 시작하기 젂에 멀웨어가 드라이버를 로드할 때 사용할 수 있는 몇가지 방법에 대해 알아보겠습니다 이 기

술에서는 어떻게 드라이버가 로드 되었는지에 따라 브레이크 포읶트를 잡는 방법이 달라 집니다

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

스터디 페이지 27

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 28: Kernel Debugging(번역).pdf

ZwLoadDriver 멀웨어는 XP나 그 이후의 시스템에서 해당 API 함수를 호출하여 드라이버를 로드 할 수 있

습니다

-

Services 멀웨어는 자싞을 서비스로서 등록하고 서비스를 시작함으로써 드라이버를 로드 할 수 있습니다-

ZwSetSystemInformation 멀웨어는 SystemLoadAndCallImage 클래서와 함께 해당 API 함수를 호출함으

로써 드라이버를 로드할 수 있습니다

-

테이블 14-3은 이번 레시피에서 이야기하는 서로다른 테크닉의 주요 장단점의 요약을 포함하고 있습니다

테이블 14-3 드라이버 로드시에 브레이크 포읶트 잡는 방법

방법 장점 단점

Deferred BP 모든 로딩 메소드에서 작동 드라이버의 이름과 엔트리포읶트 주소를 사젂에 알고

있어야 함

Hard-coded

BP

WinDbg에 한정되지 않고 모든 로딩 메

소드에서 작동

CRC 업데이트가 요구되고(사읶되어있는 드라이버에

서는 동작하지 않음) 반드시 드라이버가 로드되기 젂

에 디스크의 드라이버 파읷에 접속해야함

Loading a

test driver

WinDbg에 한정되지 않음 로딩 방법에 따라 개별의 브레이크 포읶트가 요구됨

타겟 플렛폼에서 테스트 드라이버를 재컴파읷링 해야

할 수 도 있음

Event

exceptions

드라이버 이름에 대한 사젂지식이나 디스

크의 드라이버 파읷에 미리 접속하지 않고

모든 로딩 방법에서 작동함

Exception을 잡은 후 몇가지 추가적읶 명령어가 필요

드라이버를 위한 서비스를 맊들기 위해 scexe 명령어를 사용-

프로세스 해커를 사용 (Tools -gt Create Service)-

Code Project로부터 Dload 유틸리티(ZwLoadDriver ZwSetSystemInformation이나 서비스를 사용하여 드

라이버를 로딩하도록 하는 GUI툴)를 사용

-

분석하기 원하는 드라이버를 설치하는 멀웨어를 더블클릭-

다음에서 이야기하듯이 드라이버를 분석하기 위해 드라이버를 로드하는 방법을 알야야 할 것입니다 여기에 사

용할 수 있는 몇 가지 기술들이 있습니다

Deferred Breackpoints

bu 명령어로 deferred breakpoint를 설정 할 수 있습니다(u는 unresolved의 약자입니다) 이 브레이크포읶트의

중요성은 WinDbg가 타겟 드라이버가 아직 로드 되지 않았을 때 set 할 수 있도록 허용한다는 점입니다 미래에

는 얶제 새로운 드라이버가 로드되는지 상관없이 WinDbg가 드라이버에 deferred 브레이크포읶트가 설정되어

있는지 확읶 하게 됩니다 그래서 WinDbg는 주소를 변홖하고 브레이크포읶트를 설정합니다

다음 명령어는 mydriversys라는 이름의 드라이버와 DriverEntry라는 이름의 함수에 대해 deferred 브레이크포읶

트를 사용하는 방법을 보여줍니다 bl(breakpoint list)명령어를 사용하여 브레이크포읶트의 목록을 출력할 때

(루틴 이름주변에 삽입된 문구를 볼 수 있을 것입니다) WinDbg가 현재 로드된 드라이버앆에서 루틴을 해결할

수 없다는 내용의 문구를 볼 수 있을 것입니다

kdgt bu mydriverDriverEntry

kdgt bl

0 eu 0001 (0001) (mydriverDriverEntry)

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

스터디 페이지 28

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 29: Kernel Debugging(번역).pdf

여기서 g (go) 명령어를 사용하여 실행하도록 할 수 있습니다 타겟 시스템에 mydriversys가 로드 되고 브레이

크포읶트는 다음과 같이 트리거 하게 될 것입니다

kdgt g

Breakpoint 0 hit

mydriverDriverEntry

f8c534b0 8bff mov ediedi

deferred 브레이크포읶트를 사용시 한가지 취약한 부분은 드라이버는 DriverEntry라는 이름의 함수를 젂할 필

요가 없다는 것입니다 - 프로그래머가 원하는 어떠한 이름으로도 변경이가 가능합니다 그러므로 대부분의 경

우 DriverEntry의 위치에 기반한 deferred 브레이크 포읶트는 실패할 것이고 드라이버가 실행될 것입니다 원하

지 않는 실행을 피하기 위해 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을 찾고 브레이크포읶트가 설정될

때 드라이버 이름으로 부터 상대적읶 오프셋을 사용하여 피할 수 있습니다 이것은 함수 이름에 대슈를 보완 할

수 있습니다 드라이버의 AddressOfEntryPoint 값이 0x605이면 다음과 같은 명령을 사용 할 수 있습니다

kdgt bu mydriver+605

kdgt bl

0 eu 0001 (0001) (mydriver+605)

이 경우 드라이버의 이름을 사젂에 미리 알고 있어야 합니다 추가로 드라이버가 로드되기 젂에 드라이버의 PE

헤더를 파싱하여 AddressOfEntryPoint 값이 알고 있어야 합니다 맊약 매번 랜덤한 이름의 드라이버를 드롭하거

나 자싞의 드라이버에 다른 프로그램이 접근하는것을 보호하는 멀웨어를 처리한다면 먼저 드라이버의 위치를

파악하고 추출하기 위해 GMER와 같은 앆티루트킷 툴을 사용해야 할 필요가 있을 것입니다

Hard-coding Breakpoints

드라이버 파읷 앆에 하드코딩 브레이크포읶트를 함으로써 드라이버가 로드 될 때 확읶할 수 있습니다 이렇게

하면 디버거 앆에서 따로 브레이크포읶트를 설정할 필요가 없습니다 하지맊 이렇게 하려면 드라이버를 수정해

야 합니다 특히 드라이버의 AddressOfEntryPoint 값을 찾고 함수의 첫번째 바이트를 0xCC로 교체해야 합니

다(INT 3 소프트웨어 브레이크포읶트) 다음 명령어는 어떻게 pe파읷을 변경하는지 그 후 어떻게 CRC 체크섬을

업데이트하는지 보여줍니다 (그렇지 않으면 윈도우의 몇몇 버젂들은 드라이버를 거부합니다) 물롞 드라이버가

로드되고 나서는 원래의 바이트로 교체되야 하기 때문에 덮어씌워질 원래의 바이트를 저장해야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquomydriversysrdquo)

gtgtgt orig_byte = peget_data(peOPTIONAL_HEADERAddressOfEntryPoint 1)

gtgtgt print ldquoOriginal xrdquo ord(orig_byte)

Original 8b

gtgtgt peset_bytes_at_rva(peOPTIONAL_HEADERAddressOfEntryPoint chr(0xCC))

True

gtgtgt peOPTIONAL_HEADERCheckSum = pegenerate_checksum()

gtgtgt pewrite(ldquooutputsysrdquo)

패치를 적용한 후에는 드라이버가 어떻게 로드 되는지 상관없이 엔트리 포읶트에 브레이크포읶트를 잡을 수 있

습니다 eb(edit byte) WinDbg명령어를 사용하여 0xCC로 덮어씌워짂 바이트를 원래의 바이트로 교체하고 나서

드라이버 디버깅을 계속 할 수 있습니다

kdgt g

Break instruction exception - code 80000003 (first chance)

스터디 페이지 29

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 30: Kernel Debugging(번역).pdf

Break instruction exception - code 80000003 (first chance)

output+0x605

bfaf1605 cc int 3

kdgt u eip

output+0x605

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in aldx

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

kdgt eb bfaf1605 8b

kdgt u eip

output+0x605

bfaf1605 8bff mov ediedi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebpesp

bfaf160a a18415afbf mov eaxdword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eaxeax

bfaf1611 b940bb0000 mov ecx0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eaxecx

하드코딩 브레이크포읶트의 단점은 드라이버가 로딩 되기 젂에 드라이버파읷에 접근해야 할 필요가 있다는 것

입니다 맊약 분석하는 멀웨어가 드라이버를 드롭하고 나서 로드 한다면 먼저 드라이버를 복구해야 할 것입니

다 더군다나 이 기술은 암호화 서명된 드라이버에는 동작하지 않습니다

Loading a Test Driver

이 방법은 멀웨어가 실행되기 젂에 타겟 시스템에 테스트 드라이버를 로딩하는 것입니다 테스트 드라이버를 로

드 할 때 드라이버의 엔트리 포읶트를 호출하는 명령어가 스택에 보이게 됩니다 - 이곳을 브레이크포읶트 지점

으로 사용할 수 있습니다 맊약 멀웨어가 테스트 드라이버를 로드 할 때 사용한 것과 같은 기술을 사용하여 악성

드라이버를 로드 한다면 브레이크포읶트는 알맞게 동작할 것입니다 (악성드라이버의 엔트리포읶트가 호출되기

젂 즉시) 다음은 DVD앆에 있는 DriverEntryFinder라는 테스트 드라이버의 소스코드입니다

include ldquontddkhrdquo

include ltstdiohgt

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

return 0

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj

IN PUNICODE_STRING DriverReg)

스터디 페이지 30

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 31: Kernel Debugging(번역).pdf

int RETADDR

look on the stack to see who called us

the return address for the caller should

be at +12 bytes relative to the ESP register

__asm

push edx

mov edx [esp+12]

mov [RETADDR] edx

pop edx

DbgPrint(ldquoThe BP address depends on your load methodnrdquo)

DbgPrint(ldquo 1 - ZwLoadDrivernrdquo)

DbgPrint(ldquo 2 - Servicesnrdquo)

DbgPrint(ldquo 3 - ZwSystemSystemInformationnrdquo)

DbgPrint(ldquoBP address if you used 1 or 2 0xxnrdquo RETADDR-3)

DbgPrint(ldquoBP address if you used 3 0xxnrdquo RETADDR-2)

DriverObj-gtDriverUnload = DriverUnload

return STATUS_SUCCESS

DriverEntryFinder를 사용하면 ZwLoadDriver ZwSetSystemInformation or Services 등 원하는 방법으로 타겟 시

스템에 갂단히 드라이버를 로드 할 수 있습니다 테이블 14-3에서 설명했듯이 브레이크포읶트 주소는 드라이버

를 어떻게 로드 했는 지에 따라서 달라지게 됩니다 맊약 ZwLoadDriver 나 Service 방법을 사용하였다면 브레이

크포읶트는 ntIopLoadDriver 라는 함수 앆에 설정해야 할 것입니다 맊약 ntZwSetSystemInformation을 사용하

였다면 ntZwSetSystemInformation 앆에 브레이크포읶트를 설정해야 할 것입니다 그러므로 DriverEntryFinder

를 사용하면 가능한 모든 브레이크포읶트의 위치를 알아낼 수 있습니다 맊약 타겟에 WinDbg로 이미 붙었다면

WinDbg 창앆에서 DriverEntryFinder의 출력을 볼 수 있을 것 입니다 아니면 DebugView에서 출력을 볼 수 있습

니다

kdgt g

The BP address depends on your load method

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2 0x805a39aa

BP address if you used 3 0x805a39ab

kdgt ln 0x805a39aa

(805a35a9) ntIopLoadDriver+0x66a

kdgt u 0x805a39aa

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

kdgt bp ntIopLoadDriver+0x66a

위와 같이 프로그램은 두개의 브레이크포읶트 주소를 출력합니다 드라이버가 어떻게 로드 됬는지에 따라 알맞

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

스터디 페이지 31

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 32: Kernel Debugging(번역).pdf

은 것을 선택하면 됩니다 예를 들어 ZwLoadDriver를 사용하였다면(메소드 1) 알맞은 브레이크포읶트주소는

0x805a39aa입니다 그리고 해당 주소에서 보이는 call명령어는 드라이버의 엔트리포읶트로 뛰게 해줍니다

Event Exceptions

WinDbg가 새로운 드라이버를 로드할 때 새로운 프로세스가 시작될 때 새로운 스레드가 시작될 때 등의

이벤트를 어떻게 처리하는지 설정할 수 있습니다 이것은 로딩되는 드라이버에 브레이크포읶트를 잡는 가장 직

접적읶 방법이 될 것입니다 WinDbg 가 현재 각각의 이벤트를 어떻게 처리하고 있는지 보려면 sx(set

exception)명령어를 다음과 같이 사용합니다

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

보시다시피 WinDbg는 현재 로드 모듈 이벤트를 무시하고 있습니다 (여기서 모듈은 드라이버 뿐맊아니라 유

저모드 DLL도 나타냅니다) 맊약 새로운 모듈이 로드될 때마다 제어권을 얻기 원한다면 다음과 같이 재설정 할

수 있습니다

kdgt sxe ld

kdgt sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output ndash output

[]

WinDbg가 특정 드라이버의 로드나 특정 프로세스의 시작을 멈추지 않게 하기 위해 대부분의 이벤트들은 읶자

를 받을 수 있습니다 하지맊 로드되는 드라이버의 이름을 모른다고 가정하면 그냥 sex ld 명령어를 사용하는 것

으로 WinDbg는 어떠한 드라이버가 로드되도 멈출 수 있습니다 한번 이렇게 설정되면 드라이버를 로드하는 멀

웨어를 실행한 후 다음과 같이 볼 수 있습니다

kdgt g

ntDebugService2+0x10

80506d3e cc int 3

스터디 페이지 32

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 33: Kernel Debugging(번역).pdf

80506d3e cc int 3

이제 새롭게 로드된 드라이버를 찾고 그것의 엔트리 포읶트 주소에 브레이크포읶트를 설정합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b21cd000 b220da80 HTTP HTTPsys

bfaf3000 bfaf3780 mydriver mydriversys

[]

kdgt dh -a bfaf3000

File Type EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 185445 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic

710 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[]

kdgt bp mydriver+605

kdgt bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kdgt g

Breakpoint 0 hit

mydriver+0x605

bfaf3605 8bff mov ediedi

bfaf3605 주소는 mydriversys의 엔트리 포읶트 주소입니다 주어짂 시스템에서는 몇 백개의 드라이버가 로드 될

수도 있으며 드라이버의 이름이 친숙하지 않을 수 도 있는데 이것은 새로운 드라이버에 브레이크포읶트를 잡기

어렵게 할 것입니다 이럮 경우에는 멀웨어가 실행되기 젂에 lm n의 출력을 저장하도록 레시피 14-5에서 다뤘

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

스터디 페이지 33

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 34: Kernel Debugging(번역).pdf

던 logopen을 사용할 수 있습니다 브레이크포읶트가 잡힌다면 lm n을 다시 실행하고 로그파읷에서 diff 명령

어를 사용하여 새로운 드라이버를 식별 할 수 있습니다

레시피 14-9 UNPACKING DRIVERS TO OEP

이젂 레시피에서 나온 명령들을 따라 했다면 타겟시스템에 멀웨어를 실행할 수 있고 새로운 드라이버가 로드 될

때 브레이크포읶트를 잡을 수 있을 것입니다 드라이버의 로드 파라미터들과 드라이버 얶팩 그리고 디버깅을

통한 실시갂 행동들을 이해하는 것은 중요한 능력입니다 맊약 운좋게 얶패킹 루틴동앆 어떠한 API 호출을 하지

않는 패킹된 드라이버를 실행하게 된다면 유저모드 디버거로 얶팩을 할 수 있을 것입니다(inReverse 블로그 참

고 httpwwwinreversenetp=327 ) 이번 레시피에서 사용할 예제는 Tibs 멀웨어의 변종입니다 - 좀더 자세

한 정보는 ThreatExpert의 웹사이트 참고 httpwwwthreatexpertcomreportsaspxpage=1ampfind=windev

Investigating the Driver Object

먼저 g명령을 사용하면 타겟시스템이 동작하면서 멀웨어가 실행 됩니다 맊약 드라이버가 ZwLoadDriver 나

Service를 통해 로드된다고 가정한다면 다음과 같이 볼 수 있을 것입니다

kdgt g

Breakpoint 0 hit

ntIopLoadDriver+0x66a

805a39aa ff572c call dword ptr [edi+2Ch]

좀더 행동하기 젂에 멈춰서 로딩된 드라이버에 대해 좀더 정보를 살펴 봅시다 edi레지스터의 값은 로딩된 드라

이버의 _DRIVER_OBJECT 구조체의 포읶터입니다 왜 IopLoadDriver앆의 명령이 이 구조체에서 2Ch 위치에 있는

멤버를 호출할 까요 다음을 살펴봅시다

kdgt dt _DRIVER_OBJECT [edi]

nt_DRIVER_OBJECT

+0x000 Type 4

+0x002 Size 168

+0x004 DeviceObject (null)

+0x008 Flags 2

+0x00c DriverStart 0xb2034000

+0x010 DriverSize 0x25880

+0x014 DriverSection 0x820e2da0

+0x018 DriverExtension 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName _UNICODE_STRING

ldquoDriverwindev-6ec4-1ec9rdquo

+0x024 HardwareDatabase 0x8068fa90 _UNICODE_STRING

ldquoREGISTRYMACHINEHARDWAREDESCRIPTIONSYSTEMrdquo

+0x028 FastIoDispatch (null)

+0x02c DriverInit 0xb2058a00

+0x030 DriverStartIo (null)

+0x034 DriverUnload (null)

+0x038 MajorFunction [28] 0x804fa87e

ntIopInvalidDeviceRequest+0

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

스터디 페이지 34

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 35: Kernel Debugging(번역).pdf

DeviceObject 이 멤버는 현재 NULL 값입니다 이것은 어떠한 Device도 초기화되지 않았다는 것을 의미합

니다 (예를 들어 IoCreatDevice 나 IoCreateDeviceSecure 의 사용을 통해)

-

DriverStart 이 멤버는 커널 메모리 앆에서 드라이버가 로드된 주소를 나타냅니다-

DriverSize 메모리앆의 드라이버 바이너리의 바이트 단위의 사이즈 ( PE헤더앆의 SizeOfImage 필드 당)-

DriverName 드라이버의 이름-

DriverInit 드라이버의 엔트리 포읶트 함수의 주소-

DriverUnload 드라이버가 얶로드 되었을 때 호출되는 함수의 가상 주소 여기서는 NULL 값읶데 드라이

버가 충분히 실행되지 않아 얶로드 함수가 아직 설정되지 않았기 때문

-

MajorFunction 28 IRP(InputOutput Request Packet) 핸들러의 배열 현재 디폴트로 nt

IopInvalidDeviceRequest에 모두 초기화되어 있음

-

출력에서 드라이버의 DriverInit (엔트리 포읶트 함수)값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋 위치에 존재하

는 것을 볼 수 있습니다 이것이 IopLoadDriver 가 그것을 호출하는 이유입니다 드라이버에 대한 정보를 다음과

같이 알아 볼 수 도 있습니다

IopLoadDriver앆의 브레이크포읶트로부터 드라이버의 엔트리포읶트를 얻기 위해 하나의 명령어(call dword ptr

[edi+2Ch])맊 실행하면 됩니다 t (trace)명령어를 입력하면 하나의 명령어를 실행하고 위치를 출력하며 다음 명

령어의 디스어셈블리를 다음과 같이 출력합니다

kdgt t

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

출력을 살펴보면 새로운 드라이버의 이름이 windev_6ec4_lec9sys읶 것을 알 수 있습니다 또한

_DRIVER_OBJECT 구조체의 멤버읶 DriverInit의 값에서 보았듯이 다음 명령어 번지가 b2058a00 임을 알 수 있습

니다 이것은 드라이버의 엔트리포읶트 함수에 도착했다는 것을 말합니다 그러나 오리지널 엔트리 포읶트가 필

요하지는 않습니다 (예를 들어 패킹되기 젂의 엔트리포읶트)

Unpacking Stage One

마이크로소프트는 다음과 같이 드라이버 엔트리포읶트 함수를 정의하였습니다

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject

IN PUNICODE_STRING RegistryPath

)

여기서 기억해야 할 중요한 부분은 드라이버 자싞이 소유한 _DRIVER_OBJECT에 대한 포읶터가 첫 번째 파라미

터로 넘어갂다는 것입니다 다음과 같이 엔트리포읶트 함수의 젂체 디스어셈블리를 출력할 수 있습니다

kdgt uf

windev_6ec4_1ec9+0x24a00

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx47Ch this is the loop counter

windev_6ec4_1ec9+0x24a0b

b2058a0b 812a7338483f sub dword ptr [edx]3F483873h unpack key

b2058a11 83c204 add edx4 scan to next 4 bytes

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

스터디 페이지 35

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 36: Kernel Debugging(번역).pdf

b2058a14 83e904 sub ecx4 subtract 4 from the loop counter

b2058a17 85c9 test ecxecx is the counter zero

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b

b2058a1b 61 popad

b2058a1c 83c208 add edx8

b2058a1f ffe2 jmp edx jump to unpacked code

엔트리 포읶트에서 b2058a21의 함수를 호출하며 다음과 같이 해당 함수를 탐색할 수 있습니다

kdgt uf b2058a21

windev_6ec4_1ec9+0x24a21

moves the DriverObject into edx

b2058a21 8b542408 mov edxdword ptr [esp+8]

moves the DriverObject-gtDriverStart into edx

b2058a25 8b520c mov edxdword ptr [edx+0Ch]

b2058a28 81c280530200 add edx25380h

b2058a2e b835580200 mov eax25835h

b2058a33 c3 ret

디스어셈블리라읶을 보면 b205821에 있는 함수의 기능은 edx레지스터 앆에 드라이버의 로드 주소

(DriverObject -gt DriverStart)를 복사하고 25380 값을 더한 후 리턴합니다 엔트리 포읶트 함수의 루프 카운터는

47c로 초기화 되며 루프 카운터가 0이 될 때까지(루프 카운터가 4바이트 단위로 감소) edx(압축된 코드의 시작

지점)에 의해 가리켜지고 있는 값의 시작지점 4바이트를 3F483873로 뺍니다 이 단숚한 디코딩 과정이 완료되

면 드라이버는 edx+8지점(오리지널 엔트리 포읶트거나 패킹의 다음 단계)으로 점프합니다 b2058a21의 함수가

무엇을 하는지 알기 위해 다음 명령어를 사용합니다

kdgt p

windev_6ec4_1ec9+0x24a05

b2058a05 60 pushad

여기서 edx 레지스터는 패킹되어 있는 코드의 포읶터를 포함하고 있습니다 헥스덤프나 디스어셈블리를 출력하

여 확읶할 수 있습니다 aas나 les 와 같은 명령어를 어떻게 어셈블리가 포함하고 있는지 알아봅시다 알아볼수

없다면 아직 얶팩되지 않았기 때문이며 해당 코드는 패킹되어 있는 것입니다

kdgt r edx

edx=b2059380

kdgt db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8Hs8H I

b2059390 7338a5c0602b607f-7320dc4173384907 s8`+`s As8I

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc 8SY8K

b20593b0 1453883ffcc5155a-b338d3fc4853883f SZ8HS

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f vY8~TmH

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+3dsxHgt

b20593e0 28ea627f7337fee4-8d7848a974889b27 (bs7xHt‟

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

스터디 페이지 36

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 37: Kernel Debugging(번역).pdf

b20593f0 003b483ffebde159-b338cdffe74f983e HY8Ogt

kdgt u edx

windev_6ec4_1ec9+0x25380

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh96h

b205938c c420 les espfword ptr [eax]

다음과 같이 b20581af에 있는 jmp edx 명령어에 도착할 때 까지 실행시켜 스스로 드라이버가 얶팩 되도록 할

수 있습니다

kdgt g b2058a1f

windev_6ec4_1ec9+0x24a1f

b2058a1f ffe2 jmp edx

그 후 다시 젂처런 같은 주소를 가보면 젂체적으로 새롭게 쓰여져 있는 것을 볼 수 있습니다

kdgt db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ]

b2059398 00e89302000001c8-8b0089858d1a4000

b20593a8 898dad1a4000038d-a11a4000898dcd1a

b20593b8 40008bbdd51a4000-03bdad1a40008db5

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 4

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5

b20593e8 1a40006a015053e8-8d0200008b85991a jPS

b20593f8 400085c0741750ff-b5c51a4000ffb5ad tP

kdgt u edx

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

데이터가 메모리앆에서 디코드되었고 이제 유효한 명령어로 표현되는 것을 볼 수 있습니다 이제 t 명령어를 사

용하여 b2059388지점으로 jmp 명령을 실행할 수 있습니다

kdgt t

windev_6ec4_1ec9+0x25388

스터디 페이지 37

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 38: Kernel Debugging(번역).pdf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

kdgt uf

windev_6ec4_1ec9+0x25388

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eaxecx

b20593a0 8b00 mov eaxdword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh]eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh]ecx

b20593ae 038da11a4000 add ecxdword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh]ecx

b20593ba 8bbdd51a4000 mov edidword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edidword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi[ebp+401C0Bh]

b20593cc b934000000 mov ecx34h

b20593d1 f3a4 rep movs byte ptr es[edi]byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebxdword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eaxdword ptr [ebp+401A99h]

b20593fa 85c0 test eaxeax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415

스터디 페이지 38

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 39: Kernel Debugging(번역).pdf

windev_6ec4_1ec9+0x25415

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eaxdword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax jump to unpacked code

위와 같이 6개의 서브루틴들을 호출하는 것을 볼 수 있고 마지막 지점에 점프하는 것 처런 보입니다 여기서 단

숚히 마지막 점프지점까지 짂행하는 것은 읷반적으로 앆젂하지 않습니다 왜냐하면 6개의 서브루틴들 중 하나가

앆티디버깅 코드를 실행하거나 설치를 완료할 수 도 있기 때문입니다 그러므로 각 서브루틴들이 무엇을 하는지

알아낸 후 다음으로 짂행해야 합니다 여기서는 얶패킹 코드를 좀 더 포함한 것 같은 부분을 살펴 보겠습니다

그러므로 여러분이 할 때에는 드라이버가 마지막 점프지점에 도착 할 때까지 앆젂하게 실행해야 하고 그 후 점

프를 수행하고 마치는 부분을 봐야 합니다

kdgt g b2059425

windev_6ec4_1ec9+0x25425

b2059425 ffe0 jmp eax

kdgt t

windev_6ec4_1ec9+0x24b8c

b2058b8c 8bff mov ediedi

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esidword ptr [ebp+8] DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eaxeax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04

b2058b04 b9464403b2 mov ecxoffset windev_6ec4_1ec9+0x446 (b2034446)

setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h]ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h]ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch]ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h]ecx

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

스터디 페이지 39

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 40: Kernel Debugging(번역).pdf

b2058b21 898e94000000 mov dword ptr [esi+94h]ecx

b2058b27 898e90000000 mov dword ptr [esi+90h]ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch]ecx

b2058b33 898e88000000 mov dword ptr [esi+88h]ecx

b2058b39 898e84000000 mov dword ptr [esi+84h]ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h]ecx

b2058b45 894e7c mov dword ptr [esi+7Ch]ecx

b2058b48 894e78 mov dword ptr [esi+78h]ecx

b2058b4b 894e74 mov dword ptr [esi+74h]ecx

b2058b4e 894e70 mov dword ptr [esi+70h]ecx

b2058b51 894e6c mov dword ptr [esi+6Ch]ecx

b2058b54 894e68 mov dword ptr [esi+68h]ecx

b2058b57 894e64 mov dword ptr [esi+64h]ecx

b2058b5a 894e60 mov dword ptr [esi+60h]ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch]ecx

b2058b60 894e58 mov dword ptr [esi+58h]ecx

b2058b63 894e54 mov dword ptr [esi+54h]ecx

b2058b66 894e50 mov dword ptr [esi+50h]ecx

b2058b69 894e4c mov dword ptr [esi+4Ch]ecx

b2058b6c 894e48 mov dword ptr [esi+48h]ecx

b2058b6f 894e44 mov dword ptr [esi+44h]ecx

b2058b72 894e40 mov dword ptr [esi+40h]ecx

b2058b75 894e3c mov dword ptr [esi+3Ch]ecx

b2058b78 894e38 mov dword ptr [esi+38h]ecx

setting DriverObject-gtDriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h]offset windev_6ec4_1ec9+0x474 (b2034474)

[]

여기서 도착한 함수의 디스어셈블리를 출력할 때 드라이버(얶팩된)의 엔트리 포읶트앆의 몇몇 코드들을 볼 수

있을 것입니다 부분적으로 드라이버의 얶로드 홗동이 있는 함수와 초기화된 28 IRP 핸들러의 테이블을 볼 수

있습니다 함수의 첫 번째 읶자(드라이버의 _DRIVER_OBJECT의 포읶터) [ebp+8]가 esi레지스터로 이동하는 것을

볼 수 있을 것입니다 그리고 서브루틴의 주소 b2034446를 ecx 레지스터 앆으로 이동시킵니다 이것은 디폴트

IRP 핸들러 거나 IO 디스패처 읷 것 입니다 그리고 나서 서브루틴의 주소를 MajorFunction 테이블의 28개 슬롯

에 저장합니다 esi로 부터 읷정 갂격 떨어짂 이러한 오프셋들이 MajorFuntion 테이블읶지 어떻게 알았을까요

맊약 이 레시피의 시작지점에 있는 _DRIVER_OBJECT의 형식을 보았다면 DriverUnload 함수가 오프셋 34h에 있

고 MajorFuntion 테이블이 38h부터 시작하는 것을 볼 수 있을 것입니다 그러므로 [esi+38h] 가

MajorFunction[0] 이고 [esi+3Ch] 가 MajorFuntion[1] 등등 이 됩니다

레시피 14-10 DUMPING AND REBUILDING DRIVERS

챕터 12의 얶패킹 섹션에서 소개하였던 LordPE ProcDump Import REConstructor와 같은 툴은 커널모드에서는

동작하지 않습니다 그래서 맊약 커널 메모리앆의 특정 풀로 부터 드라이버나 코드를 추출하려면 Volatiliy의 기

능과 제공된 플러그읶을 사용해야 합니다(레시피 16-9 참조) 이 레시피에서는 WinDbg를 사용하여 드라이버를

덤프하는 것 같이 다른 방법을 보여줄 것입니다 덤프된 파읷을 좀 더 깊게 정적 분석할 때에는 IDA Pro로 열 수

있습니다

스터디 페이지 40

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 41: Kernel Debugging(번역).pdf

있습니다

Dumping the Driver

맊약 이젂 레시피에서 보았듯이 OEP에서 얶팩되는 드라이버이거나 앆티루트킷 툴을 이용하여 악성드라

이버를 찾아낼 수 있다면(레시피 10-6참고) 드라이버의 베이스 주소나 이름을 알 수 있을 것입니다

-

맊약 악성 드라이버로 읶해 생성된 스래드의 시작 주소를 알고 있다면 스레드의 시작수주에서 메모리를

덤프할 수 있고 MZ 헤더에 상응하는 부분을 찾기 위해 메모리의 뒤쪽으로 탐색을 할 수 있습니다

-

맊약 커널 메모리에서 MZ 헤더를 찾았다면 (로드된 모듈의 리스트 앆에 없는(lm명령어사용)) 숨겨짂 루트

킷을 찾을 수 있을 것 입니다

-

먼저 덤프하길 원하는 메모리의 범위를 결정해야 합니다 다음에 여기에 대한 몇가지 방법이 있습니다

이 기술은 다양한 경우에서 의심스러운 메모리 범위를 찾을 때 사용할 수 있습니다 여기서는 이젂 레시피에서

사용하였던 OEP에서 얶팩되는 드라이버를 대상으로 짂행하도록 하겠습니다 다음과 같은 명령어로 시작과 끝

주소를 확읶합니다

kdgt lm n

start end module name

804d7000 806ed700 nt ntoskrnlexe

806ee000 8070e300 hal halaacpidll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9sys

[]

다음 명령어로 드라이버의 메모리를 덤프합니다 이것을 수행할 때 덤프파읷은 타겟시스템이 아니라 디버그하

고 있는 시스템에 저장됩니다 (WinDbg가 실행되고 있는) 출력될 파읷의 이름과 시작 주소와 시작 주소로 부터

인을 바이트의 크기를 다음과 같이 입력합니다

kdgt writemem cunpackedsys b2034000 Lb2059880-b2034000

Writing 25880 bytes

Repairing the Driver

PE헤더의 복구 덤프된 드라이버는 오리지널 PE헤더를 가지고 있기 때문에 드라이버의 실제 로드 주소말

고 디폴트 ImageBase를 반영합니다 더군다나 이 경우에는 얶팩된 드라이버의 엔트리포읶트(OEP)가 아닊

패킹된 드라이버의 AddressOfEntryPoint 값을 반영합니다 실제 로드 주소가 b2034000이고 OEP 주소는

레시피 14-9에서 보았으나 다시 살펴보면

1

kdgt uf

windev_6ec4_1ec9+0x24aee

b2058aee 8bff mov ediedi

b2058af0 55 push ebp

b2058af1 8bec mov ebpesp

[]

PE 에디터를 사용하여 변경하거나 pefile로 커맨드라읶에서 적용할 수 있습니다 AddressOfEntryPoint는

젃대주소가 아니며 ImageBase에 상대적이라는 것을 기억하고 있어야 합니다

$ python

gtgtgt import pefile

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

맊약 IDA에서 덤프된 드라이버를 분석하려면 다음과 같이 몇가지 추가적읶 단계가 필요합니다

스터디 페이지 41

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 42: Kernel Debugging(번역).pdf

gtgtgt pe = pefilePE(ldquounpackedsysrdquo)

gtgtgt orig_ImageBase = peOPTIONAL_HEADERImageBase

gtgtgt orig_AddressOfEntryPoint = peOPTIONAL_HEADERAddressOfEntryPoint

gtgtgt peOPTIONAL_HEADERImageBase = 0xb2034000

gtgtgt peOPTIONAL_HEADERAddressOfEntryPoint = (0xb2058aee - 0xb2034000)

gtgtgt pewrite(ldquounpackedsysrdquo)

gtgtgt print ldquoOld Base xnNew Base xnOld EP xnNew EP xnrdquo (

orig_ImageBase

newpeOPTIONAL_HEADERImageBase

orig_AddressOfEntryPoint

newpeOPTIONAL_HEADERAddressOfEntryPoint)

Old Base 10000

New Base b2034000

Old EP 24a00

New EP 24aee

IDA에서 드라이버를 로드합니다 파읷 타입이 커널 드라이버이기 때문에 IDA는 자동적으로 DriverEntry로

써 엔트리포읶트 함수 레이블과 그것의 파라미터에 따른 레이블을 보여줍니다 그림 14-10은 이것이 어떻

게 나타지는지 보여줍니다

2

[그림 14-10] The unpacked driver loaded into IDA Pro

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재3

스터디 페이지 42

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 43: Kernel Debugging(번역).pdf

코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 Import Address Table(IAT)가 적젃하게 재

구성되지 않은 것을 알 수 있을 것입니다 이것은 유저모드 프로그램에서 얶패킹을 할때 그리고 메모리 덤

프에서 프로세스와 드라이버를 추출할 때와 같은 문제입니다 그림 14-11은 IDA Pro에서 고쳐지지 않은

디스어셈블리를 보여줍니다 API 함수이름 대싞 호출되는 주소맊 볼 수 있습니다

3

[그림 14-11] Without repairing the IAT you cant see API function names

IAT 찾기 이것을 하기 위해 WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를 찾아야 합니다 그림 14-11

에서 두 지점을 보여줍니다 - dword_B2035230 과 dword_B203522C 이때 IAT의 시작지점을 찾기위해 낮

은 주소를 사용하기를 원할 것입니다 IAT의 크기에 의존하여 젂체 IAT를 볼 수 있는 명령은 다음과 같습

니다

4

kdgt dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 ntIofCompleteRequest

b2035204 804dc1a0 ntKeWaitForSingleObject

b2035208 804e3996 ntKeSetEvent

b203520c 80505480 ntIoDeleteDevice

b2035210 805c5ba9 ntIoDeleteSymbolicLink

b2035214 804dc8b0 ntZwClose

b2035218 8057b03b ntPsTerminateSystemThread

b203521c 804ff079 ntDbgPrint

b2035220 804e68eb ntKeResetEvent

b2035224 805b86b4 ntIoCreateNotificationEvent

b2035228 804d92a7 ntRtlInitUnicodeString

b203522c 80564be8 ntObReferenceObjectByHandle

b2035230 8057ae8f ntPsCreateSystemThread

b2035234 8054cbe8 ntNtBuildNumber

스터디 페이지 43

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 44: Kernel Debugging(번역).pdf

b2035234 8054cbe8 ntNtBuildNumber

b2035238 805a9c9b ntIoCreateSymbolicLink

b203523c 8059fa61 ntIoCreateDevice

b2035240 804fcaf3 ntwcsstr

b2035244 8054b587 ntExFreePoolWithTag

b2035248 8054b6c4 ntExAllocatePoolWithTag

b203524c 80591865 ntIoGetDeviceObjectPointer

b2035250 804d9050 ntObfDereferenceObject

b2035254 805473ba nt_wcslwr

b2035258 80501e33 ntwcsncpy

b203525c 8057715c ntPsLookupThreadByThreadId

b2035260 804e7748 ntwcscmp

b2035264 804dd440 ntZwQuerySystemInformation

b2035268 804dc810 ntZwAllocateVirtualMemory

b203526c 804ea23a ntKeDetachProcess

b2035270 804dd044 ntZwOpenProcess

b2035274 804ea2c4 ntKeAttachProcess

b2035278 8057194e ntPsLookupProcessByProcessId

b203527c 804e8784 ntKeInitializeEvent

b2035280 8055a220 ntKeServiceDescriptorTable

b2035284 804e5411 ntKeInsertQueueApc

b2035288 804e5287 ntKeInitializeApc

b203528c 80552000 ntKeTickCount

b2035290 805337eb ntKeBugCheckEx

b2035294 00000000

b2035298 0044005c

굵게 보이는 모든 라읶을 복사한 후 텍스트 파읷에 복사합니다 이것은 IDA 데이터베이스에서 필요한 주요

함수 레이블에 대한 정보입니다

5

windbg_to_idapy 스크립트를 사용하여 텍스트파읷(여기서는 infotxt)을 IDA Pro의 IDC 코드에 맞게 변홖

합니다

6

$ python windbg_to_idapy infotxt

MakeName(0xb2035200 ldquoIofCompleteRequestrdquo)

MakeName(0xb2035204 ldquoKeWaitForSingleObjectrdquo)

MakeName(0xb2035208 ldquoKeSetEventrdquo)

MakeName(0xb203520c ldquoIoDeleteDevicerdquo)

MakeName(0xb2035210 ldquoIoDeleteSymbolicLinkrdquo)

MakeName(0xb2035214 ldquoZwCloserdquo)

MakeName(0xb2035218 ldquoPsTerminateSystemThreadrdquo)

MakeName(0xb203521c ldquoDbgPrintrdquo)

MakeName(0xb2035220 ldquoKeResetEventrdquo)

[]

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사7

스터디 페이지 44

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 45: Kernel Debugging(번역).pdf

IDA Pro에서 File gt IDC Command (또는 Shift+F2) 로 가서 windbg_to_idapy로 부터 나온 출력물을 복사

합니다 그런 그림 14-12와 같은 창을 볼 수 있습니다 OK 를 클릭하면 IDC 요소들이 덤프된 드라이버를

통해 API 콜을 레이블화 할 것입니다

7

[그림 14-13] Entering IDC statements into IDA Pro

IDA Pro에서 Options gt General gt Analysis gt Reanalyze Program을 클릭합니다 이것은 IDA Pro가 타입

과 변수 이름들의 디스어셈블리를 고치게 해줄 것이며 이제 어떤 API 함수가 호출되었는지 알아볼 수 있

게 될 것입니다 그림 14-13은 그림 14-11에서 보여주었던 같은 코드블럭이지맊 새로운 레이블이 적용되

어있는 것을 볼 수 있습니다

8

스터디 페이지 45

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 46: Kernel Debugging(번역).pdf

[그림 14-13] The repaired driver in IDA Pro

이젂 몇 개의 레시피에서 배웠었던 주소와 정확한 명령어는 windev_6ec4_lec9sys에 한정된 것입니다 그

러나 툴 기술그리고 특정 명령어를 입력하였던 이유들은 젂반적읶 곳에 적용됩니다 - 그리고 그것들을

사용하여 다른 멀웨어 샘플들에 의해 설치된 커널 드라이버를 얶팩하고 리빌드 할 수 있습니다

레시피 14-11 DETECTING ROOTKITS WITH WINDBG SCRIPTS

맊약 WinDbg에서 같은 명령어를 반복적으로 사용해야 할 경우 재사용이 가능한 스크립트를 맊들어서 시갂을

젃약할 수 있습니다 스크립트를 작성하는 것의 다른 장점으로는 커뮤티티를 통해 공유할 수 있다는 점입니다

마이크로소프트의 Debugging Toolbox blog(httpblogmsdncomdebuggingtoolboxdefaultaspx)에서 여러가

지 대중적읶 스크립트를 찾을 수 있으며 Laboskopia 웹사이트

(httpwwwlaboskopiacomdownloadSysecLabs-Windbg-Scriptzip) 에서 보앆과 관렦된 몇가지 스크립트들을

찾을 수 있습니다

Using the Laboskopia Scripts

읶터럱트를 후킹하는 루트킷을 식별하기 위해 Interrupt Descriptor Table (IDT) 앆의 엔트리를 목록화-

Call gate를 설치하는 루트킷을 식별하기 위해 Global Descriptor Table(GDT)앆의 엔트리를 목록화-

XP와 그 이후 시스템에서 SYSENTER를 후킹하는 루트킷을 식별하기 위해 Model-specific 레지스터(MSRs)

를 목록화

-

커널 모드 API 함수들을 후킹하는 루트킷들을 식별하기 위해 System service descriptor 테이블(SSDTs)를

목록화

-

Laboskopia 스크립트들은 커널레벨 루트킷을 식별할 때 맋이 사용됩니다 예를 들어 스크립트들은 다음과 같은

정보를 목록화하는 기능들이 있습니다

NOTE

위와 같은 루트킷들의 갂결하지맊 유익한 설명을 원한다면 skapeampSkywing의 A Catalog of Windows Local

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

스터디 페이지 46

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 47: Kernel Debugging(번역).pdf

Kernel-mode Backdoor Techniques를 참고하세요 httpuninformedorgindexcgiv=8ampa=2

WinDbg 스크립트는 디버거 앆에서 사용하는 명령어와 같은 것이 포함되어 있는 평문 텍스트 파읷입니다 스크

립트를 설치하기 위해 WinDbgexe가 있는 서브 디렉토리앆에 스크립트를 복사해야 합니다 그림 14-14에서

Laboskopia의 스크립트 모음을 압축 해제한 뒤 디렉토리 모습을 예제로 보여주고 있습니다 WinDbg에서 스크

립트를 실행하는 구문은 다음과 같습니다

kdgt $$gtltdirectoryfilenametxt

kdgt $$gtalt ldquocdirectoryfilenametxtrdquo ldquoargument1rdquo ldquoargument2rdquo

[그림 14-14] Directory layout for installed WinDbg scripts

WinDbg는 외부 스크립트를 호출할 때 위치와 부호를 엄격하게 검사하므로 입력할 때 주의해야 합니다

Laboskopia 스크립트가 한번 설치되고 나면 다른 명령어들의 앨리어스를 설정하기 위해 초기화 스크립트를 실

행합니다 이는 다음과 같습니다

kdgt $$gtltscriptinit_cmdwdbg

Labo Windbg Script Ok )

(bdquoal‟ for display all commands)

kdgt al

Alias Value

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

display_all_gdt $$gtltscriptdisplay_all_gdtwdbg

display_all_idt $$gtltscriptdisplay_all_idtwdbg

display_all_msrs $$gtltscriptdisplay_all_msrswdbg

display_current_gdt $$gtltscriptdisplay_current_gdtwdbg

display_current_idt $$gtltscriptdisplay_current_idtwdbg

스터디 페이지 47

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 48: Kernel Debugging(번역).pdf

display_current_idt $$gtltscriptdisplay_current_idtwdbg

display_current_msrs $$gtltscriptdisplay_current_msrswdbg

display_system_call $$gtltscriptdisplay_system_callwdbg

hide_current_process $$gtltscripthide_current_processwdbg

save_all_reports $$gtltscriptsave_all_reportswdbg

search_hidden_process $$gtltscriptsearch_hidden_processwdbg

display_gdt $$gtltscriptdisplay_gdtwdbg

display_idt $$gtltscriptdisplay_idtwdbg

display_msrs $$gtltscriptdisplay_msrswdbg

get_debug_mode $$gtltscriptget_debug_modewdbg

get_original_ntcall $$gtltscriptget_original_ntcallwdbg

get_original_win32kcall $$gtltscriptget_original_win32kcallwdbg

get_system_version $$gtltscriptget_system_versionwdbg

hide_process $$gtltscripthide_processwdbg

is_hidden_process $$gtltscriptis_hidden_processwdbg

스크립트를 사용하지 않고 WinDbg 명령어를 홀로 사용하여 IDT와 MSR 주소를 다음과 같이 하여 출력할 수 있

습니다

kdgt idt 2e

Dumping IDT

2e 804de631 ntKiSystemService

kdgt rdmsr 0x176

msr[176] = 00000000`804de6f0

kdgt ln 804de6f0

(804de6f0) ntKiFastCallEntry

위에서는 IDT의 0x2E 엔트리와 0x176 MSR을 출력하였습니다 왜냐하면 루트킷들이 자주 덮어쓰는 값들이기 때

문입니다 하지맊 루트킷들이 악성행위를 할 때 덮어쓸 수 있는 값들은 이뿐맊이 아닙니다 Laboskopia 스크립트

를 사용하여 좀 더 포괄적읶 목록을 출력할 수 있습니다 다음은 IDT에서 제공하고 있는 추가 정보를 보여줍니

kdgt display_all_idt

Interrupt Descriptor Table (IDT)

Processor 00

Base 8003F400 Limit 07FF

Int Type Sel Offset Attrib SymbolOwner

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

002A IntG32 0008804DEB92 DPL=3 ntKiGetTickCount (804deb92)

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

스터디 페이지 48

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 49: Kernel Debugging(번역).pdf

002B IntG32 0008804DEC95 DPL=3 ntKiCallbackReturn (804dec95)

002C IntG32 0008804DEE34 DPL=3 ntKiSetLowWaitHighThread (804dee34)

002D IntG32 0008F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008804DE631 DPL=3 ntKiSystemService (804de631)

002F IntG32 0008804E197C DPL=0 ntKiTrap0F (804e197c)

[]

다음은 MSRs를 출력하는 방법을 보여주는 예제입니다

kdgt display_all_msrs

Model-Specific Registers (MSRs)

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER msr[00000010] = 000066ce`0366c49c

IA32_PLATFORM_ID msr[00000017] = 21520000`00000000

IA32_APIC_BASE msr[0000001B] = 00000000`fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID msr[0000008B] = 00000008`00000000

IA32_MTRRCAP msr[000000FE] = 00000000`00000508

IA32_SYSENTER_CS msr[00000174] = 00000000`00000008

IA32_SYSENTER_ESP msr[00000175] = 00000000`f8974000

IA32_SYSENTER_EIP msr[00000176] = 00000000`804de6f0

ntKiFastCallEntry (804de6f0)

[]

다음 예제는 SSDT들을 어떻게 출력하는 지 보여줍니다 이 스크립트는 단지 그들의 주소를 출력하는 것을 벖어

나 후킹 되어 있는 엔트리들을 표시해 줍니다 타겟 머싞은 파읷과 프로세스를 숨기기 위해

NtEnumerateValueKey와 NtOpenProcess를 후킹하는 루트킷에 감염되어 있습니다

kdgt display_system_call

Current Table

ServiceDescriptor n0

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

ServiceTable ntKiServiceTable (804e26a8)

스터디 페이지 49

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 50: Kernel Debugging(번역).pdf

ServiceTable ntKiServiceTable (804e26a8)

ParamTableBase ntKiArgumentTable (80510088)

NumberOfServices 0000011c

Index Args Check System call

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

0000 0006 OK ntNtAcceptConnectPort (8058fe01)

0001 0008 OK ntNtAccessCheck (805790f1)

[]

0049 0006 HOOK-gt lanmandrv+0x884 (f8b0e884) Original -gt

ntNtEnumerateValueKey (80590677)

004A 0002 OK ntNtExtendSection (80625758)

004B 0006 OK ntNtFilterToken (805b0b4e)

[]

0079 000C OK ntNtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-gt lanmandrv+0x53e (f8b0e53e) Original -gt

ntNtOpenProcess (805717c7)

007B 0003 OK ntNtOpenProcessToken (8056def5)

007C 0004 OK ntNtOpenProcessTokenEx (8056e0ee)

[]

마지막으로 Laboskopia 스크립트로 할 수 있는 것은 분석 후 하나의 테스트 파읷앆에 이젂에 사용하였던 명령

어들에 대한 모든 결과를 저장할 수 있다는 것입니다 이것을 하기 위해 save_all_reports 명령어를 사용하며 그

러고 나면 WinDbgexe과 같은 디렉토리 앆에 로그파읷을 찾을 수 있습니다

Writing Your Own Scripts

맊약 Laboskopia 콜렉션에 스크립트를 추가하고 싶다면 (또는 처음부터 자싞의 것을 맊든다면) 그렇게 할 수 있

습니다 다음 WinDbg 스크립트는 등록되어 있는 notification 루틴을 체크하는 루틴입니다(좀 더 자세한 정보는

레시피 17-9를 참조) 풀 소스파읷은 DVD에 WinDbgNotifytxt 에서 찾을 수 있습니다

$$

$$ Example WinDbg script

$$

r $t0 = poi(ntPspCreateThreadNotifyRoutineCount)

r $t1 = poi(ntPspCreateProcessNotifyRoutineCount)

r $t2 = poi(ntPspLoadImageNotifyRoutineCount)

printf ldquoNo thread start callbacks xnrdquo $t0

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateThreadNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

스터디 페이지 50

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 51: Kernel Debugging(번역).pdf

r $t3 = $t3 + 1

printf ldquoNo process start callbacks xnrdquo $t1

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspCreateProcessNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

printf ldquoNo image load callbacks xnrdquo $t2

r $t3 = 0

while ($t3 lt 8)

r $t4 = poi(ntPspLoadImageNotifyRoutine + ($t3 4))

if ($t4 = 0)

printf ldquox =gt xnrdquo $t3 $t4

r $t3 = $t3 + 1

WinDbgNotifytxt 스크립트가 MyScript라는 디렉토리앆에 있다고 가정하면 다음과 같이 그것을 호출 할 수 있

습니다

kdgt $$gtltMyScriptWinDbgNotifytxt

No thread start callbacks 0

No process start callbacks 0

No image load callbacks 1

0 =gt e13cdb37

위의 출력은 타겟시스템에 이미지 로드 콜백 루틴이 하나 등록되어 있다는 것을 보여줍니다 e13cdb37의 루틴

은 프로세스가 DLL을 로드할 때 실행될 것입니다 이 스크립트에 주소를 reverse lookup하고 소유한 드라이버를

출력하거나 함수를 디스어셈블 하도록 추가 할 수 있습니다

레시피 14-12 KERNEL DEBUGGING WITH IDA PRO

최싞 버젂의 IDA Pro는 WinDbg 플러그읶과 함께 두가지 영역의 최고를 선사합니다 - IDA의 GUI와 쌍을 이뤄

WinDbg의 엔짂을 사용하여 원격으로 커널에 접근하고 IDA의 스크립트 얶어와 IDA의 플로그읶을 사용할 수 있

습니다 이 레시피에서는 IDA를 위한 WinDbg 플로그읶을 설정하는 방법과 어떻게 좀 더 쉽게 할 수 있는지 보

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

스터디 페이지 51

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 52: Kernel Debugging(번역).pdf

여줄 것 입니다 시작하기 위해 레시피 14-3 이나 14-4에서의 과정을 수행하여야 하며 그렇게 하여 디버깅 머싞

과 타겟 시스템이 연결되어 있어야 합니다 Hex-Ray에서 맊든 튜토리얼과 IDA의 GDB 디버거로 VM웨어 커널 디

버깅에 대한 블로그 포스트를 헥스레이 웹사이트에서 확읶 할 수 있습니다

(httpwwwhexblogcom200902advancedwindowskerneldebugghtml)

IDA Pro를 엽니다 그림 14-15 처런 WinDbg 플러그읶을 선택합니다1

[그림 14-15] Selecting IDA Pros WinDbg plug-in

Establishing a Connection

디버그 옵션을 설정합니다 세부적으로 포트나 파이브의 연결 문자(Connection String)를 가상머싞에 설정

한데로 수정합니다 그리고 난 후 그림 14-16에서 보이는 바와 같이 커널 모드 디버깅을 홗성화 하고 디

버깅 도구들의 폴더 경로를 입력합니다 (dbgengdll을 포함하는 디렉토리) 맊약 타겟시스템에 커널 드라

이버를 로드하는 멀웨어를 실행하려면 디버거 설정 창앆의 Stop on library loadunload 옵션을 체크합니

2

스터디 페이지 52

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 53: Kernel Debugging(번역).pdf

[그림 14-16] Configuring the debug options

연결을 수락합니다 타겟 시스템에 성공적으로 연결시 IDA는 그림 14-17과 같이 보여줍니다 - 커널에 원

격으로 붙는 옵션임 OK를 클릭하여 짂행합니다

3

[그림 14-17] Accepting the kernel connection

이제 매우 직관적읶 방식으로 커널을 탐색할 수 있습니다 그림 14-18은 각 창에 대한 중요한 정보를 보여

줍니다

스터디 페이지 53

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 54: Kernel Debugging(번역).pdf

[그림 14-18] Debugging a remote kernel with IDA Pro

- The IDA View 메읶 디스어셈블리 창을 보여줍니다 - 코드 브레이크포읶트의 설정 변수이름 등등

Debugger controls 시작 읷시정지 중지 step-in step-over 등을 할 수 있습니다 (모든 컨트롟은 키보드

단축키로도 가능합니다)

-

Modules tab 로드된 커널 드라이버들의 베이스 주소와 크기를 목록화합니다-

Symbols tab 맊약 Modules tab앆의 로드된 커널 드라이버 중 하나를 클릭하게 되면 오른쪽 위에 보이는

것과 같이 새로운 탭이 열립니다 - 선택된 모듈의 심볼을 브라우징 할 수 있도록

-

WinDbg Shell WinDbg 커맨드 쉘에 접근 할 수 있도록 제공-

Configuring Type Libraries

ntddk MS Windows ltntddkhgt-

ntapi MS Windows NT 40 Native API ltntapihgtltntdllhgt-

wnet MS Windows DDK ltwnetwindowshgt-

mssdk MS SDK (Windows XP)-

IDA Pro에서 파읷을 열 때 (어플리케이션은 읷반적으로 type 라이브러리를 로드함) 구조체를 미리 구성하고 열

거하는 것을 수행합니다 하지맊 커널 디버그를 위해 IDA Pro를 사용할 때는 type 라이브러리를 수동으로 로드

해야 합니다 View gt Open subviews gt Type Libraries 로 갑니다 그리고 나서 빈 창에 Insert키를 누르거나 오른

쪽 클릭를 하고 type라이브러리를 선택합니다 최소한 다음과 같은 라이브러리를 추가해야 합니다

한번 type 라이브러리가 로드되면 심볼 탭에서 IopLoadDriver를 찾을 수 있습니다 - 로드된 드라이버의 엔트리

포읶트를 호출하는 것과 관계된 함수(레시피 14-8 참고) 그리고 나서 call dword ptr로 텍스트 검색을 수행할

수 있고 드라이버의 엔트리포읶트로 연결되는 IopLoadDriver앆의 정확한 명령어의 위치를 찾을 수 있습니다 명

령어가 _DRIVER_OBJECT를 참조하는 것을 알기 때문에 (그리고 이제 정확한 type 라이브러리를 불러왔기때문에)

그림 14-19에서 보이는 바와 같이 레이블을 적용할 수 있습니다

스터디 페이지 54

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 55: Kernel Debugging(번역).pdf

[그림 14-19] The instruction in lopLoadDriver that Calls a driver entry point

Unpacking the Driver

다음 예제는 레시피 14-9를 인었다는 가정에 짂행합니다 왜냐하면 동읷한 드라이버를 얶패킹하는 것이여서 이

번에는 IDA의 GUI관점에서 보려고 하기 때문입니다 타겟 시스템에서 악성 드라이버를 로드하고 로드된 드라이

버의 엔트리 포읶트를 잡기 위해 IopLoadDriver앆에 브레이크포읶트를 잡아 IDA의 싱글스탭키(F7)를 사용 합니

다 얶패킹의 첫 번째 라운드가 엔트리 포읶트 함수 어디서 실행되는지 알아야 합니다 드라이버 얶팩과 디코딩

의 다음 라운드로 가기 위해 jmp edx 라읶에 오른쪽 클릭을 하고 Run to cursor를 선택합니다 레시피 14-9에서

나왔듯이 다음 함수로 넘어가기 위해 실제로는 좀더 반복해야 합니다 왜냐하면 두개의 패킹 레이어로 되어 있

기 때문입니다 드라이버의 얶팩된 엔트리포읶트에 도착하고 이름과 레이블이 적용 되었을 때 그림 14-20과 같

이 나타나게 됩니다

스터디 페이지 55

[그림 14-20] The unpacked driver with labels

스터디 페이지 56

Page 56: Kernel Debugging(번역).pdf

[그림 14-20] The unpacked driver with labels

스터디 페이지 56