이 문서는 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의 툴바는 이 문서에서 사용되는 기본적인 용어를 논의하기 위한 간단한 가이드로서 역할을 할 것이다. 왼쪽부터 오른쪽으로 각 옵션의 기능은 다음과 같다:
- Open Source Code - Open associated source code for the debugging session.
- Cut - Move highlighted text to the clipboard
- Copy - Copy highlighted text to the clipboard
- Go - Execute the debugee
- Restart - Restart the debugee process
- Stop - Terminate the debugging session
- Break - Pause the currently running debugee process
다음 네개의 옵션 아이콘은 디버거가 Break가 걸린 후 사용된다. 디버거는 위의 옵션을 통해 Break 걸릴 수도 있고 사용자가 위치를 지정하여 Break (Breakpoint) 걸 수도 있다. 브레이크포인트는 다양한 상황에 할당 될 수 있다. 대부분의 공통적인 점은 프로세서가 특정 주소에서 명령행(instruction)을 실행 할 때나 메모리의 특정 영역에 접근 되었을 때의 상황이다. 브레이크포인트의 용도에 관해서 나중에 다시 자세히 언급하도록 하겠다.
브레이크포인트가 걸린 위치에 도달 하였을 때, 각각의 명령행이나 함수를 호출하여 실행하는 것은 프로세스를 수행하며 어떻게 건너뛸 것인지 확인하는데, WinDBG에는 스텝 방식에 대해 다음과 같이 네가지를 정의하고 있다.
- 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.
- 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.
- Step Out - Execute one or many instructions. Causes the debugger to execute instructions until it has returned from the current function.
- 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'를 두번 수행) 해보자. 앞의 두 명령행은 ebx와 esi 레지스터를 스택에 저장하는 것이다. 이것은 ESP에 참조되어 있는 스택 메모리 위치를 확인해 봄으로써 ebx가 스택에 저장되는 것을 확인 할 수 있다. 'p'를 눌러 push ebx 명령행을 수행하여 esp에 위치한 값을 살펴보자. esp에 ebx값이 들어가 있는 것을 다음과 같이 확인 할 수 있다.
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
StackBase와 StackLimit 를 위한 값을 보면 스택의 천장과 바닥이 있다는 것을 알 수 있다. TEB에 대한 더 자세한 정보는 레퍼런스 섹션[11]번을 참조하라. 다시 돌아와 다음 명령행을 보자:
01002f8c 8b3534530001 mov esi,[winmine!xBoxMac (01005334)]
이것은 분명 지뢰판의 가로크기의 값을 esi에 저장한다는 것을 알 수 있다. 'p'를 눌러 esi 레지스터에 0x1e 값이 저장되는 것을 확인하자. 0x1e는 10진수로 30이다. 이것은 지금 플레이 하고 있는 지뢰판의 가로크기임을 알 수 있다. 그래서 xBoxMac은 가로크기의 값을 표시한다는 것을 알 수 있다. 다음 명령행인 push edi는 edi 레지스터의 값을 스택에 저장하는 것인데 이것은 다음 명령행(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 부분에 대해 소개 할 것이다.
- 지뢰찾기 프로세스를 attach 하고 locate 한다.
- 지뢰판 영역을 read 한다.
- 폭탄을 제거하거나 나타나게 수정한다.
- 지뢰찾기 프로그램 공간에 임의의 지뢰판을 수정하여 넣어본다.
첫번째 작업을 수행하기 위해 우리는 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; }
|