티스토리 뷰

본 문서는 원문 저자인 Massimiliano Tomassoli의 허락 하에 번역이 되었으며, 원문과 관련된 모든 저작물에 대한 저작권은 원문 저자인 Massimiliano Tomassoli에 있음을 밝힙니다. 


http://expdev-kiuhnm.rhcloud.com



최신 윈도우즈 익스플로잇 개발 01. WinDbg


hackability.kr (김태범)

hackability_at_naver.com or ktb88_at_korea.ac.kr

2016.07.18


WinDbg


WinDbg 는 굉장히 훌륭한 디버거이긴 하지만 많은 명령어를 갖고 있기 때문에 시간을 들여 익숙해져야 합니다. 지루하지 않게 간단히 설명하도록 하겠습니다. 여기에서는 몇몇 주요 명령어들과 옵션들에 대해 알려드리겠습니다. 추가적인 명령어나 옵션들은 이후 진행하면서 알려드리도록 하겠습니다.



버전


32-bit 실행 파일과 64-bit 실행 파일을 디버깅 시, 버전 문제로 문제가 발생될 수 있습니다이 때, 아래 명령어를 통해 32-bit 64-bit 모드를 변경할 수 있습니다.


!wow64exts.sw



심볼


WinDbg에 새로운 창을 여시고 File->Symbol File Path 에 들어 갑니다. (이미 프로세스를 디버깅 중이시라면, WinDbg를 닫으시고 다시 실행 하시기 바랍니다.)


SRV*C:\windbgsymbols*http://msdl.microsoft.com/download/symbols


그리고 저장합니다. (File -> Save Workspace)


별표 (*, asterisk)는 구분자를 뜻합니다. WinDbg는 우리가 명시한 첫 번째 디렉토리를 심볼 캐쉬로 사용하게 됩니다. (C:\windbgsymbols) 그 이후 별표 다음에 나오는 경로나 URL은 심볼이 있는 곳을 명시 합니다. (http://msdl.microsoft.com/download/symbols)



디버깅 중 심볼 추가


디버깅 중에 심볼 경로를 추가 하기 위해 다음과 같이 명령어를 입력합니다.


.sympath+ c:\symbolpath

여기서 ‘+’ 의 의미는 기본 탐색 경로를 바꾸라는 의미 입니다. 이후 다음 명령어로 심볼을 다시 로드 할 수 있습니다.


.reload



심볼 확인


만약 심볼을 사용할 수 있다면 필요할 때 불러지게 됩니다. 만약 어떤 심볼들이 불러졌는지 확인하려면 다음 명령어를 사용합니다.


x *!


명령어 x 는 와일드 카드 (*)를 지원하는데 이는 한 개 이상의 모듈을 검색할 때 사용됩니다예로 kernel32 모듈에서 virtual 이라는 이름으로 시작하는 심볼을 찾고 싶을 때 아래와 같이 사용합니다.


0:000> x kernel32!virtual*

757d4b5f          kernel32!VirtualQueryExStub (<no parameter info>)

7576d950          kernel32!VirtualAllocExStub (<no parameter info>)

757f66f1          kernel32!VirtualAllocExNuma (<no parameter info>)

757d4b4f          kernel32!VirtualProtectExStub (<no parameter info>)

757542ff          kernel32!VirtualProtectStub (<no parameter info>)

7576d975          kernel32!VirtualFreeEx (<no parameter info>)

7575184b          kernel32!VirtualFree (<no parameter info>)

75751833          kernel32!VirtualAlloc (<no parameter info>)

757543ef          kernel32!VirtualQuery (<no parameter info>)

757510c8          kernel32!VirtualProtect (<no parameter info>)

757ff14d          kernel32!VirtualProtectEx (<no parameter info>)

7575183e          kernel32!VirtualFreeStub (<no parameter info>)

75751826          kernel32!VirtualAllocStub (<no parameter info>)

7576d968          kernel32!VirtualFreeExStub (<no parameter info>)

757543fa          kernel32!VirtualQueryStub (<no parameter info>)

7576eee1          kernel32!VirtualUnlock (<no parameter info>)

7576ebdb          kernel32!VirtualLock (<no parameter info>)

7576d95d          kernel32!VirtualAllocEx (<no parameter info>)

757d4b3f          kernel32!VirtualAllocExNumaStub (<no parameter info>)

757ff158          kernel32!VirtualQueryEx (<no parameter info>)


와일드 카드는 다음과 같이 모듈의 부분에서도 사용될 수 있습니다.


0:000> x *!messagebox*

7539fbd1          USER32!MessageBoxIndirectA (<no parameter info>)

7539fcfa          USER32!MessageBoxExW (<no parameter info>)

7539f7af          USER32!MessageBoxWorker (<no parameter info>)

7539fcd6          USER32!MessageBoxExA (<no parameter info>)

7539fc9d          USER32!MessageBoxIndirectW (<no parameter info>)

7539fd1e          USER32!MessageBoxA (<no parameter info>)

7539fd3f          USER32!MessageBoxW (<no parameter info>)

7539fb28          USER32!MessageBoxTimeoutA (<no parameter info>)

7539facd          USER32!MessageBoxTimeoutW (<no parameter info>)


다음 명령어를 이용하여 강제로 모듈 모듈을 불러 올 수 있습니다.


ld*


이 명령어는 시간이 좀 걸리는데 만약 불러오는 도중 멈추고 싶다면 Debug -> Break 를 하시면 됩니다.



도움말


아래와 같이 입력하면 도움말을 얻을 수 있습니다.


.hh


또는 F1을 이용하여 도움말 창을 열수 있습니다만약 명령어에 대한 도움말이 필요하다면 도움말 명령어와 함께 해당 명령어를 넣으시면 됩니다.


.hh <command>


도움말 창에서는 tab Index 를 이용하여 검색에 원하는 주제나 명령어들을 검색할 수 있습니다.



디버깅 모드


Locally


새로운 프로세스나 이미 실행 중인 프로세스는 디버깅 할 수 있습니다.

 

1. 새로운 프로세스를 디버깅 하기 위해서는 File -> Open Executable 로 실행

2. 이미 실행 중인 프로세스를 디버깅 하기 위해서는 File -> Attach to a Process 로 실행


Remotely


원격에 있는 프로그램을 디버깅 하기 위해서는 최소 2개 옵션이 있습니다.



1. 만약 A 컴퓨터에서 이미 로컬 디버깅을 하고 있다면 다음 명령어를 통해 원격에서 접속할 포트를 열 수 있습니다.


.server tcp:port=1234


이 명령어를 이용하여 WinDbg 서버를 시작 할 수 있습니다. B 컴퓨터에서는 WinDbg를실행하고 File -> Connect to Remote Session 으로 가서 아래 명령처럼 아이피와 포트를 입력합니다.


tcp:Port=1234,Server=<IP of Machine A>


2. A 컴퓨터에서 dbgsrv 프로그램을 아래 인자들과 함께 실행합니다.


dbgsrv.exe -t tcp:port=1234


이 프로그램을 이용하여 A 컴퓨터에서 서버를 시작 할 수 있습니다컴퓨터 B 에서는 File -> Connection to Remote Stub 에서 다음과 같이 아이피와 포트를 입력합니다.


tcp:Port=1234,Server=<IP of Machine A>


보시면 File -> Attach to a Process 는 활성화 되어 있지만 File -> Open Executable 이 비활성화 되어 있음을 볼 수 있습니다. 이 경우, A 컴퓨터에서 프로세스들의 목록을 볼 수 있습니다.


A 컴퓨터에서 서버를 종료하고 싶으시면 작업 관리자에서 dbgsrv.exe 를 종료하시면 됩니다.



모듈


실행 파일을 불러오거나 프로세스에 붙었을 때, WinDbg는 사용 중인 모듈들을 불러옵니다. 만약 모듈을 다시 불러 오고 싶다면 다음과 같이 입력합니다.


lmf


모듈 ntdll.dll 에 대해 나열하고 싶다면 다음과 같이 입력합니다.


lmf m ntdll


ntdll.dll 모듈의 이미지 해더 정보를 얻고 싶다면 다음과 같이 입력합니다.


!dh ntdll


‘!’ 의 뜻은 확장 명령으로, 외부 DLL에서 제공된 외부 명령어이며 WinDbg 에서 호출합니다. 사용자는 자신만의 확장자를 만들어 WinDbg의 기능을 추가적으로 만들 수 있습니다.


모듈의 시작 주소를 얻고 싶다면 다음과 같이 입력합니다.


0:000> lmf m ntdll

start    end        module name

77790000 77910000   ntdll    ntdll.dll   

0:000> !dh 77790000



표현들 (Expressions)


WinDbg 다양한 표현들을 지원합니다. 표현은 어떤 값이 필요할 때, 직접 값을 입력하거나 값을 얻기 위한 연산을 표현할 수 있습니다.


예를들어 EIP 77c6c70 이라 하면 아래 두 명령은 동일한 역할을 합니다.


bp 77c6cb71

bp EIP+1


또는 심볼이나 레지스터를 이용할 수 있습니다.


u ntdll!CsrSetPriorityClass+0x41

dd ebp+4


숫자는 기본적으로 16진수 기반으로 사용됩니다. 만약 진수 표기를 명시하고 싶다면 다음과 같이 접두사를 추가합니다.


0x123: 16진수

0n123: 10진수

0t123: 8진수

0y111: 2진수


.format 명령어를 이용하여 값의 다양한 포멧을 볼 수 있습니다.


0:000> .formats 123

 Evaluate expression:

 Hex:     00000000`00000123

 Decimal: 291

 Octal:   0000000000000000000443

 Binary:  00000000 00000000 00000000 00000000 00000000 00000000 00000001 00100011

 Chars:   .......#

 Time:    Thu Jan 01 01:04:51 1970

 Float:   low 4.07778e-043 high 0

 Double:  1.43773e-321


연산이 필요하다면 ‘?’ 을 사용하면 됩니다.


? eax+4



레지스터와 의사(Pseudo)-레지스터


WinDbg는 특정 값을 갖는 몇몇 의사-레지스터를 지원합니다. 의사-레지스터를 나타낼 때, 접두사로 ‘$’를 넣어 표현합니다. 레지스터나 의사-레지스터를 사용할 때, 접두사로 ‘@’ 를 넣으면 WinDbg에게 그것은 레지스터 이고 심볼이 아님을 알립니다. 만약 ‘@’ 이 사용되지 않았다면 WinDbg는 명령어에 대해 먼저 심볼로 인식을 시도 할 것입니다.


$teb 또는 @$teb (TEB의 주소)

$peb 또는 @$peb (PEB의 주소)

$thread or @$thread (현재 스레드)



예외


특정 예외에 대해 멈추고 싶다면 sxe 명령어를 사용합니다. 어떤 모듈을 불러 왔을 때 멈추고 싶다면 다음과 같이 입력합니다.


sxe ld <module name 1>,...,<module name N>


예를 들어 user32.dll 을 불러온 뒤 멈추고 싶다면 다음과 같이 입력합니다.



sxe ld user32


예외들을 나열하려면 다음과 같이 입력합니다.


sx


예외에 대해 무시하려면 sxi 명령어를 이용합니다.


sxi ld


이 명령은 첫 번재 명령어에 대해 취소하게 됩니다.

 

WinDbg single-chance 예외와 second-chance 예외에서 멈추게 됩니다. 이 둘은 다르지 않은 예외들 입니다. Single-chance의 의미는 예외가 디버거에서 오지 않음을 의미합니다. 만약 우리가 해당 예외를 이후 계속 진행 했을 때 WinDbg는 디버기(Debuggee)로 예외를 보냅니다. 만약 디버기에서 예외를 처리하지 못한다면 WinDbg는 다시 멈추고 second-chance 예외가 발생되게 됩니다.

 

EMET 5.2를 검사할 때, single-chance single step 예외를 무시하는 것이 필요합니다. 이를 위해 다음 명령어를 사용합니다.


sxd sse



Breakpoints


Software Breakpoints


어떤 명령어에 software breakpoint 를 넣었을 때, WinDbg는 메모리에 해당 명령어의 첫 번째 바이트를 기록해두고 해당 바이트를 0xCC (int 3) 로 변경합니다.

 

Int 3 명령어가 실행 되면, breakpoint 가 실행되게 되어 예외로 멈추게 되고 WinDbg는 해당 명령어의 첫 바이트를 원상태로 복구하게 됩니다.

 

Software breakpoint 0x4110a0 에 넣으려면 다음과 같이 입력합니다.


bp 4110a0


만약 위의 breakpoint 3번 발생되고 1, 2 번째 breakpoint는 무시하고 3번째만 보고 싶다면 다음과 같이 입력합니다.


bp 4110a0 3


이 뜻은 처음 2번의 breakpoint는 무시하겠다는 의미입니다Breakpoint 이후 실행을 계속 진행하고 싶다면 아래 명령을 입력합니다.


g


특정 주소를 만날 때까지 실행하고 싶다면 다음과 같이 입력합니다.


g <code location>


내부적으로 위 명령은 WinDbg에서 해당 주소에 software breakpoint 생성하지만 한 번 break point로 멈추고 난 이후에 삭제되게 됩니다. (one-time software breakpoint)


Hardware Breakpoints


Hardware breakpoints CPU의 레지스터나 software breakpoint 보단 좀더 다양하게 사용됩니다. 사실 이는 실행 중이거나 메모리에 접근할 때 멈춤이 가능합니다.

 

Hardware breakpoint는 명령어를 변경하지 않기 때문에 자가 변조 코드 (self modifying code)를 사용합니다. 아쉽지만 hardware breakpoint 4개를 넘을 수 없습니다.

 

해당 명령어를 간단히 표현하면 다음과 같습니다.


ba <mode> <size> <address> <passes (default=1)>


Mode에서 ‘e’ (실행), ‘r’ (메모리 읽고 쓰기), 그리고 ‘w’ (메모리 쓰기)를 의미합니다. Size는 특정 위치에 대한 접근을 지켜보기 위해 크기를 바이트 단위로 지정할 수 있습니다. (mode 에서 e 가 사용되었다면 size의 크기는 항상 1이 됩니다.) <address> breakpoint를 넣을 주소가 되며 <passes>는 해당 위치 접근에 대해 몇 번 무시할건지 지정할 수 있습니다.

 

Hardware breakpoint는 프로세스가 실행되지 전에 넣는 것이 불가능합니다. 그 이유는 hardware breakpoint CPU register (dr0, dr1)을 이용해야 하는데 프로세스나 스레드가 생성되어야 레지스터가 초기화 되기 때문입니다.


Breakpoints 다루기


Breakpoints 들을 열거 하려면 다음과 같이 입력합니다. (bl breakpoint list 를 의미합니다.)


bl


예제:


0:000> bl

0 e 77c6cb70     0002 (0002)  0:**** ntdll!CsrSetPriorityClass+0x40


일 때, 값들에 대한 의미는 다음과 같습니다.


0                    breakpoint ID

E                    breakpoint 상태; (E)nabled 또는 (D)isabled

77c6cb70          메모리 주소

0002 (0002)       breakpoint 에서 멈추기 까지 남은 무시 횟수와 전체 무시 횟수. (해당 값은 breakpoint 가 생성된 이후 명시 됩니다.)

0:***               연관된 프로세스와 스레드. 별표의 의미는 breakpoint 가 스레드에 의존적이지 않음을 나타냅니다.

Ntdll!CsrSetPriorityClass+0x40      모듈과 함수 그리고 breakpoint 가 위치한 상대 거리를 의미

 

Breakpoint 를 비활성화 하고 싶다면 다음과 같이 입력합니다.


bd <breakpoint id>


만약 breakpoint 를 지우고 싶다면 다음과 같이 입력합니다.


bc <breakpoint ID>


만약 모든 breakpoint 를 지우고 싶다면 다음과 같이 입력합니다.


bc *


Breakpoint 명령어들


만약 breakpoint 가 실행될 때, 특정 명령어들을 자동으로 같이 실행하고 싶다면 다음과 같이 입력합니다.


bp 40a410 ".echo \"Here are the registers:\n\"; r"


다른 예


bp jscript9+c2c47 ".printf \"new Array Data: addr = 0x%p\\n\",eax;g"



Stepping


Stepping에는 최소 3 종류가 있습니다.


1. Step-in / trace (명령어: t)

이 명령은 단일 명령어 단위로 breakpoint를 진행합니다. 만약 call 이나 int 명령에 있다면 함수의 첫 번째 명령어 또는 int 핸들러로 가게 됩니다.


2. Step-over (명령어: p)

이 명령은 단일 명령어 단위로 breakpoint를 진행합니다. 만약 call 이나 int 명령에 있다면 함수나 int 명령이 모두 실행된 다음으로 가게 됩니다.


3. Step-out (명령어: gu)

이 명령어는 (go up) ret 명령어 이후에 멈추게 됩니다. 이 명령어는 함수에서 나올 때 사용됩니다함수를 탈출하기 위한 2개 다른 명령어가 더 있습니다.

- tt (trace to next return): 이는 명령어 t 와 동일한 역할을 하며 첫 번째 ret 에서 멈추게 됩니다.

- pt (step to next return): 이는 명령어 p 와 동일한 역할을 하며 첫 번째 ret 에서 멈추게 됩니다.

- tt 는 함수 내부에 들어 갈 수 있기 때문에 만약 현재 함수의 ret 에 가고 싶다면 pt 를 사용해야 합니다. pt gu 명령어의 차이는 pt ret 명령어에서 멈추지만 gu ret 이후 명령어에서 멈추게 된다는 것 입니다.


아래는 'p'와 't'의 변종들에 대한 내용입니다.


pa/ta <address>: 주소로 step/trace

pc/tc: 다음 call/int 명령으로 step/trace

pt/tt: 다음 ret으로 step/trace

pct/tct: 다음 call/int 또는 ret으로 step/trace

ph/th: 다음 분기 명령으로 step/trace



메모리 보기


메모리 내용을 보기 위해서는 ‘d’ 명령어를 사용하면 됩니다.

 

db: bytes (1 바이트) 단위로 표시

dw: words (2 바이트) 단위로 표시

dd: dwords (4 바이트) 단위로 표시

dq: qwords (8 바이트) 단위로 표시

dyb: 비트 단위로 표시

da: null 로 끝나는 아스키 문자열을 표시

du: null 로 끝나는 유니코드 문자열을 표시


.hh d 명령을 이용하여 다른 명령어들을 볼 수 있습니다.

 

명령어 ‘d’ 는 최신 d* 명령어와 동일한 형태로 데이터를 표시합니다. 해당 명령어에 대한 포멧은 다음과 같습니다.


d* [range]


별표는 위에서 보인 모든 ‘d’ 포멧들에 대해 표시함을 의미하고 괄호는 영역을 의미하며 이는 선택적입니다. 만약 영역이 빠진다면 d*는 최근에 d* 명령어로 보인 메모리 이후의 영역을 표시합니다.

 

영역은 다양한 방법으로 표현이 가능합니다.

 

1. <시작 주소> <끝 주소>

db 77cac000 77cac0ff


2. <시작 주소> L<요소의 수>

dd 77cac000 L10


위 명령은 77cac000 부터 10개의 dwords 를 표현합니다.

 

만약 영역이 256 MB 보다 크다면 반드시 L이 아니라 L?로 요소의 수를 넣어주어야 합니다.

 

3. <시작 주소>

시작 주소만 있다면 WinDbg 128 바이트를 표시합니다.



메모리 수정


다음 명령어를 이용하여 메모리를 수정 할 수 있습니다.


e[d|w|b] <address> [<new value 1> ... <new value N>]


[d | w | b]는 선택적으로 넣을 수 있으며 이는 수정 할 요소의 크기를 의미합니다. (d = dword, w = word, b = byte). 만약 new value 가 빠져 있다면 WinDbg new value에 대해 물어볼 것 입니다.

 

예제는 다음과 같습니다.


ed eip cc cc


이는 eip 주소 이후 2개의 dwords 에 대해 값을 0xcc 로 변경함을 의미합니다.



메모리 검색


메모리를 검색하기 위해서는 ‘s’ 명령어를 이용하고 사용 방법은 다음과 같습니다.


s [-d|-w|-b|-a|-u] <start address> L?<number of elements> <search values>


d, w, b, a 그리고 u dword, word, byte, ascii 그리고 unicode를 의미합니다<search value>는 검색할 값의 순서를 의미합니다.

 

예를들어,


s -d eip L?1000 cc cc


이 명령은 [eip, eip + 1000*4-1] 위치의 영역에서 0xcc 0xcc dwords 값이 연속적으로 있는 경우를 검색합니다.



포인터


가끔 어떤 포인터에 대해 역참조가 필요할 때가 있습니다. 해당 연산은 poi 를 이용하여 가능합니다.


dd poi(ebp+4)


이 명령에서 poi(ebp+4) ebp+4 주소에 있는 dword (64-bit 모드에서는 qword)를 역참조 합니다.




그 외 다양한 명령어들


레지스터를 보고 싶다면 다음과 같이 입력합니다.


r


특정 레지스터 (eax, edx, …)들을 보고 싶다면 다음과 같이 입력합니다.



r eax, edx


EIP 주소의 3개 명령어를 보고 싶다면 다음과 같이 입력합니다.


u EIP L3


u unassemble의 약자이며 ‘L’은 표시할 수 입니다콜 스택을 보고 싶다면 다음과 같이 입력합니다.


k



구조 덤핑


아래는 구조들을 출력하기 위해 사용되는 명령어들 입니다.




제안하는 설정



이렇게 설정 한 뒤, File -> Save Workspace 로 저장합니다.

댓글