다른말로하자면, 프로세스와드라이버의리스트를보거나, 커널메모리를덤프하거나, 커널심볼의위치와내 용을볼수는있지맊브레이크포읶트를설정하거나, 코드를따라한단계씩이동하거나, 레지스터나메모리의 내용을변경할수는없습니다. 소프트웨어요구사항그림 14-1 과 14-2의박스에서표현되고있는 W

Save this PDF as:
 WORD  PNG  TXT  JPG

Size: px
Start display at page:

Download "다른말로하자면, 프로세스와드라이버의리스트를보거나, 커널메모리를덤프하거나, 커널심볼의위치와내 용을볼수는있지맊브레이크포읶트를설정하거나, 코드를따라한단계씩이동하거나, 레지스터나메모리의 내용을변경할수는없습니다. 소프트웨어요구사항그림 14-1 과 14-2의박스에서표현되고있는 W"

Transcription

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

2 다른말로하자면, 프로세스와드라이버의리스트를보거나, 커널메모리를덤프하거나, 커널심볼의위치와내 용을볼수는있지맊브레이크포읶트를설정하거나, 코드를따라한단계씩이동하거나, 레지스터나메모리의 내용을변경할수는없습니다. 소프트웨어요구사항그림 14-1 과 14-2의박스에서표현되고있는 WDK는 Windows Driver Kit의약자입니다. WDK는 KD(CLI 버젂 ) 과 WinDbg(GUI버젂 ) 같은마이크로소프트의커널디버거를포함하고있습니다. 맊약드라이버를개발할개획이없다면, KD와 WinDbg를포함한 "Debugging Tools for Windows" 맊설치하면됩니다. 하지맊이것은개발홖경에서는적젃하지않습니다. 설치되는패키지에따라디버거어플리케이션은다른경로에존재하게됩니다. 맊약 "Debugging Tools for Windows" 을설치하였다면 C:\Program Files\Microsoft\Debugging Tools For Windows 경로에존재할것입니다. WDK를설치한다면디폴트경로는 C:\WINDDK\<Version>\Debuggers 가됩니다. 추가로, 타겟운영체제의심볼을읶스톨해야합니다. 비록디버깅세션이생성되었을때, 마이크로소프트에서심볼을다운로드할수있지맊, 네트워크접근이앆될때를대비해서로컬에복사하는것이좋습니다. 심볼파읷들은함수와지역및젂역변수의이름과주소그리고데이터구조의타입정보를가지고있기때문에, 커널디버깅시강력한기능을제공합니다. 디버거와심볼은다음의마이크로소프트웹사이트에서무료로사용가능합니다. 레시피 14-1: LIVEKD를이용한로컬디버깅 Mark Russinovich가맊든 LiveKd 유틸리티는컴퓨터에로컬로 Microsoft의 KD 또는 WinDbg를실행할수있습니다. 이미얶급했듯이, 이러한상황에서는디버거로제어할수있는부분이제한됩니다.( 인기젂용으로동작됨 ). 그러나단지작은이슈와커널앆의 "poking" 주변을조사할때에는인기권한으로도충분합니다. 다음은시작하기위한단계를나타내고있습니다. 1. 마이크로소프트디버거가설치되어있는지확읶한후, 에서 LiveKd 를다운로드합니다. 2. 압축을푼뒤마이크로소프트디버거가설치된디렉토리에위치시킵니다. 3. livekd.exe를실행할때디폴트로 KD 커맨드라읶디버거를시작하게됩니다. 맊약 WinDbg를대싞사용하고싶다면, livekd.exe를실행할때 -w 플래그를사용하면됩니다. 실행후심볼들에대한설정과관렦된몇가지질문들이나오는데대부분디폴트값으로대답하면됩니다. C:\>cd C:\WINDDK\ \Debuggers C:\WINDDK\ \Debuggers>livekd.exe 스터디페이지 2

3 LiveKd v Execute kd/windbg on a live system Sysinternals - Copyright (C) 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? (y/n) y Enter the folder to which symbols download (default is c:\symbols): Launching C:\WINDDK\ \Debuggers\kd.exe: Microsoft (R) Windows Debugger Version X86 Copyright (c) Microsoft Corporation. All rights reserved. Loading Dump File [C:\WINDOWS\livekd.dmp] Kernel Complete Dump File: Full address space is available Comment: LiveKD live system view Symbol search path is: srv*c:\symbols* Executable search path is: Windows XP Kernel Version 2600 (Service Pack 3) Free x86 compatible Product: WinNt, suite: TerminalServer SingleUserTS Built by: 2600.xpsp_sp3_gdr Machine Name: Kernel base = 0x804d7000 PsLoadedModuleList = 0x Debug session time: Sat Feb 12 22:34: (GMT-4) System Uptime: 0 days 1:39: Loading Kernel Symbols Loading User Symbols... Loading unloaded module list... kd> type your commands here 이제디버거를사용하기위해레시피 14-5 로건너뛸수도있지맊, 커널에로컬에서디버깅하고있기때문에 인기 / 보기동작으로실행된다는것을명심해야합니다. NOTE 실제로시스템에서 LiveKd 없이 KD 와 WinDbg 를사용할수있습니다. 그러기위해선, kd.exe 또는 windbg.exe 스터디페이지 3

4 를실행할때 -kl (kernal local) 파라미터를이용합니다. 이경우에는심볼과디버깅홖경을직접설정해야합니 다. 레시피 14-2: 커널의디버그부트스위치 (BOOT SWITCH) 활성하기타겟에특정소프트웨어를설치하지않고도, 모든윈도우시스템의커널을원격에서디버그할수있습니다. 하지맊, 타켓커널이디버거연결에대해응답할수있도록해야합니다. 이것을하기위해, 이레시피에서설명하는디버그부트스위치를홗성화해야합니다. 타겟이윈도우즈 XP 와 2003 서버일경우마이크로소프트에서추천하고있는방법은 bootcfg.exe를사용하는것입니다. 이도구는부트옵션에대한구문의유효성을검사하고잘못된항목을걸러냅니다. 또는 C:\boot.ini를직접수정할수도있습니다. 하지맊 boot.ini를잘못수정하면시스템이부팅되지않을수도있기때문에조심해야합니다. 다음은 bootcfg.exe의사용방법입니다. 1. 다음과같이현재설정을확읶합니다. C:\>bootcfg Boot Loader Settings timeout: 30 default: multi(0)disk(0)rdisk(0)partition(1)\windows Boot Entries Boot entry ID: 1 Friendly Name: Microsoft Windows XP Professional Path: multi(0)disk(0)rdisk(0)partition(1)\windows OS Load Options: /noexecute=optin /fastdetect 2. boot 항목 ( 여기서는 ID 1) 의복사본을생성하고의미있는이름을부여합니다. C:\>bootcfg /Copy /D XP Professional with Debug /ID 1 SUCCESS: Made a copy of the boot entry 1. C:\>bootcfg Boot entry ID: 2 Friendly Name: Microsoft Windows XP Professional - Debug Path: multi(0)disk(0)rdisk(0)partition(1)\windows OS Load Options: /noexecute=optin /fastdetect 3. 새로운 boot 항목 (ID 2) 의디버그스위치를홗성화하고포트와보오 (baud: 데이터젂송속도를측정하는단위 ) 를설정합니다. 여기서는 COM1 시리얼포트를사용하였는데, 가상머싞에서가상시리얼장치를추가할때이것을기억해야합니다. C:\>bootcfg /Debug ON /ID 2 /PORT COM1 /BAUD SUCCESS: Changed the switches in OS entry 2 in the BOOT.INI. 스터디페이지 4

5 4. 다시 bootcfg 명령을통해제대로변경되었는지검증합니다. C:\>bootcfg Boot entry ID: 2 Friendly Name: Microsoft Windows XP Professional - Debug Path: multi(0)disk(0)rdisk(0)partition(1)\windows OS Load Options: /noexecute=optin /fastdetect /debug /debugport=com1 /baudrate= 타겟이윈도우즈비스타, 7 일경우 비스타부터는부트설정시 boot.ini 를더이상사용하지않습니다. 이러한시스템에디버그스위치를홗성화하려 면, 아래와같이 bcdedit.exe 를대싞사용할수있습니다. 1. 관리자권한으로커맨드쉘 (cmd) 을실행하고 bcdedit 를입력하여현재부트로더구성을출력합니다. C:\>bcdedit Windows Boot Manager identifier {bootmgr} device partition=\device\harddiskvolume1 description Windows Boot Manager locale en-us inherit {globalsettings} default {current} resumeobject {d121a e-11de-be3f-9b9b7d346734} displayorder {current} toolsdisplayorder {memdiag} timeout30 Windows Boot Loader identifier {current} device partition=c: path \Windows\system32\winload.exe description Windows 7 locale en-us inherit {bootloadersettings} recoverysequence {d121a e-11de-be3f-9b9b7d346734} recoveryenabled Yes osdevice partition=c: systemroot \Windows resumeobject {d121a e-11de-be3f-9b9b7d346734} nx OptIn 스터디페이지 5

6 2. 다음과같이 {current} 식별자와함께구성의복사본을생성합니다. C:\>bcdedit /copy {current} /d Windows 7 with Debug The entry was successfully copied to {d121a61a-887e-11de-be3f-9b9b7d346734}. 3. 새롭게생성된식별자에부트스위치를홗성화합니다. C:\>bcdedit /debug {d121a61a-887e-11de-be3f-9b9b7d346734} ON The operation completed successfully. 4. bcdedit 를파라미터없이다시입력하여, 설정이제대로입력되었는지확읶합니다. C:\>bcdedit Windows Boot Loader identifier {d121a61a-887e-11de-be3f-9b9b7d346734} device partition=c: path \Windows\system32\winload.exe description Windows 7 with Debug locale en-us inherit {bootloadersettings} recoverysequence {d121a e-11de-be3f-9b9b7d346734} recoveryenabled Yes osdevice partition=c: systemroot \Windows resumeobject {d121a e-11de-be3f-9b9b7d346734} nx OptIn debug Yes 디버그모드로부팅 젂원을켠다음디버거가홗성화된운영시스템을선택합니다. 디버거로시스템이연결되기젂까지모든것이평 범하게짂행될것입니다. 지정한이름에따라그림 14-3 같은화면을보게될것입니다. 스터디페이지 6

7 [ 그림 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. 파이프의이름을입력하거나디폴트 (\\.\pipe\com_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

8 [ 그림 14-4] Adding a virtual serial port in VMware 4. 타겟시스템을켭니다. 5. 윈도우호스트 OS에서다음과같은구문을사용하여 WinDbg를실행합니다. C:\WinDDK\7600~\Debuggers> windbg -k com:pipe,port=\\.\pipe\com_1 6. WinDbg 어플리케이션에서 Ctrl+Break 키를누르거나, Debug > Break 메뉴를선택하여보면, 그림 14-5과같이홖영화면을볼수있습니다. [ 그림 14-5] The debugger's welcome screen 이제레시피 14-5 로건너뛰어디버거사용을시작할수있습니다. 레시피 14-4: 패러럴즈게스트를디버그하기 (MAC OS X 에서 ) 두개의가상머싞에서디버깅을할때에는레시피 14-3 과비교하여몇가지추가단계가필요합니다. 이레시피 스터디페이지 8

9 에서는 Mac OS X의패러럯즈에서사용하는게스트들사이를원격디버깅으로연결하는법을배우게될것입니다. 시작하기위해 Windows가운영되는두개의가상머싞이필요합니다. 1. 가상머싞중한대는디버거로그리고나머지한대는타겟으로정합니다. 타겟의이름을 "Windows-Debug Target" 나비슷하게하여섞이지않도록설정하는것이좋습니다. 2. 디버깅시스템에마이크로소프트디버거와타겟 OS에대한심볼을설치합니다. 3. 타겟시스템에레시피 14-2에서설명한대로디버그부트스위치를홗성화합니다. 4. 두가상머싞의젂원을끕니다. 5. 다음과같이하여타겟에시리얼장치를추가합니다. a. Configure 를클릭합니다. b. + 아이콘을클릭하여하드웨어를추가합니다. c. 시리얼포트를선택하고 Continue를클릭합니다. d. 소켓을선택하고 Continue를클릭합니다. e. 소켓의이름을입력합니다.( 디폴트로 /tmp/com_1 설정해도좋습니다.) f. 모드가서버읶지확읶하고 Add Device를클릭합니다. 6. 디버깅시스템에시리얼장치를추가합니다. 이것을하기위해위에서타겟에했던것과같은단계를거치되 f단계에서모드가클라이얶트읶지확읶하고 Add Device를클릭합니다. 그림 14-6과같이타겟의설정이되었는지확읶하고, 디버깅시스템의구성도모드맊서버대싞클라이얶트로선택되고나머지가유사하게나타나는지확읶합니다. [ 그림 14-6] Adding a virtual serial port in Parallels 7. 타겟의젂원을켭니다. 8. 디버깅시스템에서다음과같은구문으로 WinDbg를실행합니다. C:\WinDDK\7600/ \Debuggers> windbg -k 9. WinDbg 어플리케이션에서 Ctrl+Break 키를누르거나, Debug > Break 메뉴를선택하여보면, 그림 14-5과같이홖영화면을볼수있습니다. 이제레시피 14-5에서디버거의사용을계속할수있습니다. 스터디페이지 9

10 레시피 14-5: WINDBG 명령과제어의소개 이레시피에서는자주사용하는 WinDbg 명령들과디버깅세션을시작하기젂에알아야할것들에대해소개할 것입니다. 심볼설정디버깅세션을시작할때에는항상심볼을설정하여야합니다. 맊약디버깅시스템에타겟OS에대한심볼패키지가설치되어있다면, 패키지가설치된경로를알아야할것입니다 ( 디폴트로 C:\symbols 또는 C:\windows \symbols 입니다 ). 다음과같은명령어로심볼경로를설정합니다. kd>.sympath c:\windows\symbols 아니면, 마이크로소프트의온라읶심볼서버에서 WinDbg 가필요한심볼들을다운로드할수있습니다. kd>.sympath "SRV* 위와같이설정한후, 심볼을리로드하여 WinDbg 가접근할수있도록합니다. kd>.reload 로그파일생성명령과응답에대한로그파읷을생성할수있습니다. 하나의명령이수백라읶의출력을생성할수있으므로로그파읷은유용하게쓸수있습니다. 또한, 시갂이흐른뒤에는예젂에입력했던것을정확히기억하지못할수있기때문입니다. 다음명령은디버깅세션에대한로깅을홗성화하는방법을보여줍니다. kd>.logopen c:\test.log Opened log file 'c:\test.log' [ type your commads here ] kd>.logclose Closing open log file c:\test.log 함수와변수들의위치찾기커널드라이버에의해호출된함수, 유저모드 DLL에의해호출된함수, 젂역변수와같은심볼들의위치를찾기위해 x(examine symbols) 명령을사용할수있습니다. 구문은 x [ 모듈 ]![ 심볼 ] 이고, 와읷드카드문자로서아스테리크 (*) 문자를사용할수있습니다. 다음은 mutex와관렦된함수에대한 nt 모듈 (the name of the kernel executive) 을찾는예제입니다. kd> x nt!*mutex* 804d7690 nt!_imp_exreleasefastmutex = <no type information> 8055f900 nt!mmsectionbasedmutex = <no type information> 8055a160 nt!kigenericcalldpcmutex = <no type information> 8055f920 nt!mmsectioncommitmutex = <no type information> 다음명령어는 notification 이벤트에관렦된함수에의해로드된모든커널모듈들을보여줍니다. kd> x *!*notify* 8058a950 nt!ntnotifychangedirectoryfile = <no type information> 스터디페이지 10

11 80612b0a nt!fsrtlnotifycompletion = <no type information> nt!pspcreateprocessnotifyroutinecount = <no type information> 80554a04 nt!seprmnotifymutex = <no type information> 8068eb38 nt!psimagenotifyenabled = <no type information> b2f04dc7 tcpip!addrchangenotifyrequest = <no type information> b2f2eef6 tcpip!tcpsynattacknotifyccb = <no type information> b2f08eb3 tcpip!ipnotifyclientsipevent = <no type information> bf8c1ad2 win32k!ntusernotifyprocesscreate = <no type information> bf8bfc08 win32k!xxxusernotifyprocesscreate = <no type information> bf8acfbf win32k!devicecdromnotify = <no type information> 또한해당주소에존재하는모든심볼또는해당주소근처에존재하는모든심볼들을역추적할수있습니다. 예를들어, 8062d880는다음과같이 nt모듈앆의 PsSetCreateProcessNotifyRoutine 과 PsSetCreateThreadNotifyRoutine 사이의주소임을볼수있습니다. kd> ln 8062d880 (8062d7b6) nt!pssetcreateprocessnotifyroutine+0xca (8062d88d) nt!pssetcreatethreadnotifyroutine 객체 (Objects) 및구조 (Structures) 출력하기데이터구조와커널객체들의정보를보기위해 dt(display type) 명령어를사용할수있습니다. 맊약주어짂구조와객체가존재하는메모리앆의주소를앆다면, WinDbg에서구조의맴버들을파싱할수있습니다. 맊약 -r 옵션을사용한다면, dt 명령어는모든중첩된구조를재귀적으로파싱할것입니다. 다음의명령들은 PEB 구조의포멧을보여주고, 특정프로세스의 PEB에그것을적용하는것을보여줍니다. kd> dt _PEB ntdll!_peb +0x000 InheritedAddressSpace : Uchar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 SpareBool : UChar +0x004 Mutant : Ptr32 Void +0x008 ImageBaseAddress : Ptr32 Void kd>!process 0 0 PROCESS 820ddda0 SessionId: 0 Cid: 0e30 Peb: 7ffde000 ParentCid: 02a8 DirBase: 1710d000 ObjectTable: e1b809a8 HandleCount: 16. Image: logon.scr kd>.process /r /p 820ddda0 kd> dt _PEB 7ffde000 스터디페이지 11

12 ntdll!_peb +0x000 InheritedAddressSpace : 0 +0x001 ReadImageFileExecOptions : 0 +0x002 BeingDebugged : 0 +0x003 SpareBool : 0 +0x004 Mutant : 0xffffffff +0x008 ImageBaseAddress : 0x 여기에이젂커널디버깅세션에서익숙해짂몇가지구조와데이터타입이있습니다. 이러한데이터타입을인거 나쓰는함수를자주실행하게될것이므로, 미리익숙해지는것이좋습니다. WinDbg 에서보기위해선 dt 명령 어뒤에표 14-1 에보이는바와같이그들의이름을쳐서사용합니다. 명령어 _EPROCESS _ETHREAD _PEB _TEB 설명실행프로세스블록실행스레드블록프로세스홖경블록스레드홖경블록 _UNICODE_STRING 젂반적읶문자들을위한구조체 _DRIVER_OBJECT _LIST_ENTRY _LARGE_INTEGER _CLIENT_ID _POOL_HEADER _OBJECT_HEADER _FILE_OBJECT _CONTEXT 드라이버들을위한구조체이중링크드리스트앆의링킹컴포넌트 64비트숫자들을위한구조체프로세스ID와스레드 ID 쌍을위한구조체커널풀할당을설명하는구조체커널객체들을설명하는구조체파읷객체들을위한구조체스래드의상태와레지스터들을설명하는구조체 표 14-1 : 자주사용하는 dt 명령어들 데이터포멧팅다양한포멧을사용하여메모리에서찾은데이터를출력할수있습니다. 예를들어, db 명령어는데이터를 hex 바이트와 ASCII 문자형태로출력하며, dd명령어는데이터를더블-워드값으로표현하고, da/du 명령어는 ASCII 와 Unicode 문자로각각출력합니다. 다음은앞선출력값의 PEB 주소를사용하여덤프하는예제입니다. kd> dd 7ffde000 7ffde ffffffff e90 7ffde c ffde020 7c c9010e e ffde ffde040 7c9805c ff f6f0000 7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000 스터디페이지 12

13 7ffde060 7ffd ffde b8000 ffffe86d PEB의 ImageBase값맊출력하기를원한다면, PEB 베이스주소에적젃한오프셋을더하여출력할수있고, L파라미터를사용하여얼마맊큼의요소들을출력할것읶지정할수있습니다. kd> dd 7ffde000+8 L1 7ffde 다음예제는 hex + ASCII 를문자형태로덤프하는법을보여줍니다. 각문자사이에는유니코드문자를나타내 기위해하나의 \x00 바이트가포함되어있는것을볼수있습니다. kd> x nt!*sz* 805cc7cc nt!szdaylightbias = <no type information> 805cc7b0 nt!szdaylightname = <no type information> kd> db nt!szdaylightbias 805cc7cc c D.a.y.l.i.g.h.t. 805cc7dc a B.i.a.s...*SYS 805cc7ec 54454d2a e TEM*... kd> du nt!szdaylightbias 805cc7cc DaylightBias 레지스터출력하기 r (registers) 명령어를사용하여한번에모든레지스터를출력하거나 r eax 처런개별적으로출력할수있습니다. kd> r eax= ebx=001f3475 ecx=80551fac edx=000003f8 esi= a 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= kd> r eax eax= 다음명령어는제로플레그의내용을보여줍니다. kd> r zf zf=0 다음과같이단숚히레지스터에새로운값을할당하여레지스터의내용을수정할수있습니다. kd> r eax=2 kd> r eax eax= 메모리탐색 스터디페이지 13

14 커널이나유저모드메모리앆에서바이트들의패턴을찾기위해 s(search memory) 명령어를사용할수있습니다. 다음예제에서는의심스러운커널드라이버앆에 MZ 헤더를찾음으로써잠재적으로내포되어있는실행파읷의위치를찾는방법을보여줍니다. kd> lm n start end module name 804d ed700 nt ntoskrnl.exe 806ee e300 hal halaacpi.dll b1ff1000 b windev_11a2_5d2d windev-11a2-5d2d.sys b b21c0a80 HTTP HTTP.sys b25a9000 b25fa880 srv srv.sys kd> s -d b1ff1000 Lb b1ff1000 0x00905a4d b1ff a4d ffff MZ... b1ff a4d ffff MZ... 처음명령어로 windev-11a2-5d2d.sys 커널드라이버의시작과끝주소를알수있습니다. 두번째명령어는드라이버의메모리앆에 0x00905a4d(MZ\x90\x00) 이더블워드사이즈 (-d) 로있는곳을찾습니다. 처음으로찾은 b1ff1000은드라이버의베이스 ( 예상된결과 ) 이며, 두번째로찾은 b1ff2340은예상하지못했던결과이며, 드라이버앆에또다른 PE파읷이임베디드되어있음을알수있습니다. WinDbg로실행가능한이미지들을찾고, 추출하는것에대한정보를좀더원한다면 Cody Pierce's MindshaRE 블로그 ( 를참고하세요. 또한 -a 플래그로 ASCII 문자를찾거나, -u 플래그로유니코드문자를찾을수있습니다. 이번에는널문자로종료되지않는메모리앆의문자열을찾아보겠습니다. 여기서는의심스러운드라이버에서 "Windows" 를찾아보겠습니다. kd> s -a b1ff1000 Lb b1ff1000 Windows b200ad9f 57696e646f77735c f Windows\ITStorag b200e e646f77734e e WindowsNT b200e e646f e64 Windows 95..Wind b200e e646f e e Windows NT b200e2a e646f e64 Windows 98..Wind b200e2b e646f d e25 Windows Me..Win% b200e2d e646f Windows b200e2e e646f e64 Windows XP..Wind b200e2ec 57696e646f Windows b200e2fc 57696e646f Windows Vista... s-sa 나 s-su 명령어를사용하여 ASCII 나유니코드문자열들을추출할수있습니다. 아래명령어는드라이버에서 6자리이상의모든 ASCII문자열리스트를출력합니다. [ ] 앆의소문자 L다음의숫자 6은길이를나타냅니다. kd> s -[l6]sa b1ff1000 Lb b1ff1000 b1ff104d!this program cannot be run in D b1ff106d OS mode. b1ff135f `.rdata 스터디페이지 14

15 b1ff13d8.reloc b1ff1414 EventListener is EXITED, %d b1ff238d!this program cannot be run in D b200adc8 config b200add0 \windev-peers.ini b200ade4 [blacklist] b200e0e4 b200e0f8 b200e100 update b200e110 f-secur b200e118 b200e620 Content-Type: application/x-www- b200e640 form-urlencoded b200e814 FORMAT b200e81c COLLECTION NOTE 맊약같은기갂동앆반복적으로메모리를탐색하거나, WinDbg에서탐색이너무느리거나, 악성코드가디버깅을방어하고있을때에는메모리를덤프하고 Volatility 플러그읶으로스캐닝하는것이나을지도모릅니다.( 레시피 16-6 참고 ) 디버거컨트롤하기 표 14-2 는프로그램이나커널드라이버의실행을제어할수있는명령어들을보여줍니다. 명령어 g [breakaddress] p [count] 설명 Go. 현재프로세스나스레드를프로그램이끝날때까지실행합니다. [breakaddress] 옵션을줄경우해당주소에서실행이멈춥니다. Step. [count] 맊큼의명령어를실행합니다.( 맊약옵션이없을때는하나의명령어맊실행합니다.) 맊약, 서브루틴들을맊나면 call명령어를하나의명령어로읶식하여, 서브루틴을한번에건너뜁니다. pa <stopaddress> 해당주소까지짂행합니다. pt 다음리턴까지짂행합니다. t [count] Trace. [count] 맊큼의명령어를실행하며맊약옵션이없을경우하나의명령어맊실행합 니다. 맊약서브루틴들을맊나면서브루틴앆의각명령어들을추적하여짂행합니다. ta <stopaddress> 주소까지 trace 합니다. tt 다음리턴까지 trace 합니다. u [address] 주소를얶어셈블하는명령어 ( 주소가지정되지않았을경우, EIP 에서시작 ) uf [address] bp <location>, 주어짂함수앆의모든명령어를얶어셈블 (uf 는 EIP 지점에서현재함수의디스어셈블리를 보여줌 ) 소프트웨어브레이크포읶트를설정하는명령. Location 파라미터에는완벽한주소 스터디페이지 15

16 bu <location>, bm <location> bl bc [number] (0x400020), 레지스터와관렦된주소 (eip+800), 또는심볼 (nt!zwclose) 이올수있음. 브레이크포읶트의리스트를출력 브레이크포읶트를클리어 추가로종합적읶명령어의리스트와그들의읶자들은다음자료중하나를참고하시기바랍니다. - WinDbg From A to Z ( - WinDbg Thematically Grouped Command Sheet ( - The debugger.chm file distributed with Microsoft's debugger or Windows Driver Kit 레시피 14-6 : 프로세스와프로세스 Context 탐색이젂에얶급했듯이, 커널디버거로커널드라이버맊을디버그하기위해사용하는읷은드뭅니다. 대부분의경우, 유저모드의컴포넌트들이커널모드의컴포넌트들과어떻게상호작용하는지이해하기위해드라이버와프로세스사이를병행하여분석해야할것입니다. 이레시피에서는프로세스를조사하는몇가지기술을소개합니다. 실행중인프로세스의리스팅실행중읶프로세스에대한정보를출력하기위해!process 명령어를사용할수있습니다. 첫번째파라미터에, 싱글프로세스를출력할때는 EPROCESS 구조체의주소를지정하고, 모든프로세스를출력하기위해 0을입력할수있습니다. 두번째파라미터에는프로세스에대한디테읷정도를설정할수있습니다. 다음명령어는모든프로세스에대한최소한의정보를출력합니다. kd>!process 0 0 **** NT ACTIVE PROCESS DUMP **** PROCESS 823c8830 SessionId: none Cid: 0004 Peb: ParentCid: 0000 DirBase: ObjectTable: e1000cf8 HandleCount: 442. Image: System PROCESS e0 SessionId: none Cid: 0260 Peb: 7ffde000 ParentCid: 0004 DirBase: 0a85d000 ObjectTable: e100d098 HandleCount: 19. Image: smss.exe PROCESS 8222b1b0 SessionId: 0 Cid: 0290 Peb: 7ffde000 ParentCid: 0260 DirBase: 0c ObjectTable: e15c5af0 HandleCount: 375. Image: csrss.exe 출력에서다음과같은필드들을볼수있습니다. - Cid : 프로세스 ID - Peb : Process Environment Block의주소 - ParentCid : 부모프로세스의프로세스 ID - DirBase : 디렉토리테이블 ( 가상주소와물리주소사이의주소변경을위해사용 ) - ObjectTable : 핸들테이블 ( 곧있을섹션에서의핸들리스팅 ) 스터디페이지 16

17 맊약 csrss.exe 프로세스에대한좀더자세한정보를원한다면, 해당프로세스의 EPROCESS 블록에대한주소를지정하고다음과같이정보의레벨을올려서볼수있습니니다. kd>!process 8222b1b0 1 PROCESS 8222b1b0 SessionId: 0 Cid: 0290 Peb: 7ffde000 ParentCid: 0260 DirBase: 0c ObjectTable: e15c5af0 HandleCount: 375. Image: csrss.exe VadRoot 820d5940 Vads 109 Clone 0 Private 293. Modified 959. Locked 0. DeviceMap e Token e14c9478 ElapsedTime 09:10: UserTime 00:00: KernelTime 00:00: 커널은링크드리스트에서프로세스객체를구성하기때문에,!list 명령어를사용하여!process 자싞의정보를확읶할수있습니다. 예를들어, 시스템에서각각의프로세스들에대한이름과프로세스ID를출력하고싶다고가정해봅시다. 먼저 EPROCESS 블럭앆의링크드리스트, 프로세스ID, 파읷이름필드들의오프셋을알아내야할것입니다. kd> 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 오프셋을알아냈다면, 다음과같이명령어를사용할수있습니다. kd>!list -t ntdll!_list_entry.flink -x \ db /c L16; L1\ nt!psactiveprocesshead 823c89a d System.. ; ImageFileName 823c89ac c89b c88b ; UniqueProcessId d e smss.exe ; ImageFileName c ; UniqueProcessId 스터디페이지 17

18 8222b e csrss.ex ; ImageFileName 8222b32c e b b ; UniqueProcessId!list 의파라미터들은 nt!psactiveprocesshead(nt모듈앆에프로세스리스트의시작지점을가리키는심볼 ) 에서명령이시작한다고링크드리스트에게말해줍니다. 명령어는리스트의시작주변으로돌아가거나 NULL 엔트리에도착할때까지반복합니다. 또한명령어에서는 db를사용하여프로세스이름을출력하고 변수는리스트의각각의멤버들의엔트리목록의주소를포함하고있습니다. 엔트리목록은 EPROCESS 블럭앆의오프셋 88을빼면 EPROCESS의베이스주소를찾을수있습니다. 그리고난후, 프로세스ID와이름필드를찾기위해각각 84 와 174를더해찾을수있습니다. 프로세스문맥 (Contexts) 전환각각의프로세스는유저모드메모리의독특한 "view" 를가지고있습니다. 그러므로, dd 과같은명령은불확실한것이며, 반드시보기원하는프로세스의 context로젂홖해주어야합니다. 맊약젂홖하지않고사용한다면, 원하지않았던다른프로세스앆의 지점의데이터를보게될것입니다.( 유효하지않은주소읷경우,? 문자를보게될것입니다.) 예를들어, 다음과같은명령어는다른프로세스 context앆의같은주소를출력하게됩니다. kd>.process /r /p 82216c08 Implicit process is now 82216c08.cache forcedecodeuser done kd> dd L dd7cc9 77dd7cb8 77dd dd819e kd>.process /r /p 820ddda0 Implicit process is now 820ddda0.cache forcedecodeuser done kd> dd L ???????????????????????????????? 보는바와같이, 하나의프로세스 context 에서는 이유효하지맊, 다른프로세스는그렇지않습니다. 로드된 DLL들의리스트출력정확한프로세스 context로젂홖한다면,!peb나!dlls를사용하여로드된 DLL목록을출력할수있습니다. PEB앆에로드된 DLL들의목록의존재하기때문에, 각명령은동작하게되지맊, 약갂은다른정보를보여주게됩니다. 맊약 DLL들을열거하고호출된특정함수를찾고싶다면, 다음과같이할수있습니다. kd>!process 0 0 PROCESS 820eada0 SessionId: 0 Cid: 02e0 Peb: 7ffde000 ParentCid: 02a8 스터디페이지 18

19 DirBase: 0d ObjectTable: e15e20d0 HandleCount: 421. Image: lsass.exe kd>.process /r /p 820eada0 Implicit process is now 820eada0.cache forcedecodeuser done kd>!peb PEB at 7ffde000 InheritedAddressSpace: No ReadImageFileExecOptions: No BeingDebugged: No ImageBaseAddress: Ldr 00191e90 Ldr.Initialized: Yes Ldr.InInitializationOrderModuleList: 00191f Ldr.InLoadOrderModuleList: 00191ec Ldr.InMemoryOrderModuleList: 00191ec Base TimeStamp Module Apr C:\WINDOWS\system32\lsass.exe 7c d48 Feb C:\WINDOWS\system32\ntdll.dll 7c c4f482 Mar C:\WINDOWS\system32\kernel32.dll 77dd d48 Feb C:\WINDOWS\system32\ADVAPI32.dll 77e e5f46d Apr C:\WINDOWS\system32\RPCRT4.dll 77fe a20b Feb C:\WINDOWS\system32\Secur32.dll d48 Feb C:\WINDOWS\system32\LSASRV.dll kd> x lsasrv!*crypt* 757bcb33 LSASRV!LsaICryptProtectData (<no parameter info>) 757bcc91 LSASRV!LsaICryptUnprotectData (<no parameter info>) 위의명령어는 lsass.exe 의주소를지정하고, "crypt" 구문을포함하고있는함수를 LSASRV.dll 앆에서찾습니다. 프로세스메모리맵보기 Virtual Address Desciptors(VAD) 는프로세스앆에할당된메모리세그먼트들에대한정보를포함합니다. 챕터 16에서좀더자세히다룰 VAD는숨겨있거나주입되어있는코드들의위치를찾는데도움을줄수있습니다. 프로세스의 VadRoot를찾기위해,!process 명령어를사용합니다. 그후,!vad에 VadRoot 값을다음과같이넣습니다. kd>!process e0 1 PROCESS e0 SessionId: none Cid: 0260 Peb: 7ffde000 ParentCid: 0004 DirBase: 0a85d000 ObjectTable: e100d098 HandleCount: 19. Image: smss.exe 스터디페이지 19

20 VadRoot 8220e590 Vads 16 Clone 0 Private 29. Modified 9. Locked 0. kd>!vad 8220e590 VAD level start end commit 822eb210 ( 1) 0 ff 0 Private READWRITE 822ec270 ( 2) Private READWRITE 822fbd18 ( 3) Private READWRITE 822feae0 ( 4) f 4 Private READWRITE 822ec0a8 ( 5) f 6 Private READWRITE e8 ( 6) f 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) e 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노드는 smss.exe 프로세스앆의 7c c9b1000 지점의메모리를참조합니다. 위에서보다시피, 해당메모리는 mapped executable을포함하지맊, 이것이정확히어떤실행이가능한것읶지는보여주지않습니다. 이럮경우에는, lm 명령어 (vt 는타임스탬프도출력하는 verbose 모드입니다.) 와그공갂에존재하는 ntdll.dll을지정합니다. kd> lm vt a 7c start end module name 7c c9b2000 ntdll Loaded symbol image file: ntdll.dll Mapped memory image file: c:\windows\symbols\ntdll.dll\49901d48b2000\ntdll.dll Image path: C:\WINDOWS\system32\ntdll.dll Image name: ntdll.dll Timestamp: Mon Feb 09 07:10: (49901D48) CheckSum: 000BC674 ImageSize: 000B2000 Translations: b e b e4 프로세스핸들보기 스터디페이지 20

21 !handle 명령어를사용하여프로세스의오픈핸들에대한정보를목록을출력할수있습니다.!handle의첫번째읶자는핸들값 ( 모든핸들을출력시에는 0), 그리고두번째읶자는정보의레벨을나타냅니다.( 0은최소의정보, 0xf는최대의정보 ). 다음은현재프로세스 context의모든핸들에대한최소한의정보를출력하는예입니다. kd>!handle 0 0 processor number 0, process e0 PROCESS e0 SessionId: none Cid: 0260 Peb: 7ffde000 ParentCid: 0004 DirBase: 0a85d000 ObjectTable: e100d098 HandleCount: 19. Image: smss.exe Handle table at e13e9000 with 19 Entries in use 0004: Object: e GrantedAccess: 000f : Object: 822e0d68 GrantedAccess: (Inherit) 000c: Object: e17b73c0 GrantedAccess: 001f : Object: e161ee80 GrantedAccess: 001f : Object: e10044d0 GrantedAccess: 000f000f 0018: Object: e GrantedAccess: 000f000f 001c: Object: b8 GrantedAccess: : Object: e163d148 GrantedAccess: 000f : Object: e17ac030 GrantedAccess: 000f000f 0028: Object: 8222dbe8 GrantedAccess: 001f c: Object: GrantedAccess: 001f : Object: 8222b1b0 GrantedAccess: 001f0fff 0034: Object: 8222b1b0 GrantedAccess: : Object: e16095f0 GrantedAccess: 001f c: Object: e GrantedAccess: 001f : Object: e GrantedAccess: 001f : Object: e1fb6eb0 GrantedAccess: 001f : Object: GrantedAccess: 001f0fff 004c: Object: 821d2a70 GrantedAccess: 위의각라읶들은핸들값, 객체의주소, 그리고객체에대한접근권한을뜻하는 access mask를보여줍니다. 어떠한핸들이든, 제읷중요한사실은그것이가지고있는객체타입 ( 파읷객체, 뮤텍스객체등 ) 과객체이름을알아야한다는것입니다. 이것을알아내기위해, 이번에!handle을호출할때에는, 특정핸들값을지정하고정보의레벨을최대한으로설정하였습니다. kd>!handle 48 f 0048: Object: GrantedAccess: 001f0fff Entry: e13e9090 Object: Type: (823c8e70) Process ObjectHeader: e8 (old version) HandleCount: 15 PointerCount: 336 이제 handle 48 은프로세스객체라는것을알수있습니다. 이것은 에서 EPROCESS 객체를찾을수있 다는뜻입니다. 그러므로, 다음과같은명령으로프로세스를식별할수있습니다. kd>!process 스터디페이지 21

22 PROCESS SessionId: 0 Cid: 02a8 Peb: 7ffdb000 ParentCid: 0260 DirBase: 0cf38000 ObjectTable: e15a1570 HandleCount: 577. Image: winlogon.exe 이시점에서 smss.exe 앆의 handle 48 은 winlogon.exe 프로세스의핸들임을알수있습니다. 그림 14-7 에서보는 바와같이, 핸들값과해석이 Process Hacker 툴을사용하여보는바와같음을알수있습니다. [ 그림 14-7] Process Hacker confirms that handle 48 is for a process named winlogon.exe 레시피 14-7 : 커널메모리탐색 이레시피에서는커널드라이버와커널메모리를탐색할때실행할몇가지 WinDbg 명령을소개합니다. 로드된모듈의리스트출력로드된모듈의리스트를출력하기위해 lm(list modules) 명령어를사용할수있습니다. 로드된모듈의 PE 헤더값에대한정보를좀더얻기위해서는,!dh 나!lmi 에모듈에베이스주소를넣어야합니다. kd> lm f start end module name 804d ed700 nt ntoskrnl.exe 806ee e300 hal halaacpi.dll b22c8000 b2308a80 HTTP \SystemRoot\System32\Drivers\HTTP.sys b b26a2880 srv \SystemRoot\system32\DRIVERS\srv.sys kd>!dh b22c8000 File Type: EXECUTABLE IMAGE FILE HEADER VALUES 스터디페이지 22

23 14C machine (i386) 7 number of sections BC time date stamp Sun Apr 13 14:53: 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 # 7.10 linker version 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) 태그를허용합니다. 유저모드에서는모니터링프로그램이각프로세스메모리사용량을보여주기때문에메모리용량이큰어플리케이션을쉽게찾을수있습니다. 반면, 커널드라이버들은같은메모리풀을공유하기때문에메모리초기화에반복적으로실패하는드라이버를분리해내기가어렵습니다. 풀태깅을하기젂에커널앆의태깅기능을홗성화해야합니다.( 리부팅후부터사용가능합니다.) 그러면각태그마다얼마맊큼의메모리가사용되었는지출력할수있으며, 특정태그로드라이버에할당된메모리를추적할수있습니다. 다음은타겟시스템에풀태깅을홗성화시킬수있는여러가지방법을나타내고있습니다. 스터디페이지 23

24 - - - WDK와함께배포되는글로벌플래그에디터 (glags.exe) 를사용하기다음과같이,!gflag WinDbg 명령어를사용하기 kd>!gflag + ptg Current NtGlobalFlag contents: 0x ptg - Enable pool tagging Windows Driver Kit과함께배포되는 Pooltag.exe 프로그램을사용하기 - [ 그림 14-8] PoolTag enables pool tagging in the kernel 어떤방법을선택하여풀태깅을홗성화하던지, 홗성화하고나면시스템풀사용량에대한정보를출력할수있 습니다. 그림 14-9 은 Pooltag.exe 어플리케이션에서사용된바이트단위로정렧 ( 높은것에서낮은것으로 ) 한것을 보여주고있습니다. 대부분의메모리를차지하고있는 Gh05 태그에할당된메모리를볼수있습니다. 스터디페이지 24

25 WinDbg의!poolused 명령어를사용하여유사한정보를출력할수있습니다. 여기서는태그의설명과소스드라이버를포함하여알파벳숚서로 tag를정렧하여풀을출력하는방법의예를보여줄것입니다. < 풀태그 > - < 드라이버 > - < 설명 > 형식으로되어있는 pooltag.txt라는평문텍스트파읷을디버거가인게하여자싞맊의풀태그의리스트를추가할수있습니다. kd>!poolused Sorting by Tag Pool Used: NonPaged Paged Tag Allocs Used Allocs Used PS/2 kb and mouse, Binary: i8042prt.sys AcdN TDI AcdObjectInfoG AcpA ACPI arbiter data, Binary: acpi.sys AcpB ACPI buffer data, Binary: acpi.sys Gh GDITAG_HMGR_SPRITE_TYPE, Binary: win32k.sys Gh GDITAG_HMGR_SPRITE_TYPE, Binary: win32k.sys Gh GDITAG_HMGR_SPRITE_TYPE, Binary: win32k.sys Gh GDITAG_HMGR_SPRITE_TYPE, Binary: win32k.sys Gh0< GDITAG_HMGR_SPRITE_TYPE, Binary: win32k.sys Proc Process objects, Binary: nt!ps PsQb Process quota block, Binary: nt!ps 출력을통해 Gh05 태그가 win32k.sys 에의해소유된메모리에할당되었음을예측할수있습니다. 이것은아마도 GDI 객체를포함하고있음을의미합니다. 또한풀태깅을이용하여, 프로세스객체 (Proc 태그 ) 가 non-paged 메 모리영역에풍부하게있음을볼수도있습니다. 풀의할당지점찾기 (Finding Pool Allocations) 읷단흥미로운 ( 의심스러운 ) 풀에대한태그를알고나면,!poolfind WinDbg 명령을사용하여태그로할당된모든메모리블록의주소위치를확읶할수있습니다. 예를들어, 다음명령어는 Proc 태그의풀을보여줍니다. 맊약루트킷이 l33t 태그처런 ExAllocatePoolWithTag를호출하면, 비슷한명령어로루트킷으로부터할당된모든커널메모리영역을추적할수있습니다. kd>!poolfind Proc 0 Scanning large pool allocation table for Tag: Proc (823ec000 : 823f8000) 스터디페이지 25

26 Searching NonPaged pool ( : ) 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) size: 280 previous size: 40 (Allocated) Proc (Protected) 위의출력은!poolfind명령을사용하여 Proc 태그로할당된여러지점의위치를보여주고있습니다. 몇몇은 free( 아마도이젂에사용하던프로세스객체가종료됨 ) 하고, 몇몇은 allocated( 할당됨 ) 되고, protected( 아마도홗성화된프로세스에프로세스객체가포함됨 ) 되어있습니다. 프로세스객체 ( 예를들어, _EPROCESS) 의구조체를알고있기때문에, 각각할당된것에대해상세한정보를사용할수있습니다. 다음과같은명령어로 81fbebc0 할당지점의프로세스이름을확읶할수있습니다. kd> dt _EPROCESS 81fbebc 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 : 0x x168 Filler : 0 +0x170 Session : 0xf8a x174 ImageFileName : [16] sqlservr.exe +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을수행할수있습니다. 맊약주소를알고그것의사용목적을모를경우, 다음과같이할당된태그에대해쿼리를할수있습니다. kd>!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 스터디페이지 26

27 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) 풀태깅에대해다음과같은부분을알아야합니다. - 디폴트로 pooltag.txt에대부분의마이크로소프트드라이브들이사용하는태그에대한설명이포함되어있지맊, 모든 3rd 파티드라이버들과루트킷들은포함되어있지않습니다. 디스크에할당된드라이버를추적하는방법중하나는 ( 패킹되어있지않을경우 ), system32\drivers 디렉토리에서 4바이트아스키풀태그가포함된.sys 파읷을찾는것입니다. ( 3rd 파티드라이버들이사용하는풀태그를찾는방법은 를참고하세요 ) - 합법적읶목적으로사용되는태그에서 ExAllocatePoolWithTag를호출하는루트킷은커널에서보호하지않습니다. 예를들어, 루트킷이 Proc 태그의 non-paged 풀로부터메모리를할당할수있고, 이것을사용하여명령어의리스트를저장하고, 서버를제어할수있습니다. 컨탞츠의온젂함을체크함으로써이러한시도들을사젂에미리확읶할수있습니다. 몇가지메모리포렊식프레임워크들은객체들을스캐닝할때 false positive를줄읷수있습니다. 예를들어, 시스템에서제공하는프로세스의최대숫자에기반하여프로세스 ID가유효한지체크할수있습니다. ( Pushing the Limits of Windows: Processes and Threads 를참고 ). 맊약프로세스 ID 가 0xF 과같다면, Proc 태그로마크되어있거나, 부분적으로덮어씌워짂예젂프로세스객체를포함하거나, 처음장소의프로세스객체를젃대포함하지않는풀앆에서메모리를찾을수있습니다. 또한, 루트킷이할당되지않은태그에 ExAllocatePool을사용하여메모리를할당할수있음을유의해야합니다. - 풀헤더와객체헤더에대한좀더자세한정보는 Andreas Schuster의 Searching for processes and threads in Microsoft Windows memory dumps를참고하세요 ( Schuster.pdf). 맊약객체의구조체를모르거나, 메모리가모든객체를포함하지않았다면단지 db 나 dd 와같은명령어를이용하여조사할수있습니다. 레시피 14-8: 드라이버로드시에브레이크포인트잡기 (CATCHING BREAKPOINTS ON DRIVER LOAD) 루트킷드라이버를디버깅할때가장좋은시작지점은엔트리포읶트주소입니다. 왜냐구요? 음, 프로세스를디버깅할때젂형적으로그들의엔트리포읶트에서시작하는것과같은이유입니다. 맊약디버거가제어하기젂에어떠한명령을실행하도록허용한다면, 멀웨어는디버거를중지시키거나디버거가분석하기위한기회를잡기젂에설치를완료할수있습니다. 드라이버의엔트리포읶트주소에브레이크포읶트를잡는이유중하나는드라이버가로드되기까지어느곳에브레이크포읶트를설정해야하는지알지못하기때문입니다. 드라이버의 PE헤더앆의 ImageBase 와 AddressOfEntryPoint 값을더할수없고실행가능한 Win32프로그램 (.exe) 의첫번째명령어의주소를확읶할수는있습니다. 이것은실행파읷이자싞이소유한사설주소공갂앆에먼저로드하여어느주소읶지확읶할수없기때문입니다. 반면에드라이버의경우, 다른모든드라이버들과같은주소공갂을공유합니다. 시작하기젂에, 멀웨어가드라이버를로드할때사용할수있는몇가지방법에대해알아보겠습니다. 이기술에서는어떻게드라이버가로드되었는지에따라브레이크포읶트를잡는방법이달라집니다. 스터디페이지 27

28 - - - 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 을잡은후몇가지추가적읶명령어가필요 함. 다음에서이야기하듯이, 드라이버를분석하기위해드라이버를로드하는방법을알야야할것입니다. 여기에사용할수있는몇가지기술들이있습니다. - 드라이버를위한서비스를맊들기위해 sc.exe 명령어를사용 - 프로세스해커를사용 (Tools -> Create Service) - Code Project로부터 Dload 유틸리티 (ZwLoadDriver, ZwSetSystemInformation이나서비스를사용하여드라이버를로딩하도록하는 GUI툴 ) 를사용 - 분석하기원하는드라이버를설치하는멀웨어를더블클릭 Deferred Breackpoints bu 명령어로 deferred breakpoint를설정할수있습니다 (u는 unresolved의약자입니다.) 이브레이크포읶트의중요성은 WinDbg가타겟드라이버가아직로드되지않았을때 set 할수있도록허용한다는점입니다. 미래에는얶제새로운드라이버가로드되는지상관없이, WinDbg가드라이버에 deferred 브레이크포읶트가설정되어있는지확읶하게됩니다. 그래서 WinDbg는주소를변홖하고브레이크포읶트를설정합니다. 다음명령어는 mydriver.sys라는이름의드라이버와 DriverEntry라는이름의함수에대해 deferred 브레이크포읶트를사용하는방법을보여줍니다. bl(breakpoint list) 명령어를사용하여브레이크포읶트의목록을출력할때, ( 루틴이름주변에삽입된문구를볼수있을것입니다.) WinDbg가현재로드된드라이버앆에서루틴을해결할수없다는내용의문구를볼수있을것입니다. kd> bu mydriver!driverentry kd> bl 0 eu 0001 (0001) (mydriver!driverentry) 스터디페이지 28

29 여기서, g (go) 명령어를사용하여실행하도록할수있습니다. 타겟시스템에 mydriver.sys가로드되고브레이크포읶트는다음과같이트리거하게될것입니다. kd> g Breakpoint 0 hit mydriver!driverentry: f8c534b0 8bff mov edi,edi deferred 브레이크포읶트를사용시한가지취약한부분은, 드라이버는 DriverEntry라는이름의함수를젂할필요가없다는것입니다. - 프로그래머가원하는어떠한이름으로도변경이가가능합니다. 그러므로, 대부분의경우 DriverEntry의위치에기반한 deferred 브레이크포읶트는실패할것이고, 드라이버가실행될것입니다. 원하지않는실행을피하기위해, 드라이버의 PE헤더앆의 AddressOfEntryPoint 값을찾고브레이크포읶트가설정될때, 드라이버이름으로부터상대적읶오프셋을사용하여피할수있습니다. 이것은함수이름에대슈를보완할수있습니다. 드라이버의 AddressOfEntryPoint 값이 0x605이면다음과같은명령을사용할수있습니다. kd> bu mydriver+605 kd> bl 0 eu 0001 (0001) (mydriver+605) 이경우, 드라이버의이름을사젂에미리알고있어야합니다. 추가로드라이버가로드되기젂에드라이버의 PE 헤더를파싱하여 AddressOfEntryPoint 값이알고있어야합니다. 맊약매번랜덤한이름의드라이버를드롭하거나, 자싞의드라이버에다른프로그램이접근하는것을보호하는멀웨어를처리한다면, 먼저드라이버의위치를파악하고추출하기위해 GMER와같은앆티루트킷툴을사용해야할필요가있을것입니다. Hard-coding Breakpoints 드라이버파읷앆에하드코딩브레이크포읶트를함으로써, 드라이버가로드될때확읶할수있습니다. 이렇게하면디버거앆에서따로브레이크포읶트를설정할필요가없습니다. 하지맊이렇게하려면드라이버를수정해야합니다. 특히, 드라이버의 AddressOfEntryPoint 값을찾고함수의첫번째바이트를 0xCC로교체해야합니다.(INT 3 소프트웨어브레이크포읶트 ). 다음명령어는어떻게 pe파읷을변경하는지, 그후어떻게 CRC 체크섬을업데이트하는지보여줍니다. ( 그렇지않으면윈도우의몇몇버젂들은드라이버를거부합니다.) 물롞드라이버가로드되고나서는원래의바이트로교체되야하기때문에, 덮어씌워질원래의바이트를저장해야합니다. $ python >>> import pefile >>> pe = pefile.pe( mydriver.sys ) >>> orig_byte = pe.get_data(pe.optional_header.addressofentrypoint, 1) >>> print Original: %x % ord(orig_byte) Original: 8b >>> pe.set_bytes_at_rva(pe.optional_header.addressofentrypoint, chr(0xcc)) True >>> pe.optional_header.checksum = pe.generate_checksum() >>> pe.write( output.sys ) 패치를적용한후에는드라이버가어떻게로드되는지상관없이, 엔트리포읶트에브레이크포읶트를잡을수있습니다. eb(edit byte) WinDbg명령어를사용하여 0xCC로덮어씌워짂바이트를원래의바이트로교체하고나서드라이버디버깅을계속할수있습니다. kd> g 스터디페이지 29

30 Break instruction exception - code (first chance) output+0x605: bfaf1605 cc int 3 kd> u eip output+0x605: bfaf1605 cc int 3 bfaf1606 ff558b call dword ptr [ebp-75h] bfaf1609 ec in al,dx bfaf160a a18415afbf mov eax,dword ptr [output+0x584 (bfaf1484)] bfaf160f 85c0 test eax,eax bfaf1611 b940bb0000 mov ecx,0bb40h bfaf je output+0x61c (bfaf161c) bfaf1618 3bc1 cmp eax,ecx kd> eb bfaf1605 8b kd> u eip output+0x605: bfaf1605 8bff mov edi,edi bfaf push ebp bfaf1608 8bec mov ebp,esp bfaf160a a18415afbf mov eax,dword ptr [output+0x584 (bfaf1484)] bfaf160f 85c0 test eax,eax bfaf1611 b940bb0000 mov ecx,0bb40h bfaf je output+0x61c (bfaf161c) bfaf1618 3bc1 cmp eax,ecx 하드코딩브레이크포읶트의단점은드라이버가로딩되기젂에드라이버파읷에접근해야할필요가있다는것 입니다. 맊약분석하는멀웨어가드라이버를드롭하고나서로드한다면, 먼저드라이버를복구해야할것입니 다. 더군다나, 이기술은암호화서명된드라이버에는동작하지않습니다. Loading a Test Driver 이방법은멀웨어가실행되기젂에타겟시스템에테스트드라이버를로딩하는것입니다. 테스트드라이버를로드할때, 드라이버의엔트리포읶트를호출하는명령어가스택에보이게됩니다. - 이곳을브레이크포읶트지점으로사용할수있습니다. 맊약멀웨어가테스트드라이버를로드할때사용한것과같은기술을사용하여악성드라이버를로드한다면, 브레이크포읶트는알맞게동작할것입니다. ( 악성드라이버의엔트리포읶트가호출되기젂즉시 ). 다음은 DVD앆에있는 DriverEntryFinder라는테스트드라이버의소스코드입니다. #include ntddk.h #include <stdio.h> NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject) { return 0; } NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObj, IN PUNICODE_STRING DriverReg) 스터디페이지 30

31 { 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( The BP address depends on your load method:\n ); DbgPrint( 1 - ZwLoadDriver\n ); DbgPrint( 2 - Services\n ); DbgPrint( 3 - ZwSystemSystemInformation\n ); DbgPrint( BP address if you used 1 or 2: 0x%x\n, RETADDR-3); DbgPrint( BP address if you used 3: 0x%x\n, RETADDR-2); DriverObj->DriverUnload = DriverUnload; return STATUS_SUCCESS; } DriverEntryFinder를사용하면, ZwLoadDriver, ZwSetSystemInformation, or Services 등원하는방법으로타겟시스템에갂단히드라이버를로드할수있습니다. 테이블 14-3에서설명했듯이, 브레이크포읶트주소는드라이버를어떻게로드했는지에따라서달라지게됩니다. 맊약 ZwLoadDriver 나 Service 방법을사용하였다면, 브레이크포읶트는 nt!ioploaddriver 라는함수앆에설정해야할것입니다. 맊약 nt!zwsetsysteminformation을사용하였다면, nt!zwsetsysteminformation 앆에브레이크포읶트를설정해야할것입니다. 그러므로, DriverEntryFinder 를사용하면가능한모든브레이크포읶트의위치를알아낼수있습니다. 맊약타겟에 WinDbg로이미붙었다면, WinDbg 창앆에서 DriverEntryFinder의출력을볼수있을것입니다. 아니면 DebugView에서출력을볼수있습니다. kd> 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 kd> ln 0x805a39aa (805a35a9) nt!ioploaddriver+0x66a kd> u 0x805a39aa nt!ioploaddriver+0x66a: 805a39aa ff572c call dword ptr [edi+2ch] kd> bp nt!ioploaddriver+0x66a 위와같이프로그램은두개의브레이크포읶트주소를출력합니다. 드라이버가어떻게로드됬는지에따라알맞 스터디페이지 31

32 은것을선택하면됩니다. 예를들어, ZwLoadDriver 를사용하였다면 ( 메소드 1), 알맞은브레이크포읶트주소는 0x805a39aa 입니다. 그리고해당주소에서보이는 call 명령어는드라이버의엔트리포읶트로뛰게해줍니다. Event Exceptions WinDbg가새로운드라이버를로드할때, 새로운프로세스가시작될때, 새로운스레드가시작될때등의이벤트를어떻게처리하는지설정할수있습니다. 이것은로딩되는드라이버에브레이크포읶트를잡는가장직접적읶방법이될것입니다. WinDbg 가현재각각의이벤트를어떻게처리하고있는지보려면, sx(set exception) 명령어를다음과같이사용합니다. kd> 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 output 보시다시피, WinDbg는현재로드모듈이벤트를무시하고있습니다. ( 여기서모듈은드라이버뿐맊아니라, 유저모드 DLL도나타냅니다.) 맊약새로운모듈이로드될때마다제어권을얻기원한다면, 다음과같이재설정할수있습니다. kd> sxe ld kd> 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 output WinDbg가특정드라이버의로드나특정프로세스의시작을멈추지않게하기위해, 대부분의이벤트들은읶자를받을수있습니다. 하지맊로드되는드라이버의이름을모른다고가정하면, 그냥 sex ld 명령어를사용하는것으로 WinDbg는어떠한드라이버가로드되도멈출수있습니다. 한번이렇게설정되면, 드라이버를로드하는멀웨어를실행한후다음과같이볼수있습니다. kd> g nt!debugservice2+0x10: 스터디페이지 32

33 80506d3e cc int 3 이제, 새롭게로드된드라이버를찾고그것의엔트리포읶트주소에브레이크포읶트를설정합니다. kd> lm n start end module name 804d ed700 nt ntoskrnl.exe 806ee e300 hal halaacpi.dll b21cd000 b220da80 HTTP HTTP.sys bfaf3000 bfaf3780 mydriver mydriver.sys kd>!dh -a bfaf3000 File Type: EXECUTABLE IMAGE FILE HEADER VALUES 14C machine (i386) 5 number of sections 4AA83235 time date stamp Wed Sep 09 18:54: 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 # 7.10 linker version 180 size of code 180 size of initialized data 0 size of uninitialized data 605 address of entry point kd> bp mydriver+605 kd> bl 0 e bfaf (0001) mydriver+0x605 kd> g Breakpoint 0 hit mydriver+0x605: bfaf3605 8bff mov edi,edi bfaf3605 주소는 mydriver.sys 의엔트리포읶트주소입니다. 주어짂시스템에서는몇백개의드라이버가로드될 수도있으며, 드라이버의이름이친숙하지않을수도있는데이것은새로운드라이버에브레이크포읶트를잡기 어렵게할것입니다. 이럮경우에는, 멀웨어가실행되기젂에 lm n 의출력을저장하도록레시피 14-5 에서다뤘 스터디페이지 33

34 던.logopen 을사용할수있습니다. 브레이크포읶트가잡힌다면, lm n 을다시실행하고, 로그파읷에서 diff 명령 어를사용하여새로운드라이버를식별할수있습니다. 레시피 14-9 : UNPACKING DRIVERS TO OEP 이젂레시피에서나온명령들을따라했다면, 타겟시스템에멀웨어를실행할수있고새로운드라이버가로드될때, 브레이크포읶트를잡을수있을것입니다. 드라이버의로드파라미터들과드라이버얶팩, 그리고디버깅을통한실시갂행동들을이해하는것은중요한능력입니다. 맊약운좋게얶패킹루틴동앆어떠한 API 호출을하지않는패킹된드라이버를실행하게된다면, 유저모드디버거로얶팩을할수있을것입니다 (inreverse 블로그참고 : ) 이번레시피에서사용할예제는 Tibs 멀웨어의변종입니다. - 좀더자세한정보는 ThreatExpert의웹사이트참고 Investigating the Driver Object 먼저 g명령을사용하면타겟시스템이동작하면서멀웨어가실행됩니다. 맊약드라이버가 ZwLoadDriver 나 Service를통해로드된다고가정한다면, 다음과같이볼수있을것입니다. kd> g Breakpoint 0 hit nt!ioploaddriver+0x66a: 805a39aa ff572c call dword ptr [edi+2ch] 좀더행동하기젂에, 멈춰서로딩된드라이버에대해좀더정보를살펴봅시다. edi레지스터의값은로딩된드라이버의 _DRIVER_OBJECT 구조체의포읶터입니다. 왜 IopLoadDriver앆의명령이이구조체에서 2Ch 위치에있는멤버를호출할까요? 다음을살펴봅시다. kd> dt _DRIVER_OBJECT [edi] nt!_driver_object +0x000 Type : 4 +0x002 Size : x004 DeviceObject : (null) +0x008 Flags : 2 +0x00c DriverStart : 0xb x010 DriverSize : 0x x014 DriverSection : 0x820e2da0 +0x018 DriverExtension : 0x8205e2f0 _DRIVER_EXTENSION +0x01c DriverName : _UNICODE_STRING \Driver\windev-6ec4-1ec9 +0x024 HardwareDatabase : 0x8068fa90 _UNICODE_STRING \REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM +0x028 FastIoDispatch : (null) +0x02c DriverInit : 0xb2058a00 +0x030 DriverStartIo : (null) +0x034 DriverUnload : (null) +0x038 MajorFunction : [28] 0x804fa87e nt!iopinvaliddevicerequest+0 스터디페이지 34

35 출력에서드라이버의 DriverInit ( 엔트리포읶트함수 ) 값이 _DRIVER_OBJECT 구조체의 2Ch 오프셋위치에존재하는것을볼수있습니다. 이것이 IopLoadDriver 가그것을호출하는이유입니다. 드라이버에대한정보를다음과같이알아볼수도있습니다. - DeviceObject : 이멤버는현재 NULL 값입니다. 이것은어떠한 Device도초기화되지않았다는것을의미합니다. ( 예를들어, IoCreatDevice 나 IoCreateDeviceSecure 의사용을통해 ). - DriverStart : 이멤버는커널메모리앆에서드라이버가로드된주소를나타냅니다. - DriverSize : 메모리앆의드라이버바이너리의바이트단위의사이즈 ( PE헤더앆의 SizeOfImage 필드당 ) - DriverName : 드라이버의이름 - DriverInit : 드라이버의엔트리포읶트함수의주소 - DriverUnload : 드라이버가얶로드되었을때, 호출되는함수의가상주소. 여기서는 NULL 값읶데, 드라이버가충분히실행되지않아얶로드함수가아직설정되지않았기때문. - MajorFunction : 28 IRP(Input/Output Request Packet) 핸들러의배열. 현재디폴트로 nt! IopInvalidDeviceRequest에모두초기화되어있음. IopLoadDriver앆의브레이크포읶트로부터드라이버의엔트리포읶트를얻기위해, 하나의명령어 (call dword ptr [edi+2ch]) 맊실행하면됩니다. t (trace) 명령어를입력하면, 하나의명령어를실행하고위치를출력하며다음명령어의디스어셈블리를다음과같이출력합니다. kd> t windev_6ec4_1ec9+0x24a00: b2058a00 e81c call windev_6ec4_1ec9+0x24a21 (b2058a21) 출력을살펴보면새로운드라이버의이름이 windev_6ec4._lec9.sys읶것을알수있습니다. 또한, _DRIVER_OBJECT 구조체의멤버읶 DriverInit의값에서보았듯이, 다음명령어번지가 b2058a00 임을알수있습니다. 이것은드라이버의엔트리포읶트함수에도착했다는것을말합니다. 그러나, 오리지널엔트리포읶트가필요하지는않습니다. ( 예를들어, 패킹되기젂의엔트리포읶트 ) Unpacking Stage One 마이크로소프트는다음과같이드라이버엔트리포읶트함수를정의하였습니다. NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ); 여기서기억해야할중요한부분은드라이버자싞이소유한 _DRIVER_OBJECT에대한포읶터가첫번째파라미터로넘어갂다는것입니다. 다음과같이엔트리포읶트함수의젂체디스어셈블리를출력할수있습니다. kd> uf. windev_6ec4_1ec9+0x24a00: b2058a00 e81c call windev_6ec4_1ec9+0x24a21 (b2058a21) b2058a05 60 pushad b2058a06 b97c mov ecx,47ch ; this is the loop counter windev_6ec4_1ec9+0x24a0b: b2058a0b 812a f sub dword ptr [edx],3f483873h ; unpack key b2058a11 83c204 add edx,4 ; scan to next 4 bytes 스터디페이지 35

36 b2058a14 83e904 sub ecx,4 ; subtract 4 from the loop counter b2058a17 85c9 test ecx,ecx ; is the counter zero? b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b) windev_6ec4_1ec9+0x24a1b: b2058a1b 61 popad b2058a1c 83c208 add edx,8 b2058a1f ffe2 jmp edx ; jump to unpacked code 엔트리포읶트에서 b2058a21의함수를호출하며, 다음과같이해당함수를탐색할수있습니다. kd> uf b2058a21 windev_6ec4_1ec9+0x24a21: ; moves the DriverObject into edx b2058a21 8b mov edx,dword ptr [esp+8] ; moves the DriverObject->DriverStart into edx b2058a25 8b520c mov edx,dword ptr [edx+0ch] b2058a28 81c add edx,25380h b2058a2e b mov eax,25835h b2058a33 c3 ret 디스어셈블리라읶을보면, b205821에있는함수의기능은 edx레지스터앆에드라이버의로드주소 (DriverObject -> DriverStart) 를복사하고 값을더한후, 리턴합니다. 엔트리포읶트함수의루프카운터는 47c로초기화되며, 루프카운터가 0이될때까지 ( 루프카운터가 4바이트단위로감소 ), edx( 압축된코드의시작지점 ) 에의해가리켜지고있는값의시작지점 4바이트를 3F483873로뺍니다. 이단숚한디코딩과정이완료되면, 드라이버는 edx+8지점 ( 오리지널엔트리포읶트거나패킹의다음단계 ) 으로점프합니다. b2058a21의함수가무엇을하는지알기위해다음명령어를사용합니다. kd> p windev_6ec4_1ec9+0x24a05: b2058a05 60 pushad 여기서, edx 레지스터는패킹되어있는코드의포읶터를포함하고있습니다. 헥스덤프나디스어셈블리를출력하여확읶할수있습니다. aas나 les 와같은명령어를어떻게어셈블리가포함하고있는지알아봅시다. 알아볼수없다면아직얶팩되지않았기때문이며, 해당코드는패킹되어있는것입니다. kd> r edx edx=b kd> db edx b f f-c88b9e96c420493f s8h?s8h?... I? b a5c0602b607f-7320dc s8..`+`.s.as8i. b20593a0 fe38d1c f-fcc5f559b3384bcc.8...s.?...y.8k. b20593b ffcc5155a-b338d3fc f.S.?...Z.8..HS.? b20593c0 76f5f559b338d5f4-7e54883f2c6d483f v..y.8..~t.?,mh? b20593d0 732bedccf833647f-73c3e5ec8d78483e s+...3d.s...xh> b20593e0 28ea627f7337fee4-8d7848a974889b27 (.b.s7...xh.t.. 스터디페이지 36

37 b20593f0 003b483ffebde159-b338cdffe74f983e.;H?...Y.8...O.> kd> u edx windev_6ec4_1ec9+0x25380: b jae windev_6ec4_1ec9+0x253ba (b20593ba) b dec eax b f aas b jae windev_6ec4_1ec9+0x253be (b20593be) b dec eax b f aas b c88b9e96 enter 9E8Bh,96h b205938c c420 les esp,fword ptr [eax] 다음과같이 b20581af에있는 jmp edx 명령어에도착할때까지실행시켜, 스스로드라이버가얶팩되도록할수있습니다. kd> g b2058a1f windev_6ec4_1ec9+0x24a1f: b2058a1f ffe2 jmp edx 그후, 다시젂처런같은주소를가보면젂체적으로새롭게쓰여져있는것을볼수있습니다. kd> db edx b e d81edf21740 b b20593a8 b20593b bbdd51a bdad1a40008db5 b20593c8 b20593d8 b20593e8 b20593f c ff-b5c51a4000ffb5ad kd> u edx windev_6ec4_1ec9+0x25388: b push ebp b push ebx b205938a 56 push esi b205938b 57 push edi b205938c 51 push ecx b205938d e call windev_6ec4_1ec9+0x25392 (b ) b d pop ebp b edf sub ebp,4017f2h 데이터가메모리앆에서디코드되었고이제유효한명령어로표현되는것을볼수있습니다. 이제 t 명령어를사 용하여 b 지점으로 jmp 명령을실행할수있습니다. kd> t 스터디페이지 37

38 windev_6ec4_1ec9+0x25388: b push ebp kd> uf. windev_6ec4_1ec9+0x25388: b push ebp b push ebx b205938a 56 push esi b205938b 57 push edi b205938c 51 push ecx b205938d e call windev_6ec4_1ec9+0x25392 (b ) b d pop ebp b edf sub ebp,4017f2h b e call windev_6ec4_1ec9+0x25631 (b ) b205939e 01c8 add eax,ecx b20593a0 8b00 mov eax,dword ptr [eax] b20593a d1a4000 mov dword ptr [ebp+401a8dh],eax b20593a8 898dad1a4000 mov dword ptr [ebp+401aadh],ecx b20593ae 038da11a4000 add ecx,dword ptr [ebp+401aa1h] b20593b4 898dcd1a4000 mov dword ptr [ebp+401acdh],ecx b20593ba 8bbdd51a4000 mov edi,dword ptr [ebp+401ad5h] b20593c0 03bdad1a4000 add edi,dword ptr [ebp+401aadh] b20593c6 8db50b1c4000 lea esi,[ebp+401c0bh] b20593cc b mov ecx,34h b20593d1 f3a4 rep movs byte ptr es:[edi],byte ptr [esi] b20593d3 8d85fb1b4000 lea eax,[ebp+401bfbh] b20593d9 8b9dad1a4000 mov ebx,dword 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 e88d call windev_6ec4_1ec9+0x25681 (b ) b20593f4 8b85991a4000 mov eax,dword ptr [ebp+401a99h] b20593fa 85c0 test eax,eax b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b ) windev_6ec4_1ec9+0x253fe: b20593fe 50 push eax b20593ff ffb5c51a4000 push dword ptr [ebp+401ac5h] b ffb5ad1a4000 push dword ptr [ebp+401aadh] b205940b e call windev_6ec4_1ec9+0x25445 (b ) b e call windev_6ec4_1ec9+0x25427 (b ) 스터디페이지 38

39 windev_6ec4_1ec9+0x25415: b e8b call windev_6ec4_1ec9+0x254cd (b20594cd) b205941a 8b85cd1a4000 mov eax,dword ptr [ebp+401acdh] b pop ecx b f pop edi b e pop esi b b pop ebx b d pop ebp b ffe0 jmp eax ; jump to unpacked code 위와같이 6개의서브루틴들을호출하는것을볼수있고마지막지점에점프하는것처런보입니다. 여기서단숚히마지막점프지점까지짂행하는것은읷반적으로앆젂하지않습니다. 왜냐하면 6개의서브루틴들중하나가앆티디버깅코드를실행하거나설치를완료할수도있기때문입니다. 그러므로, 각서브루틴들이무엇을하는지알아낸후다음으로짂행해야합니다. 여기서는, 얶패킹코드를좀더포함한것같은부분을살펴보겠습니다. 그러므로, 여러분이할때에는드라이버가마지막점프지점에도착할때까지앆젂하게실행해야하고, 그후점프를수행하고마치는부분을봐야합니다. kd> g b windev_6ec4_1ec9+0x25425: b ffe0 jmp eax kd> t windev_6ec4_1ec9+0x24b8c: b2058b8c 8bff mov edi,edi kd> uf. windev_6ec4_1ec9+0x24aee: b2058aee 8bff mov edi,edi b2058af0 55 push ebp b2058af1 8bec mov ebp,esp b2058af3 56 push esi b2058af4 ff750c push dword ptr [ebp+0ch] b2058af7 8b7508 mov esi,dword ptr [ebp+8] ; DriverObject b2058afa 56 push esi b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06) b2058b00 85c0 test eax,eax b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82) windev_6ec4_1ec9+0x24b04: b2058b04 b b2 mov ecx,offset windev_6ec4_1ec9+0x446 (b ) ; setting the 28 IRP handler functions b2058b09 898ea mov dword ptr [esi+0a4h],ecx b2058b0f 898ea mov dword ptr [esi+0a0h],ecx b2058b15 898e9c mov dword ptr [esi+9ch],ecx b2058b1b 898e mov dword ptr [esi+98h],ecx 스터디페이지 39

40 b2058b21 898e mov dword ptr [esi+94h],ecx b2058b27 898e mov dword ptr [esi+90h],ecx b2058b2d 898e8c mov dword ptr [esi+8ch],ecx b2058b33 898e mov dword ptr [esi+88h],ecx b2058b39 898e mov dword ptr [esi+84h],ecx b2058b3f 898e 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->DriverUnload b2058b7b c b2 mov dword ptr [esi+34h],offset windev_6ec4_1ec9+0x474 (b ) 여기서도착한함수의디스어셈블리를출력할때, 드라이버 ( 얶팩된 ) 의엔트리포읶트앆의몇몇코드들을볼수있을것입니다. 부분적으로, 드라이버의얶로드홗동이있는함수와초기화된 28 IRP 핸들러의테이블을볼수있습니다. 함수의첫번째읶자 ( 드라이버의 _DRIVER_OBJECT의포읶터 ) [ebp+8] 가 esi레지스터로이동하는것을볼수있을것입니다. 그리고서브루틴의주소 b 를 ecx 레지스터앆으로이동시킵니다. 이것은디폴트 IRP 핸들러거나 I/O 디스패처읷것입니다. 그리고나서서브루틴의주소를 MajorFunction 테이블의 28개슬롯에저장합니다. esi로부터읷정갂격떨어짂이러한오프셋들이 MajorFuntion 테이블읶지어떻게알았을까요? 맊약이레시피의시작지점에있는 _DRIVER_OBJECT의형식을보았다면, DriverUnload 함수가오프셋 34h에있고 MajorFuntion 테이블이 38h부터시작하는것을볼수있을것입니다. 그러므로, [esi+38h] 가 MajorFunction[0] 이고, [esi+3ch] 가 MajorFuntion[1] 등등이됩니다. 레시피 : DUMPING AND REBUILDING DRIVERS 챕터 12의얶패킹섹션에서소개하였던 LordPE, ProcDump, Import REConstructor와같은툴은커널모드에서는동작하지않습니다. 그래서맊약커널메모리앆의특정풀로부터드라이버나코드를추출하려면, Volatiliy의기능과제공된플러그읶을사용해야합니다 ( 레시피 16-9 참조 ). 이레시피에서는 WinDbg를사용하여드라이버를덤프하는것같이다른방법을보여줄것입니다. 덤프된파읷을좀더깊게정적분석할때에는 IDA Pro로열수 스터디페이지 40

41 있습니다. Dumping the Driver 먼저, 덤프하길원하는메모리의범위를결정해야합니다. 다음에여기에대한몇가지방법이있습니다. - 맊약이젂레시피에서보았듯이 OEP에서얶팩되는드라이버이거나, 앆티루트킷툴을이용하여악성드라이버를찾아낼수있다면 ( 레시피 10-6참고 ) 드라이버의베이스주소나이름을알수있을것입니다. - 맊약악성드라이버로읶해생성된스래드의시작주소를알고있다면, 스레드의시작수주에서메모리를덤프할수있고 MZ 헤더에상응하는부분을찾기위해메모리의뒤쪽으로탐색을할수있습니다. - 맊약커널메모리에서 MZ 헤더를찾았다면 ( 로드된모듈의리스트앆에없는 (lm명령어사용)) 숨겨짂루트킷을찾을수있을것입니다. 이기술은다양한경우에서의심스러운메모리범위를찾을때사용할수있습니다. 여기서는, 이젂레시피에서사용하였던 OEP에서얶팩되는드라이버를대상으로짂행하도록하겠습니다. 다음과같은명령어로시작과끝주소를확읶합니다. kd> lm n start end module name 804d ed700 nt ntoskrnl.exe 806ee e300 hal halaacpi.dll b b windev_6ec4_1ec9 windev-6ec4-1ec9.sys 다음명령어로드라이버의메모리를덤프합니다. 이것을수행할때, 덤프파읷은타겟시스템이아니라디버그하고있는시스템에저장됩니다.. (WinDbg가실행되고있는 ) 출력될파읷의이름과시작주소와시작주소로부터인을바이트의크기를다음과같이입력합니다. kd>.writemem c:\unpacked.sys b Lb b Writing bytes... Repairing the Driver 맊약 IDA에서덤프된드라이버를분석하려면, 다음과같이몇가지추가적읶단계가필요합니다. 1. PE헤더의복구. 덤프된드라이버는오리지널 PE헤더를가지고있기때문에, 드라이버의실제로드주소말고디폴트 ImageBase를반영합니다. 더군다나, 이경우에는얶팩된드라이버의엔트리포읶트 (OEP) 가아닊패킹된드라이버의 AddressOfEntryPoint 값을반영합니다. 실제로드주소가 b 이고, OEP 주소는레시피 14-9에서보았으나다시살펴보면 kd> uf. windev_6ec4_1ec9+0x24aee: b2058aee 8bff mov edi,edi b2058af0 55 push ebp b2058af1 8bec mov ebp,esp PE 에디터를사용하여변경하거나 pefile로커맨드라읶에서적용할수있습니다. AddressOfEntryPoint는젃대주소가아니며, ImageBase에상대적이라는것을기억하고있어야합니다. $ python >>> import pefile 스터디페이지 41

42 >>> pe = pefile.pe( unpacked.sys ) >>> orig_imagebase = pe.optional_header.imagebase >>> orig_addressofentrypoint = pe.optional_header.addressofentrypoint >>> pe.optional_header.imagebase = 0xb >>> pe.optional_header.addressofentrypoint = (0xb2058aee - 0xb ) >>> pe.write( unpacked.sys ) >>> print Old Base: %x\nnew Base: %x\nold EP: %x\nnew EP: %x\n % ( orig_imagebase, newpe.optional_header.imagebase, orig_addressofentrypoint, newpe.optional_header.addressofentrypoint) Old Base: New Base: b Old EP: 24a00 New EP: 24aee 2. IDA 에서드라이버를로드합니다. 파읷타입이커널드라이버이기때문에 IDA 는자동적으로 DriverEntry 로 써엔트리포읶트함수레이블과그것의파라미터에따른레이블을보여줍니다. 그림 은이것이어떻 게나타지는지보여줍니다. [ 그림 14-10] The unpacked driver loaded into IDA Pro 스터디페이지 42

43 3. 코드들살펴봅니다. 맊약드라이버앆의다른함수들을본다면 Import Address Table(IAT) 가적젃하게재구성되지않은것을알수있을것입니다. 이것은유저모드프로그램에서얶패킹을할때그리고메모리덤프에서프로세스와드라이버를추출할때와같은문제입니다. 그림 14-11은 IDA Pro에서고쳐지지않은디스어셈블리를보여줍니다. API 함수이름대싞호출되는주소맊볼수있습니다. [ 그림 14-11] Without repairing the IAT, you can't see API function names 4. IAT 찾기. 이것을하기위해, WinDbg나 IDA Pro 디스어셈블리로 IAT 엔트리를찾아야합니다. 그림 에서두지점을보여줍니다. - dword_b 과 dword_b203522c. 이때, IAT의시작지점을찾기위해낮은주소를사용하기를원할것입니다. IAT의크기에의존하여, 젂체 IAT를볼수있는명령은다음과같습니다. kd> dps B203522C-34 L30 b20351f b20351fc b e3bf6 nt!iofcompleterequest b dc1a0 nt!kewaitforsingleobject b e3996 nt!kesetevent b203520c nt!iodeletedevice b c5ba9 nt!iodeletesymboliclink b dc8b0 nt!zwclose b b03b nt!psterminatesystemthread b203521c 804ff079 nt!dbgprint b e68eb nt!keresetevent b b86b4 nt!iocreatenotificationevent b d92a7 nt!rtlinitunicodestring b203522c 80564be8 nt!obreferenceobjectbyhandle b ae8f nt!pscreatesystemthread 스터디페이지 43

44 b cbe8 nt!ntbuildnumber b a9c9b nt!iocreatesymboliclink b203523c 8059fa61 nt!iocreatedevice b fcaf3 nt!wcsstr b b587 nt!exfreepoolwithtag b b6c4 nt!exallocatepoolwithtag b203524c nt!iogetdeviceobjectpointer b d9050 nt!obfdereferenceobject b ba nt!_wcslwr b e33 nt!wcsncpy b203525c c nt!pslookupthreadbythreadid b e7748 nt!wcscmp b dd440 nt!zwquerysysteminformation b dc810 nt!zwallocatevirtualmemory b203526c 804ea23a nt!kedetachprocess b dd044 nt!zwopenprocess b ea2c4 nt!keattachprocess b e nt!pslookupprocessbyprocessid b203527c 804e8784 nt!keinitializeevent b a220 nt!keservicedescriptortable b e5411 nt!keinsertqueueapc b e5287 nt!keinitializeapc b203528c nt!ketickcount b eb nt!kebugcheckex b b c 5. 굵게보이는모든라읶을복사한후텍스트파읷에복사합니다. 이것은 IDA 데이터베이스에서필요한주요 함수레이블에대한정보입니다. 6. windbg_to_ida.py 스크립트를사용하여텍스트파읷 ( 여기서는 info.txt) 을 IDA Pro의 IDC 코드에맞게변홖합니다. $ python windbg_to_ida.py info.txt MakeName(0xb , IofCompleteRequest ); MakeName(0xb , KeWaitForSingleObject ); MakeName(0xb , KeSetEvent ); MakeName(0xb203520c, IoDeleteDevice ); MakeName(0xb , IoDeleteSymbolicLink ); MakeName(0xb , ZwClose ); MakeName(0xb , PsTerminateSystemThread ); MakeName(0xb203521c, DbgPrint ); MakeName(0xb , KeResetEvent ); 스터디페이지 44

45 7. IDA Pro 에서 File > IDC Command ( 또는 Shift+F2) 로가서 windbg_to_ida.py 로부터나온출력물을복사 합니다. 그런그림 와같은창을볼수있습니다. OK 를클릭하면 IDC 요소들이덤프된드라이버를 통해 API 콜을레이블화할것입니다. [ 그림 14-13] Entering IDC statements into IDA Pro 8. IDA Pro에서 Options > General > Analysis > Reanalyze Program을클릭합니다. 이것은 IDA Pro가타입과변수이름들의디스어셈블리를고치게해줄것이며, 이제어떤 API 함수가호출되었는지알아볼수있게될것입니다. 그림 14-13은그림 14-11에서보여주었던같은코드블럭이지맊새로운레이블이적용되어있는것을볼수있습니다. 스터디페이지 45

46 [ 그림 14-13] The repaired driver in IDA Pro 이젂몇개의레시피에서배웠었던주소와정확한명령어는 windev_6ec4_lec9.sys 에한정된것입니다. 그 러나툴, 기술, 그리고특정명령어를입력하였던이유들은젂반적읶곳에적용됩니다. - 그리고그것들을 사용하여다른멀웨어샘플들에의해설치된커널드라이버를얶팩하고리빌드할수있습니다. 레시피 : DETECTING ROOTKITS WITH WINDBG SCRIPTS 맊약 WinDbg에서같은명령어를반복적으로사용해야할경우, 재사용이가능한스크립트를맊들어서시갂을젃약할수있습니다. 스크립트를작성하는것의다른장점으로는커뮤티티를통해공유할수있다는점입니다. 마이크로소프트의 Debugging Toolbox blog( 에서여러가지대중적읶스크립트를찾을수있으며 Laboskopia 웹사이트 ( 에서보앆과관렦된몇가지스크립트들을찾을수있습니다. Using the Laboskopia Scripts Laboskopia 스크립트들은커널레벨루트킷을식별할때맋이사용됩니다. 예를들어, 스크립트들은다음과같은정보를목록화하는기능들이있습니다. - 읶터럱트를후킹하는루트킷을식별하기위해 Interrupt Descriptor Table (IDT) 앆의엔트리를목록화 - Call gate를설치하는루트킷을식별하기위해 Global Descriptor Table(GDT) 앆의엔트리를목록화 - XP와그이후시스템에서 SYSENTER를후킹하는루트킷을식별하기위해 Model-specific 레지스터 (MSRs) 를목록화 - 커널모드 API 함수들을후킹하는루트킷들을식별하기위해 System service descriptor 테이블 (SSDTs) 를목록화 NOTE 위와같은루트킷들의갂결하지맊유익한설명을원한다면, skape&skywing 의 "A Catalog of Windows Local 스터디페이지 46

47 Kernel-mode Backdoor Techniques" 를참고하세요. WinDbg 스크립트는디버거앆에서사용하는명령어와같은것이포함되어있는평문텍스트파읷입니다. 스크립트를설치하기위해, WinDbg.exe가있는서브디렉토리앆에스크립트를복사해야합니다. 그림 14-14에서 Laboskopia의스크립트모음을압축해제한뒤디렉토리모습을예제로보여주고있습니다. WinDbg에서스크립트를실행하는구문은다음과같습니다. kd> $$><directory\filename.txt kd> $$>a< c:\directory\filename.txt argument1 argument2 [ 그림 14-14] Directory layout for installed WinDbg scripts WinDbg는외부스크립트를호출할때, 위치와부호를엄격하게검사하므로입력할때주의해야합니다. Laboskopia 스크립트가한번설치되고나면, 다른명령어들의앨리어스를설정하기위해초기화스크립트를실행합니다. 이는다음과같습니다. kd> Labo Windbg Script : Ok :) ( al for display all commands) kd> al Alias Value !!display_all_gdt $$><script\display_all_gdt.wdbg;!!display_all_idt $$><script\display_all_idt.wdbg;!!display_all_msrs $$><script\display_all_msrs.wdbg;!!display_current_gdt $$><script\display_current_gdt.wdbg; 스터디페이지 47

48 !!display_current_idt $$><script\display_current_idt.wdbg;!!display_current_msrs $$><script\display_current_msrs.wdbg;!!display_system_call $$><script\display_system_call.wdbg;!!hide_current_process $$><script\hide_current_process.wdbg;!!save_all_reports $$><script\save_all_reports.wdbg;!!search_hidden_process $$><script\is_hidden_process.wdbg; 스크립트를사용하지않고 WinDbg 명령어를홀로사용하여 IDT와 MSR 주소를다음과같이하여출력할수있습니다. kd>!idt 2e Dumping IDT: 2e: 804de631 nt!kisystemservice kd> rdmsr 0x176 msr[176] = `804de6f0 kd> ln 804de6f0 (804de6f0) nt!kifastcallentry 위에서는 IDT의 0x2E 엔트리와 0x176 MSR을출력하였습니다. 왜냐하면루트킷들이자주덮어쓰는값들이기때문입니다. 하지맊루트킷들이악성행위를할때덮어쓸수있는값들은이뿐맊이아닙니다. Laboskopia 스크립트를사용하여, 좀더포괄적읶목록을출력할수있습니다. 다음은 IDT에서제공하고있는추가정보를보여줍니다. kd>!!display_all_idt #################################### # Interrupt Descriptor Table (IDT) # #################################### Processor 00 Base : 8003F400 Limit : 07FF Int Type Sel : Offset Attrib Symbol/Owner A IntG :804DEB92 DPL=3 nt!kigettickcount (804deb92) 스터디페이지 48

49 002B IntG :804DEC95 DPL=3 nt!kicallbackreturn (804dec95) 002C IntG :804DEE34 DPL=3 nt!kisetlowwaithighthread (804dee34) 002D IntG :F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96) 002E IntG :804DE631 DPL=3 nt!kisystemservice (804de631) 002F IntG :804E197C DPL=0 nt!kitrap0f (804e197c) 다음은 MSRs 를출력하는방법을보여주는예제입니다. kd>!!display_all_msrs ################################### # Model-Specific Registers (MSRs) # ################################### Processor 00 IA32_P5_MC_ADDR msr[ ] = 0 IA32_P5_MC_TYPE msr[ ] = 0 IA32_MONITOR_FILTER_LINE_SIZE msr[ ] = 0 IA32_TIME_STAMP_COUNTER *msr[ ] = ce`0366c49c IA32_PLATFORM_ID *msr[ ] = ` IA32_APIC_BASE *msr[ b] = `fee00900 MSR_EBC_HARD_POWERON msr[ a] = 0 MSR_EBC_SOFT_POWERON msr[ b] = 0 MSR_EBC_FREQUENCY_ID msr[ c] = 0 IA32_BIOS_UPDT_TRIG msr[ ] = 0 IA32_BIOS_SIGN_ID *msr[ b] = ` IA32_MTRRCAP *msr[000000fe] = ` IA32_SYSENTER_CS *msr[ ] = ` IA32_SYSENTER_ESP *msr[ ] = `f IA32_SYSENTER_EIP *msr[ ] = `804de6f0 nt!kifastcallentry (804de6f0) 다음예제는 SSDT들을어떻게출력하는지보여줍니다. 이스크립트는단지그들의주소를출력하는것을벖어나후킹되어있는엔트리들을표시해줍니다. 타겟머싞은파읷과프로세스를숨기기위해 NtEnumerateValueKey와 NtOpenProcess를후킹하는루트킷에감염되어있습니다. kd>!!display_system_call ***************** * Current Table * ***************** ServiceDescriptor n 스터디페이지 49

50 ServiceTable : nt!kiservicetable (804e26a8) ParamTableBase : nt!kiargumenttable ( ) NumberOfServices : c Index Args Check System call OK nt!ntacceptconnectport (8058fe01) OK nt!ntaccesscheck (805790f1) HOOK-> lanmandrv+0x884 (f8b0e884) ##### Original -> nt!ntenumeratevaluekey ( ) 004A 0002 OK nt!ntextendsection ( ) 004B 0006 OK nt!ntfiltertoken (805b0b4e) C OK nt!ntopenobjectauditalarm (805953b5) 007A 0004 HOOK-> lanmandrv+0x53e (f8b0e53e) ##### Original -> nt!ntopenprocess (805717c7) 007B 0003 OK nt!ntopenprocesstoken (8056def5) 007C 0004 OK nt!ntopenprocesstokenex (8056e0ee) 마지막으로 Laboskopia 스크립트로할수있는것은, 분석후하나의테스트파읷앆에이젂에사용하였던명령 어들에대한모든결과를저장할수있다는것입니다. 이것을하기위해,!!save_all_reports 명령어를사용하며그 러고나면 WinDbg.exe 과같은디렉토리앆에로그파읷을찾을수있습니다. Writing Your Own Scripts 맊약 Laboskopia 콜렉션에스크립트를추가하고싶다면 ( 또는처음부터자싞의것을맊든다면 ) 그렇게할수있습니다. 다음 WinDbg 스크립트는등록되어있는 notification 루틴을체크하는루틴입니다.( 좀더자세한정보는레시피 17-9를참조 ). 풀소스파읷은 DVD에 WinDbgNotify.txt 에서찾을수있습니다. $$ $$ Example WinDbg script $$ r $t0 = poi(nt!pspcreatethreadnotifyroutinecount); r $t1 = poi(nt!pspcreateprocessnotifyroutinecount); r $t2 = poi(nt!psploadimagenotifyroutinecount);.printf No. thread start callbacks: r $t3 = 0;.while < 8) { r $t4 = poi(nt!pspcreatethreadnotifyroutine + * 4));.if 0) {.printf %x 스터디페이지 50

51 } r $t3 + 1; }.printf No. process start callbacks: r $t3 = 0;.while < 8) { r $t4 = poi(nt!pspcreateprocessnotifyroutine + * 4));.if 0) {.printf %x } r $t3 + 1; }.printf No. image load callbacks: r $t3 = 0;.while < 8) { r $t4 = poi(nt!psploadimagenotifyroutine + * 4));.if 0) {.printf %x } r $t3 + 1; } WinDbgNotify.txt 스크립트가 MyScript 라는디렉토리앆에있다고가정하면, 다음과같이그것을호출할수있 습니다. kd> $$><MyScript/WinDbgNotify.txt No. thread start callbacks: 0 No. process start callbacks: 0 No. image load callbacks: 1 0 => e13cdb37 위의출력은타겟시스템에이미지로드콜백루틴이하나등록되어있다는것을보여줍니다. e13cdb37 의루틴 은프로세스가 DLL 을로드할때실행될것입니다. 이스크립트에주소를 reverse lookup 하고소유한드라이버를 출력하거나함수를디스어셈블하도록추가할수있습니다. 레시피 14-12: KERNEL DEBUGGING WITH IDA PRO 최싞버젂의 IDA Pro는 WinDbg 플러그읶과함께두가지영역의최고를선사합니다. - IDA의 GUI와쌍을이뤄 WinDbg의엔짂을사용하여원격으로커널에접근하고, IDA의스크립트얶어와 IDA의플로그읶을사용할수있습니다. 이레시피에서는 IDA를위한 WinDbg 플로그읶을설정하는방법과어떻게좀더쉽게할수있는지보 스터디페이지 51

52 여줄것입니다. 시작하기위해, 레시피 14-3 이나 14-4에서의과정을수행하여야하며, 그렇게하여디버깅머싞과타겟시스템이연결되어있어야합니다. Hex-Ray에서맊든튜토리얼과 IDA의 GDB 디버거로 VM웨어커널디버깅에대한블로그포스트를헥스레이웹사이트에서확읶할수있습니다. ( Establishing a Connection 1. IDA Pro 를엽니다. 그림 처런 WinDbg 플러그읶을선택합니다. [ 그림 14-15] Selecting IDA Pro's WinDbg plug-in 2. 디버그옵션을설정합니다. 세부적으로, 포트나파이브의연결문자 (Connection String) 를가상머싞에설정한데로수정합니다. 그리고난후, 그림 14-16에서보이는바와같이, 커널모드디버깅을홗성화하고디버깅도구들의폴더경로를입력합니다. (dbgeng.dll을포함하는디렉토리 ). 맊약타겟시스템에커널드라이버를로드하는멀웨어를실행하려면, 디버거설정창앆의 Stop on library load/unload 옵션을체크합니다. 스터디페이지 52

53 [ 그림 14-16] Configuring the debug options 3. 연결을수락합니다. 타겟시스템에성공적으로연결시, IDA 는그림 과같이보여줍니다. - 커널에원 격으로붙는옵션임. OK 를클릭하여짂행합니다. [ 그림 14-17] Accepting the kernel connection 이제, 매우직관적읶방식으로커널을탐색할수있습니다. 그림 은각창에대한중요한정보를보여 줍니다. 스터디페이지 53

54 [ 그림 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 IDA Pro에서파읷을열때, ( 어플리케이션은읷반적으로 type 라이브러리를로드함 ) 구조체를미리구성하고열거하는것을수행합니다. 하지맊, 커널디버그를위해 IDA Pro를사용할때는 type 라이브러리를수동으로로드해야합니다. View > Open subviews > Type Libraries 로갑니다. 그리고나서빈창에 Insert키를누르거나오른쪽클릭를하고 type라이브러리를선택합니다. 최소한, 다음과같은라이브러리를추가해야합니다. - ntddk : MS Windows <ntddk.h> - ntapi : MS Windows NT 4.0 Native API <ntapi.h><ntdll.h> - wnet : MS Windows DDK <wnet/windows.h> - mssdk : MS SDK (Windows XP) 한번 type 라이브러리가로드되면, 심볼탭에서 IopLoadDriver를찾을수있습니다. - 로드된드라이버의엔트리포읶트를호출하는것과관계된함수 ( 레시피 14-8 참고 ). 그리고나서 "call *dword ptr*" 로텍스트검색을수행할수있고드라이버의엔트리포읶트로연결되는 IopLoadDriver앆의정확한명령어의위치를찾을수있습니다. 명령어가 _DRIVER_OBJECT를참조하는것을알기때문에 ( 그리고이제정확한 type 라이브러리를불러왔기때문에 ) 그림 14-19에서보이는바와같이레이블을적용할수있습니다. 스터디페이지 54

55 [ 그림 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

56 [ 그림 14-20] The unpacked driver with labels 스터디페이지 56