출처: http://xxkim.springnote.com/pages/546195

II. WinDbg


 WinDbg는 NTSD와 KD를 더 나은 UI로 래핑한 디버거이다. 최소화 상태로 시작(-m), pid를 통한 프로세스에 연결(-p), 크래쉬 파일들을 자동 열기(-z)와 같은 명령행 옵션들을 제공한다. 세가지 유형의 명령들을 지원한다.


정규 명령 : 프로세스들을 디버깅한다. (e.g. : k)

닷(dot) 명령 : 디버거를 제어한다. (e.g. : .sympath)

확장 명령 : WinDbg에 추가할 수 있는 커스텀 명령들로 확장 DLL들에서 익스포트된 함수들로 구현된다. (e.g. : !handle)


 PDB 파일들

PDB 파일들은 링커에 의해 생성된 프로그램 데이터베이스 파일들이다. 비공개(private) PDB 파일들은 비공개 및 공개 심볼들, 소스 라인들, 타입들, 지역 및 전역변수들에 대한 정보를 포함한다. 공개(public) PDB 파일들은 타입들, 지역 및 소스 라인 정보를 포함하지 않는다.


디버깅 시나리오

 리모트 디버깅

디버깅 서버(debugging server)라 함은 디버그 하고자 하는 머신상에 동작중인 디버거이며,  디버깅 클라이언트(debugging client)라 함은 세션을 제어하는 디버거이다.

서버상에는 CDB, NTSD, 또는 WinDbg가 필요한다. WinDbg 클라이언트는 CDB, NTSD 및 WinDbg중 아무에라도 연결할 수 있다. 서버와 클라이언트는 통신프로토콜로 TCP와 명명된 파이프들중에서 선택할 수 있다.


디버거를 이용하는 경우

서버 시작

WinDbg -server npipe:pipe=파이프명 (다수의 클라이언트가 접속할 수 있다.)

또는 WinDbg내에서

.server npipe:pipe=파이프명 (단일 클라이언트만 접속할 수 있다.)


클라이언트로 접속

WinDbg -remote npipe:server=서버, pipe=파이프명[,password=패스워드]

또는 WinDbg내에서 File->Connect to Remote Session

npipe:server=서버, pipe=파이프명 [,password=패스워드]


 remote.exe를 이용하는 경우

 remote.exe는 통신에 명명된 파이프들을 이용한다. 만약 KD, CDB 또는 NTSD와 같은 콘솔 기반 응용프로그램을 이용한다면, 리모트 디버깅을 하기위해 remote.exe를 이용할 수 있다.


서버 시작

remote.exe /s "cdb -p <pid>" test1


클라이언트로 연결

remote.exe /c <머신명> test1


'qq'로 서버를 종료하거나 또는 File->Exit를 사용하여 클라이언트를 종료할 수 있다. 리모트 디버깅을 하기 위해서는 사용자 그룹으로서 Debugger Users에 속해야 하며, 서버는 원격 연결을 허용해야만 한다.


 Just-in-time 디버깅

WinDbg -I로 실행하면 기본 JIT 디버거로서 설정할 수 있다. 이 명령은 레지스트리 키 HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug를 WinDbg롤 설정한다. JIT 디버거로 설정되면, 응용프로그램인 디버깅중이 아닐 경우에 예외를 던지고 그 예외를 처리하지 않을 경우 WinDbg가 동작하게된다.


 예외 디버깅

디버거는 각각의 예외에 2번 통지받는다. 응용프로그램이 예외를 처리하기 전에 first chance exception을 통지받고, 응용프로그램이 그 예외를 처리하지 않을 경우 second-chance excepton을 처리할 기회를 얻게된다. 디버거가 second-chance exception을 처리하지 않는다면, 응용프로그램은 종료한다.

.lastevent 또는 !analyze -v는 예외 기록과 예외가 발생했을 때의 함수의 스택 트레이스를 보여준다. .exr, .cxr 및 .ecxr 명령들을 예외 및 문맥 기록들(context records)을 화면에 표시하는데 사용할 수 있다. sxe, sxd, sxn 및 sxi 명령들을 이용하여 예외에 대해 first-chance 처리 옵션을 변경할 수 있다.


WinDbg 특징들

디버거 확장 DLL

디버거 확장은 디버거 내에서 커스텀 명령들을 실행할 수 있도록 디버거를 후킹(hook up)할 수 있도록 하는 DLL을 말한다. ! 명령들은 확장 DLL들로부터 실행되는 명령들이다. 확장 DLL들은 디버거의 프로세스 공간에 로드된다.


덤프 파일들

윈도우 OS가 깨지게 되면, 물리적 메모리 내용들과 모든 프로세스 정보가 덤프 파일에 기록된다. WinDbg를 JIT 디버거로 설정함으로써 비정상적으로 종료하는 모든 프로세스의 덤프를 만들 수 있다.  

덤프 파일을 분석하기 위해 다음 단계들을 따른다.

Step 1: File->Open Crash Dump. 덤프 파일을 선택하여 연다.

Step 2: 실행 중인 응용프로그램의 명령들을 보여준다.

Step 3: 심볼 경로와 소스 경로를 적절히 설정한다.


WinDbg 설정

 설치(Debugging Tools for Windows)

http://www.microsoft.com/whdc/devtools/debugging/default.mspx

설정

WinDbg에서 디버깅을 수행하기 위해서는 pdb 파일이 필요하다. Visual Studio에서 Debug Build로 빌드하면 Debug 폴더 안에 pdb 확장자 파일이 생긴다. 이 pdb파일을 WinDbg의 Symbol File Path에 복사해야 한다. Symbol File Path는 WinDbg의 File | Symbol File Path...[Ctrl+S] 메뉴에서 설정 가능하다.

실행

WinDbg의 File | Open Executable...[Ctrl+E] 메뉴를 클릭하여, 디버그할 실행 파일을 연다.

소스 레벨 디버깅

소스 레벨 디버깅을 하기 위해서는 소스 파일을 열어야 한다.

File | Open Source File...[Ctrl+O]

1) 소제목은 이렇게 넣어보세요

III. 결론

이 곳에 레포트 결론을 적어보세요. 마무리는 짧고 굵게!

참고문헌

각주

AND

AND

출처 :http://beist.org/research/trans_doc/win32rv.html


32에서 리버스엔지니어링

 

이 문서는 Uninformed 매거진 Vol.1 Introduction to Reverse Engineering Win32 Applications  - trew를 번역한 것으로, orpe님이 beist.org에 제공해주셨습니다. 힘들게 번역한 문서를 공유해주신 orpe님께 감사드립니다.

 

오리지날 문서 URL : http://www.uninformed.org/?v=1&a=7&t=txt

번역: orpelove@gmail.com




 


 1) 서문

 

 이 문서를 통해 여러분은 WinDBG를 통해 윈32 프로그램을 제작할 수 있고 근본적인 것을 이해하기 위해 많은 개념과 툴들이 소개 될 것이다. 그리고 '지뢰찾기' 게임을 통해 WinDBG에서 제공하는 기능과 시연을 토대로 어떻게 윈32 응용프로그램을 리버스엔지니어링 하는지에 대해 채워질 것이다. IA-32 어셈블리, 레지스터의 중요성, 보호메모리, 스택의 사용과정, 다양한 WinDBG의 명령어들 call stack개념, 에디안 그리고 윈도우 API 등에 대해 다룰 것이다. 이 지식을 바탕으로 지뢰찾기 게임에서 지뢰를 제거하고 노출시키는 프로그램을 만들어 볼 것이다.

 


2) 소개 (Introduction)

 게임은 간혹 사람을 좌절시킨다. 이 좌절은 게임의 설계상 플레이어에게 알려지지 않은 것들을 제공해야 한다는 필요에서 비롯되는 것이다. 예를 들어 넘버 3번 문 뒤에 잠복한 몬스터의 수가 얼마나 되는지, 그리고 90, 50 구경의 8개의 총탄으로 충분히 죽일 수 있을까 하는 것들이다. 열 번을 하고 키보드가 부숴지고 나서야 플레이 하고 있던 필드의 레벨뿐만 아니라 게임의 기질이 높아져 가는 능력을 가진다. 어떤 부류의 사람들은 명성이나 카르마 데미지를 입고도(역자주: 한마디로 얍삽하게) 이득을 취득한다. 바로 치팅(cheating)으로 말이다.

 많은 사람들은 이런저런 이유로 치트를 만들어 불공평한 이점을 챙긴다. 그러나 그와는 다른 사람은 완전히 다른 동기를 가지고 게임에 도전한다. 동기에 관해서 일단 제쳐두고 이 문서의 목적은 독자에게 윈도우 응용프로그램의 리버스 엔지니어링에 대해 도움을 주고 기본적인 방법론과 툴 사용법에 친숙하도록 만드는 것이다. 독자들은 WinDBG, IA-32 어셈블러, 윈도우 API와 관련된 부분들에 대해 알게 될 것이다. 지뢰찾기 게임으로 탐스러운 '불공평한 이점'을 챙기는 것에 대해 차근차근 예를 들어 이 개념들을 설명할 것이다.

3) 시작(Getting Started)

 이 문서를 읽기 위해 독자들은 다음과 같은 요구사항을 만족해야 한다.

    1. HEX 값을 이해함

    2. 기본적인 C 코딩 가능함

    3. WinDBG를 설치하고 적당히 설정 가능함

    4. 윈 XP를 사용하고 지뢰찾기 게임이 실행가능함

 

 이 문서를 읽으며 옆에 준비하고 있어야 하는 것들:

    1. IA-32 Instruction Set Reference A-M [7]

    2. IA-32 Instruction Set Reference N-Z [7]

    3. IA-32 Volume 1 - Basic Architecture [7]

    4. Microsoft Platform SDK [4]

    5. Debugger Quick Reference [8]

먼저, WinDBG와 Symbol 패키지가 필요한데 WinDBG는 The Debugging Tools Windows 패키지의 한 부분이다.

다운로드 링크정보:

 

http://msdl.microsoft.com/download/symbols/packages/windowsxp/WindowsXP-KB835935-SP2-slp-Symbols.exe

http://msdl.microsoft.com/download/symbols/debuggers/dbg_x86_6.4.7.2.exe

 

다운로드 받는 동안 잠재적인 목적과 디버깅이 무슨 일을 해 줄 것인지, 그에 관한 중요성 그리고 심볼은 무엇이며 심볼이 응용프로그램을 디버깅 할 때 어떻게 유용한지 알아보자.


3.1) 목표

 지뢰찾기 게임을 하기 위한 기본적인 전략은 주어진 지뢰판에서 폭탄이 어디에 위치하고 있는지 확인하고 가능한 빠른 시간내에 지뢰가 없는 공간을 클리어 하는 것이다. 각 칸에 플레이어는 물음표를 찍을 수 있고 폭탄임에 확신이 드는 칸이라면 깃발을 찍을 수 있다. 이걸 명심하고 다음의 목표를 끌어 낼 수 있다:

 

    1. 타임 정보를 컨트롤하고 수정하기

    2. 깃발과 물음표 마크의 정확한 위치 확인하기

    3. 폭탄의 위치 확인하기

    4. 지뢰판에서 폭탄제거하기

 

이 목표를 이루기 위해 독자는 다음과 같은 사항을 먼저 확보해야 한다.

 

    1. 지뢰찾기 프로세스에서 지뢰판이 위치하는 곳

    2. 지뢰판을 어떻게 해부할 것인가

    3. 지뢰찾기 프로세스에서 타임(clock)이 위치하는 곳

 

이 문서의 범위는 지뢰판에서 폭탄을 제거하고, locating, interpeting, 그리고 reversing 에 초점을 둘 것이다.

 


3.2) 심볼(Symbols) 과 디버거(Debuggers)

 디버거는 프로세스의 영역을 컨트롤, 수정, 시험 해보기 위해 그 프로세스를 불러(attach) 들이는 하나의 툴셋이다. 좀 더 자세히 말하면 디버거는 여러분에게 실행흐름(flow)를 수정할 수 있게 하고 프로세스 메모리를 읽거나 쓸 수 있으며 레지스터 값을 변경할 수 있게 한다. 이런 이유로 디버거는 당신이 원하는 대로 가공하기 위해 응용프로그램이 어떻게 수행 되는지 이해 할 수 있는 필수품인 것이다.

 일반적으로 응용프로그램이 배포(release)단계에서 컴파일 되었을 때는 소스와 관련된 정보나 함수나 변수의 이름은 디버깅 정보에 포함하지 않는다. 이러한 정보의 부재는 리버스엔지니어링을 통해 응용프로그램을 이해하는것을 더 어렵게 만든다. 하지만 디버거는 심볼이라는 것을 도입하여 보이지 않던 함수나 변수의 이름을 대신하여 사용한다. 좀 더 심볼에 관한 정보를 알고 싶다면 레퍼런스 섹션[3]의 관련 문서를 참고하라.


3.3) 심볼 서버(Symbol Server)

 아까 다운로드 받은 윈도우용 디버깅 툴과 심볼 패키지 두 개의 파일을 지금쯤이면 다 받았을 것이다. 이제 순서대로 인스톨 한 후 심볼 패키지가 설치된 위치를 잘 적어 놓는다. 설치 후 시작 메뉴에 들어가 프로그램>Debugging Tools for Windows 아래 WinDBG를 실행한다. WinDBG가 실행되었다면 File 메뉴에서 심볼 파일 패스(Symbol File Path)를 클릭한 후 아래와 같이 입력한다:


SRV**http://msdl.microsoft.com/download/symbols

예를 들어, 심볼이 다음과 같은 위치에 설치되었다면:


C:\Windows\Symbols

당신은 다음과 같이 입력해야 한다:


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

 이 설정은 WinDBG에게 아까 설치한 심볼의 위치를 알려주기 위한 것이다. 그리고 만약 심볼이 사용할 수 없을 때 심볼 서버로 부터 받아 올 위치를 설정한 것이다. 심볼서버에 관한 더 자세한 정보는 레퍼런스 섹션[2]를 참고하라.

4) WinDBG와 친해지기

 마우스를 사용하던지 단축키를 사용하던지 WinDBG툴바는 이 문서에서 사용되는 기본적인 용어를 논의하기 위한 간단한 가이드로서 역할을 할 것이다. 왼쪽부터 오른쪽으로 각 옵션의 기능은 다음과 같다:

  1. Open Source Code - Open associated source code for the debugging session.
  2. Cut - Move highlighted text to the clipboard
  3. Copy - Copy highlighted text to the clipboard
  4. Go - Execute the debugee
  5. Restart - Restart the debugee process
  6. Stop - Terminate the debugging session
  7. Break - Pause the currently running debugee process

 다음 네개의 옵션 아이콘은 디버거가 Break가 걸린 후 사용된다. 디버거는 위의 옵션을 통해 Break 걸릴 수도 있고 사용자가 위치를 지정하여 Break (Breakpoint) 걸 수도 있다. 브레이크포인트는 다양한 상황에 할당 될 수 있다. 대부분의 공통적인 점은 프로세서가 특정 주소에서 명령행(instruction)을 실행 할 때나 메모리의 특정 영역에 접근 되었을 때의 상황이다. 브레이크포인트의 용도에 관해서 나중에 다시 자세히 언급하도록 하겠다.

 브레이크포인트가 걸린 위치에 도달 하였을 때, 각각의 명령행이나 함수를 호출하여 실행하는 것은 프로세스를 수행하며 어떻게 건너뛸 것인지 확인하는데, WinDBG에는 스텝 방식에 대해 다음과 같이 네가지를 정의하고 있다.

  1. Step Into - Execute a single instruction. When a function is called, this will cause the debugger to step into that function and break, instead of executing the function in its entirety.
  2. Step Over - Execute one or many instructions. When a function is called, this will cause the debugger to execute the called function and break after it has returned.
  3. Step Out - Execute one or many instructions. Causes the debugger to execute instructions until it has returned from the current function.
  4. Run to Cursor - Execute one or many instructions. Causes the debugger to execute instructions until it has reached the addresses highlighted by the cursor.


4.1) WinDBG 윈도우

 

 WinDBG는 View 툴바 옵션 아래 나열된 다양한 정보를 포함하는 윈도우를 제공한다. 우리는 Register, Disassembly, Command 윈도우를 사용할 것이다.

 

 Register 윈도우에는 값을 포함하고 있는 프로세서의 모든 레지스터 리스트를 포함하고 있다. 눈치챘겠지만 레지스터의 값이 바뀌면 값 그 부분의 색깔이 빨간색으로 변한다. 간단하게 eip, ebp, esp, eax, ebx, ecx, edx, esi, and edi 레지스터 중 다음의 레지스터에 대해서만 잠깐 브리핑 하겠다.

 

1. eip - 다음 실행될 명령행의 주소값을 가짐

2. ebp - 현재 스택프레임의 주소값을 가짐

3. esp - 스택의 꼭대기 주소값을 가짐

 

 나머지는 일반적으로 사용되기 위한 레지스터다. 이 레지스터는 특정한 명령에 의해 이용되며 명령행에 기초하여 레지스터가 사용되는 것에 관해 다 자세히 알고 싶으면 IA-32 Command Reference[7]을 참고하라.

 

 Disassembly 윈도우는 eip 레지스터에 저장된 값에 의해 불려진 주소에 어셈블리 명령어(instruction)를 포함할 것이다.

 

 Command 윈도우는 디버거로 만들어진 요청문의 결과를 출력한다. 아래 보이는 라인 윈도우는 텍스트 박스인데 여기 디버거 명령을 입력한다. 그 왼쪽 옆에 보이는 박스는 프로세스를 detach한 상태나 요청을 처리할 때, debugee is running 메시지를 뿌려줄 때 빈칸으로 보일 것이다. 유저-모드에서 단일 로컬 프로세스를 디버깅 할때에는 이 박스에 "0:001>"이 나타날 것이다. 프롬프트에 대한 자세한 정보를 원하면 레퍼런스 섹션[9]를 참고하라.

 

 우리가 입력할 수 있는 명령어의 클래스에는 regular, meta, extention(확장기능) 이 있다. regular(정규) 명령어는 디버그 인터페이스를 허용하며 meta 명령어는 (.)을 첫글자에 쓰고 디버거의 설정을 정할 때 쓰인다. 그리고 extention 명령은 (!)를 첫글자에 쓰고 WinDBG의 플러그인과 연결시켜 준다.

 

 

5) 지뢰찾기 로케이팅 시키기(Locating the WinMine Playing Grid)

 

윈도우의 시작 메뉴에서 지뢰찾기 프로그램을 실행시키고 메뉴에서 사용자 정의로 다음과 같이 입력한다.

 

Level: Custom

    Height: 900

    Width:  800

    Mines:  300

Marks: Enabled

Color: Enabled

Sound: Disabled

 

이제 레퍼런스 섹셕[12]에 포함되어 있는 SetGrid 코드를 컴파일 하여 실행 시키면 여러분들의 지뢰판이 이 문서에서 언급하는 것과 똑같은 지뢰판을 가질 수 있다. WinDBG로 돌아와 F6키를 누르면 프로세스 리스트가 뜨는데 winmine.exe를 선택하고 엔터키를 친다. 그러면 WinDBG지뢰찾기(winmine) 프로세스를 attach 하게 된다. 여러분은 이제 Command, Register 그리고 Disassembly Window에 값들이 들어가 있는 것을 확인할 수 있을 것이다.

 

5.1) 로드된 모듈들 (Loaded Modules)

 

Command Window에 주의를 깊게 보면 하나의 모듈 시리즈가 로드된 것을 볼 수있다. 그리고  winmine 프로세스가 브레이크 이슈에 걸린 상태를 확인 할 수 있다.

 

ModLoad: 01000000 01020000   C:\WINDOWS\System32\winmine.exe

ModLoad: 77f50000 77ff7000   C:\WINDOWS\System32\ntdll.dll

ModLoad: 77e60000 77f46000   C:\WINDOWS\system32\kernel32.dll

ModLoad: 77c10000 77c63000   C:\WINDOWS\system32\msvcrt.dll

...

ModLoad: 77c00000 77c07000   C:\WINDOWS\system32\VERSION.dll

ModLoad: 77120000 771ab000   C:\WINDOWS\system32\OLEAUT32.DLL

ModLoad: 771b0000 772d4000   C:\WINDOWS\system32\OLE32.DLL

(9b0.a2c): Break instruction exception - code 80000003 (first chance)

eax=7ffdf000 ebx=00000001 ecx=00000002 edx=00000003 esi=00000004

eip=77f75a58 esp=00cfffcc ebp=00cffff4 iopl=0   nv up ei pl zr na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000   efl=00000246

ntdll!DbgBreakPoint:

77f75a58 cc               int     3

 

 ModLoad 다음에 두개의 32 비트 어드레스가 표시되는데 이것은 각 프로세스에 연관된 가상메모리의 범위를 나타내는 것이다. 그리고 이것들은 지뢰찾기 프로세스와 의존적인 관계에 있는 모듈들이다. 로드된 모듈의 리스트를 보기 위해서 다음과 같은 커맨드를 사용한다: lm, !lm, !dlls

 

그리고 Command Window에 각 스텝에 도달된 곳 혹은 브레이크포인트에 걸린 레지스터 값들을 정확하게 나타내고 있는 것을 확인 할 수 있을 것이다.

 

 

5.2) 로드된 심볼들 (Loaded Symbols)

 

 자 그럼 이제 다운로드 받은 Symbol 패키지를 설치하도록 하자. 그리고 잠깐 힌트를 주면 Command Window에 다음과 같이 명령을 내리면 지뢰찾기(winmine)에서 사용가능한 Symbol 리스트를 얻을 수 있다.

 

x WinMine!*

 

 e(x)amine 명령어는 모듈 이름을 위해 정규 표현 마스크와 같이 느낌표 왼쪽에다 쓰고 오른쪽에는 심볼 이름을 쓴다. 더 자세한 표현 구문법 정보를 알고 싶다면 레퍼런스 섹션[10]을 참조하라.

 

Command 윈도우에서 심볼 리스트를 스크롤 할 수 있다.

 

...

01003df6 winmine!GetDlgInt = <no type information>

010026a7 winmine!DrawGrid = <no type information>

0100263c winmine!CleanUp = <no type information>

01005b30 winmine!hInst = <no type information>

01003940 winmine!Rnd = <no type information>

01001b81 winmine!DoEnterName = <no type information>

...

 

 이 리스트를 보고 어떤 심볼이 함수인지 변수인지 명확하게 확인하는 것은 불가능하다. 이것은 WinDBG를 떠나서 타입 정보가 없기 때문이다. 이것은 전형적인 public 심볼 파일이다. 감사하게도 함수가 아닌 것으로 부터 함수를 분별해내는 방법론이 있긴 있다. 그 기술에 요구되는 것이나 어셈블리를 읽는 것에 여러분은 숙달되지 않아서 잠깐 뒤로 미루겠다. 그 외 가상 가상 보호메모리를 시험해보는 기술 같은 것으로 쉽게 이해하고 사용할 수 있는 방법들을 알아보도록 하겠다.

 


5.3) 보호 메모리 (Memory Protection)

 지금까지 응용프로그램 메모리와 관련된 논의에 대해 피해 왔었다. 그렇다고 지금부터 윈도우 메모리의 내부 관리가 어떻게 돌아가는지 들춰낼 생각은 없다. 우리가 접근해 가는 관심사는 간결함을 유지하면서도 당면한 실리위주의 필요사항만 언급하도록 할 것이다.

 응용프로그램이 메모리를 요청 할 때, 그 요청된 용량이 허용 가능하다면 어떤 영역(region)를 확보하게 된다. 할당(allocation)이 성공적으로 되었다면 이 메모리의 영역은 다른 무엇으로부터 보호 된다. 더 세부적으로 얘기하면 이 영역에는 특정 액세스 타입을 허용할 것인지 거부할 것인지 puedo access control 리스트를 통해 적용된다. 이 액세스 타입의 예를 들어 보면 정보를 읽을 수 있는 액세스 타입, 정보를 쓸 수 있는 액세스 타입, 명령을 실행하는 것들이다. 이런 액세스 타입은 심볼이 함수인지 아닌지 가능한 높은 수준으로 확인할 수 있는 여력을 제공한다. 함수로서 존재한다는 것의 이점으로 그 메모리 영역에서는 '실행'을 허락한다는 의미이다. 거꾸로 이 메모리 영역이 변수를 위한 영역 일 때는 실행을 허락하지 않는다. IA-32 아키텍쳐의 메모리 페이지는 보호 메모리임에도 불구하고 하드웨어 레벨에서 실행된다. 편리하게도 WinDBG는 사용자에게 주어진 주소지의 보호메모리를 검색 할 수 있도록 허락하는 확장기능이 포함되어 있다. 이 확장기능의 명령어는 '!vprot' 이다. 이 기능을 확인하기 위해 적절한 심볼 이름을 선택하고 다음과 같이 Command Window에 타이핑한다:

!vprot WinMine!ShowBombs

 

ShowBombs는 함수임을 암시하고 있다.
그럼 !vprot 이 무엇을 말하고 있는지 살펴보자:

BaseAddress:       01002000
AllocationBase:    01000000
AllocationProtect: 00000080 PAGE_EXECUTE_WRITECOPY
RegionSize:        00003000
State:             00001000 MEM_COMMIT
Protect:           00000020 PAGE_EXECUTE_READ
Type:              01000000 MEM_IMAGE

 

 대충 봤을 때 어렵게 보일 수도 있다. 그러나 AllocationProtect 필드는 전체 메모리 영역을 위한 디폴트 보호모드를 나타내는 것이다. Protect 필드는 처음에 말했던 대로 특별한 영역에 대한 현재의 보호모드를 나타낸다. 이것은 PAGE_EXECUTE_READ에 표시된 것처럼 읽고 실행하기 위해 세팅되어 있음을 알 수 있다. 다음은 WinMine!scClass 같이 변수로 의심되는 것에 위치한 영역의 보호모드에 관해 살펴보자.

!vprot WinMine!szClass

우리의 기대대로라면 !vprot은 그 영역에 대해 읽고 쓸 수 있는 것만 허락하는 페이지 보호모드를 출력해야 한다.

BaseAddress:       01005000
AllocationBase:    01000000
AllocationProtect: 00000080 PAGE_EXECUTE_WRITECOPY
RegionSize:        00001000
State:             00001000 MEM_COMMIT
Protect:           00000004 PAGE_READWRITE
Type:              01000000 MEM_IMAGE


 우리가 기대했던 대로 출력되었다. 이름에 대한 규칙으로 판단해 볼 때(앞에 붙은 sz 같은 것), 스트링 타입임을 암시하고 있는데 이 메모리 위치에 있는 데이터를 실험해봄으로써 우리가 가정하고 있는걸 확인해 볼 수 있다. 이 작업을 하기 위해서 메모리 출력 명령어를 다음과 같이 타입핑한다:

du WinMine!szClass

'u' 변경자는 메모리 출력 명령어와 함께 쓰면 유니코드 스트링을 해석할 수 있다. 출력 결과는 다음과 같이:

01005aa0 "Minesweeper" (한글판에서는 지뢰찾기로 출력 됨)

확인할 수 있다.

5.4) 어셈블리의 이해 (Understanding Assemblies)
 챕터의 목적은 지뢰판이 존재하는 곳에 위치하는 것이다. 이것을 명심하고 앞서 확인된 ShowBombs 함수를 다시 확인하자. 다음과 같은 명령으로 이 함수에 브레이크포인트를 걸어보자:

bp WinMine!ShowBombs

 

 WinDBG는 브레이크포인트가 성공적으로 세팅 되었는지 명확한 피드백을 주지 않는다. 그러나 WinDBG는 요청된 이름이나 주소를 해석하지 못할 때 사용자에게 경고할 것이다. 브레이크포인트가 걸린 리스트를 보기 위해서는 다음과 같은 명령어를 입력한다:

bl

이 명령어는 다음과 같은 출력을 할 것이다:

0 e 01002f80     0001 (0001)  0:*** WinMine!ShowBombs

 

 가장 앞의 수는 브레이크 포인트의 숫자를 나타낸다. 바로 뒤는 (e)nable 혹은 (d)isable 인지 브레이크포인트 상태 정보를 알려준다. 다음은 브레이트코인트의 가상주소다. 다음 4자리 수는 브레이크포인트가 활성화될 때까지의 남은 수를 의미한다. 그 다음 괄호안의 수는 처음으로 통과된 카운트이다. 다음은 프로세스 번호인데 프로세스 ID와 혼동하지 마라. 콜론으로 분리된 것은 특정 쓰레드의 브레이크포인트일때 쓰레드 ID를 나타낸다. 쓰레드가 아니므로 별표로 나타낸다. 마지막으로 모듈 이름이거나 WinDBG가 브레이크가 걸린 심볼/함수 이다.

 

 지뢰찾기를 동작상태로 돌리기 위해 F5(Go) 나 'g'를 Command Window에 입력한다. 그러면 여러분은 WinDBG에서 "Debugee is running"이라는 메시지를 볼 수 있을 것이다. 지뢰찾기 윈도우로 전환하여 가장 위쪽의 왼쪽 박스를 클릭하면 박스에 2숫자가 나타난다. 다음 오른쪽 박스를 클릭하면 분명 폭탄이 나타나면서 지뢰찾기를 더이상 컨트롤 할 수 없을 것이다. 이것은 WinDBG가 브레이크포인트 상태를 만나서 명령을 기다리고 있기 때문이다. WinDBG로 돌아와 Command Window에 다음의 명령행 부분에 하이라이트 되어 있는 것을 확인 할 수 있을 것이다.

01002f80 a138530001       mov     eax,[WinMine!yBoxMac (01005338)]

 

 이것은 ShowBombs 함수의 처음 명령행 부분이다. 이 주소는 아까 브레이크포인트 리스트에서 확인했던 것과 일치한다. 이 명령행을 이해하기 전에 IA-32 어셈블리에 관해 알아보자.  이것으로 여러분은 챕터 2에서 언급되었던 것에 대해 확신을 가질 수 있을 것이다.

 디스어셈블 된 각 행은 하나의 명령을 뜻한다. 위의 명령행을 풀이 하면 다음과 같은 식으로 표현할 수 있다.

 

<address> <opcodes> <mnemonic> <operand1>, <operand2>

 

 <address>는 의 가상주소 위치를 나타낸다. <opcode>는 프로세서가 해석하여 수행할 수 있는 문자 그대로의 명령(instruction)이다. 그 이후 오른쪽에 나열된 것들은 어셈블리 언어로 번역된 것들인데 <mnemonic> 는 각 <operand>(매게변수 같은 역할)를 다루기 위한 동사 역할을 한다. 인텔계열 어셈블리에서는 오른쪽의 것을 왼쪽으로 이동시킨다는 것에 주의하자. 즉 산술이나 데이터를 이동시킬 때 에 <operand1>에 결과를 저장한다.

 다시 원래의 명령행을 해석해보면 eax에 0x1005338의 위치한 32비트의 워드 값을 이동시키라는 의미가 된다. 대괄호([,])는 그 주소에 포함된 값을 사용할 때 쓰인다. 이것은 C에서 포인터(*)를 사용하는 것과 비슷하다. eax 레지스터에 mov 명령을 수행 할 때의 opcode를 보면 0xa1이 포함 되어 있다는 것을 찾아 볼 수 있을 것이다.

Opcode Instruction      Description
...
A0     MOV AL,moffs8*   Move byte at (seg:offset) to AL.
A1     MOV AX,moffs16*  Move word at (seg:offset) to AX.
A1     MOV EAX,moffs32* Move doubleword at (seg:offset) to EAX.
...

 

 opcode첫번째 바이트가 0xa1로 모두 동일하지 않다. <operand2>와 관련해서는 다음에 살펴볼 endianness 장에서 논의하도록 하겠다.


5.5) Endianness

 멀티바이트가 저장 될 때 그 순서를 정의하는 것이 Endianness(에디안)이다. Ednianness에는 두가지의 방식의 규칙이 있는데 그것이 바로 little 에디안 과 big 에디안 이다.  IA-32 아키텍쳐에서 little 에디안은 마지막 바이트를 가장 처음 바이트로 저장하는 방식이고 big 에디안은 그 반대다. 예를 들어 0x11223344 가 있을때 little 에디안은 0x44332211로 저장하고 big 에디안은 0x11223344로 저장한다.

0x01005338 이 있을 때 little 에디안의 바이트 순서대로 나열하면 다음과 같이 에 저장된다.

0x01005338, rewritten for clarity: 0x01 0x00 0x53 0x38
                                      |    |    |    |
                                      |    |    +----|-+
                                      |     +--------|-|-+
                                      +--------------|-|-|-+
                                                     V V V V
                                                  0x38530001


에디안에 관한 자세한 정보는 레퍼런스 섹션[5]를 참고 하라.


5.6) Conditions

 지뢰판의 위치를 찾아 내는 목적에 도달하기 위해 새로운 정보를 살펴보도록 하자. 현재 한 명령행만 수행하고 브레이크를 걸기 위해 F10 키를 누르거나 Command Window(이하 커맨드윈도우) 에서 'p'를 친다. 먼저 방금 전의 명령행이 빨간색으로 하이라이트 되고 바로 아래 파란색으로 하이라이트 되는 것을 눈치챌 것이다. 기본적으로 WinDBG에서 브레이크포인트 컨디션에 빨간색, 현재의 대기중인 명령행은 파란색으로 표시한다. 그리고 Register Window 의 레지스터 값들 중 빨간색으로 변한 것은 값이 변경되었다는 것을 의미한다. eax 값에는 0x18 값으로 업데이트 된 것을 확인할 수 있을 것이다. 여기서 0x18은 10진수로 24이다. 지금 우리가 플레이 하고 있는 지뢰판은 800x900으로 사용자정의 옵션에 넣었음에도 불구하고 30x24로 만들어 졌음을 알 수 있다. 지뢰찾기를 다시 실행시켜 여러가지 지뢰판 크기로 변경해 보면 쉽게 확인 할 수 있을 것이다. 어째튼 문서의 간결함을 위해 다음과 같은 상태를 우리는 확인할 수 있다.

winmine!yBoxMac == Height of Playing Grid

 

그리고 다음 명령행들을 살펴보자:

 

01002f85 83f801 cmp     eax,0x1
01002f88 7c4    jl      winmine!ShowBombs+0x58 (01002fd8)

 

 첫번째 행은 최대 지뢰판의 높이 값과 0x1을 비교하고 있다. 아래의 레퍼런스의 문서를 확인해 보면 알겠지만 cmp 명령은 그 결과가 EFLAG[6]에 비트값으로 세팅된다. 그 다음 명령행은 점프하라는 의미인데 이 명령행은 "그 값이 0x1보다 이하일 때" 0x01002fd8로 점프하라는 의미이다. jl의 앞 문자 'j'를 통해 jmp를 의미한다는 것을 알 수 있을 것이다. 'l'은 'less' 의미한다. 이 명령행을 일반적으로 다음과 같은 코드로 표현할 수 있다.

 

if(iGridHeight < 1) {
        //jmp winmine!ShowBombs+0x58
}

 

 이렇게 의사C코드로 표현하다 보면 복잡한 함수를 이해하는데 많은 도움이 된다. 그럼 0x18과 비교되었으므로 점프를 피해갈 것임을 예상할 수 있다. 그러나 우리의 학구욕을 위해 Command Window에 다음과 같은 명령을 실행 시켜 보자.

u 0x1002fd8

 

그러면 이 명령을 통해, 만약 점프되었을 때의 명령어들을 보여 줄 것이다.


5.7) 스택과 프레임(Stack and Frames)

우리는 'u' 명령어를 통해 WinDBG에서 (u)nassemble 된 정보를 다음과 같은 주소 위치에서 찾아 볼 수 있다.

01002fd8 e851f7ffff     call    WinMine!DisplayGrid (0100272e)
01002fdd c20400         ret     0x4

 

 이 코드는 ShowBombs함수가 DisplayGrid함수를 호출하고 자신(ShowBombs 함수)을 불렀던 호출자에게 리턴하는 것이다. 그런데 '호출(=call)'은 도대체 무슨 일을 하는 것이며, ret은 어디로 돌려주는 것이고 0x4는 또 뭐란 말인가? IA-32 Command Reference에서는 다음과 같이 서술하고 있다. "스택에 프로시져를 연결할 수 있는 정보를 저장하고 destination operand(타겟) 에 지정된 프로시저로 분기한다. 이 타겟 operand는 호출된 프로시저 내의 최초의 instruction 주소를 기입한다."  Command Reference에는 call 타입에 종속되어 있는 call instruction을 위한 변수 행위에 대해 언급하고 있다. 그러니까 call이 무슨 일을 하는지 확인하기 위해 opcode를 살펴봤을 때 우리는 0xe8을 찾을 수 있다. 0xe8은 near call(근접호출) 을 의미한다. "near call 이 실행 될 때, 프로세서는 EIP 레지스터의 값(CALL 명령행에 따르는 다음 명령행의 offset 값을 가지고 있음)을 스택(복귀 명령행을 가리키는 값)에 저장(push) 한다." 이것이 프레임을 만드는 첫번째 단계이다. 각각 함수 콜이 만들어 질 때 호출된 함수가 매게변수에 액세스 하거나, 로컬 변수를 만들거나, 호출한 함수에 복귀하는 메커니즘을 위해 또다른 프레임이 생성되어진다. 프레임의 구조에 대해서는 함수 호출 규칙에 따르는데 더 자세한 정보는 레퍼런스 섹션[1]을 참고하라.  현재 call stack을 보고 싶거나 각각 연결된 프레임 시리즈를 보기 위해 'k' 명령어를 이용한다.:

ChildEBP RetAddr
0006fd34 010034b0 winmine!ShowBombs
0006fd40 010035b0 winmine!GameOver+0x34
0006fd58 010038b6 winmine!StepSquare+0x9e
0006fd84 77d43b1f winmine!DoButton1Up+0xd5
0006fdb8 77d43a50 USER32!UserCallWinProcCheckWow+0x150
0006fde4 77d43b1f USER32!InternalCallWinProc+0x1b
0006fe4c 77d43d79 USER32!UserCallWinProcCheckWow+0x150
0006feac 77d43ddf USER32!DispatchMessageWorker+0x306
0006feb8 010023a4 USER32!DispatchMessageW+0xb
0006ff1c 01003f95 winmine!WinMain+0x1b4
0006ffc0 77e814c7 winmine!WinMainCRTStartup+0x174
0006fff0 00000000 kernel32!BaseProcessStart+0x23

 

 이 정보를 토대로 여러분은 응용프로그램의 흐름을 역순서로 추적해 나갈 수 있다. Call Stack Window 에서 Alt+6을 눌러 불러 내면 보다 쉽게 call stack을 살펴볼 수 있다. 이 윈도우에 나타난 call stack 정보는 표 형식으로 나타내어 좀 더 잘 간추려 보여주고 있다. 이 call stack 정보를 통해 이제 ret이 도대체 어디로 향하는가에 대해 앞서 질문했던 물음에 대답을 찾아 낼 수 있다. 예를 들어 ShowBombs리턴될eip(Instruction Pointer) 레지스터에는 0x010034b0으로 세팅 될 것이다. 마지막으로 우리는 0x4의 중요성을 ret 명령에 의해 정의되어 값을 나타내는 형태를 통해 알 수가 있다. "....리턴 어드레스가 pop된 후 방출되는 스택의 바이트 수는, 기본값으로 none이다. 이 operand(ret을 말함)는 더 이상 필요가 없어지거나 호출된 프로시져가 끝난 상태의 스택에서 파라미터를 방출한다."  좀 더 명확하게 프로세서가 ret(복귀) instruction(명령)을 만나면 스택의 꼭대기에 있는 즉 ESP가 가리키고 있는 값을 불러 들여 EIP 레지스터에 저장한다. 만약 ret instruction 이 operand 값을 가지면 operand는 스택의 꼭대기(Top of the Stack)에서 얼마나 많은 바이트가 제거되어야 하는지 표시하는 것이다. 이러한 지식을 토대로 이제 우리는 '32비트의 주소는 4바이트 길이를 가지니까 ShowBombs 함수는 하나의 인수(argument)를 수용하고 있다'는 것으로 판단 할 수 있게 된다.

자 다시 하던 일로 돌아와 ShowBombs 함수가 하는 일을 다음과 같은 코드로 나타낼 수 있다:

 

if(iGridHeight < 1) {
        DisplayGrid();
        return;
} else {
        //do stuff
}

 

계속해서 Disassemble Window에 나와있는 다음 행(//do stuff 부분)들을 살펴보자.

 

01002f8a 53               push    ebx
01002f8b 56               push    esi
01002f8c 8b3534530001     mov     esi,[winmine!xBoxMac (01005334)]
01002f92 57               push    edi
01002f93 bf60530001       mov     edi,0x1005360

 

 EIP 가 0x1002f8a가 될 때까지 WinDBG에서 스텝핑('p'를 두번 수행) 해보자. 앞의 두 명령행은 ebxesi 레지스터를 스택에 저장하는 것이다.  이것은 ESP에 참조되어 있는 스택 메모리 위치를 확인해 봄으로써 ebx스택에 저장되는 것을 확인 할 수 있다. 'p'를 눌러 push ebx 명령행을 수행하여 esp에 위치한 값을 살펴보자. espebx값이 들어가 있는 것을 다음과 같이 확인 할 수 있다.

 

0:000> dd esp
0006fd38  010034b0 0000000a 00000002 010035b0
0006fd48  00000000 00000000 00000200 0006fdb8
...
0:000> r ebx
ebx=00000001
0:000> p
eax=00000018 ebx=00000001 ecx=0006fd14 edx=7ffe0304 esi=00000000 edi=00000000
eip=01002f8b esp=0006fd34 ebp=0006fdb8 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000206
winmine!ShowBombs+0xb:
01002f8b 56               push    esi
0:000> dd esp
0006fd34  00000001 010034b0 0000000a 00000002
0006fd44  010035b0 00000000 00000000 00000200

 

 esp 의 값이 4바이트 줄어 들고 그 위치에 ebx의 값이 저장되어 있는 것을 확인할 수 있다. 다시 push esi를 실행시켜 똑같은 방법으로 확인해보면 esp가 또 4만큼 줄어 들어 있고 그자리에 esi값이 저장되어 있음을 확인 할 수 있다. 이것이 근본적으로 스택이 일하는 방식이다. 이런식으로 스택 포인터는 감소하고 새로운 esp가 가리키고 있는 위치에 push 된 값이 저장되는 것이다. 여기서 스택이 높은곳에서 낮은곳으로 저장되고 있음에 주의해야 한다. 값이 저장될때는 스택 포인터가 감소하여 그 저장될 공간을 확보한다. 그렇다면 스택의 가장 높은 곳과 낮은 곳의 한계(limit)가 있는가? 계속해서 스택이 커질 수는 없다. 스택은 천장과 바닥이 분명히 있다. 이것은 Thread Environment Block 이나 TEB를 통해 확인할 수 있다. WinDBG에서 !teb 통해 확인할 수 있다.

 

0:000> !teb
TEB at 7ffde000
    ExceptionList:        0006fe3c
    StackBase:            00070000
    StackLimit:           0006c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 7ffde000
    EnvironmentPointer:   00000000
    ClientId:             00000ff4 . 00000ff8
    RpcHandle:            00000000
    Tls Storage:          00000000
    PEB Address:          7ffdf000
    LastErrorValue:       183
    LastStatusValue:      c0000008
    Count Owned Locks:    0
    HardErrorMode:        0

 StackBaseStackLimit 위한 값을 보면 스택의 천장과 바닥이 있다는 것을 알 수 있다. TEB에 대한 더 자세한 정보는 레퍼런스 섹션[11]번을 참조하라.  다시 돌아와 다음 명령행을 보자:

 

01002f8c 8b3534530001     mov     esi,[winmine!xBoxMac (01005334)]

 

 이것은 분명 지뢰판의 가로크기의 값을 esi에 저장한다는 것을 알 수 있다. 'p'를 눌러 esi 레지스터에 0x1e 값이 저장되는 것을 확인하자. 0x1e는 10진수로 30이다. 이것은 지금 플레이 하고 있는 지뢰판의 가로크기임을 알 수 있다. 그래서 xBoxMac은 가로크기의 값을 표시한다는 것을 알 수 있다.  다음 명령행인 push ediedi 레지스터의 값을 스택에 저장하는 것인데 이것은 다음 명령행(mov edi, 0x1005360)을 수행하기 위한 사전 준비작업에 해당한다. 여기서 부터 흥미로워 지는데 도대체 0x1005360은 무슨 값인지 의문스럽다. 지금까지의 명령행들을 돌이켜 볼 때 아마 이 주소는 지뢰판에 대한 정보를 담고 있을지 모른다. 이 점을 확인하기 위해 이 메모리 주소의 형태를 들여다 보자.  앞서 말한 !vprot 기능을 통해 이 메모리 주소지에 대한 접근 권한 타입을 확인하면, PAGE_READWRITE임을 알수 있다. 이 사실은 충분히 가치있지는 않지만 이 주소에는 실행을 위한 영역이 아니고 변수들의 값이 위치하고 있음이 느껴진다. 사실 이 위치는 지뢰판에 대한 정보를 담고 있다. 이것은 누구나 메모리를 들여다 보며 패턴을 확인해 보면 알 수 있는 문제다. 다음과 같이 Memory Window에 0x1005360의 값을 입력시키면 아래와 같이 메모리의 내용이 덤프될 것이다.

01005360 10 42 cc 8f 8f 8f 8f 0f 8f 8f 8f 8f 0f 0f 8f 0f  .B..............
01005370 0f 8f 8f 8f 8f 8f 8f 0f 0f 0f 8f 0f 0f 8f 8f 10  ................
01005380 10 8f 0f 0f 8f 8f 0f 0f 0f 0f 0f 8f 0f 0f 8f 8f  ................
01005390 0f 8f 0f 0f 0f 8f 8f 8f 0f 0f 8f 8f 8f 8f 8f 10  ................
010053a0 10 0f 0f 8f 0f 0f 8f 0f 0f 0f 0f 0f 8f 0f 0f 0f  ................
010053b0 8f 0f 0f 0f 8f 8f 0f 0f 8f 0f 8f 0f 8f 8f 0f 10  ................
010053c0 10 0f 0f 8f 0f 0f 8f 0f 0f 0f 8f 0f 0f 8f 0f 0f  ................
010053d0 8f 0f 0f 8f 0f 0f 0f 8f 0f 0f 0f 8f 0f 0f 0f 10  ................
010053e0 10 0f 0f 8f 0f 8f 8f 0f 0f 8f 8f 0f 0f 8f 0f 0f  ................
010053f0 0f 0f 0f 0f 8f 0f 0f 0f 0f 0f 0f 0f 8f 0f 0f 10  ................
01005400 10 8f 0f 0f 0f 0f 0f 0f 8f 8f 0f 8f 8f 0f 0f 8f  ................
01005410 0f 8f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 8f 8f 0f 10  ................
01005420 10 8f 0f 8f 8f 0f 8f 8f 0f 0f 0f 8f 8f 0f 8f 0f  ................


6) 지뢰판 분석 (Interpreting the Playing Grid)

 독자들은 이 메모리의 영역에 제한된 값들이 여기저기 흩어져 있음을 관찰 할 수 있을 것이다. 가장 많이 보이는 것들은 0x8f, 0x0f, 0x10, 0x42, 0xcc 이다. 추가적으로 이것들이 다음과 같은 패턴으로 반복됨이 확인된다:

 

0x10 <30 bytes> 0x10.

 

 30 이라는 수는 아까 발견했던 가로크기의 값과 비슷해 보인다. 그렇다면 이것이 지뢰판의 각 행을 나타내고 있는 패턴이라는 추측을 해 볼 수 있다. 이것을 확인하기 위해 WinDBG로 돌아와 Command Window에서 'g'키를 눌러 다시 지뢰찾기 프로그램을 실행한다. 지뢰찾기 윈도우로 전환한 후 메모리의 값들과 지뢰판과 비교해보자. 이렇게 비교해 볼때 각 폭탄의 위치에 0x8f가 위치하고 있고 빈칸은 0x0f값이 위치하고 있음이 확인된다. 그리고 폭발된 폭탄은 0xcc, 숫자 2는 0x42 나타내고 있다.

 이 모든 것들을 확인했다면 그 row(각 행) 패턴의 길이 32에 총 높이(각 행)가 24칸을 곱하면 768 (0x300)이 나오게 된다. 구해진 이 값으로 아까 처음의 주소지 0x01005360에서 시작한 지뢰판이 0x01005660에서 끝난다는 것으로 계산이 된다. 지뢰찾기 게임을 재시작하고 SetGrid 프로그램을 재실행 시키자. 그리고 오른쪽 아래 칸을 클릭하면 숫자 2가 보일 것이다. 바로 왼쪽 칸을 클릭하면 다시 폭탄이 나타나고 다시 브레이크포인트 걸릴 것이다. WinDBG로 전환하여 Momory Window에 주의깊게 살펴보자. 아래의 영역이 나올 때 까지 Next 버튼을 누른다.

 

01005640 10 8f 0f 0f 0f 8f 0f 0f 8f 0f 8f 0f 0f 0f 0f 8f  ................
01005650 0f 8f 0f 0f 0f 8f 0f 0f 0f 0f 0f 0f 0f cc 42 10  ..............B.
01005660 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10  ................
01005670 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10  ................


 이 0x1005640의 영역은 바로 가장 지뢰판의 가장 아래 row(행) 이다. 각 폭탄이 0x8f로 나타나는 것을 확인할 수 있다. 이제 이것이 확실히 지뢰판을 나타내는 메모리 영역임을 우린 확신할 수 있게 되었다.


7) 지뢰 제거 (Removing Mines)

 (e)nter 명령어는 가상메모리의 특정 위치에 값을 입력할 수 있게 한다. 자 그럼 지뢰를 제거 해 보도록 하자. 먼저 지뢰찾기를 WinDBG에서 재시작하고 SetGrid 툴을 실행 시킨다. 그리고 가장 위쪽줄의 첫번째 칸을 클릭하면 숫자 2가 나온다. 그럼 다시 WinDBG로 다시 돌아와 Ctrol+Break 키를 눌러 지뢰찾기 프로그램에 브레이크를 건다. 이제 메모리 0x01005362에 있는 영역을 불어와 바로 오른쪽 칸에는 폭탄이 들어 있는 것이 확인 될 것이다. Command Window에 다음과 같이 명령을 수행해 보자:

 

eb 0x01005362 0x0f

 

 F5를 누르고 지뢰찾기 프로그램으로 다시 돌아와 오른쪽 칸을 확인 해보면 폭탄이 아닌 숫자 2가 표시 될 것이다. 자 이제 폭탄을 제거 할 수 있지만 하나하나 손으로 하기에는 너무 힘들다. 그래서 우리는 폭탄을 탐지하고 제거하는 툴을 만들어 볼 것이다.


7.1) 가상 지뢰 탐지기( Vitual Mine Sweepr)

이번 섹션에서는 다음과 같은 것들을 수행하고 개발하기 위해 윈도우 API 부분에 대해 소개 할 것이다.

  1. 지뢰찾기 프로세스를 attach 하고 locate 한다.
  2. 지뢰판 영역을 read 한다.
  3. 폭탄을 제거하거나 나타나게 수정한다.
  4. 지뢰찾기 프로그램 공간에 임의의 지뢰판을 수정하여 넣어본다.


첫번째 작업을 수행하기 위해 우리는 Tlhelp32.h에 관해 알아 볼 수 있는 Tool Help Library의 서비스들을 참고한다. 실행중인 프로세스를 의 스냅샷을 잡기 위해 CreateToolhelp32Snapshot를 호출 한다. 아래는 프로토타입이다:

 

HANDLE WINAPI CreateToolhelp32Snapshot(
  DWORD dwFlags,
  DWORD th32ProcessID
);

 

이 함수의 dwFlag가 TH32CS_SNAPPROCESS로 세팅되어 호출되면 현재의 프로세스 리스트의 핸들을 확보 할 수 있다. 이 리스트를 열거하기 위해 Process32First 함수를 먼저 불러내야 한다. 다음은 프로토타입이다.

 

BOOL WINAPI Process32First(
  HANDLE hSnapshot,
  LPPROCESSENTRY32 lppe
);

 

반복해서 일어나는 이 프로세스 리스트에 접근하기 위해서는 Process32Next 함수를 사용한다. 다음은 프로토타입이다.

 

BOOL WINAPI Process32Next(
  HANDLE hSnapshot,
  LPPROCESSENTRY32 lppe
);

 

위 두 함수 모두 인자로 LPPROCESSENTRY32 구조체를 가지고 있다. 다음은 그 구조체의 모습이다.

 

typedef struct tagPROCESSENTRY32 {
  DWORD dwSize;
  DWORD cntUsage;
  DWORD th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD th32ModuleID;
  DWORD cntThreads;
  DWORD th32ParentProcessID;
  LONG pcPriClassBase;
  DWORD dwFlags;
  TCHAR szExeFile[MAX_PATH];
} PROCESSENTRY32, *PPROCESSENTRY32;

 

 자 여기서 szExeFile눈여겨 볼 필요가 있는데 이것은 지뢰찾기 프로세스를 locate 할 수 있도록 만든다. 그리고 th32ProcessID는 지뢰찾기 프로세스가 발견되었을 때 attach 할 수 있도록 프로세스 ID를 제공해 준다. 지뢰찾기가 locate되었다면 OpenProcess 함수로 attach 시킬 수 있다. 다음은 그 프로토타입이다.

 

HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL bInheritHandle,
  DWORD dwProcessId
);

 

 지뢰찾기 프로세스가 오픈되면 ReadProcessMemory 함수를 통해 가상메모리에 존재하는 지뢰판 정보를 획득 할 수 있다. 다음은 그 프로토타입이다.

 

BOOL ReadProcessMemory(
  HANDLE hProcess,
  LPCVOID lpBaseAddress,
  LPVOID lpBuffer,
  SIZE_T nSize,
  SIZE_T* lpNumberOfBytesRead
);

 

 버퍼에 그 지뢰판 영역의 메모리를 읽어 올 수 있게 되었다면 0x8f를 0x8a(노출) 혹은 0x0f(제거)로 변경 하도록 루프를 돌려야 할 것이다. 이 변경된 버퍼를 다시 지뢰찾기 프로세스로 재작성 되어야 한다. 이것을 위해 WriteProcessMemory 함수를 사용한다. 다음은 프로토타입이다.

 

BOOL WriteProcessMemory(
  HANDLE hProcess,
  LPVOID lpBaseAddress,
  LPCVOID lpBuffer,
  SIZE_T nSize,
  SIZE_T* lpNumberOfBytesWritten
);

 

 이 정보를 토대로 이제 여러분은 지금까지의 지뢰 노출 혹은 제거를 위한 목적을 위해 툴을 작성 할 수 있을 것이다. 이것을 확인하기 위한 소스코드는 레퍼런스 섹셕 13번에 포함시켰다.


8) 결말

 지금까지 이 문서를 읽은 독자는 성공적으로 위치를 발견하는 것, 정확히 파악하는 것, 지뢰찾기의 바둑판 무늬를 교묘히 다루는 것 까지 많은 개념의 부분들을 알게 되었을 것이다. 이 개념들 뿐만 아니라 더 많은 세부적인 것들에 관해서는 문서의 간결함을 유지하기 위해 쓰지 않았다. 더 많은 전체적인 개념들에 대해 쓰여진 것을 원한다면 레펀런스의 각 세션을 참고하라.


9) 레퍼런스(References)

    1. Calling Conventions  http://www.unixwiz.net/techtips/win32-callconv-asm.html
    2. Symbol Server  http://www.microsoft.com/whdc/devtools/debugging/debugstart.mspx
    3. Symbols  ms-help://MS.PSDK.1033/debug/base/symbolfiles.htm
    4. Platform SDK  www.microsoft.com/msdownload/platformsdk/sdkupdate/
    5. Endianness  http://www.intel.com/design/intarch/papers/endian.pdf
    6. EFLAGS  ftp://download.intel.com/design/Pentium4/manuals/25366514.pdf Appendix B
    7. Intel Command References  http://www.intel.com/design/pentium4/manuals/indexnew.htm
    8. Debugger Quick Reference  http://www.tonyschr.net/debugging.htm
    9. WinDBG Prompt  Reference WinDBG Help, Search, Command Window Prompt
    10. Regular Expressions  Reference WinDBG Help, Search, Regular Expression Syntax
    11. TEB  http://msdn.microsoft.com/library/en-us/dllproc/base/teb.asp
    12. SetGrid.cpp
/**********************************************************************
 * SetGrid.cpp - trew@exploit.us
 *
 * This is supplemental code intended to accompany 'Introduction to
 * Reverse Engineering Windows Applications' as part of the Uninformed
 * Journal.  This application sets the reader's playing grid in a
 * deterministic manner so that demonstrations made within the paper
 * correlate with what the reader encounters in his or her instance of
 * WinMine.
 *
 *********************************************************************/

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

#pragma comment(lib, "advapi32.lib")

#define GRID_ADDRESS     0x1005360
#define GRID_SIZE        0x300

int main(int argc, char *argv[]) {

    HANDLE    hProcessSnap        = NULL;
    HANDLE    hWinMineProc        = NULL;
       
    PROCESSENTRY32 peProcess      = {0};

    unsigned int procFound        = 0;
    unsigned long bytesWritten    = 0;

    unsigned char grid[] =       
   
    "\x10\x0f\x8f\x8f\x8f\x8f\x8f\x0f\x8f\x8f\x8f\x8f\x0f\x0f\x8f\x0f"
    "\x0f\x8f\x8f\x8f\x8f\x8f\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x8f\x8f\x10"
    "\x10\x8f\x0f\x0f\x8f\x8f\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x0f\x8f\x8f"
    "\x0f\x8f\x0f\x0f\x0f\x8f\x8f\x8f\x0f\x0f\x8f\x8f\x8f\x8f\x8f\x10"
    "\x10\x0f\x0f\x8f\x0f\x0f\x8f\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x0f\x0f"
    "\x8f\x0f\x0f\x0f\x8f\x8f\x0f\x0f\x8f\x0f\x8f\x0f\x8f\x8f\x0f\x10"
    "\x10\x0f\x0f\x8f\x0f\x0f\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x8f\x0f\x0f"
    "\x8f\x0f\x0f\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x10"
    "\x10\x0f\x0f\x8f\x0f\x8f\x8f\x0f\x0f\x8f\x8f\x0f\x0f\x8f\x0f\x0f"
    "\x0f\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x0f\x10"
    "\x10\x8f\x0f\x0f\x0f\x0f\x0f\x0f\x8f\x8f\x0f\x8f\x8f\x0f\x0f\x8f"
    "\x0f\x8f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x8f\x8f\x0f\x10"
    "\x10\x8f\x0f\x8f\x8f\x0f\x8f\x8f\x0f\x0f\x0f\x8f\x8f\x0f\x8f\x0f"
    "\x0f\x0f\x0f\x8f\x0f\x8f\x0f\x8f\x0f\x0f\x8f\x8f\x0f\x8f\x0f\x10"
    "\x10\x8f\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x0f\x8f\x8f\x8f\x8f\x0f"
    "\x0f\x0f\x0f\x0f\x0f\x8f\x8f\x8f\x0f\x0f\x0f\x0f\x8f\x8f\x8f\x10"
    "\x10\x8f\x0f\x8f\x8f\x8f\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x8f\x0f\x0f"
    "\x8f\x8f\x0f\x0f\x0f\x8f\x0f\x8f\x0f\x8f\x0f\x0f\x0f\x0f\x0f\x10"
    "\x10\x0f\x0f\x8f\x8f\x0f\x8f\x8f\x8f\x8f\x0f\x0f\x0f\x0f\x0f\x0f"
    "\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x10"
    "\x10\x0f\x0f\x0f\x8f\x8f\x8f\x0f\x8f\x8f\x0f\x0f\x0f\x8f\x0f\x0f"
    "\x0f\x8f\x0f\x8f\x0f\x0f\x0f\x8f\x8f\x0f\x0f\x0f\x0f\x8f\x8f\x10"
    "\x10\x0f\x8f\x8f\x0f\x8f\x0f\x8f\x0f\x8f\x0f\x8f\x8f\x0f\x0f\x8f"
    "\x0f\x0f\x0f\x0f\x0f\x0f\x8f\x8f\x0f\x0f\x8f\x0f\x8f\x0f\x0f\x10"
    "\x10\x0f\x0f\x8f\x8f\x0f\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x8f\x0f"
    "\x8f\x0f\x8f\x8f\x8f\x0f\x0f\x8f\x0f\x8f\x0f\x8f\x8f\x8f\x8f\x10"
    "\x10\x8f\x8f\x0f\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x8f\x0f\x0f\x8f\x0f"
    "\x0f\x0f\x8f\x8f\x8f\x8f\x8f\x0f\x0f\x8f\x8f\x0f\x0f\x8f\x8f\x10"
    "\x10\x8f\x0f\x0f\x0f\x8f\x0f\x8f\x8f\x8f\x8f\x0f\x0f\x8f\x8f\x0f"
    "\x0f\x8f\x0f\x0f\x8f\x8f\x8f\x8f\x0f\x8f\x0f\x8f\x0f\x8f\x8f\x10"
    "\x10\x0f\x8f\x8f\x0f\x0f\x8f\x8f\x8f\x0f\x8f\x0f\x0f\x0f\x0f\x0f"
    "\x0f\x8f\x8f\x8f\x0f\x0f\x8f\x0f\x8f\x8f\x8f\x0f\x8f\x8f\x0f\x10"
    "\x10\x8f\x0f\x0f\x8f\x8f\x8f\x8f\x0f\x0f\x8f\x0f\x0f\x0f\x8f\x8f"
    "\x8f\x8f\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x8f\x8f\x0f\x0f\x8f\x0f\x10"
    "\x10\x0f\x8f\x8f\x0f\x0f\x0f\x0f\x8f\x0f\x8f\x0f\x8f\x0f\x0f\x0f"
    "\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x8f\x0f\x8f\x0f\x10"
    "\x10\x0f\x0f\x0f\x0f\x8f\x8f\x8f\x8f\x8f\x0f\x0f\x0f\x8f\x0f\x0f"
    "\x8f\x8f\x8f\x0f\x0f\x8f\x8f\x8f\x0f\x0f\x8f\x0f\x0f\x8f\x0f\x10"
    "\x10\x8f\x8f\x0f\x8f\x8f\x0f\x8f\x8f\x0f\x0f\x0f\x0f\x8f\x8f\x8f"
    "\x8f\x0f\x8f\x0f\x0f\x0f\x8f\x0f\x8f\x8f\x8f\x0f\x8f\x0f\x0f\x10"
    "\x10\x0f\x8f\x8f\x0f\x0f\x8f\x8f\x8f\x0f\x0f\x8f\x0f\x0f\x0f\x0f"
    "\x0f\x0f\x8f\x8f\x0f\x8f\x0f\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x8f\x10"
    "\x10\x0f\x0f\x8f\x0f\x8f\x0f\x8f\x8f\x0f\x0f\x0f\x0f\x0f\x0f\x0f"
    "\x0f\x8f\x0f\x0f\x0f\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x0f\x0f\x8f\x10"
    "\x10\x0f\x8f\x8f\x8f\x0f\x8f\x0f\x8f\x0f\x0f\x8f\x0f\x0f\x8f\x0f"
    "\x0f\x8f\x8f\x0f\x0f\x0f\x0f\x8f\x0f\x8f\x8f\x0f\x0f\x0f\x8f\x10"
    "\x10\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x8f\x0f\x8f\x0f\x0f\x0f\x0f\x8f"
    "\x0f\x8f\x0f\x0f\x0f\x8f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x8f\x8f\x10";


    //Get a list of running processes
    hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
       
    if(hProcessSnap == INVALID_HANDLE_VALUE) {
        printf("Unable to get process list (%d).\n", GetLastError());
        return 0;
    }
               
    peProcess.dwSize = sizeof(PROCESSENTRY32);

    //Get first process in list
    if(Process32First(hProcessSnap, &peProcess)) {

        do {
            //Is it's winmine.exe?
            if(!stricmp(peProcess.szExeFile, "winmine.exe")) {

                printf("Found WinMine Process ID (%d)\n", peProcess.th32ProcessID);
                procFound = 1;

                //Get handle on winmine process
                hWinMineProc = OpenProcess(PROCESS_ALL_ACCESS,
                                           1,
                                           peProcess.th32ProcessID);
                       
                //Make sure the handle is valid

                if(hWinMineProc == NULL) {
                    printf("Unable to open minesweep process (%d).\n", GetLastError());
                    return 0;
                }

                //Write grid
                if(WriteProcessMemory(hWinMineProc,
                                      (LPVOID)GRID_ADDRESS,
                                      (LPCVOID)grid,
                                      GRID_SIZE,
                                      &bytesWritten) == 0) {
                    printf("Unable to write process memory (%d).\n", GetLastError());       
                    return 0;
                } else {
                    printf("Grid Update Successful\n");
                }

                //Let go of minesweep
                CloseHandle(hWinMineProc);
                break;
            }

        //Get next process
        } while(Process32Next(hProcessSnap, &peProcess));  
    }
           
    if(!procFound)
        printf("WinMine Process Not Found\n");

    return 0;
}


    13. MineSweeper.cpp
/**********************************************************************
 * MineSweeper.cpp - trew@exploit.us
 *
 * This is supplemental code intended to accompany 'Introduction to
 * Reverse Engineering Windows Applications' as part of the Uninformed
 * Journal.  This application reveals and/or removes mines from the
 * WinMine grid.  Note, this code only works on the version of WinMine
 * shipped with WinXP, as the versions differ between releases of
 * Windows.
 *
 *********************************************************************/

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

#pragma comment(lib, "advapi32.lib")

#define BOMB_HIDDEN      0x8f
#define BOMB_REVEALED    0x8a
#define BLANK            0x0f
#define GRID_ADDRESS     0x1005360
#define GRID_SIZE        0x300

int main(int argc, char *argv[]) {

    HANDLE    hProcessSnap        = NULL;
    HANDLE    hWinMineProc        = NULL;
       
    PROCESSENTRY32 peProcess      = {0};

    unsigned char procFound       = 0;
    unsigned long bytesWritten    = 0;
    unsigned char *grid           = 0;
    unsigned char replacement     = BOMB_REVEALED;
    unsigned int x                = 0;

    grid = (unsigned char *)malloc(GRID_SIZE);

    if(!grid)
        return 0;

    if(argc > 1) {
        if(stricmp(argv[1], "remove") == 0) {
            replacement = BLANK;
        }
    }

    //Get a list of running processes
    hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
       
    //Ensure the handle is valid
    if(hProcessSnap == INVALID_HANDLE_VALUE) {
        printf("Unable to get process list (%d).\n", GetLastError());
        return 0;
    }
               
    peProcess.dwSize = sizeof(PROCESSENTRY32);

    //Get first process in list
    if(Process32First(hProcessSnap, &peProcess)) {

        do {
            //Is it's winmine.exe?
            if(!stricmp(peProcess.szExeFile, "winmine.exe")) {

                printf("Found WinMine Process ID (%d)\n", peProcess.th32ProcessID);
                procFound = 1;

                //Get handle on winmine process
                hWinMineProc = OpenProcess(PROCESS_ALL_ACCESS,
                                           1,
                                           peProcess.th32ProcessID);
                       
                //Make sure the handle is valid
                if(hWinMineProc == NULL) {
                    printf("Unable to open minesweep process (%d).\n", GetLastError());
                    return 0;
                }

                //Read Grid
                if(ReadProcessMemory(hWinMineProc,
                                     (LPVOID)GRID_ADDRESS,
                                     (LPVOID)grid, GRID_SIZE,
                                     &bytesWritten) == 0) {
                    printf("Unable to read process memory (%d).\n", GetLastError());       
                    return 0;
                } else {
                    //Modify Grid
                    for(x=0;x<=GRID_SIZE;x++) {
                        if((*(grid + x) & 0xff) == BOMB_HIDDEN) {
                            *(grid + x) = replacement;
                        }
                    }
                }

                //Write grid
                if(WriteProcessMemory(hWinMineProc,
                                      (LPVOID)GRID_ADDRESS,
                                      (LPCVOID)grid,
                                      GRID_SIZE,
                                      &bytesWritten) == 0) {
                    printf("Unable to write process memory (%d).\n", GetLastError());       
                    return 0;
                } else {
                    printf("Grid Update Successful\n");
                }

                //Let go of minesweep
                CloseHandle(hWinMineProc);
                break;
            }

        //Get next process
        } while(Process32Next(hProcessSnap, &peProcess));  
    }
   
    if(!procFound)
        printf("WinMine Process Not Found\n");

    return 0;
}

AND

출처: http://xxkim.springnote.com/pages/546195

I. 소개

예제들을 통해 WinDbg의 사용법을 익힌다. WinDbg의 특정 명령에 대해서 더 자세히 알고자 하면 WinDbg의 문서를 참조해야 한다. http://www.microsoft.com/whdc/ddk/debugging/에서 아래의 윈도우즈 디버거들을 다운 받을 수 있다.

KD - Kernel debugger. OS에 문제가 생기거나 디바이스 드라이버들을 개발하는 경우에 사용한다.

CDB - Command-line debugger. 콘솔용 디버거이다.

NTSD - NT debugger. CDB에 윈도우 스타일 UI가 있는 유저-모드 디버거이다.

WinDbg - KD와 NTSD를 랩핑한 것으로 커널-모드와 유저-모드 디버거로서의 기능을 할 수 있다.

Visual Studio, Visual Studio .NET -  KD와 NTSD와 동일한 디버깅 엔진을 사용하며, WinDbg보다 더 풍부한 디버깅 목적의 UI를 제공한다.



디버거들의 비교

 특징  KD  NTSD  WinDbg  Visual Studio .NET
 커널-모드 디버깅  Y N Y N
 유저-모드 디버깅    Y  Y  Y
 원격 디버깅  Y  Y  Y  Y
 동작중 프로세스에 연결  Y  Y Y Y
         
         
         
         

II. WinDbg


 WinDbg는 NTSD와 KD를 더 나은 UI로 래핑한 디버거이다. 최소화 상태로 시작(-m), pid를 통한 프로세스에 연결(-p), 크래쉬 파일들을 자동 열기(-z)와 같은 명령행 옵션들을 제공한다. 세가지 유형의 명령들을 지원한다.


정규 명령 : 프로세스들을 디버깅한다. (e.g. : k)

닷(dot) 명령 : 디버거를 제어한다. (e.g. : .sympath)

확장 명령 : WinDbg에 추가할 수 있는 커스텀 명령들로 확장 DLL들에서 익스포트된 함수들로 구현된다. (e.g. : !handle)


 PDB 파일들

PDB 파일들은 링커에 의해 생성된 프로그램 데이터베이스 파일들이다. 비공개(private) PDB 파일들은 비공개 및 공개 심볼들, 소스 라인들, 타입들, 지역 및 전역변수들에 대한 정보를 포함한다. 공개(public) PDB 파일들은 타입들, 지역 및 소스 라인 정보를 포함하지 않는다.


디버깅 시나리오

 리모트 디버깅

디버깅 서버(debugging server)라 함은 디버그 하고자 하는 머신상에 동작중인 디버거이며,  디버깅 클라이언트(debugging client)라 함은 세션을 제어하는 디버거이다.

서버상에는 CDB, NTSD, 또는 WinDbg가 필요한다. WinDbg 클라이언트는 CDB, NTSD 및 WinDbg중 아무에라도 연결할 수 있다. 서버와 클라이언트는 통신프로토콜로 TCP와 명명된 파이프들중에서 선택할 수 있다.


디버거를 이용하는 경우

서버 시작

WinDbg -server npipe:pipe=파이프명 (다수의 클라이언트가 접속할 수 있다.)

또는 WinDbg내에서

.server npipe:pipe=파이프명 (단일 클라이언트만 접속할 수 있다.)


클라이언트로 접속

WinDbg -remote npipe:server=서버, pipe=파이프명[,password=패스워드]

또는 WinDbg내에서 File->Connect to Remote Session

npipe:server=서버, pipe=파이프명 [,password=패스워드]


 remote.exe를 이용하는 경우

 remote.exe는 통신에 명명된 파이프들을 이용한다. 만약 KD, CDB 또는 NTSD와 같은 콘솔 기반 응용프로그램을 이용한다면, 리모트 디버깅을 하기위해 remote.exe를 이용할 수 있다.


서버 시작

remote.exe /s "cdb -p <pid>" test1


클라이언트로 연결

remote.exe /c <머신명> test1


'qq'로 서버를 종료하거나 또는 File->Exit를 사용하여 클라이언트를 종료할 수 있다. 리모트 디버깅을 하기 위해서는 사용자 그룹으로서 Debugger Users에 속해야 하며, 서버는 원격 연결을 허용해야만 한다.


 Just-in-time 디버깅

WinDbg -I로 실행하면 기본 JIT 디버거로서 설정할 수 있다. 이 명령은 레지스트리 키 HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug를 WinDbg롤 설정한다. JIT 디버거로 설정되면, 응용프로그램인 디버깅중이 아닐 경우에 예외를 던지고 그 예외를 처리하지 않을 경우 WinDbg가 동작하게된다.


 예외 디버깅

디버거는 각각의 예외에 2번 통지받는다. 응용프로그램이 예외를 처리하기 전에 first chance exception을 통지받고, 응용프로그램이 그 예외를 처리하지 않을 경우 second-chance excepton을 처리할 기회를 얻게된다. 디버거가 second-chance exception을 처리하지 않는다면, 응용프로그램은 종료한다.

.lastevent 또는 !analyze -v는 예외 기록과 예외가 발생했을 때의 함수의 스택 트레이스를 보여준다. .exr, .cxr 및 .ecxr 명령들을 예외 및 문맥 기록들(context records)을 화면에 표시하는데 사용할 수 있다. sxe, sxd, sxn 및 sxi 명령들을 이용하여 예외에 대해 first-chance 처리 옵션을 변경할 수 있다.


WinDbg 특징들

디버거 확장 DLL

디버거 확장은 디버거 내에서 커스텀 명령들을 실행할 수 있도록 디버거를 후킹(hook up)할 수 있도록 하는 DLL을 말한다. ! 명령들은 확장 DLL들로부터 실행되는 명령들이다. 확장 DLL들은 디버거의 프로세스 공간에 로드된다.


덤프 파일들

윈도우 OS가 깨지게 되면, 물리적 메모리 내용들과 모든 프로세스 정보가 덤프 파일에 기록된다. WinDbg를 JIT 디버거로 설정함으로써 비정상적으로 종료하는 모든 프로세스의 덤프를 만들 수 있다.  

덤프 파일을 분석하기 위해 다음 단계들을 따른다.

Step 1: File->Open Crash Dump. 덤프 파일을 선택하여 연다.

Step 2: 실행 중인 응용프로그램의 명령들을 보여준다.

Step 3: 심볼 경로와 소스 경로를 적절히 설정한다.


WinDbg 설정

 설치(Debugging Tools for Windows)

http://www.microsoft.com/whdc/devtools/debugging/default.mspx

설정

WinDbg에서 디버깅을 수행하기 위해서는 pdb 파일이 필요하다. Visual Studio에서 Debug Build로 빌드하면 Debug 폴더 안에 pdb 확장자 파일이 생긴다. 이 pdb파일을 WinDbg의 Symbol File Path에 복사해야 한다. Symbol File Path는 WinDbg의 File | Symbol File Path...[Ctrl+S] 메뉴에서 설정 가능하다.

실행

WinDbg의 File | Open Executable...[Ctrl+E] 메뉴를 클릭하여, 디버그할 실행 파일을 연다.

소스 레벨 디버깅

소스 레벨 디버깅을 하기 위해서는 소스 파일을 열어야 한다.

File | Open Source File...[Ctrl+O]

AND

출처 : http://support.microsoft.com/kb/177101

BUG: An ASSERT may occur in AfxWndProc when two or more threads display modal dialog boxes in an MFC regular DLL in Visual C++

Article ID : 177101
Last Review : November 21, 2006
Revision : 3.1
This article was previously published under Q177101

SYMPTOMS

If two or more threads display modal dialog boxes at the same time inside an MFC regular dynamic-link library (DLL) (USRDLL), the following ASSERT may be generated in AfxWndProc, on Wincore.cpp, line 365 in Visual C++ 6.0 (line 368 in Visual C++ 5.0, line 360 in Visual C++ 4.2):
// All other messages route through message map.
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
ASSERT(pWnd != NULL);
				
This ASSERT occurs only if one of the threads was created outside of the DLL.

The probability of this problem occurring increases with the number of threads and modal dialog boxes.

Back to the top

CAUSE

When AfxGetThread() is called in an MFC regular DLL from a secondary thread that was not created inside the DLL, it returns the CWinApp object for the DLL because a CWinThread object was not created for the thread in the context of the DLL.

When a modal dialog box is displayed, CWnd::RunModalLoop() pumps messages by calling AfxGetThread()->PumpMessage(). If two modal dialog boxes both call the CWinApp object's PumpMessage at the same time, synchronization problems cause the wrong message to get processed on the wrong thread.

Back to the top

RESOLUTION

One possible work around is to spawn secondary threads, which in turn display the modal dialog boxes. Each new thread created inside the MFC regular DLL will have a new CWinThread object and a separate message pump.

Back to the top

STATUS

Microsoft has confirmed that this is a bug in the Microsoft products that are listed at the beginning of this article.

This problem was corrected in Microsoft Visual C++ .NET.

Back to the top

MORE INFORMATION

In Visual C++ versions earlier than Visual C++ 4.2, MFC regular DLLs could be accessed only from the same thread that loaded the DLL. Support for external secondary threads was added in Visual C++ 4.2.

The following sample code shows one possible workaround:

      UINT ShowMeTheDlg( LPVOID pParam )

   {

    // Create CWnd object from passed in HWND
    CFileSecurity FS(CWnd::FromHandle((HWND)pParam));
    FS.DoModal();
    return TRUE;
   }

   void CSubfolderPage::OnBreakit()
   {
   // Start MFC Worker thread for modal dialog box. Modal dialog box has
   // PumpMessage code so, user interface thread is not necessary.
   // Can't pass CWnds between threads, so pass HWND
   // After DoModal call above, function returns
   // and thread terminates.
    AfxBeginThread(ShowMeTheDlg, (LPVOID)this->GetSafeHwnd());
   }
				

Back to the top

REFERENCES

For additional information, please see the following article(s) in the Microsoft Knowledge Base:
122676 (http://support.microsoft.com/kb/122676/EN-US/)INFO: Multiple Threads and MFC _USRDLLs

Back to the top



 

AND

출처: http://cafe.naver.com/gamecoding.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=24

_ASSERT

Evaluate an expression and generates a debug report when the result is FALSE

System::Diagnostics::Debug::Assert

_ASSERTE

Similar to _ASSERT, but includes the failed expression in the generated report

System::Diagnostics::Debug::Assert

_CrtCheckMemory

Confirm the integrity of the memory blocks allocated on the debug heap

System::Diagnostics::PerformanceCounter

_CrtDbgBreak

Sets a break point.

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtDbgReport, _CrtDbgReportW

Generate a debug report with a user message and send the report to three possible destinations

System::Diagnostics::Debug::Write, System::Diagnostics::Debug::Writeline, System::Diagnostics::Debug::WriteIf, System::Diagnostics::Debug::WriteLineIf

_CrtDoForAllClientObjects

Call an application-supplied function for all _CLIENT_BLOCK types on the heap

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtDumpMemoryLeaks

Dump all of the memory blocks on the debug heap when a significant memory leak has occurred

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtIsMemoryBlock

Verify that a specified memory block is located within the local heap and that it has a valid debug heap block type identifier

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtIsValidHeapPointer

Verifies that a specified pointer is in the local heap

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtIsValidPointer

Verify that a specified memory range is valid for reading and writing

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtMemCheckpoint

Obtain the current state of the debug heap and store it in an application-supplied _CrtMemState structure

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtMemDifference

Compare two memory states for significant differences and return the results

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtMemDumpAllObjectsSince

Dump information about objects on the heap since a specified checkpoint was taken or from the start of program execution

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtMemDumpStatistics

Dump the debug header information for a specified memory state in a user-readable form

System::Diagnostics::PerformanceCounter

_CrtReportBlockType

Returns the block type/subtype associated with a given debug heap block pointer.

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtSetAllocHook

Install a client-defined allocation function by hooking it into the C run-time debug memory allocation process

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtSetBreakAlloc

Set a breakpoint on a specified object allocation order number

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtSetDbgFlag

Retrieve or modify the state of the _crtDbgFlag flag to control the allocation behavior of the debug heap manager

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtSetDumpClient

Install an application-defined function that is called every time a debug dump function is called to dump _CLIENT_BLOCK type memory blocks

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtSetReportFile

Identify the file or stream to be used as a destination for a specific report type by _CrtDbgReport

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtSetReportHook

Install a client-defined reporting function by hooking it into the C run-time debug reporting process

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtSetReportHook2, _CrtSetReportHookW2

Installs or uninstalls a client-defined reporting function by hooking it into the C run-time debug reporting process.

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_CrtSetReportMode

Specify the general destination(s) for a specific report type generated by _CrtDbgReport

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_RPT[0,1,2,3,4]

Track the application's progress by generating a debug report by calling _CrtDbgReport with a format string and a variable number of arguments. Provides no source file and line number information.

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_RPTF[0,1,2,3,4]

Similar to the _RPTn macros, but provides the source file name and line number where the report request originated

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_calloc_dbg

Allocate a specified number of memory blocks on the heap with additional space for a debugging header and overwrite buffers

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_expand_dbg

Resize a specified block of memory on the heap by expanding or contracting the block

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_free_dbg

Free a block of memory on the heap

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_fullpath_dbg, _wfullpath_dbg

Create an absolute or full path name for the specified relative path name, using _malloc_dbg to allocate memory.

System::IO::File::Create

_getcwd_dbg, _wgetcwd_dbg

Get the current working directory, using _malloc_dbg to allocate memory.

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_malloc_dbg

Allocate a block of memory on the heap with additional space for a debugging header and overwrite buffers

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_msize_dbg

Calculate the size of a block of memory on the heap

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_realloc_dbg

Reallocate a specified block of memory on the heap by moving and/or resizing the block

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

_strdup_dbg, _wcsdup_dbg

Duplicates a string, using _malloc_dbg to allocate memory.

System::String::Clone

_tempnam_dbg, _wtempnam_dbg

Generate names you can use to create temporary files, using _malloc_dbg to allocate memory.

Not applicable. To call the standard C function, use PInvoke. For more information, see Platform Invoke Examples.

AND