kernel debugging(번역).pdf
TRANSCRIPT
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
다른 말로 하자면 프로세스와 드라이버의 리스트를 보거나 커널 메모리를 덤프 하거나 커널 심볼의 위치와 내
용을 볼 수는 있지맊 브레이크포읶트를 설정하거나 코드를 따라 한 단계씩 이동하거나 레지스터 나 메모리의
내용을 변경 할 수는 없습니다
소프트웨어 요구사항
그림 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
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
를 실행 할 때 -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
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
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
[그림 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
[그림 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
에서는 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
레시피 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
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
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
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
커널이나 유저모드 메모리앆 에서 바이트들의 패턴을 찾기 위해 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
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
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
맊약 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
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
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
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
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
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
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
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
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
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
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
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
여기서 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
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
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
은 것을 선택하면 됩니다 예를 들어 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
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
던 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
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
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
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
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
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
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
있습니다
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
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
코드들 살펴봅니다 맊약 드라이버 앆의 다른 함수들을 본다면 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
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
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
[그림 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
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
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
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
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
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
여줄 것 입니다 시작하기 위해 레시피 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
[그림 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
[그림 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
[그림 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
[그림 14-20] The unpacked driver with labels
스터디 페이지 56